Skip to content

Commit

Permalink
Fix RCA panic when compiling lambda (#2102)
Browse files Browse the repository at this point in the history
Compiling a lambda that has more than one parameter can trigger a panic
in RCA due to processing the fixed arguments for captures. This adds new
tests for the lambda cases and confirms the dynamic runtime features for
paramters and captures are detected as expected.
  • Loading branch information
swernli authored Jan 14, 2025
1 parent 342f40a commit 5f5546f
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 5 deletions.
21 changes: 16 additions & 5 deletions compiler/qsc_rca/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ impl<'a> Analyzer<'a> {
callee_input_pattern_id,
args_input_id,
self.package_store,
fixed_args.as_ref().map_or(0, Vec::len),
fixed_args.as_ref().map(Vec::len),
);
let application_instance = self.get_current_application_instance();

Expand Down Expand Up @@ -2509,17 +2509,28 @@ fn map_input_pattern_to_input_expressions(
pat_id: StorePatId,
expr_id: StoreExprId,
package_store: &impl PackageStoreLookup,
skip_ahead: usize,
skip_ahead: Option<usize>,
) -> Vec<ExprId> {
let pat = package_store.get_pat(pat_id);
match &pat.kind {
PatKind::Bind(_) | PatKind::Discard => vec![expr_id.expr],
PatKind::Tuple(pats) => {
// Map each one of the elements in the pattern to an expression in the tuple.
let pats = &pats[skip_ahead..];
let pats = &pats[skip_ahead.unwrap_or_default()..];
let expr = package_store.get_expr(expr_id);
if let ExprKind::Tuple(exprs) = &expr.kind {
assert!(pats.len() == exprs.len());
let pats = if skip_ahead.is_some() && exprs.len() > 1 {
// When skip_ahead is not None we know we are processing a lambda, so the final pattern is itself a tuple.
// Unpack the tuple pattern to get the individual elements and map input to those.
let PatKind::Tuple(pats) =
&package_store.get_pat((pat_id.package, pats[0]).into()).kind
else {
panic!("expected tuple pattern for lambda receiving multiple arguments");
};
pats
} else {
pats
};
let mut input_param_exprs = Vec::<ExprId>::with_capacity(pats.len());
for (local_pat_id, local_expr_id) in pats.iter().zip(exprs.iter()) {
let global_pat_id = StorePatId::from((pat_id.package, *local_pat_id));
Expand All @@ -2528,7 +2539,7 @@ fn map_input_pattern_to_input_expressions(
global_pat_id,
global_expr_id,
package_store,
0,
None,
);
input_param_exprs.append(&mut sub_input_param_exprs);
}
Expand Down
1 change: 1 addition & 0 deletions compiler/qsc_rca/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod calls;
mod cycles;
mod ifs;
mod intrinsics;
mod lambdas;
mod loops;
mod measurements;
mod overrides;
Expand Down
219 changes: 219 additions & 0 deletions compiler/qsc_rca/src/tests/lambdas.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use super::{check_last_statement_compute_properties, CompilationContext};
use expect_test::expect;

#[test]
fn check_rca_for_classical_lambda_one_parameter() {
let mut compilation_context = CompilationContext::default();
compilation_context.update(
r#"
let myOp = a -> {};
myOp(1)"#,
);
let package_store_compute_properties = compilation_context.get_compute_properties();
check_last_statement_compute_properties(
package_store_compute_properties,
&expect![[r#"
ApplicationsGeneratorSet:
inherent: Classical
dynamic_param_applications: <empty>"#]],
);
}

#[test]
fn check_rca_for_classical_lambda_two_parameters() {
let mut compilation_context = CompilationContext::default();
compilation_context.update(
r#"
let myOp = (a, b) -> {};
myOp(1, 2)"#,
);
let package_store_compute_properties = compilation_context.get_compute_properties();
check_last_statement_compute_properties(
package_store_compute_properties,
&expect![[r#"
ApplicationsGeneratorSet:
inherent: Classical
dynamic_param_applications: <empty>"#]],
);
}

#[test]
fn check_rca_for_classical_lambda_one_parameter_one_capture() {
let mut compilation_context = CompilationContext::default();
compilation_context.update(
r#"
let x = 0.0;
let myOp = a -> {x};
myOp(1)"#,
);
let package_store_compute_properties = compilation_context.get_compute_properties();
check_last_statement_compute_properties(
package_store_compute_properties,
&expect![[r#"
ApplicationsGeneratorSet:
inherent: Classical
dynamic_param_applications: <empty>"#]],
);
}

#[test]
fn check_rca_for_classical_lambda_one_parameter_two_captures() {
let mut compilation_context = CompilationContext::default();
compilation_context.update(
r#"
let x = 0.0;
let y = 0.0;
let myOp = a -> {x+y};
myOp(1)"#,
);
let package_store_compute_properties = compilation_context.get_compute_properties();
check_last_statement_compute_properties(
package_store_compute_properties,
&expect![[r#"
ApplicationsGeneratorSet:
inherent: Classical
dynamic_param_applications: <empty>"#]],
);
}

#[test]
fn check_rca_for_classical_lambda_two_parameters_one_capture() {
let mut compilation_context = CompilationContext::default();
compilation_context.update(
r#"
let x = 0.0;
let myOp = (a, b) -> {x};
myOp(1, 2)"#,
);
let package_store_compute_properties = compilation_context.get_compute_properties();
check_last_statement_compute_properties(
package_store_compute_properties,
&expect![[r#"
ApplicationsGeneratorSet:
inherent: Classical
dynamic_param_applications: <empty>"#]],
);
}

#[test]
fn check_rca_for_classical_lambda_two_parameters_two_captures() {
let mut compilation_context = CompilationContext::default();
compilation_context.update(
r#"
let x = 0.0;
let y = 0.0;
let myOp = (a, b) -> {x+y};
myOp(1, 2)"#,
);
let package_store_compute_properties = compilation_context.get_compute_properties();
check_last_statement_compute_properties(
package_store_compute_properties,
&expect![[r#"
ApplicationsGeneratorSet:
inherent: Classical
dynamic_param_applications: <empty>"#]],
);
}

#[test]
fn check_rca_for_dynamic_lambda_two_classical_parameters_one_dynamic_capture() {
let mut compilation_context = CompilationContext::default();
compilation_context.update(
r#"
let x = {
use q = Qubit();
if MResetZ(q) == One {
1.0
} else {
0.0
}
};
let myOp = (a, b) -> {x};
myOp(1, 2)"#,
);
let package_store_compute_properties = compilation_context.get_compute_properties();
check_last_statement_compute_properties(
package_store_compute_properties,
&expect![[r#"
ApplicationsGeneratorSet:
inherent: Quantum: QuantumProperties:
runtime_features: RuntimeFeatureFlags(UseOfDynamicDouble)
value_kind: Element(Dynamic)
dynamic_param_applications: <empty>"#]],
);
}

#[test]
fn check_rca_for_dynamic_lambda_two_dynamic_parameters_one_classical_capture() {
let mut compilation_context = CompilationContext::default();
compilation_context.update(
r#"
let x = 0.0;
let myOp = (a, b) -> {a + x};
let a = {
use q = Qubit();
if MResetZ(q) == One {
1.0
} else {
0.0
}
};
myOp(a, 2)"#,
);
let package_store_compute_properties = compilation_context.get_compute_properties();
check_last_statement_compute_properties(
package_store_compute_properties,
&expect![[r#"
ApplicationsGeneratorSet:
inherent: Quantum: QuantumProperties:
runtime_features: RuntimeFeatureFlags(UseOfDynamicBool | UseOfDynamicDouble)
value_kind: Element(Dynamic)
dynamic_param_applications: <empty>"#]],
);
}

#[test]
fn check_rca_for_operation_lambda_two_parameters() {
let mut compilation_context = CompilationContext::default();
compilation_context.update(
r#"
use (q0, q1) = (Qubit(), Qubit());
let myOp = (a, b) => MResetEachZ([a, b]);
myOp(q0, q1)"#,
);
let package_store_compute_properties = compilation_context.get_compute_properties();
check_last_statement_compute_properties(
package_store_compute_properties,
&expect![[r#"
ApplicationsGeneratorSet:
inherent: Quantum: QuantumProperties:
runtime_features: RuntimeFeatureFlags(0x0)
value_kind: Array(Content: Dynamic, Size: Static)
dynamic_param_applications: <empty>"#]],
);
}

#[test]
fn check_rca_for_operation_lambda_two_parameters_with_controls() {
let mut compilation_context = CompilationContext::default();
compilation_context.update(
r#"
use (q0, q1) = (Qubit(), Qubit());
use (qs0, qs1) = (Qubit[2], Qubit[2]);
let myOp = (a, b) => CNOT(a, b);
Controlled Controlled myOp(qs0, (qs1, (q0, q1)))"#,
);
let package_store_compute_properties = compilation_context.get_compute_properties();
check_last_statement_compute_properties(
package_store_compute_properties,
&expect![[r#"
ApplicationsGeneratorSet:
inherent: Quantum: QuantumProperties:
runtime_features: RuntimeFeatureFlags(UseOfDynamicBool)
value_kind: Element(Static)
dynamic_param_applications: <empty>"#]],
);
}

0 comments on commit 5f5546f

Please sign in to comment.