From b6fe61b86f05afaf86d927db17ae4b3f1d8a64cf Mon Sep 17 00:00:00 2001 From: "Stefan J. Wernli" Date: Wed, 22 Jul 2026 14:25:13 -0700 Subject: [PATCH] Allow dynamic constant values to coerce to static across callable boundaries in RCA This adds functionality to RCA that coerces some dynamic constants into static during calculation of callable arguments, mimicking functionality added to partial eval in #3491. This allows APIs like `Std.TableLookup.Select` to pass RCA, but notably only fixes generation of body implementation; calls to `Adjoint Select` invoke a code path that passes RCA but fails in partial eval due to missing support for emitting arrays with dynamic contents into QIR. --- source/compiler/qsc/src/codegen/tests.rs | 48 ++++++++++++++++++++++++ source/compiler/qsc_rca/src/core.rs | 28 +++++++++++++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/source/compiler/qsc/src/codegen/tests.rs b/source/compiler/qsc/src/codegen/tests.rs index 4bb6d037c97..667546597e1 100644 --- a/source/compiler/qsc/src/codegen/tests.rs +++ b/source/compiler/qsc/src/codegen/tests.rs @@ -5388,3 +5388,51 @@ fn chemistry_like_iqpe_with_udt_capture_closure_generates_base_profile_qir() { "expected threaded Controlled X capture in QIR:\n{qir}" ); } + +#[test] +fn foreign_table_lookup_callable_generates_qir() { + let source = indoc::indoc! {r#" + operation Invoke() : Unit { + use address = Qubit[1]; + use output = Qubit[1]; + Std.TableLookup.Select([[false], [true]], address, output); + + // Adjoint of Select does not work until support for static sized, dynamic content arrays is added. + // See related issue: https://github.com/microsoft/qdk/issues/3388 + // Adjoint Std.TableLookup.Select([[false], [true]], address, output); + } + "#}; + for profile in [Profile::AdaptiveRI, Profile::AdaptiveRIF, Profile::Adaptive] { + let capabilities = profile.into(); + let (mut store, package_id, items) = + compile_and_locate_items(source, &[("Invoke", true)], capabilities); + let root_sources = + source_map_from_source("namespace DriverEmpty { function Anchor() : Unit {} }"); + let root_dependencies = vec![(package_id, Some(Arc::from("ReproLib")))]; + let (root_unit, root_errors) = crate::compile::compile( + &store, + &root_dependencies, + root_sources, + PackageType::Lib, + capabilities, + LanguageFeatures::default(), + ); + assert!( + root_errors.is_empty(), + "{profile:?} root compilation failed: {root_errors:?}" + ); + store.insert(root_unit); + + let qir = callable_args_to_qir( + &store, + package_id, + items["Invoke"], + &Value::unit(), + capabilities, + ); + assert!( + qir.contains("define i64 @ENTRYPOINT__main()"), + "{profile:?} callable should emit an entry point, got:\n{qir}" + ); + } +} diff --git a/source/compiler/qsc_rca/src/core.rs b/source/compiler/qsc_rca/src/core.rs index 06b562423b3..a6158e792f5 100644 --- a/source/compiler/qsc_rca/src/core.rs +++ b/source/compiler/qsc_rca/src/core.rs @@ -1542,7 +1542,25 @@ impl<'a> Analyzer<'a> { let mut args_compute_kinds = Vec::::with_capacity(args.len()); for arg_expr_id in args { let arg_compute_kind = application_instance.get_expr_compute_kind(*arg_expr_id); - args_compute_kinds.push(*arg_compute_kind); + let arg_ty = &self.get_expr(*arg_expr_id).ty; + if matches!( + arg_compute_kind, + ComputeKind::Dynamic { + value_kind: ValueKind::Constant, + .. + } + ) && !is_any_result(arg_ty) + && !is_any_array(arg_ty) + { + // If the argument is expected to have a constant value, we can consider it static across function boundaries. + // This matches the behavior in partial evaluation, captured in source/compiler/qsc_partial_eval/src/evaluation_context.rs + // `Scope` construction, where non-result runtime values are set as static when computing argument `ComputeKind`. + // We skip this for result types, which must remain dynamic constant as they are the source of later variables during + // comparison of result values, and for arrays, which during evaluation do not have investigatable contents. + args_compute_kinds.push(ComputeKind::Static); + } else { + args_compute_kinds.push(*arg_compute_kind); + } } args_compute_kinds } @@ -2803,6 +2821,14 @@ fn is_any_result(t: &Ty) -> bool { } } +fn is_any_array(t: &Ty) -> bool { + match t { + Ty::Array(_) => true, + Ty::Tuple(ts) => ts.iter().any(is_any_array), + _ => false, + } +} + struct ClearComputeKinds<'a, 'b> { analyzer: &'a mut Analyzer<'b>, }