Skip to content

Commit 910cdd8

Browse files
minestarksswernli
andauthored
[Circuit diagrams] 4 - Show conditionals in circuits based on RIR debug metadata (#2943)
This PR completes the feature for rendering classical control flow and conditionals in quantum circuit diagrams. The changes span the compiler, code generation, circuit visualization, and VS Code integration. **Circuit Generation from RIR** - Implemented new RIR-to-circuit conversion (rir_to_circuit.rs module) that reconstructs control flow structure from RIR blocks - Added structured control flow analysis to properly traverse branches in execution order - Built logic to handle complex scenarios: - Measurement-based conditionals (if-else based on qubit measurement results) - Nested conditionals and loops with quantum operations - Binary operation short-circuit evaluation - Multiple possible values in function arguments based on control flow branches **VS Code Extension Fallback Logic** - Added fallback mechanism: if circuit generation fails due to adaptive profile compliance issues when using "static" method, the extension automatically retries with "classicalEval" method and "Unrestricted" profile - Preserves existing fallback for result comparison errors in "classicalEval" mode ### Usage Example For Q# code with measurement-based conditionals: ```qsharp operation TestConditional(q : Qubit) : Unit { H(q); let r = M(q); if (r == One) { X(q); } } ``` The circuit diagram now displays the conditional control flow structure, showing which gates execute based on measurement outcomes, alongside the quantum operations. --------- Co-authored-by: Mine Starks <> Co-authored-by: Stefan J. Wernli <swernli@microsoft.com>
1 parent fcaf99f commit 910cdd8

40 files changed

Lines changed: 32615 additions & 5050 deletions

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

source/compiler/qsc/src/interpret.rs

Lines changed: 125 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
#[cfg(test)]
5+
mod circuit_classical_ctl_tests;
46
#[cfg(test)]
57
mod circuit_tests;
68
mod debug;
@@ -25,8 +27,9 @@ use num_complex::Complex;
2527
use qsc_circuit::{
2628
Circuit, CircuitTracer, TracerConfig,
2729
operations::{entry_expr_for_qubit_operation, qubit_param_info},
30+
rir_to_circuit::rir_to_circuit,
2831
};
29-
use qsc_codegen::qir::{fir_to_qir, fir_to_qir_from_callable};
32+
use qsc_codegen::qir::{fir_to_qir, fir_to_qir_from_callable, fir_to_rir};
3033
use qsc_data_structures::{
3134
error::WithSource,
3235
functors::FunctorApp,
@@ -68,7 +71,7 @@ use qsc_lowerer::{
6871
map_fir_local_item_to_hir, map_fir_package_to_hir, map_hir_local_item_to_fir,
6972
map_hir_package_to_fir,
7073
};
71-
use qsc_partial_eval::ProgramEntry;
74+
use qsc_partial_eval::{PartialEvalConfig, ProgramEntry};
7275
use qsc_passes::{PackageType, PassContext};
7376
use qsc_rca::PackageStoreComputeProperties;
7477
use rustc_hash::FxHashSet;
@@ -122,6 +125,8 @@ pub struct Interpreter {
122125
compiler: Compiler,
123126
/// The target capabilities used for compilation.
124127
capabilities: TargetCapabilityFlags,
128+
/// The computed properties for the package store, if any, used for code generation.
129+
compute_properties: Option<PackageStoreComputeProperties>,
125130
/// The number of lines that have so far been compiled.
126131
/// This field is used to generate a unique label
127132
/// for each line evaluated with `eval_fragments`.
@@ -331,8 +336,10 @@ impl Interpreter {
331336
let package_id = compiler.package_id();
332337

333338
let package = map_hir_package_to_fir(package_id);
334-
if capabilities != TargetCapabilityFlags::all() {
335-
let _ = PassContext::run_fir_passes_on_fir(
339+
let compute_properties = if capabilities == TargetCapabilityFlags::all() {
340+
None
341+
} else {
342+
let compute_properties = PassContext::run_fir_passes_on_fir(
336343
&fir_store,
337344
map_hir_package_to_fir(source_package_id),
338345
capabilities,
@@ -348,12 +355,15 @@ impl Interpreter {
348355
.map(|error| Error::Pass(WithSource::from_map(&source_package.sources, error)))
349356
.collect::<Vec<_>>()
350357
})?;
351-
}
358+
359+
Some(compute_properties)
360+
};
352361

353362
Ok(Self {
354363
compiler,
355364
lines: 0,
356365
capabilities,
366+
compute_properties,
357367
fir_store,
358368
lowerer: qsc_lowerer::Lowerer::new(),
359369
expr_graph: None,
@@ -1041,11 +1051,118 @@ impl Interpreter {
10411051
)?;
10421052
}
10431053
}
1054+
CircuitGenerationMethod::Static => {
1055+
if let Some((callable, args)) = invoke_params {
1056+
// Static circuit generation from a callable is not yet supported.
1057+
// Fall back to classical eval.
1058+
self.invoke_with_tracing_backend(
1059+
&mut TracingBackend::<SparseSim>::no_backend(&mut tracer),
1060+
&mut out,
1061+
callable,
1062+
args,
1063+
eval_config,
1064+
)?;
1065+
} else {
1066+
return self.static_circuit(entry_expr.as_deref(), tracer_config);
1067+
}
1068+
}
10441069
}
10451070
let circuit = tracer.finish(&(self.compiler.package_store(), &self.fir_store));
10461071
Ok(circuit)
10471072
}
10481073

1074+
fn static_circuit(
1075+
&mut self,
1076+
entry_expr: Option<&str>,
1077+
tracer_config: TracerConfig,
1078+
) -> std::result::Result<Circuit, Vec<Error>> {
1079+
if self.capabilities == TargetCapabilityFlags::all() {
1080+
return Err(vec![Error::UnsupportedRuntimeCapabilities]);
1081+
}
1082+
1083+
let program = self.compile_to_rir_with_debug_metadata(entry_expr)?;
1084+
rir_to_circuit(
1085+
&program,
1086+
tracer_config,
1087+
&[self.package, self.source_package],
1088+
&(self.compiler.package_store(), &self.fir_store),
1089+
)
1090+
.map_err(|e| vec![e.into()])
1091+
}
1092+
1093+
fn compile_to_rir_with_debug_metadata(
1094+
&mut self,
1095+
entry_expr: Option<&str>,
1096+
) -> std::result::Result<qsc_partial_eval::Program, Vec<Error>> {
1097+
let (entry, compute_properties) = if let Some(entry_expr) = &entry_expr {
1098+
// Compile the expression. This operation will set the expression as
1099+
// the entry-point in the FIR store.
1100+
let (graph, compute_properties) = self.compile_entry_expr(entry_expr)?;
1101+
1102+
let Some(compute_properties) = compute_properties else {
1103+
// This can only happen if capability analysis was not run.
1104+
panic!(
1105+
"internal error: compute properties not set after lowering entry expression"
1106+
);
1107+
};
1108+
let package = self.fir_store.get(self.package);
1109+
let entry = ProgramEntry {
1110+
exec_graph: graph,
1111+
expr: (
1112+
self.package,
1113+
package
1114+
.entry
1115+
.expect("package must have an entry expression"),
1116+
)
1117+
.into(),
1118+
};
1119+
(entry, compute_properties)
1120+
} else {
1121+
let package = self.fir_store.get(self.source_package);
1122+
let entry = ProgramEntry {
1123+
exec_graph: package.entry_exec_graph.clone(),
1124+
expr: (
1125+
self.source_package,
1126+
package
1127+
.entry
1128+
.expect("package must have an entry expression"),
1129+
)
1130+
.into(),
1131+
};
1132+
(
1133+
entry,
1134+
self.compute_properties.clone().expect(
1135+
"compute properties should be set if target profile isn't unrestricted",
1136+
),
1137+
)
1138+
};
1139+
let (_original, transformed) = fir_to_rir(
1140+
&self.fir_store,
1141+
self.capabilities,
1142+
Some(compute_properties),
1143+
&entry,
1144+
PartialEvalConfig {
1145+
generate_debug_metadata: true,
1146+
},
1147+
)
1148+
.map_err(|e| {
1149+
let hir_package_id = match e.span() {
1150+
Some(span) => span.package,
1151+
None => map_fir_package_to_hir(self.package),
1152+
};
1153+
let source_package = self
1154+
.compiler
1155+
.package_store()
1156+
.get(hir_package_id)
1157+
.expect("package should exist in the package store");
1158+
vec![Error::PartialEvaluation(WithSource::from_map(
1159+
&source_package.sources,
1160+
e,
1161+
))]
1162+
})?;
1163+
Ok(transformed)
1164+
}
1165+
10491166
/// Sets the entry expression for the interpreter.
10501167
pub fn set_entry_expr(&mut self, entry_expr: &str) -> std::result::Result<(), Vec<Error>> {
10511168
let (graph, _) = self.compile_entry_expr(entry_expr)?;
@@ -1309,6 +1426,9 @@ pub enum CircuitGenerationMethod {
13091426
/// Evaluate the classical parts of the program. No quantum simulation.
13101427
/// Will fail if a measurement comparison occurs during evaluation.
13111428
ClassicalEval,
1429+
/// Compile the program and transform to a circuit with only partial evaluation.
1430+
/// Only works for `AdaptiveRIF` compliant programs.
1431+
Static,
13121432
}
13131433

13141434
/// A debugger that enables step-by-step evaluation of code

0 commit comments

Comments
 (0)