From bd60b24f1cf8592e88e6d32dc9cf33c5178e3f03 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Wed, 17 Jun 2026 15:28:00 -0700 Subject: [PATCH 1/5] Use Result comparison as source of dynamism in RCA This change adjusts how `Result` values are treated in Runtime Capability Analysis, shifting them from being treated as dynamic variable values by default to treating them as dynamic constant values. This matches the behavior for `Qubit` variables, as both are identifiers in QIR. Instead of counting on the `Result` value returned from measurement intrinsics as the source of dynamism, RCA instead tracks when a `Result` value is used in a comparison and marks the resulting `Bool` value as a dynamic variable. This more directly maps to how dynamism enters a program, and sets the stage for emitting constant `Result` arrays into the QIR in the same fashion as `Qubit` arrays. This builds off of the work in #3349 as we now track the difference between dynamic constants and dyanmic variables across function call boundaries, allowing RCA to properly detect comparison of dynamic constant results throughout the program. --- .../qsc_partial_eval/src/tests/misc.rs | 13 ++++--- source/compiler/qsc_rca/src/core.rs | 36 ++++++++++++++----- source/compiler/qsc_rca/src/tests/arrays.rs | 8 ++--- source/compiler/qsc_rca/src/tests/assigns.rs | 12 +++---- source/compiler/qsc_rca/src/tests/bindings.rs | 2 +- .../compiler/qsc_rca/src/tests/intrinsics.rs | 4 +-- source/compiler/qsc_rca/src/tests/lambdas.rs | 2 +- .../qsc_rca/src/tests/measurements.rs | 6 ++-- source/compiler/qsc_rca/src/tests/types.rs | 2 +- source/compiler/qsc_rca/src/tests/vars.rs | 2 +- 10 files changed, 55 insertions(+), 32 deletions(-) diff --git a/source/compiler/qsc_partial_eval/src/tests/misc.rs b/source/compiler/qsc_partial_eval/src/tests/misc.rs index 00ec531a0f..70a660a717 100644 --- a/source/compiler/qsc_partial_eval/src/tests/misc.rs +++ b/source/compiler/qsc_partial_eval/src/tests/misc.rs @@ -607,10 +607,15 @@ fn evaluation_error_within_stdlib_yield_correct_package_span() { namespace Test { import Std.Arrays.*; @EntryPoint() - operation Main() : Result[] { - use qs = Qubit[1]; - let rs = ForEach(MResetZ, qs); - return rs; + operation Main() : Int[] { + use q = Qubit(); + let a = if MResetZ(q) == One { + 1 + } else { + 0 + }; + let b = [(a, a)]; + ForEach(t => Fst(t), b) } } "#, diff --git a/source/compiler/qsc_rca/src/core.rs b/source/compiler/qsc_rca/src/core.rs index b59dfd9ef9..2b3ece3708 100644 --- a/source/compiler/qsc_rca/src/core.rs +++ b/source/compiler/qsc_rca/src/core.rs @@ -262,13 +262,21 @@ impl<'a> Analyzer<'a> { value_kind, } = &mut compute_kind { + let lhs_expr_ty = &self.get_expr(lhs_expr_id).ty; + if is_any_result(lhs_expr_ty) { + // The only binary operators on result types are equality and inequality. In this path, we know + // at least one side of the comparison is dynamic (constant or variable), so the boolean that comes + // from the comparison must be variable. This is the critical source of dynamic variable values + // in a program that operates on qubits measurements. + *value_kind = ValueKind::Variable; + } + *runtime_features |= derive_runtime_features_for_value_kind_associated_to_type(*value_kind, expr_type); - let lhs_expr_ty = &self.get_expr(lhs_expr_id).ty; if *value_kind == ValueKind::Variable - && matches!(lhs_expr_ty, Ty::Prim(Prim::String)) - && expr_type == &Ty::Prim(Prim::Bool) + && *lhs_expr_ty == Ty::Prim(Prim::String) + && *expr_type == Ty::Prim(Prim::Bool) { // Strings can only be concatenated or compared for equality, and only equality comparison // can affect control flow of the program in a way that required runtime features, @@ -748,6 +756,11 @@ impl<'a> Analyzer<'a> { // runtime feature since the result of the index expression can be used in a context that requires tuple-specific runtime features. dynamic_runtime_features |= RuntimeFeatureFlags::UseOfDynamicTuple; } + Ty::Prim(Prim::Result) => { + // If the type of the index expression is a Result, we need to add the `UseOfDynamicResult` + // runtime feature since the output will need to be stored in a variable of type Result. + dynamic_runtime_features |= RuntimeFeatureFlags::UseOfDynamicResult; + } _ => { // Other dynamic content types are already handled by the `derive_runtime_features_for_value_kind_associated_to_type` function // called below, so we don't need to do anything else here. @@ -896,8 +909,9 @@ impl<'a> Analyzer<'a> { fn analyze_expr_string(&mut self, components: &Vec) -> ComputeKind { // Visit the string components to determine their compute kind, aggregate its runtime features and track whether - // any of them is variable to construct the compute kind of the string expression itself. + // any of them is variable or a result to construct the compute kind of the string expression itself. let mut has_variable_components = false; + let mut has_dynamic_result = false; let mut compute_kind = ComputeKind::Static; for component in components { match component { @@ -909,6 +923,8 @@ impl<'a> Analyzer<'a> { compute_kind = compute_kind .aggregate_runtime_features(component_compute_kind, ValueKind::Constant); has_variable_components |= component_compute_kind.is_variable_value_kind(); + has_dynamic_result |= is_any_result(&self.get_expr(*expr_id).ty) + && !matches!(component_compute_kind, ComputeKind::Static); } StringComponent::Lit(_) => { // Nothing to aggregate. @@ -916,8 +932,8 @@ impl<'a> Analyzer<'a> { } } - // If any of the string components is variable, then the string expression is variable as well. - if has_variable_components { + // If any of the string components is variable or contains a dynamic result, then the string expression is variable as well. + if has_variable_components || has_dynamic_result { compute_kind.set_variable_value_kind(); } @@ -2345,10 +2361,12 @@ fn derive_instrinsic_operation_application_generator_set( ) -> ApplicationGeneratorSet { assert!(matches!(callable_context.kind, CallableKind::Operation)); - // The value kind of intrinsic operations is inherently dynamic if their output is not `Unit` or `Qubit`. + // The value kind of intrinsic operations is inherently variable if their output is not `Unit`, `Qubit`, or `Result`. let runtime_kind = if callable_context.output_type == Ty::UNIT - || callable_context.output_type == Ty::Prim(Prim::Qubit) - { + || matches!( + callable_context.output_type, + Ty::Prim(Prim::Qubit | Prim::Result) + ) { ValueKind::Constant } else { ValueKind::new_variable_from_type(&callable_context.output_type) diff --git a/source/compiler/qsc_rca/src/tests/arrays.rs b/source/compiler/qsc_rca/src/tests/arrays.rs index dccb493a52..0a21f3ef31 100644 --- a/source/compiler/qsc_rca/src/tests/arrays.rs +++ b/source/compiler/qsc_rca/src/tests/arrays.rs @@ -34,7 +34,7 @@ fn check_rca_for_array_with_dynamic_results() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } @@ -89,7 +89,7 @@ fn check_rca_for_array_repeat_with_dynamic_result_value_and_classical_size() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } @@ -179,7 +179,7 @@ fn check_rca_for_mutable_array_statically_appended() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } @@ -522,7 +522,7 @@ fn check_rca_for_array_with_static_size_bound_through_dynamic_tuple() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } diff --git a/source/compiler/qsc_rca/src/tests/assigns.rs b/source/compiler/qsc_rca/src/tests/assigns.rs index 2e957f9bdd..d63fb8c2e0 100644 --- a/source/compiler/qsc_rca/src/tests/assigns.rs +++ b/source/compiler/qsc_rca/src/tests/assigns.rs @@ -42,7 +42,7 @@ fn check_rca_for_dynamic_result_assign_to_local() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } @@ -284,8 +284,8 @@ fn check_rca_for_assign_dynamic_static_mix_call_result_to_tuple_of_vars() { &expect![[r#" ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(UseOfDynamicInt | QubitAllocation) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(QubitAllocation) + value_kind: Constant dynamic_param_applications: "#]], ); compilation_context.update( @@ -298,8 +298,8 @@ fn check_rca_for_assign_dynamic_static_mix_call_result_to_tuple_of_vars() { &expect![[r#" ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(UseOfDynamicInt | QubitAllocation) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(QubitAllocation) + value_kind: Constant dynamic_param_applications: "#]], ); } @@ -472,7 +472,7 @@ fn check_rca_for_immutable_dynamic_result_bound_to_result_from_classical_conditi ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } diff --git a/source/compiler/qsc_rca/src/tests/bindings.rs b/source/compiler/qsc_rca/src/tests/bindings.rs index 4f2e5ee0ad..0c12fc97b0 100644 --- a/source/compiler/qsc_rca/src/tests/bindings.rs +++ b/source/compiler/qsc_rca/src/tests/bindings.rs @@ -40,7 +40,7 @@ fn check_rca_for_immutable_dynamic_result_binding() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } diff --git a/source/compiler/qsc_rca/src/tests/intrinsics.rs b/source/compiler/qsc_rca/src/tests/intrinsics.rs index 2bb8dad5e1..01bf2e9cd8 100644 --- a/source/compiler/qsc_rca/src/tests/intrinsics.rs +++ b/source/compiler/qsc_rca/src/tests/intrinsics.rs @@ -63,7 +63,7 @@ fn check_rca_for_quantum_qis_m_body() { body: ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(0x0) - value_kind: Variable + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: @@ -114,7 +114,7 @@ fn check_rca_for_quantum_qis_mresetz_body() { body: ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(0x0) - value_kind: Variable + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: diff --git a/source/compiler/qsc_rca/src/tests/lambdas.rs b/source/compiler/qsc_rca/src/tests/lambdas.rs index 07886faf41..911f31722f 100644 --- a/source/compiler/qsc_rca/src/tests/lambdas.rs +++ b/source/compiler/qsc_rca/src/tests/lambdas.rs @@ -191,7 +191,7 @@ fn check_rca_for_operation_lambda_two_parameters() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } diff --git a/source/compiler/qsc_rca/src/tests/measurements.rs b/source/compiler/qsc_rca/src/tests/measurements.rs index e7ac29bae3..7fa32284c5 100644 --- a/source/compiler/qsc_rca/src/tests/measurements.rs +++ b/source/compiler/qsc_rca/src/tests/measurements.rs @@ -19,7 +19,7 @@ fn check_rca_for_static_single_qubit_measurement() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } @@ -64,7 +64,7 @@ fn check_rca_for_static_single_measurement_and_reset() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } @@ -110,7 +110,7 @@ fn check_rca_for_static_multi_qubit_measurement() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } diff --git a/source/compiler/qsc_rca/src/tests/types.rs b/source/compiler/qsc_rca/src/tests/types.rs index f2f364a952..214f4a9f41 100644 --- a/source/compiler/qsc_rca/src/tests/types.rs +++ b/source/compiler/qsc_rca/src/tests/types.rs @@ -33,7 +33,7 @@ fn check_rca_for_dynamic_result() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } diff --git a/source/compiler/qsc_rca/src/tests/vars.rs b/source/compiler/qsc_rca/src/tests/vars.rs index 44d84ffb3c..4e67ecf971 100644 --- a/source/compiler/qsc_rca/src/tests/vars.rs +++ b/source/compiler/qsc_rca/src/tests/vars.rs @@ -90,7 +90,7 @@ fn check_rca_for_dynamic_result_var() { ApplicationsGeneratorSet: inherent: Dynamic: runtime_features: RuntimeFeatureFlags(QubitAllocation) - value_kind: Variable + value_kind: Constant dynamic_param_applications: "#]], ); } From 9a51a124eff721b92281b7d7ad6179a40aab069e Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Thu, 18 Jun 2026 20:36:40 -0700 Subject: [PATCH 2/5] Change how cycles handled, Fixes #3357 --- .../qsc/src/codegen/tests/adaptive_profile.rs | 111 ++++- .../src/codegen/tests/adaptive_ri_profile.rs | 58 +++ .../src/codegen/tests/adaptive_rif_profile.rs | 59 +++ .../qsc/src/codegen/tests/base_profile.rs | 57 +++ .../src/evaluation_context.rs | 4 +- source/compiler/qsc_partial_eval/src/lib.rs | 49 +- .../src/tests/ir_functions.rs | 10 +- .../src/capabilitiesck/tests_adaptive.rs | 39 +- .../tests_adaptive_plus_integers.rs | 39 +- ...tests_adaptive_plus_integers_and_floats.rs | 39 +- .../src/capabilitiesck/tests_base.rs | 39 +- source/compiler/qsc_rca/src/analyzer.rs | 21 +- source/compiler/qsc_rca/src/core.rs | 18 +- .../compiler/qsc_rca/src/cycle_detection.rs | 296 ------------ .../compiler/qsc_rca/src/cyclic_callables.rs | 312 ------------- source/compiler/qsc_rca/src/lib.rs | 2 - source/compiler/qsc_rca/src/tests/calls.rs | 12 +- source/compiler/qsc_rca/src/tests/cycles.rs | 427 ++++++++++-------- 18 files changed, 615 insertions(+), 977 deletions(-) delete mode 100644 source/compiler/qsc_rca/src/cycle_detection.rs delete mode 100644 source/compiler/qsc_rca/src/cyclic_callables.rs diff --git a/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs b/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs index c4fbf20fdb..b602f9ebec 100644 --- a/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs +++ b/source/compiler/qsc/src/codegen/tests/adaptive_profile.rs @@ -1756,9 +1756,9 @@ fn controlled_specialization_inlines() { ); } -/// A recursive operation inlines (recursion is excluded from eligibility). +/// A recursive operation can be emitted into an IR function. #[test] -fn recursive_operation_inlines() { +fn recursive_operation_emits_to_ir_function() { let source = "namespace Test { operation Recurse(n : Int, q : Qubit) : Unit { if n > 0 { @@ -1773,7 +1773,53 @@ fn recursive_operation_inlines() { } }"; let qir = compile_source_to_qir(source, *CAPABILITIES); - assert_inlined(&qir, "Recurse"); + expect![[r#" + @0 = internal constant [4 x i8] c"0_t\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__rt__initialize(ptr null) + call void @Recurse(i64 3, ptr inttoptr (i64 0 to ptr)) + call void @__quantum__rt__tuple_record_output(i64 0, ptr @0) + ret i64 0 + } + + declare void @__quantum__rt__initialize(ptr) + + define void @Recurse(i64 %var_0, ptr %var_1) { + block_1: + %var_2 = icmp sgt i64 %var_0, 0 + br i1 %var_2, label %block_2, label %block_3 + block_2: + call void @__quantum__qis__x__body(ptr %var_1) + %var_3 = sub i64 %var_0, 1 + call void @Recurse(i64 %var_3, ptr %var_1) + br label %block_3 + block_3: + ret void + } + + declare void @__quantum__qis__x__body(ptr) + + declare void @__quantum__rt__tuple_record_output(i64, ptr) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="0" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7, !8} + + !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} + "#]].assert_eq(&qir); } /// A call into a stdlib/library operation (cross-package) still inlines. @@ -2097,3 +2143,62 @@ fn value_returning_ir_function_rir_reloads_after_same_block_store() { "#]] .assert_eq(ssa); } + +#[test] +fn preparepurestated_cyclic_library_calls_generate_correct_qir() { + let source = " + operation Main() : Result { + use q = Qubit(); + Std.StatePreparation.PreparePureStateD([0.0, 1.0], [q]); + MResetZ(q) + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + @0 = internal constant [4 x i8] c"0_r\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__rt__initialize(ptr null) + call void @__quantum__qis__s__adj(ptr inttoptr (i64 0 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 0 to ptr)) + call void @__quantum__qis__rz__body(double 3.141592653589793, ptr inttoptr (i64 0 to ptr)) + call void @__quantum__qis__h__body(ptr inttoptr (i64 0 to ptr)) + call void @__quantum__qis__s__body(ptr inttoptr (i64 0 to ptr)) + call void @__quantum__qis__mresetz__body(ptr inttoptr (i64 0 to ptr), ptr inttoptr (i64 0 to ptr)) + call void @__quantum__rt__result_record_output(ptr inttoptr (i64 0 to ptr), ptr @0) + ret i64 0 + } + + declare void @__quantum__rt__initialize(ptr) + + declare void @__quantum__qis__s__adj(ptr) + + declare void @__quantum__qis__h__body(ptr) + + declare void @__quantum__qis__rz__body(double, ptr) + + declare void @__quantum__qis__s__body(ptr) + + declare void @__quantum__qis__mresetz__body(ptr, ptr) #1 + + declare void @__quantum__rt__result_record_output(ptr, ptr) + + 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, !5, !6, !7, !8} + + !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} + "#]].assert_eq(&qir); +} 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 d65dbb0c02..7ea4f9c79a 100644 --- a/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs +++ b/source/compiler/qsc/src/codegen/tests/adaptive_ri_profile.rs @@ -610,3 +610,61 @@ fn terminal_result_return_with_qubit_cleanup_generates_rir() { assert_terminal_result_return_with_qubit_cleanup_rir(raw, "raw"); assert_terminal_result_return_with_qubit_cleanup_rir(ssa, "ssa"); } + +#[test] +fn preparepurestated_cyclic_library_calls_generate_correct_qir() { + let source = " + operation Main() : Result { + use q = Qubit(); + Std.StatePreparation.PreparePureStateD([0.0, 1.0], [q]); + MResetZ(q) + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + @0 = internal constant [4 x i8] c"0_r\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__rt__initialize(i8* null) + call void @__quantum__qis__s__adj(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__rz__body(double 3.141592653589793, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__s__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* 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__s__adj(%Qubit*) + + declare void @__quantum__qis__h__body(%Qubit*) + + declare void @__quantum__qis__rz__body(double, %Qubit*) + + declare void @__quantum__qis__s__body(%Qubit*) + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !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/src/codegen/tests/adaptive_rif_profile.rs b/source/compiler/qsc/src/codegen/tests/adaptive_rif_profile.rs index f15abd0c68..bb6271ff53 100644 --- a/source/compiler/qsc/src/codegen/tests/adaptive_rif_profile.rs +++ b/source/compiler/qsc/src/codegen/tests/adaptive_rif_profile.rs @@ -707,3 +707,62 @@ fn dynamic_double_intrinsic() { !5 = !{i32 5, !"float_computations", !{!"double"}} "#]].assert_eq(&qir); } + +#[test] +fn preparepurestated_cyclic_library_calls_generate_correct_qir() { + let source = " + operation Main() : Result { + use q = Qubit(); + Std.StatePreparation.PreparePureStateD([0.0, 1.0], [q]); + MResetZ(q) + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + @0 = internal constant [4 x i8] c"0_r\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__rt__initialize(i8* null) + call void @__quantum__qis__s__adj(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__rz__body(double 3.141592653589793, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__s__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__mresetz__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* 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__s__adj(%Qubit*) + + declare void @__quantum__qis__h__body(%Qubit*) + + declare void @__quantum__qis__rz__body(double, %Qubit*) + + declare void @__quantum__qis__s__body(%Qubit*) + + declare void @__quantum__qis__mresetz__body(%Qubit*, %Result*) #1 + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="adaptive_profile" "required_num_qubits"="1" "required_num_results"="1" } + attributes #1 = { "irreversible" } + + ; module flags + + !llvm.module.flags = !{!0, !1, !2, !3, !4, !5} + + !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"}} + !5 = !{i32 5, !"float_computations", !{!"double"}} + "#]].assert_eq(&qir); +} diff --git a/source/compiler/qsc/src/codegen/tests/base_profile.rs b/source/compiler/qsc/src/codegen/tests/base_profile.rs index f99512ddaa..911119fddc 100644 --- a/source/compiler/qsc/src/codegen/tests/base_profile.rs +++ b/source/compiler/qsc/src/codegen/tests/base_profile.rs @@ -351,3 +351,60 @@ fn noise_intrinsic_generates_correct_qir() { !3 = !{i32 1, !"dynamic_result_management", i1 false} "#]].assert_eq(&qir); } + +#[test] +fn preparepurestated_cyclic_library_calls_generate_correct_qir() { + let source = " + operation Main() : Result { + use q = Qubit(); + Std.StatePreparation.PreparePureStateD([0.0, 1.0], [q]); + MResetZ(q) + } + "; + let qir = compile_source_to_qir(source, *CAPABILITIES); + expect![[r#" + %Result = type opaque + %Qubit = type opaque + + @0 = internal constant [4 x i8] c"0_r\00" + + define i64 @ENTRYPOINT__main() #0 { + block_0: + call void @__quantum__rt__initialize(i8* null) + call void @__quantum__qis__s__adj(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__rz__body(double 3.141592653589793, %Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__h__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__s__body(%Qubit* inttoptr (i64 0 to %Qubit*)) + call void @__quantum__qis__m__body(%Qubit* inttoptr (i64 0 to %Qubit*), %Result* inttoptr (i64 0 to %Result*)) + call void @__quantum__rt__result_record_output(%Result* inttoptr (i64 0 to %Result*), i8* 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__s__adj(%Qubit*) + + declare void @__quantum__qis__h__body(%Qubit*) + + declare void @__quantum__qis__rz__body(double, %Qubit*) + + declare void @__quantum__qis__s__body(%Qubit*) + + declare void @__quantum__rt__result_record_output(%Result*, i8*) + + declare void @__quantum__qis__m__body(%Qubit*, %Result*) #1 + + attributes #0 = { "entry_point" "output_labeling_schema" "qir_profiles"="base_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(&qir); +} diff --git a/source/compiler/qsc_partial_eval/src/evaluation_context.rs b/source/compiler/qsc_partial_eval/src/evaluation_context.rs index 429db4b1f4..3f85a7d995 100644 --- a/source/compiler/qsc_partial_eval/src/evaluation_context.rs +++ b/source/compiler/qsc_partial_eval/src/evaluation_context.rs @@ -323,7 +323,7 @@ fn map_eval_value_to_value_kind(value: &Value) -> ValueKind { } ValueKind::Constant } - Value::Result(Result::Id(_) | Result::Loss) | Value::Var(_) => ValueKind::Variable, + Value::Result(Result::Loss) | Value::Var(_) => ValueKind::Variable, Value::BigInt(_) | Value::Bool(_) | Value::Closure(_) @@ -333,7 +333,7 @@ fn map_eval_value_to_value_kind(value: &Value) -> ValueKind { | Value::Pauli(_) | Value::Qubit(_) | Value::Range(_) - | Value::Result(Result::Val(_)) + | Value::Result(Result::Val(_) | Result::Id(_)) | Value::String(_) => ValueKind::Constant, } } diff --git a/source/compiler/qsc_partial_eval/src/lib.rs b/source/compiler/qsc_partial_eval/src/lib.rs index 6b64ce40bd..0157a40019 100644 --- a/source/compiler/qsc_partial_eval/src/lib.rs +++ b/source/compiler/qsc_partial_eval/src/lib.rs @@ -15,6 +15,7 @@ use core::panic; use evaluation_context::{Arg, BlockNode, EvalControlFlow, EvaluationContext, Scope}; use management::{QuantumIntrinsicsChecker, ResourceManager}; use miette::Diagnostic; +use num_bigint::BigInt; use qsc_data_structures::{functors::FunctorApp, span::Span, target::TargetCapabilityFlags}; use qsc_eval::{ self, Error as EvalError, ErrorBehavior, PackageSpan, State, StepAction, StepResult, Variable, @@ -24,8 +25,8 @@ use qsc_eval::{ output::GenericReceiver, resolve_closure, val::{ - self, Value, Var, VarTy, index_array, slice_array, update_functor_app, update_index_range, - update_index_single, + self, Value, Var, VarTy, index_array, slice_array, unwrap_tuple, update_functor_app, + update_index_range, update_index_single, }, }; use qsc_fir::{ @@ -1850,6 +1851,8 @@ impl<'a> PartialEvaluator<'a> { ); } + let args_statically_known = is_static_value(&args_value); + // There are a few special cases regarding intrinsic callables. Identify them and handle them properly. match callable_decl.name.name.as_ref() { // Qubit allocations and measurements have special handling. @@ -1916,6 +1919,35 @@ impl<'a> PartialEvaluator<'a> { } } "IntAsDouble" | "Truncate" => self.convert_value(&args_value, args_span), + + // These intrinsic functions should be evaluated immediately rather than emitted if all + // arguments can be treated as statically known values. + "ArcCos" if args_statically_known => { + Ok(Value::Double(args_value.unwrap_double().acos())) + } + "ArcSin" if args_statically_known => { + Ok(Value::Double(args_value.unwrap_double().asin())) + } + "ArcTan" if args_statically_known => { + Ok(Value::Double(args_value.unwrap_double().atan())) + } + "ArcTan2" if args_statically_known => { + let [x, y] = unwrap_tuple(args_value); + Ok(Value::Double(x.unwrap_double().atan2(y.unwrap_double()))) + } + "Cos" if args_statically_known => Ok(Value::Double(args_value.unwrap_double().cos())), + "Cosh" if args_statically_known => Ok(Value::Double(args_value.unwrap_double().cosh())), + "Sin" if args_statically_known => Ok(Value::Double(args_value.unwrap_double().sin())), + "Sinh" if args_statically_known => Ok(Value::Double(args_value.unwrap_double().sinh())), + "Tan" if args_statically_known => Ok(Value::Double(args_value.unwrap_double().tan())), + "Tanh" if args_statically_known => Ok(Value::Double(args_value.unwrap_double().tanh())), + "Sqrt" if args_statically_known => Ok(Value::Double(args_value.unwrap_double().sqrt())), + "Log" if args_statically_known => Ok(Value::Double(args_value.unwrap_double().ln())), + "IntAsBigInt" if args_statically_known => { + Ok(Value::BigInt(BigInt::from(args_value.unwrap_int()))) + } + + // Otherwise, we will try to emit the call as a RIR instruction. _ => self.eval_expr_call_to_intrinsic_qis( store_item_id, callable_decl, @@ -4618,6 +4650,19 @@ impl<'a> PartialEvaluator<'a> { } } +// Determines if a value can be treated as a static value, meaning something that can be directly passed +// to full evaluation without requiring any emission of RIR instructions. +fn is_static_value(args_value: &Value) -> bool { + match args_value { + // Qubit/Result ids and variables values cannot be treated as static. + Value::Qubit(_) | Value::Result(val::Result::Id(_)) | Value::Var(_) => false, + Value::Array(inner) => inner.iter().all(is_static_value), + Value::Tuple(inner, _) => inner.iter().all(is_static_value), + Value::Closure(c) => c.fixed_args.iter().all(is_static_value), + _ => true, + } +} + #[derive(Default)] pub(crate) struct DbgContext { /// (`CallableId`, isAdjoint) -> Scope index diff --git a/source/compiler/qsc_partial_eval/src/tests/ir_functions.rs b/source/compiler/qsc_partial_eval/src/tests/ir_functions.rs index 7d62e9b4d0..9a621b352c 100644 --- a/source/compiler/qsc_partial_eval/src/tests/ir_functions.rs +++ b/source/compiler/qsc_partial_eval/src/tests/ir_functions.rs @@ -261,7 +261,7 @@ fn un_promoted_tuple_parameter_callee_is_inlined() { } #[test] -fn recursive_callee_is_inlined() { +fn recursive_callee_is_emitted() { let program = get_rir_program_with_adaptive_profile( r#" namespace Test { @@ -280,7 +280,13 @@ fn recursive_callee_is_inlined() { "#, ); - assert_ir_function_names(&program, &expect!["[]"]); + assert_ir_function_names( + &program, + &expect![[r#" + [ + "Recurse", + ]"#]], + ); } #[test] diff --git a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs index 743240a038..4b96363fe8 100644 --- a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs +++ b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs @@ -287,12 +287,6 @@ fn call_cyclic_function_with_dynamic_argument_yields_errors() { hi: 243, }, ), - CallToCyclicFunctionWithDynamicArg( - Span { - lo: 211, - hi: 243, - }, - ), ] "#]], ); @@ -303,26 +297,7 @@ fn call_cyclic_operation_with_classical_argument_yields_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_CLASSICAL_ARGUMENT, &expect![[r#" - [ - CyclicOperationSpec( - Span { - lo: 15, - hi: 23, - }, - ), - UseOfDynamicInt( - Span { - lo: 187, - hi: 199, - }, - ), - CallToCyclicOperation( - Span { - lo: 187, - hi: 199, - }, - ), - ] + [] "#]], ); } @@ -333,24 +308,12 @@ fn call_cyclic_operation_with_dynamic_argument_yields_errors() { CALL_TO_CYCLIC_OPERATION_WITH_DYNAMIC_ARGUMENT, &expect![[r#" [ - CyclicOperationSpec( - Span { - lo: 15, - hi: 23, - }, - ), UseOfDynamicInt( Span { lo: 212, hi: 244, }, ), - CallToCyclicOperation( - Span { - lo: 212, - hi: 244, - }, - ), ] "#]], ); diff --git a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs index b1c055d2ec..6fa087a6f7 100644 --- a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs +++ b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs @@ -251,14 +251,7 @@ fn call_cyclic_function_with_dynamic_argument_yields_error() { check_profile( CALL_TO_CYCLIC_FUNCTION_WITH_DYNAMIC_ARGUMENT, &expect![[r#" - [ - CallToCyclicFunctionWithDynamicArg( - Span { - lo: 211, - hi: 243, - }, - ), - ] + [] "#]], ); } @@ -268,20 +261,7 @@ fn call_cyclic_operation_with_classical_argument_yields_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_CLASSICAL_ARGUMENT, &expect![[r#" - [ - CyclicOperationSpec( - Span { - lo: 15, - hi: 23, - }, - ), - CallToCyclicOperation( - Span { - lo: 187, - hi: 199, - }, - ), - ] + [] "#]], ); } @@ -291,20 +271,7 @@ fn call_cyclic_operation_with_dynamic_argument_yields_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_DYNAMIC_ARGUMENT, &expect![[r#" - [ - CyclicOperationSpec( - Span { - lo: 15, - hi: 23, - }, - ), - CallToCyclicOperation( - Span { - lo: 212, - hi: 244, - }, - ), - ] + [] "#]], ); } diff --git a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers_and_floats.rs b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers_and_floats.rs index bcd333d0ab..f2d5ba86cf 100644 --- a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers_and_floats.rs +++ b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers_and_floats.rs @@ -242,14 +242,7 @@ fn call_cyclic_function_with_dynamic_argument_yields_error() { check_profile( CALL_TO_CYCLIC_FUNCTION_WITH_DYNAMIC_ARGUMENT, &expect![[r#" - [ - CallToCyclicFunctionWithDynamicArg( - Span { - lo: 211, - hi: 243, - }, - ), - ] + [] "#]], ); } @@ -259,20 +252,7 @@ fn call_cyclic_operation_with_classical_argument_yields_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_CLASSICAL_ARGUMENT, &expect![[r#" - [ - CyclicOperationSpec( - Span { - lo: 15, - hi: 23, - }, - ), - CallToCyclicOperation( - Span { - lo: 187, - hi: 199, - }, - ), - ] + [] "#]], ); } @@ -282,20 +262,7 @@ fn call_cyclic_operation_with_dynamic_argument_yields_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_DYNAMIC_ARGUMENT, &expect![[r#" - [ - CyclicOperationSpec( - Span { - lo: 15, - hi: 23, - }, - ), - CallToCyclicOperation( - Span { - lo: 212, - hi: 244, - }, - ), - ] + [] "#]], ); } diff --git a/source/compiler/qsc_passes/src/capabilitiesck/tests_base.rs b/source/compiler/qsc_passes/src/capabilitiesck/tests_base.rs index 4fa3dad001..0bd2fb7b1d 100644 --- a/source/compiler/qsc_passes/src/capabilitiesck/tests_base.rs +++ b/source/compiler/qsc_passes/src/capabilitiesck/tests_base.rs @@ -358,12 +358,6 @@ fn call_cyclic_function_with_dynamic_argument_yields_errors() { hi: 243, }, ), - CallToCyclicFunctionWithDynamicArg( - Span { - lo: 211, - hi: 243, - }, - ), ] "#]], ); @@ -374,26 +368,7 @@ fn call_cyclic_operation_with_classical_argument_yields_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_CLASSICAL_ARGUMENT, &expect![[r#" - [ - CyclicOperationSpec( - Span { - lo: 15, - hi: 23, - }, - ), - UseOfDynamicInt( - Span { - lo: 187, - hi: 199, - }, - ), - CallToCyclicOperation( - Span { - lo: 187, - hi: 199, - }, - ), - ] + [] "#]], ); } @@ -404,12 +379,6 @@ fn call_cyclic_operation_with_dynamic_argument_yields_errors() { CALL_TO_CYCLIC_OPERATION_WITH_DYNAMIC_ARGUMENT, &expect![[r#" [ - CyclicOperationSpec( - Span { - lo: 15, - hi: 23, - }, - ), UseOfDynamicBool( Span { lo: 212, @@ -422,12 +391,6 @@ fn call_cyclic_operation_with_dynamic_argument_yields_errors() { hi: 244, }, ), - CallToCyclicOperation( - Span { - lo: 212, - hi: 244, - }, - ), ] "#]], ); diff --git a/source/compiler/qsc_rca/src/analyzer.rs b/source/compiler/qsc_rca/src/analyzer.rs index 8a90552cd0..7406137ce1 100644 --- a/source/compiler/qsc_rca/src/analyzer.rs +++ b/source/compiler/qsc_rca/src/analyzer.rs @@ -2,8 +2,7 @@ // Licensed under the MIT License. use crate::{ - PackageStoreComputeProperties, core, cyclic_callables, - scaffolding::InternalPackageStoreComputeProperties, + PackageStoreComputeProperties, core, scaffolding::InternalPackageStoreComputeProperties, }; use qsc_data_structures::target::TargetCapabilityFlags; use qsc_fir::fir::{PackageId, PackageStore}; @@ -45,12 +44,6 @@ impl<'a> Analyzer<'a> { pub fn analyze_all(self) -> PackageStoreComputeProperties { let scaffolding = InternalPackageStoreComputeProperties::init(self.package_store); - // Then, we need to analyze the callable specializations with cycles. Otherwise, we cannot safely analyze the - // rest of the items without causing an infinite analysis loop. - let cyclic_callables_analyzer = - cyclic_callables::Analyzer::new(self.package_store, scaffolding); - let scaffolding = cyclic_callables_analyzer.analyze_all(); - // Now we can safely analyze the rest of the items. let core_analyzer = core::Analyzer::new(self.package_store, scaffolding, self.target_capabilities); @@ -64,13 +57,11 @@ impl<'a> Analyzer<'a> { #[must_use] pub fn analyze_package(self, package_id: PackageId) -> PackageStoreComputeProperties { - // Even when analyzing just one package we need to first analyze cyclic callables and then the rest of the items - // to avoid an infinite analysis loop. - let cyclic_callables_analyzer = - cyclic_callables::Analyzer::new(self.package_store, self.scaffolding); - let scaffolding = cyclic_callables_analyzer.analyze_package(package_id); - let core_analyzer = - core::Analyzer::new(self.package_store, scaffolding, self.target_capabilities); + let core_analyzer = core::Analyzer::new( + self.package_store, + self.scaffolding, + self.target_capabilities, + ); let result: PackageStoreComputeProperties = core_analyzer.analyze_package(package_id).into(); diff --git a/source/compiler/qsc_rca/src/core.rs b/source/compiler/qsc_rca/src/core.rs index 2b3ece3708..ff90b57386 100644 --- a/source/compiler/qsc_rca/src/core.rs +++ b/source/compiler/qsc_rca/src/core.rs @@ -351,7 +351,7 @@ impl<'a> Analyzer<'a> { value_kind, } } else { - self.analyze_expr_call_with_static_callee(callee_expr_id, args_expr_id, expr_type) + self.analyze_expr_call_with_static_callee(callee_expr_id, args_expr_id) }; // If this call happens within a dynamic scope, there might be additional runtime features being used. @@ -486,7 +486,6 @@ impl<'a> Analyzer<'a> { &mut self, callee_expr_id: ExprId, args_expr_id: ExprId, - expr_type: &Ty, ) -> ComputeKind { // Try to resolve the callee. let package_id = self.get_current_package_id(); @@ -515,23 +514,18 @@ impl<'a> Analyzer<'a> { }; if self.callee_in_active_contexts(&callee) { - assert_eq!( - expr_type, - &Ty::UNIT, - "output type for allowed recursive call should be Unit" - ); - // This is a recursive call, which we allow with some deferred capabilities checks at runtime. - // We treat the call as an unresolved callee, like above, such that partial evaluation will perform - // extra validation on the capabilities at runtime. - // This covers the corner case where a recursive call is made with a dynamic argument whose + // We treat the call as if it were an unresolved callee, like above, such that partial evaluation will perform + // extra validation on the capabilities at runtime. By adding the call expression to the list of unresolved callees, + // we ensure that the call is additionally validated at runtime regardless of any runtime flags. + // This runtime check covers the corner case where a recursive call is made with a dynamic argument whose // type is allowed to be dynamic but whose usage in later recursion could require additional // capabilities. self.get_current_application_instance_mut() .unresolved_callee_exprs .push(callee_expr_id); return ComputeKind::Dynamic { - runtime_features: RuntimeFeatureFlags::CallToUnresolvedCallee, + runtime_features: RuntimeFeatureFlags::empty(), value_kind: ValueKind::Constant, }; } diff --git a/source/compiler/qsc_rca/src/cycle_detection.rs b/source/compiler/qsc_rca/src/cycle_detection.rs deleted file mode 100644 index 61356506c2..0000000000 --- a/source/compiler/qsc_rca/src/cycle_detection.rs +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::common::{ - FunctorAppExt, Local, LocalKind, LocalSpecId, initialize_locals_map, try_resolve_callee, -}; -use qsc_fir::{ - fir::{ - Block, BlockId, CallableDecl, CallableImpl, Expr, ExprId, ExprKind, Item, ItemKind, - LocalVarId, Mutability, Package, PackageId, PackageLookup, PackageStore, Pat, PatId, - PatKind, Res, SpecDecl, Stmt, StmtId, StmtKind, - }, - ty::FunctorSetValue, - visit::{Visitor, walk_expr}, -}; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::collections::hash_map::Entry; - -pub struct CycleDetector<'a> { - package_id: PackageId, - package: &'a Package, - stack: CallStack, - specializations_locals: FxHashMap>, - specializations_with_cycles: FxHashSet, - store: &'a PackageStore, -} - -impl<'a> CycleDetector<'a> { - pub fn new(package_id: PackageId, package: &'a Package, store: &'a PackageStore) -> Self { - Self { - package_id, - package, - stack: CallStack::default(), - specializations_locals: FxHashMap::default(), - specializations_with_cycles: FxHashSet::::default(), - store, - } - } - - pub fn detect_specializations_with_cycles(mut self) -> Vec { - self.visit_package(self.package, self.store); - self.specializations_with_cycles.drain().collect() - } - - fn map_pat_to_expr(&mut self, mutability: Mutability, pat_id: PatId, expr_id: ExprId) { - let pat = self.get_pat(pat_id); - match &pat.kind { - PatKind::Bind(ident) => { - let local_spec_id = self.stack.peak(); - let locals_map = self - .specializations_locals - .get_mut(local_spec_id) - .expect("node map should exist"); - let kind = match mutability { - Mutability::Immutable => LocalKind::Immutable(expr_id), - Mutability::Mutable => LocalKind::Mutable(None), - }; - locals_map.insert( - ident.id, - Local { - var: ident.id, - kind, - }, - ); - } - PatKind::Tuple(pats) => { - let expr = self.get_expr(expr_id); - if let ExprKind::Tuple(exprs) = &expr.kind { - for (pat_id, expr_id) in pats.iter().zip(exprs.iter()) { - self.map_pat_to_expr(mutability, *pat_id, *expr_id); - } - } - } - PatKind::Discard => {} - } - } - - fn walk_callable_decl(&mut self, local_spec_id: LocalSpecId, callable_decl: &'a CallableDecl) { - // We only need to go deeper for non-intrinsic callables. - let CallableImpl::Spec(spec_impl) = &callable_decl.implementation else { - return; - }; - - let spec_decl = match local_spec_id.functor_set_value { - FunctorSetValue::Empty => &spec_impl.body, - FunctorSetValue::Adj => spec_impl - .adj - .as_ref() - .expect("adj specialization should exist"), - FunctorSetValue::Ctl => spec_impl - .ctl - .as_ref() - .expect("ctl specialization should exist"), - FunctorSetValue::CtlAdj => spec_impl - .ctl_adj - .as_ref() - .expect("ctl_adj specialization should exist"), - }; - self.walk_spec_decl(local_spec_id, spec_decl); - } - - fn walk_call_expr(&mut self, callee: ExprId, args: ExprId) { - // Visit the arguments expression in case it triggers a call already in the stack. - self.visit_expr(args); - - // Visit the callee if it resolves to something. - let local_spec_id = self.stack.peak(); - let locals_map = self - .specializations_locals - .get_mut(local_spec_id) - .expect("node map should exist"); - let (maybe_callee, _) = - try_resolve_callee(callee, self.package_id, self.package, locals_map); - if let Some(callee) = maybe_callee { - // We are not interested in visiting callables outside this package. - if callee.item.package != self.package_id { - return; - } - let item = self.package.get_item(callee.item.item); - self.handle_item(item, &callee); - } - } - - fn handle_item(&mut self, item: &'a Item, callee: &crate::common::Callee) { - match &item.kind { - ItemKind::Callable(callable_decl) => self.walk_callable_decl( - (callee.item.item, callee.functor_app.functor_set_value()).into(), - callable_decl, - ), - ItemKind::Namespace(_, _) => panic!("calls to namespaces are invalid"), - ItemKind::Export(_, Res::Item(id)) => { - // resolve the item, which may exist in another package - let item = self.resolve_item(*id); - self.handle_item(item, callee); - } - ItemKind::Export(_, _) | ItemKind::Ty(_, _) => { - // Skip types and unresolved exports. - } - } - } - - fn resolve_item(&self, item: qsc_fir::fir::ItemId) -> &'a Item { - let package_id = item.package; - let package = self.store.get(package_id); - package.get_item(item.item) - } - - fn walk_spec_decl(&mut self, local_spec_id: LocalSpecId, spec_decl: &'a SpecDecl) { - // If the specialization is already in the stack, it means the callable has a cycle. - if self.stack.contains(&local_spec_id) { - self.specializations_with_cycles.insert(local_spec_id); - return; - } - - // If this is the first time we are walking this specialization, create a node map for it. - if let Entry::Vacant(entry) = self.specializations_locals.entry(local_spec_id) { - let ItemKind::Callable(callable_decl) = - &self.package.get_item(local_spec_id.callable).kind - else { - panic!("item must be a callable"); - }; - - let input_params = self.package.derive_callable_input_params(callable_decl); - let locals_map = initialize_locals_map(&input_params); - entry.insert(locals_map); - } - - // Push the callable specialization to the stack, visit it and then pop it. - self.stack.push(local_spec_id); - self.visit_spec_decl(spec_decl); - _ = self.stack.pop(); - } - - fn walk_local_stmt(&mut self, mutability: Mutability, pat_id: PatId, expr_id: ExprId) { - self.map_pat_to_expr(mutability, pat_id, expr_id); - self.visit_expr(expr_id); - } -} - -impl<'a> Visitor<'a> for CycleDetector<'a> { - fn get_block(&self, id: BlockId) -> &'a Block { - self.package - .blocks - .get(id) - .expect("couldn't find block in FIR") - } - - fn get_expr(&self, id: ExprId) -> &'a Expr { - self.package - .exprs - .get(id) - .expect("couldn't find expr in FIR") - } - - fn get_pat(&self, id: PatId) -> &'a Pat { - self.package.pats.get(id).expect("couldn't find pat in FIR") - } - - fn get_stmt(&self, id: StmtId) -> &'a Stmt { - self.package - .stmts - .get(id) - .expect("couldn't find stmt in FIR") - } - - fn visit_callable_decl(&mut self, _: &'a CallableDecl) { - panic!("visiting a callable declaration through this method is unexpected"); - } - - fn visit_expr(&mut self, expr_id: ExprId) { - let expr = self.get_expr(expr_id); - if let ExprKind::Call(callee, args) = &expr.kind { - self.walk_call_expr(*callee, *args); - return; - } - walk_expr(self, expr_id); - } - - fn visit_item(&mut self, item: &'a Item) { - // We are only interested in visiting callables. - let ItemKind::Callable(callable_decl) = &item.kind else { - return; - }; - - // We are only interested in non-intrinsic callables. - let CallableImpl::Spec(spec_impl) = &callable_decl.implementation else { - return; - }; - - // Visit the body specialization. - self.walk_spec_decl((item.id, FunctorSetValue::Empty).into(), &spec_impl.body); - - // Visit the adj specialization. - if let Some(adj_decl) = &spec_impl.adj { - self.walk_spec_decl((item.id, FunctorSetValue::Adj).into(), adj_decl); - } - - // Visit the ctl specialization. - if let Some(ctl_decl) = &spec_impl.ctl { - self.walk_spec_decl((item.id, FunctorSetValue::Ctl).into(), ctl_decl); - } - - // Visit the ctl_adj specialization. - if let Some(ctl_adj_decl) = &spec_impl.ctl_adj { - self.walk_spec_decl((item.id, FunctorSetValue::CtlAdj).into(), ctl_adj_decl); - } - } - - fn visit_package(&mut self, package: &'a Package, _: &PackageStore) { - // We are only interested in visiting items. - package.items.values().for_each(|i| self.visit_item(i)); - } - - fn visit_spec_decl(&mut self, decl: &'a SpecDecl) { - // For cycle detection we only need to visit the specialization block. - self.visit_block(decl.block); - } - - fn visit_stmt(&mut self, stmt_id: StmtId) { - let stmt = self.get_stmt(stmt_id); - match &stmt.kind { - StmtKind::Item(_) => {} - StmtKind::Expr(expr_id) | StmtKind::Semi(expr_id) => self.visit_expr(*expr_id), - StmtKind::Local(mutability, pat_id, expr_id) => { - self.walk_local_stmt(*mutability, *pat_id, *expr_id); - } - } - } -} - -#[derive(Default)] -struct CallStack { - set: FxHashSet, - stack: Vec, -} - -impl CallStack { - fn contains(&self, value: &LocalSpecId) -> bool { - self.set.contains(value) - } - - fn peak(&self) -> &LocalSpecId { - self.stack.last().expect("stack should not be empty") - } - - fn pop(&mut self) -> LocalSpecId { - let popped = self.stack.pop().expect("stack should not be empty"); - self.set.remove(&popped); - popped - } - - fn push(&mut self, value: LocalSpecId) { - self.set.insert(value); - self.stack.push(value); - } -} diff --git a/source/compiler/qsc_rca/src/cyclic_callables.rs b/source/compiler/qsc_rca/src/cyclic_callables.rs deleted file mode 100644 index 552378dc87..0000000000 --- a/source/compiler/qsc_rca/src/cyclic_callables.rs +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -use crate::{ - ApplicationGeneratorSet, ArrayParamApplication, ComputeKind, ElementParamApplication, - ParamApplication, RuntimeFeatureFlags, ValueKind, common::LocalSpecId, - cycle_detection::CycleDetector, scaffolding::InternalPackageStoreComputeProperties, -}; -use qsc_fir::{ - extensions::InputParam, - fir::{ - Block, BlockId, CallableDecl, CallableImpl, CallableKind, Expr, ExprId, Global, Item, - Package, PackageId, PackageStore, PackageStoreLookup, Pat, PatId, SpecImpl, Stmt, StmtId, - }, - ty::{FunctorSetValue, Ty}, - visit::{self, Visitor}, -}; - -pub struct Analyzer<'a> { - package_store: &'a PackageStore, - package_store_compute_properties: InternalPackageStoreComputeProperties, - current_package: Option, - current_application_generator_set: Option, -} - -impl<'a> Analyzer<'a> { - pub fn new( - package_store: &'a PackageStore, - package_store_compute_properties: InternalPackageStoreComputeProperties, - ) -> Self { - Self { - package_store, - package_store_compute_properties, - current_package: None, - current_application_generator_set: None, - } - } - - pub fn analyze_all(mut self) -> InternalPackageStoreComputeProperties { - for (package_id, package) in self.package_store { - self.analyze_package_internal(package_id, package); - } - self.package_store_compute_properties - } - - pub fn analyze_package( - mut self, - package_id: PackageId, - ) -> InternalPackageStoreComputeProperties { - let package = self.package_store.get(package_id); - self.analyze_package_internal(package_id, package); - self.package_store_compute_properties - } - - fn analyze_cyclic_specialization(&mut self, spec_id: LocalSpecId) { - let package_id = self.get_current_package(); - - // Do nothing if the compute properties for the specialization are already populated. - if self - .package_store_compute_properties - .find_specialization((package_id, spec_id).into()) - .is_some() - { - return; - } - - let Some(Global::Callable(callable)) = self - .package_store - .get_global((package_id, spec_id.callable).into()) - else { - panic!("global item should exist and it should be a global"); - }; - - let CallableImpl::Spec(spec_impl) = &callable.implementation else { - panic!("callable implementation should not be intrinsic"); - }; - - // Create the application generator set differently depending on whether the callable is a function or an - // operation. - let package = self.package_store.get(package_id); - let input_params = package.derive_callable_input_params(callable); - let application_generator_set = match callable.kind { - CallableKind::Function => { - Self::create_function_specialization_application_generator_set( - &input_params, - &callable.output, - ) - } - CallableKind::Operation if callable.output != Ty::UNIT => { - create_operation_specialization_application_generator_set( - &input_params, - &callable.output, - ) - } - CallableKind::Operation => { - // This is an operation with return type `Unit`, so any recursive calls into it will be - // handled by normal analysis later. Leave the compute properties unpopulated. - return; - } - }; - - // Find the specialization. - let spec_decl = match spec_id.functor_set_value { - FunctorSetValue::Empty => &spec_impl.body, - FunctorSetValue::Adj => spec_impl - .adj - .as_ref() - .expect("adj specialization should exist"), - FunctorSetValue::Ctl => spec_impl - .ctl - .as_ref() - .expect("ctl specialization should exist"), - FunctorSetValue::CtlAdj => spec_impl - .ctl_adj - .as_ref() - .expect("ctl_adj specializatiob should exist"), - }; - - // First visit the specialization to propagate the application generator set throughout all the relevant - // sub-elements. - // Then, insert the application generator set into the package store compute properties data structure. - self.current_application_generator_set = Some(application_generator_set); - self.visit_spec_decl(spec_decl); - self.package_store_compute_properties.insert_spec( - (package_id, spec_id).into(), - self.get_current_application_generator_set().clone(), - ); - self.current_application_generator_set = None; - } - - fn analyze_package_internal(&mut self, package_id: PackageId, package: &'a Package) { - self.current_package = Some(package_id); - self.visit_package(package, self.package_store); - self.current_package = None; - } - - fn create_function_specialization_application_generator_set( - input_params: &Vec, - output_type: &Ty, - ) -> ApplicationGeneratorSet { - // Determine each dynamic param application. - let mut dynamic_param_applications = - Vec::::with_capacity(input_params.len()); - for param in input_params { - // If any parameter is dynamic, we assume the output of a function with cycles requires a variable value kind. - let value_kind = ValueKind::new_variable_from_type(output_type); - - // Since using cyclic functions with dynamic parameters requires advanced runtime capabilities, we use the - // corresponding runtime feature. - let param_compute_kind = ComputeKind::Dynamic { - runtime_features: RuntimeFeatureFlags::CallToCyclicFunctionWithDynamicArg, - value_kind, - }; - - // Create a parameter application depending on the parameter type. - let param_application = match ¶m.ty { - Ty::Array(_) => ParamApplication::Array(ArrayParamApplication { - constant_content: param_compute_kind, - static_size: param_compute_kind, - dynamic_size: param_compute_kind, - }), - _ => ParamApplication::Element(ElementParamApplication { - constant: param_compute_kind, - variable: param_compute_kind, - }), - }; - dynamic_param_applications.push(param_application); - } - - ApplicationGeneratorSet { - // Functions are inherently classically pure, so when passed static parameters their output is static. - inherent: ComputeKind::Static, - dynamic_param_applications, - } - } - - fn get_current_application_generator_set(&self) -> &ApplicationGeneratorSet { - self.current_application_generator_set - .as_ref() - .expect("current application generator set should be valid") - } - - fn get_current_package(&self) -> PackageId { - self.current_package - .expect("current package should be valid") - } -} - -impl<'a> Visitor<'a> for Analyzer<'a> { - fn get_block(&self, id: BlockId) -> &'a Block { - let package_id = self.get_current_package(); - self.package_store.get_block((package_id, id).into()) - } - - fn get_expr(&self, id: ExprId) -> &'a Expr { - let package_id = self.get_current_package(); - self.package_store.get_expr((package_id, id).into()) - } - - fn get_pat(&self, _: PatId) -> &'a Pat { - // Should never be used. - unimplemented!() - } - - fn get_stmt(&self, id: StmtId) -> &'a Stmt { - let package_id = self.get_current_package(); - self.package_store.get_stmt((package_id, id).into()) - } - - fn visit_callable_decl(&mut self, _: &'a CallableDecl) { - // Should never be used. - unimplemented!(); - } - - fn visit_callable_impl(&mut self, _: &'a CallableImpl) { - // Should never be used. - unimplemented!(); - } - - fn visit_expr(&mut self, expr: ExprId) { - // First, visit the expression as it would normally be done. - visit::walk_expr(self, expr); - - // Then, insert the application generator set into the package store compute properties data structure. - let package_id = self.get_current_package(); - let application_generator_set = self.get_current_application_generator_set().clone(); - self.package_store_compute_properties - .insert_expr((package_id, expr).into(), application_generator_set); - } - - fn visit_item(&mut self, _: &'a Item) { - // Should never be used. - unimplemented!(); - } - - fn visit_package(&mut self, package: &'a Package, store: &PackageStore) { - let package_id = self.get_current_package(); - let cycle_detector = CycleDetector::new(package_id, package, store); - let specializations_with_cycles = cycle_detector.detect_specializations_with_cycles(); - for cyclic_specialization in specializations_with_cycles { - self.analyze_cyclic_specialization(cyclic_specialization); - } - } - - fn visit_block(&mut self, block: BlockId) { - // First, visit the block like it would normally be done. - visit::walk_block(self, block); - - // Then, insert the application generator set into the package store compute properties data structure. - let package_id = self.get_current_package(); - let application_generator_set = self.get_current_application_generator_set().clone(); - self.package_store_compute_properties - .insert_block((package_id, block).into(), application_generator_set); - } - - fn visit_pat(&mut self, _: PatId) { - // Do nothing. - } - - fn visit_spec_impl(&mut self, _: &'a SpecImpl) { - // Should never be used. - unimplemented!(); - } - - fn visit_stmt(&mut self, stmt: StmtId) { - // First, visit the statement like it would normally be done. - visit::walk_stmt(self, stmt); - - // Then, insert the application generator set into the package store compute properties data structure. - let package_id = self.get_current_package(); - let application_generator_set = self.get_current_application_generator_set().clone(); - self.package_store_compute_properties - .insert_stmt((package_id, stmt).into(), application_generator_set); - } -} - -fn create_operation_specialization_application_generator_set( - input_params: &Vec, - output_type: &Ty, -) -> ApplicationGeneratorSet { - // Since operations can allocate and measure qubits freely, we assume its compute kind is quantum and that their - // value kind is dynamic. - let value_kind = ValueKind::new_variable_from_type(output_type); - let inherent_compute_kind = ComputeKind::Dynamic { - runtime_features: RuntimeFeatureFlags::CyclicOperationSpec, - value_kind, - }; - - // The compute kind of a cyclic operation for all dynamic parameter applications is the same as its inherent - // compute kind. - let mut dynamic_param_applications = Vec::::with_capacity(input_params.len()); - for param in input_params { - // Create a parameter application depending on the parameter type. - let param_application = match ¶m.ty { - Ty::Array(_) => ParamApplication::Array(ArrayParamApplication { - constant_content: inherent_compute_kind, - static_size: inherent_compute_kind, - dynamic_size: inherent_compute_kind, - }), - _ => ParamApplication::Element(ElementParamApplication { - constant: inherent_compute_kind, - variable: inherent_compute_kind, - }), - }; - dynamic_param_applications.push(param_application); - } - - ApplicationGeneratorSet { - inherent: inherent_compute_kind, - dynamic_param_applications, - } -} diff --git a/source/compiler/qsc_rca/src/lib.rs b/source/compiler/qsc_rca/src/lib.rs index 99c5eeefda..0f7a01d71a 100644 --- a/source/compiler/qsc_rca/src/lib.rs +++ b/source/compiler/qsc_rca/src/lib.rs @@ -13,8 +13,6 @@ mod analyzer; mod applications; mod common; mod core; -mod cycle_detection; -mod cyclic_callables; pub mod errors; #[cfg(debug_assertions)] mod invariants; diff --git a/source/compiler/qsc_rca/src/tests/calls.rs b/source/compiler/qsc_rca/src/tests/calls.rs index 6f7a620d2a..42c0bca8d1 100644 --- a/source/compiler/qsc_rca/src/tests/calls.rs +++ b/source/compiler/qsc_rca/src/tests/calls.rs @@ -23,7 +23,9 @@ fn check_rca_for_call_to_cyclic_function_with_classical_argument() { package_store_compute_properties, &expect![[r#" ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: "#]], ); } @@ -49,7 +51,7 @@ fn check_rca_for_call_to_cyclic_function_with_dynamic_argument() { &expect![[r#" ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicInt | CallToCyclicFunctionWithDynamicArg | QubitAllocation) + runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicInt | QubitAllocation) value_kind: Variable dynamic_param_applications: "#]], ); @@ -75,8 +77,8 @@ fn check_rca_for_call_to_cyclic_operation_with_classical_argument() { &expect![[r#" ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(UseOfDynamicInt | CallToCyclicOperation) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: "#]], ); } @@ -102,7 +104,7 @@ fn check_rca_for_call_to_cyclic_operation_with_dynamic_argument() { &expect![[r#" ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicInt | CallToCyclicOperation | QubitAllocation) + runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicInt | QubitAllocation) value_kind: Variable dynamic_param_applications: "#]], ); diff --git a/source/compiler/qsc_rca/src/tests/cycles.rs b/source/compiler/qsc_rca/src/tests/cycles.rs index 4690689086..d324332b73 100644 --- a/source/compiler/qsc_rca/src/tests/cycles.rs +++ b/source/compiler/qsc_rca/src/tests/cycles.rs @@ -21,15 +21,17 @@ fn check_rca_for_one_function_cycle() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -56,15 +58,17 @@ fn check_rca_for_two_functions_cycle() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -77,15 +81,17 @@ fn check_rca_for_two_functions_cycle() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -114,15 +120,17 @@ fn check_rca_for_three_functions_cycle() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -134,15 +142,17 @@ fn check_rca_for_three_functions_cycle() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -154,15 +164,17 @@ fn check_rca_for_three_functions_cycle() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -186,15 +198,17 @@ fn check_rca_for_indirect_function_cycle() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -220,15 +234,17 @@ fn check_rca_for_indirect_chain_function_cycle() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -252,15 +268,17 @@ fn check_rca_for_indirect_tuple_function_cycle() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -284,15 +302,17 @@ fn check_rca_for_function_cycle_within_binding() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -317,15 +337,17 @@ fn check_rca_for_function_cycle_within_assignment() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -348,15 +370,17 @@ fn check_rca_for_function_cycle_within_return() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -380,15 +404,17 @@ fn check_rca_for_function_cycle_within_tuple() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -437,24 +463,26 @@ fn check_rca_for_function_cycle_within_call_input() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) + runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicInt | UseOfDynamicallySizedArray | CallToDynamicCallee | CallToUnresolvedCallee | LoopWithDynamicCondition) value_kind: Variable [1]: [Parameter Type Array] ArrayParamApplication: constant_content: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + value_kind: Constant static_size: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) + runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) value_kind: Variable dynamic_size: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) + runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicInt | UseOfDynamicRange | UseOfDynamicArray | UseOfDynamicallySizedArray | CallToUnresolvedCallee | UseOfDynamicIndex | ReturnWithinDynamicScope) value_kind: Variable adj: ctl: @@ -482,14 +510,16 @@ fn check_rca_for_function_cycle_within_if_block() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) + runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicInt) value_kind: Variable adj: ctl: @@ -517,15 +547,17 @@ fn check_rca_for_function_cycle_within_if_condition() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -551,15 +583,17 @@ fn check_rca_for_function_cycle_within_for_block() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -585,15 +619,17 @@ fn check_rca_for_function_cycle_within_while_block() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -618,15 +654,17 @@ fn check_rca_for_function_cycle_within_while_condition() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -649,29 +687,31 @@ fn check_rca_for_multi_param_recursive_bool_function() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant [1]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant [2]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -694,31 +734,33 @@ fn check_rca_for_multi_param_recursive_unit_function() { &expect![[r#" Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: - inherent: Static + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant [1]: [Parameter Type Array] ArrayParamApplication: constant_content: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant static_size: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant dynamic_size: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) + runtime_features: RuntimeFeatureFlags(UseOfDynamicallySizedArray) value_kind: Constant [2]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToCyclicFunctionWithDynamicArg) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant adj: ctl: @@ -743,16 +785,16 @@ fn check_rca_for_result_recursive_operation() { Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -776,37 +818,37 @@ fn check_rca_for_multi_param_result_recursive_operation() { Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant [1]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant [2]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant [3]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -830,15 +872,15 @@ fn check_rca_for_operation_body_recursion() { Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant adj: ctl: @@ -868,27 +910,27 @@ fn check_rca_for_operation_body_adj_recursion() { Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant adj: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant ctl: ctl-adj: "#]], @@ -917,28 +959,28 @@ fn check_rca_for_operation_body_ctl_recursion() { Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant adj: ctl: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant ctl-adj: "#]], ); @@ -966,28 +1008,28 @@ fn check_rca_for_operation_multi_controlled_functor_recursion() { Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant adj: ctl: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant ctl-adj: "#]], ); @@ -1010,16 +1052,16 @@ fn check_rca_for_operation_body_recursion_non_unit_return() { Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CyclicOperationSpec) - value_kind: Variable + runtime_features: RuntimeFeatureFlags(0x0) + value_kind: Constant adj: ctl: ctl-adj: "#]], @@ -1046,15 +1088,15 @@ fn check_rca_for_operation_body_recursion_preserves_inherent_capabilities() { Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | CallToUnresolvedCallee | QubitAllocation) + runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | QubitAllocation) value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | CallToUnresolvedCallee | QubitAllocation) + runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | QubitAllocation) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicQubit | CallToUnresolvedCallee | QubitAllocation) + runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicQubit | QubitAllocation) value_kind: Constant adj: ctl: @@ -1088,15 +1130,15 @@ fn check_rca_for_two_operation_cycle() { Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant adj: ctl: @@ -1111,18 +1153,47 @@ fn check_rca_for_two_operation_cycle() { Callable: CallableComputeProperties: body: ApplicationsGeneratorSet: inherent: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant dynamic_param_applications: [0]: [Parameter Type Element] ElementParamApplication: constant: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant variable: Dynamic: - runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee) + runtime_features: RuntimeFeatureFlags(0x0) value_kind: Constant adj: ctl: ctl-adj: "#]], ); } + +#[test] +fn check_rca_for_call_to_preparepurestated_cyclic_library_operation() { + let mut compilation_context = CompilationContext::default(); + compilation_context.update( + r#" + operation Main() : Unit { + use q = Qubit(); + Std.StatePreparation.PreparePureStateD([0.0, 1.0], [q]); + } + "#, + ); + + check_callable_compute_properties( + &compilation_context.fir_store, + compilation_context.get_compute_properties(), + "Main", + &expect![[r#" + Callable: CallableComputeProperties: + body: ApplicationsGeneratorSet: + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(CallToUnresolvedCallee | QubitAllocation) + value_kind: Constant + dynamic_param_applications: + adj: + ctl: + ctl-adj: "#]], + ); +} From 53aad8d6c874b7a53fb3b2739443ae32c274e73f Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Thu, 18 Jun 2026 21:51:39 -0700 Subject: [PATCH 3/5] Remove unused capabilities for cyclic checks --- source/compiler/qsc_partial_eval/src/lib.rs | 11 +-- .../compiler/qsc_passes/src/capabilitiesck.rs | 84 ++----------------- source/compiler/qsc_rca/src/core.rs | 11 --- source/compiler/qsc_rca/src/errors.rs | 33 -------- source/compiler/qsc_rca/src/lib.rs | 51 ++++------- 5 files changed, 30 insertions(+), 160 deletions(-) diff --git a/source/compiler/qsc_partial_eval/src/lib.rs b/source/compiler/qsc_partial_eval/src/lib.rs index 0157a40019..6a49661f8e 100644 --- a/source/compiler/qsc_partial_eval/src/lib.rs +++ b/source/compiler/qsc_partial_eval/src/lib.rs @@ -2126,18 +2126,13 @@ impl<'a> PartialEvaluator<'a> { return false; } - // Recursive callables, callables whose bodies contain calls that RCA could not + // Callables whose bodies contain calls that RCA could not // statically resolve, and callables that transitively allocate qubits (unless dynamic qubit // allocation is enabled) must be inlined. These are surfaced as inherent runtime features of - // the specialization by RCA. Recursion appears as `CyclicOperationSpec`/ - // `CallToCyclicOperation`, while unresolved-callee paths surface as + // the specialization by RCA. Unresolved-callee paths surface as // `CallToUnresolvedCallee`; in all such cases the specialization is inlined. let inherent_features = self.spec_inherent_runtime_features(store_item_id, functor_app); - if inherent_features.intersects( - RuntimeFeatureFlags::CyclicOperationSpec - | RuntimeFeatureFlags::CallToCyclicOperation - | RuntimeFeatureFlags::CallToUnresolvedCallee, - ) { + if inherent_features.contains(RuntimeFeatureFlags::CallToUnresolvedCallee) { return false; } if inherent_features.contains(RuntimeFeatureFlags::QubitAllocation) diff --git a/source/compiler/qsc_passes/src/capabilitiesck.rs b/source/compiler/qsc_passes/src/capabilitiesck.rs index 2efc3d7c8b..35f8904d23 100644 --- a/source/compiler/qsc_passes/src/capabilitiesck.rs +++ b/source/compiler/qsc_passes/src/capabilitiesck.rs @@ -24,14 +24,14 @@ use qsc_fir::{ Item, ItemKind, LocalItemId, LocalVarId, Package, PackageLookup, Pat, PatId, PatKind, Res, SpecDecl, SpecImpl, Stmt, StmtId, StmtKind, }, - ty::{FunctorSetValue, Prim, Ty}, + ty::{Prim, Ty}, visit::{Visitor, walk_callable_decl}, }; use qsc_lowerer::map_hir_package_to_fir; use qsc_rca::{ - Analyzer, ComputeKind, ItemComputeProperties, PackageComputeProperties, - PackageStoreComputeProperties, RuntimeFeatureFlags, + Analyzer, ComputeKind, PackageComputeProperties, PackageStoreComputeProperties, + RuntimeFeatureFlags, errors::{Error, generate_errors_from_runtime_features, get_missing_runtime_features}, }; use rustc_hash::FxHashMap; @@ -171,18 +171,18 @@ impl<'a> Visitor<'a> for Checker<'a> { fn visit_callable_impl(&mut self, callable_impl: &'a CallableImpl) { match callable_impl { CallableImpl::Intrinsic | CallableImpl::SimulatableIntrinsic(_) => { - self.check_spec_decl(FunctorSetValue::Empty, None); + self.check_spec_decl(None); } CallableImpl::Spec(spec_impl) => { - self.check_spec_decl(FunctorSetValue::Empty, Some(&spec_impl.body)); + self.check_spec_decl(Some(&spec_impl.body)); spec_impl.adj.iter().for_each(|spec_decl| { - self.check_spec_decl(FunctorSetValue::Adj, Some(spec_decl)); + self.check_spec_decl(Some(spec_decl)); }); spec_impl.ctl.iter().for_each(|spec_decl| { - self.check_spec_decl(FunctorSetValue::Ctl, Some(spec_decl)); + self.check_spec_decl(Some(spec_decl)); }); spec_impl.ctl_adj.iter().for_each(|spec_decl| { - self.check_spec_decl(FunctorSetValue::CtlAdj, Some(spec_decl)); + self.check_spec_decl(Some(spec_decl)); }); } } @@ -341,63 +341,7 @@ impl<'a> Checker<'a> { } } - fn check_spec_decl( - &mut self, - functor_set_value: FunctorSetValue, - spec_decl: Option<&'a SpecDecl>, - ) { - let current_callable_id = self.get_current_callable(); - let ItemComputeProperties::Callable(callable_compute_properties) = - self.compute_properties.get_item(current_callable_id) - else { - panic!("expected callable variant of item compute properties"); - }; - - let spec_compute_properties = match functor_set_value { - FunctorSetValue::Empty => &callable_compute_properties.body, - FunctorSetValue::Adj => callable_compute_properties - .adj - .as_ref() - .expect("adj specialization is none"), - FunctorSetValue::Ctl => callable_compute_properties - .ctl - .as_ref() - .expect("ctl specialization is none"), - FunctorSetValue::CtlAdj => callable_compute_properties - .ctl_adj - .as_ref() - .expect("ctl_adj specialization is none"), - }; - - if let ComputeKind::Dynamic { - runtime_features, .. - } = spec_compute_properties.inherent - { - let missing_features = - get_missing_runtime_features(runtime_features, self.target_capabilities); - let missing_spec_level_runtime_features = - get_spec_level_runtime_features(missing_features); - - // If there are any missing specialization-level runtime features, runtime features that affect the whole - // specialization, just generate errors for the missing specialization-level runtime features and do not - // generate statement-level or expression-level errors for these specializations. - if !missing_spec_level_runtime_features.is_empty() { - let current_callable = self - .package - .get_global(current_callable_id) - .expect("callable not present in package"); - let Global::Callable(callable_decl) = current_callable else { - panic!(""); - }; - - self.missing_features_map - .entry(callable_decl.name.span) - .and_modify(|f| *f |= missing_spec_level_runtime_features) - .or_insert(missing_spec_level_runtime_features); - return; - } - } - + fn check_spec_decl(&mut self, spec_decl: Option<&'a SpecDecl>) { // Visit the specialization block. if let Some(spec_decl) = spec_decl { self.visit_block(spec_decl.block); @@ -481,10 +425,6 @@ impl<'a> Checker<'a> { errors } - fn get_current_callable(&self) -> LocalItemId { - self.current_callable.expect("current callable is not set") - } - fn is_expr_auto_generated(&self, expr: &Expr) -> bool { if expr.span.hi == 0 && expr.span.lo == 0 { return true; @@ -506,12 +446,6 @@ impl<'a> Checker<'a> { } } -fn get_spec_level_runtime_features(runtime_features: RuntimeFeatureFlags) -> RuntimeFeatureFlags { - const SPEC_LEVEL_RUNTIME_FEATURES: RuntimeFeatureFlags = - RuntimeFeatureFlags::CyclicOperationSpec; - runtime_features & SPEC_LEVEL_RUNTIME_FEATURES -} - fn output_recording_runtime_features_for_ty(ty: &Ty) -> RuntimeFeatureFlags { match ty { Ty::Array(item) => output_recording_runtime_features_for_ty(item), diff --git a/source/compiler/qsc_rca/src/core.rs b/source/compiler/qsc_rca/src/core.rs index ff90b57386..9a18b347a3 100644 --- a/source/compiler/qsc_rca/src/core.rs +++ b/source/compiler/qsc_rca/src/core.rs @@ -468,17 +468,6 @@ impl<'a> Analyzer<'a> { compute_kind.aggregate_value_kind(value_kind); } - // To distinguish between a cyclic operation and a call to a cyclic operation, replace the cyclic operation - // runtime feature (if any) by a call to a cyclic operation. - if let ComputeKind::Dynamic { - runtime_features, .. - } = &mut compute_kind - && runtime_features.contains(RuntimeFeatureFlags::CyclicOperationSpec) - { - runtime_features.remove(RuntimeFeatureFlags::CyclicOperationSpec); - runtime_features.insert(RuntimeFeatureFlags::CallToCyclicOperation); - } - compute_kind } diff --git a/source/compiler/qsc_rca/src/errors.rs b/source/compiler/qsc_rca/src/errors.rs index 14b9249adf..6cf81f7e63 100644 --- a/source/compiler/qsc_rca/src/errors.rs +++ b/source/compiler/qsc_rca/src/errors.rs @@ -137,30 +137,6 @@ pub enum Error { #[diagnostic(code("Qsc.CapabilitiesCk.UseOfDynamicArrowOperation"))] UseOfDynamicArrowOperation(#[label] Span), - #[error("cannot call a cyclic function with a dynamic value as argument")] - #[diagnostic(help( - "calling a cyclic function with an argument value that depends on a measurement result is not supported by the configured target profile" - ))] - #[diagnostic(url("https://aka.ms/qdk.qir#call-to-cyclic-function-with-dynamic-argument"))] - #[diagnostic(code("Qsc.CapabilitiesCk.CallToCyclicFunctionWithDynamicArg"))] - CallToCyclicFunctionWithDynamicArg(#[label] Span), - - #[error("cannot define a cyclic operation specialization")] - #[diagnostic(help( - "operation specializations that contain call cycles are not supported by the configured target profile" - ))] - #[diagnostic(url("https://aka.ms/qdk.qir#cyclic-operation-definition"))] - #[diagnostic(code("Qsc.CapabilitiesCk.CyclicOperationSpec"))] - CyclicOperationSpec(#[label] Span), - - #[error("cannot call a cyclic operation")] - #[diagnostic(help( - "calling an operation specialization that contains call cycles is not supported by the configured target profile" - ))] - #[diagnostic(url("https://aka.ms/qdk.qir#call-to-cyclic-operation"))] - #[diagnostic(code("Qsc.CapabilitiesCk.CallToCyclicOperation"))] - CallToCyclicOperation(#[label] Span), - #[error("cannot call a function or operation whose resolution is dynamic")] #[diagnostic(help( "calling a function or operation whose resolution depends on a measurement result is not supported by the configured target profile" @@ -311,15 +287,6 @@ pub fn generate_errors_from_runtime_features( if runtime_features.contains(RuntimeFeatureFlags::UseOfDynamicArrowOperation) { errors.push(Error::UseOfDynamicArrowOperation(span)); } - if runtime_features.contains(RuntimeFeatureFlags::CallToCyclicFunctionWithDynamicArg) { - errors.push(Error::CallToCyclicFunctionWithDynamicArg(span)); - } - if runtime_features.contains(RuntimeFeatureFlags::CyclicOperationSpec) { - errors.push(Error::CyclicOperationSpec(span)); - } - if runtime_features.contains(RuntimeFeatureFlags::CallToCyclicOperation) { - errors.push(Error::CallToCyclicOperation(span)); - } if runtime_features.contains(RuntimeFeatureFlags::CallToDynamicCallee) { errors.push(Error::CallToDynamicCallee(span)); } diff --git a/source/compiler/qsc_rca/src/lib.rs b/source/compiler/qsc_rca/src/lib.rs index 0f7a01d71a..85d0372663 100644 --- a/source/compiler/qsc_rca/src/lib.rs +++ b/source/compiler/qsc_rca/src/lib.rs @@ -635,7 +635,7 @@ bitflags! { /// Runtime features represent anything a program can do that is more complex than executing quantum operations on /// statically allocated qubits and using constant arguments. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] - pub struct RuntimeFeatureFlags: u64 { + pub struct RuntimeFeatureFlags: u32 { /// Use of a dynamic `Bool`. const UseOfDynamicBool = 1 << 0; /// Use of a dynamic `Int`. @@ -662,46 +662,40 @@ bitflags! { const UseOfDynamicArrowFunction = 1 << 11; /// Use of a dynamic arrow operation. const UseOfDynamicArrowOperation = 1 << 12; - /// A function with cycles used with a dynamic argument. - const CallToCyclicFunctionWithDynamicArg = 1 << 13; - /// An operation specialization with cycles exists. - const CyclicOperationSpec = 1 << 14; - /// A call to an operation with cycles. - const CallToCyclicOperation = 1 << 15; /// A callee expression is dynamic. - const CallToDynamicCallee = 1 << 16; + const CallToDynamicCallee = 1 << 13; /// A callee expression could not be resolved to a specific callable. - const CallToUnresolvedCallee = 1 << 17; + const CallToUnresolvedCallee = 1 << 14; /// Performing a measurement within a dynamic scope. - const MeasurementWithinDynamicScope = 1 << 18; + const MeasurementWithinDynamicScope = 1 << 15; /// Use of a dynamic index to access or update an array. - const UseOfDynamicIndex = 1 << 19; + const UseOfDynamicIndex = 1 << 16; /// A return expression within a dynamic scope. - const ReturnWithinDynamicScope = 1 << 20; + const ReturnWithinDynamicScope = 1 << 17; /// A loop with a dynamic condition. - const LoopWithDynamicCondition = 1 << 21; + const LoopWithDynamicCondition = 1 << 18; /// Use of an advanced type as output of a computation. - const UseOfAdvancedOutput = 1 << 22; + const UseOfAdvancedOutput = 1 << 19; /// Use of a `Bool` as output of a computation. - const UseOfBoolOutput = 1 << 23; + const UseOfBoolOutput = 1 << 20; /// Use of a `Double` as output of a computation. - const UseOfDoubleOutput = 1 << 24; + const UseOfDoubleOutput = 1 << 21; /// Use of an `Int` as output of a computation. - const UseOfIntOutput = 1 << 25; + const UseOfIntOutput = 1 << 22; /// Use of a dynamic exponent in a computation. - const UseOfDynamicExponent = 1 << 26; + const UseOfDynamicExponent = 1 << 23; /// Use of a dynamic `Result` variable in a computation. - const UseOfDynamicResult = 1 << 27; + const UseOfDynamicResult = 1 << 24; /// Use of a dynamic tuple variable. - const UseOfDynamicTuple = 1 << 28; + const UseOfDynamicTuple = 1 << 25; /// A callee expression to a measurement. - const CallToCustomMeasurement = 1 << 29; + const CallToCustomMeasurement = 1 << 26; /// A callee expression to a reset. - const CallToCustomReset = 1 << 30; + const CallToCustomReset = 1 << 27; /// Use of a dynamic generic parameter. - const UseOfDynamicGeneric = 1 << 31; + const UseOfDynamicGeneric = 1 << 28; /// A callable allocates qubits (directly or transitively). - const QubitAllocation = 1 << 32; + const QubitAllocation = 1 << 29; } } @@ -766,15 +760,6 @@ impl RuntimeFeatureFlags { if self.contains(RuntimeFeatureFlags::UseOfDynamicArrowOperation) { capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } - if self.contains(RuntimeFeatureFlags::CallToCyclicFunctionWithDynamicArg) { - capabilities |= TargetCapabilityFlags::HigherLevelConstructs; - } - if self.contains(RuntimeFeatureFlags::CyclicOperationSpec) { - capabilities |= TargetCapabilityFlags::HigherLevelConstructs; - } - if self.contains(RuntimeFeatureFlags::CallToCyclicOperation) { - capabilities |= TargetCapabilityFlags::HigherLevelConstructs; - } if self.contains(RuntimeFeatureFlags::CallToDynamicCallee) { capabilities |= TargetCapabilityFlags::HigherLevelConstructs; } From 07ca15bca0aaf4a7146e0373eee7fe2c3788d898 Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Sun, 21 Jun 2026 22:10:11 -0700 Subject: [PATCH 4/5] Fix comparison check to allow result array concat --- source/compiler/qsc_partial_eval/src/lib.rs | 4 ++ source/compiler/qsc_rca/src/core.rs | 13 ++--- source/compiler/qsc_rca/src/tests/arrays.rs | 60 +++++++++++++++++++++ 3 files changed, 71 insertions(+), 6 deletions(-) diff --git a/source/compiler/qsc_partial_eval/src/lib.rs b/source/compiler/qsc_partial_eval/src/lib.rs index 6a49661f8e..e7e9686729 100644 --- a/source/compiler/qsc_partial_eval/src/lib.rs +++ b/source/compiler/qsc_partial_eval/src/lib.rs @@ -1946,6 +1946,10 @@ impl<'a> PartialEvaluator<'a> { "IntAsBigInt" if args_statically_known => { Ok(Value::BigInt(BigInt::from(args_value.unwrap_int()))) } + "DoubleAsStringWithPrecision" if args_statically_known => { + // Strings are not populated during partial evaluation, so leave this empty. + Ok(Value::String("".into())) + } // Otherwise, we will try to emit the call as a RIR instruction. _ => self.eval_expr_call_to_intrinsic_qis( diff --git a/source/compiler/qsc_rca/src/core.rs b/source/compiler/qsc_rca/src/core.rs index 9a18b347a3..2059f031ac 100644 --- a/source/compiler/qsc_rca/src/core.rs +++ b/source/compiler/qsc_rca/src/core.rs @@ -239,6 +239,7 @@ impl<'a> Analyzer<'a> { fn analyze_expr_bin_op( &mut self, + op: BinOp, lhs_expr_id: ExprId, rhs_expr_id: ExprId, expr_type: &Ty, @@ -263,10 +264,10 @@ impl<'a> Analyzer<'a> { } = &mut compute_kind { let lhs_expr_ty = &self.get_expr(lhs_expr_id).ty; - if is_any_result(lhs_expr_ty) { - // The only binary operators on result types are equality and inequality. In this path, we know - // at least one side of the comparison is dynamic (constant or variable), so the boolean that comes - // from the comparison must be variable. This is the critical source of dynamic variable values + if (op == BinOp::Eq || op == BinOp::Neq) && is_any_result(lhs_expr_ty) { + // When binary operators on result types are equality or inequality, the Boolean outcome may be a dynamic variable. + // In this path, we know at least one side of the comparison is dynamic (constant or variable), so the boolean + // that comes from the comparison must be variable. This is the critical source of dynamic variable values // in a program that operates on qubits measurements. *value_kind = ValueKind::Variable; } @@ -1895,8 +1896,8 @@ impl<'a> Visitor<'a> for Analyzer<'a> { ExprKind::BinOp(BinOp::Exp, lhs_expr_id, rhs_expr_id) => { self.analyze_expr_bin_op_exp(*lhs_expr_id, *rhs_expr_id) } - ExprKind::BinOp(_, lhs_expr_id, rhs_expr_id) => { - self.analyze_expr_bin_op(*lhs_expr_id, *rhs_expr_id, &expr.ty) + ExprKind::BinOp(op, lhs_expr_id, rhs_expr_id) => { + self.analyze_expr_bin_op(*op, *lhs_expr_id, *rhs_expr_id, &expr.ty) } ExprKind::Block(block_id) => self.analyze_expr_block(*block_id), ExprKind::Call(callee_expr_id, args_expr_id) => { diff --git a/source/compiler/qsc_rca/src/tests/arrays.rs b/source/compiler/qsc_rca/src/tests/arrays.rs index 0a21f3ef31..a5d725755b 100644 --- a/source/compiler/qsc_rca/src/tests/arrays.rs +++ b/source/compiler/qsc_rca/src/tests/arrays.rs @@ -94,6 +94,66 @@ fn check_rca_for_array_repeat_with_dynamic_result_value_and_classical_size() { ); } +#[test] +fn check_rca_for_concatenation_of_static_arrays() { + let mut compilation_context = CompilationContext::default(); + compilation_context.update( + r#" + let a = [1, 2, 3] + [4, 5, 6]; + a"#, + ); + let package_store_compute_properties = compilation_context.get_compute_properties(); + check_last_statement_compute_properties( + package_store_compute_properties, + &expect![[r#" + ApplicationsGeneratorSet: + inherent: Static + dynamic_param_applications: "#]], + ); +} + +#[test] +fn check_rca_for_concatenation_of_dynamic_variable_array() { + let mut compilation_context = CompilationContext::default(); + compilation_context.update( + r#" + use q = Qubit(); + let a = [1, 2, 3] + [4, 5, if M(q) == Zero { 6 } else { 7 }]; + a"#, + ); + let package_store_compute_properties = compilation_context.get_compute_properties(); + check_last_statement_compute_properties( + package_store_compute_properties, + &expect![[r#" + ApplicationsGeneratorSet: + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicInt | QubitAllocation) + value_kind: Variable + dynamic_param_applications: "#]], + ); +} + +#[test] +fn check_rca_for_concatenation_of_result_dynamic_constant_array() { + let mut compilation_context = CompilationContext::default(); + compilation_context.update( + r#" + use q = Qubit(); + let a = [M(q)] + [M(q)]; + a"#, + ); + let package_store_compute_properties = compilation_context.get_compute_properties(); + check_last_statement_compute_properties( + package_store_compute_properties, + &expect![[r#" + ApplicationsGeneratorSet: + inherent: Dynamic: + runtime_features: RuntimeFeatureFlags(QubitAllocation) + value_kind: Constant + dynamic_param_applications: "#]], + ); +} + #[test] fn check_rca_for_array_repeat_with_dynamic_bool_value_and_classical_size() { let mut compilation_context = CompilationContext::default(); From 0dfd2abf2381c88e40fa5c174c192f2b1b32975d Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Mon, 22 Jun 2026 11:45:36 -0700 Subject: [PATCH 5/5] Fix test names --- .../qsc_passes/src/capabilitiesck/tests_adaptive.rs | 2 +- .../src/capabilitiesck/tests_adaptive_plus_integers.rs | 6 +++--- .../tests_adaptive_plus_integers_and_floats.rs | 6 +++--- source/compiler/qsc_passes/src/capabilitiesck/tests_base.rs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs index 4b96363fe8..bd1cff6750 100644 --- a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs +++ b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive.rs @@ -293,7 +293,7 @@ fn call_cyclic_function_with_dynamic_argument_yields_errors() { } #[test] -fn call_cyclic_operation_with_classical_argument_yields_errors() { +fn call_cyclic_operation_with_classical_argument_yields_no_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_CLASSICAL_ARGUMENT, &expect![[r#" diff --git a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs index 6fa087a6f7..54c1d43f42 100644 --- a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs +++ b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers.rs @@ -247,7 +247,7 @@ fn call_cyclic_function_with_classical_argument_yields_no_errors() { } #[test] -fn call_cyclic_function_with_dynamic_argument_yields_error() { +fn call_cyclic_function_with_dynamic_argument_yields_no_errors() { check_profile( CALL_TO_CYCLIC_FUNCTION_WITH_DYNAMIC_ARGUMENT, &expect![[r#" @@ -257,7 +257,7 @@ fn call_cyclic_function_with_dynamic_argument_yields_error() { } #[test] -fn call_cyclic_operation_with_classical_argument_yields_errors() { +fn call_cyclic_operation_with_classical_argument_yields_no_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_CLASSICAL_ARGUMENT, &expect![[r#" @@ -267,7 +267,7 @@ fn call_cyclic_operation_with_classical_argument_yields_errors() { } #[test] -fn call_cyclic_operation_with_dynamic_argument_yields_errors() { +fn call_cyclic_operation_with_dynamic_argument_yields_no_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_DYNAMIC_ARGUMENT, &expect![[r#" diff --git a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers_and_floats.rs b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers_and_floats.rs index f2d5ba86cf..17d4d02856 100644 --- a/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers_and_floats.rs +++ b/source/compiler/qsc_passes/src/capabilitiesck/tests_adaptive_plus_integers_and_floats.rs @@ -238,7 +238,7 @@ fn call_cyclic_function_with_classical_argument_yields_no_errors() { } #[test] -fn call_cyclic_function_with_dynamic_argument_yields_error() { +fn call_cyclic_function_with_dynamic_argument_yields_no_errors() { check_profile( CALL_TO_CYCLIC_FUNCTION_WITH_DYNAMIC_ARGUMENT, &expect![[r#" @@ -248,7 +248,7 @@ fn call_cyclic_function_with_dynamic_argument_yields_error() { } #[test] -fn call_cyclic_operation_with_classical_argument_yields_errors() { +fn call_cyclic_operation_with_classical_argument_yields_no_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_CLASSICAL_ARGUMENT, &expect![[r#" @@ -258,7 +258,7 @@ fn call_cyclic_operation_with_classical_argument_yields_errors() { } #[test] -fn call_cyclic_operation_with_dynamic_argument_yields_errors() { +fn call_cyclic_operation_with_dynamic_argument_yields_no_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_DYNAMIC_ARGUMENT, &expect![[r#" diff --git a/source/compiler/qsc_passes/src/capabilitiesck/tests_base.rs b/source/compiler/qsc_passes/src/capabilitiesck/tests_base.rs index 0bd2fb7b1d..3267000f35 100644 --- a/source/compiler/qsc_passes/src/capabilitiesck/tests_base.rs +++ b/source/compiler/qsc_passes/src/capabilitiesck/tests_base.rs @@ -364,7 +364,7 @@ fn call_cyclic_function_with_dynamic_argument_yields_errors() { } #[test] -fn call_cyclic_operation_with_classical_argument_yields_errors() { +fn call_cyclic_operation_with_classical_argument_yields_no_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_CLASSICAL_ARGUMENT, &expect![[r#" @@ -374,7 +374,7 @@ fn call_cyclic_operation_with_classical_argument_yields_errors() { } #[test] -fn call_cyclic_operation_with_dynamic_argument_yields_errors() { +fn call_cyclic_operation_with_dynamic_argument_yields_no_errors() { check_profile( CALL_TO_CYCLIC_OPERATION_WITH_DYNAMIC_ARGUMENT, &expect![[r#"