1use crate::circuit::circuit_param::{CircuitParam, ParameterValue};
27use crate::circuit::gate::instruction::Instruction;
28use crate::circuit::value_instruction::storage_operation_to_value;
29use crate::circuit::{
30 Circuit, CircuitError, ClassicalControlOp, ClassicalDataOp, ClassicalExpr, ClassicalType,
31 ClassicalVar, ControlBody, ForOp, IfOp, Operation, Parameter, Qubit, SwitchCase, SwitchOp,
32 ValueOperation, WhileOp,
33};
34use indexmap::IndexSet;
35use rustworkx_core::petgraph::prelude::{EdgeIndex, NodeIndex, StableDiGraph};
36use rustworkx_core::petgraph::visit::EdgeRef;
37use smallvec::SmallVec;
38use std::collections::{HashMap, HashSet};
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum FlowEdge {
43 TrueBranch,
45 FalseBranch,
47 Unconditional,
49 Case(u128),
51 DefaultCase,
54 Break,
56 Continue,
58}
59
60#[derive(Debug, Clone)]
61pub enum Terminator {
66 Branch(ClassicalExpr),
68 ForLoop {
72 var: ClassicalVar,
74 start: ClassicalExpr,
76 stop: ClassicalExpr,
78 step: ClassicalExpr,
80 },
81 Switch(ClassicalExpr),
83 Jump(NodeIndex),
85 Break(NodeIndex),
87 Continue(NodeIndex),
89 Return,
91}
92
93#[derive(Debug, Clone)]
96pub struct OperationMetadata {
97 pub qubits: SmallVec<[Qubit; 3]>,
99 pub params: SmallVec<[CircuitParam; 1]>,
101 pub label: Option<Box<str>>,
103}
104
105impl OperationMetadata {
106 fn from_operation(operation: &Operation) -> Self {
107 Self {
108 qubits: operation.qubits.clone(),
109 params: operation.params.clone(),
110 label: operation.label.clone(),
111 }
112 }
113}
114
115#[derive(Debug, Clone)]
116pub struct SwitchRegionCase {
118 pub value: u128,
120 pub entry: NodeIndex,
122}
123
124#[derive(Debug, Clone)]
129pub enum ControlFlowRegion {
130 If {
132 then_entry: NodeIndex,
134 else_entry: NodeIndex,
136 merge_block: NodeIndex,
138 has_else: bool,
140 outer: OperationMetadata,
142 },
143 While {
145 body_entry: NodeIndex,
147 exit_block: NodeIndex,
149 outer: OperationMetadata,
151 },
152 For {
154 body_entry: NodeIndex,
156 exit_block: NodeIndex,
158 outer: OperationMetadata,
160 },
161 Switch {
163 cases: Vec<SwitchRegionCase>,
165 default_entry: NodeIndex,
167 merge_block: NodeIndex,
169 has_default: bool,
171 outer: OperationMetadata,
173 },
174}
175
176#[derive(Debug, Clone)]
178pub struct BasicBlock {
179 pub(crate) operations: Vec<Operation>,
180 pub(crate) terminator: Option<Terminator>,
181 pub(crate) label: Option<String>,
182}
183
184impl BasicBlock {
185 pub fn new() -> Self {
187 Self {
188 operations: Vec::new(),
189 terminator: None,
190 label: None,
191 }
192 }
193
194 pub fn with_label(mut self, label: impl Into<String>) -> Self {
196 self.label = Some(label.into());
197 self
198 }
199
200 pub fn push_operation(&mut self, op: Operation) {
202 self.operations.push(op);
203 }
204
205 pub fn extend_operations(&mut self, ops: impl IntoIterator<Item = Operation>) {
207 self.operations.extend(ops);
208 }
209
210 pub fn set_terminator(&mut self, terminator: Terminator) {
212 self.terminator = Some(terminator);
213 }
214
215 pub fn is_empty(&self) -> bool {
217 self.operations.is_empty() && self.terminator.is_none()
218 }
219
220 pub fn has_terminator(&self) -> bool {
222 self.terminator.is_some()
223 }
224
225 pub fn len(&self) -> usize {
227 self.operations.len()
228 }
229
230 pub fn label(&self) -> Option<&str> {
232 self.label.as_deref()
233 }
234
235 pub fn terminator(&self) -> Option<&Terminator> {
237 self.terminator.as_ref()
238 }
239
240 pub fn operations(&self) -> &[Operation] {
242 &self.operations
243 }
244}
245
246impl Default for BasicBlock {
247 fn default() -> Self {
248 Self::new()
249 }
250}
251
252#[derive(Debug)]
258pub struct CircuitCFG {
259 pub(crate) qubits: IndexSet<Qubit>,
260 pub(crate) symbols: IndexSet<String>,
261 pub(crate) parameters: IndexSet<Parameter>,
262 pub(crate) classical_vars: Vec<ClassicalType>,
263 pub(crate) classical_values: Vec<ClassicalType>,
264 pub(crate) global_phase: CircuitParam,
265 pub(crate) data: StableDiGraph<BasicBlock, FlowEdge>,
266 pub(crate) entry_block: Option<NodeIndex>,
267 pub(crate) control_flow_regions: HashMap<NodeIndex, ControlFlowRegion>,
268}
269
270impl CircuitCFG {
271 pub fn new(num_qubits: usize) -> Self {
273 let qubits = (0..num_qubits).map(|i| Qubit::new(i as u32)).collect();
274 Self {
275 qubits,
276 symbols: IndexSet::new(),
277 parameters: IndexSet::new(),
278 classical_vars: vec![],
279 classical_values: vec![],
280 global_phase: CircuitParam::Fixed(0.0),
281 data: StableDiGraph::new(),
282 entry_block: None,
283 control_flow_regions: HashMap::new(),
284 }
285 }
286
287 pub fn from_qubits(qubits: Vec<Qubit>) -> Self {
289 Self {
290 qubits: qubits.into_iter().collect(),
291 symbols: IndexSet::new(),
292 parameters: IndexSet::new(),
293 classical_vars: vec![],
294 classical_values: vec![],
295 global_phase: CircuitParam::Fixed(0.0),
296 data: StableDiGraph::new(),
297 entry_block: None,
298 control_flow_regions: HashMap::new(),
299 }
300 }
301
302 pub fn add_block(&mut self, block: BasicBlock) -> NodeIndex {
304 self.data.add_node(block)
305 }
306
307 pub fn add_edge(
311 &mut self,
312 source: NodeIndex,
313 target: NodeIndex,
314 flow: FlowEdge,
315 ) -> Option<EdgeIndex> {
316 if self.data.node_weight(source).is_none() || self.data.node_weight(target).is_none() {
317 return None;
318 }
319 Some(self.data.add_edge(source, target, flow))
320 }
321
322 pub fn entry_block(&self) -> Option<NodeIndex> {
324 self.entry_block
325 }
326
327 pub fn set_entry_block(&mut self, index: NodeIndex) {
331 self.entry_block = Some(index);
332 }
333
334 pub fn set_control_flow_region(&mut self, branch_block: NodeIndex, region: ControlFlowRegion) {
336 self.control_flow_regions.insert(branch_block, region);
337 }
338
339 pub fn control_flow_region(&self, branch_block: NodeIndex) -> Option<&ControlFlowRegion> {
341 self.control_flow_regions.get(&branch_block)
342 }
343
344 pub fn is_loop_header(&self, block: NodeIndex) -> bool {
346 matches!(
347 self.control_flow_region(block),
348 Some(ControlFlowRegion::While { .. } | ControlFlowRegion::For { .. })
349 )
350 }
351
352 pub fn blocks(&self) -> impl Iterator<Item = (NodeIndex, &BasicBlock)> {
354 self.data.node_indices().map(|i| (i, &self.data[i]))
355 }
356
357 pub fn block_mut(&mut self, index: NodeIndex) -> Option<&mut BasicBlock> {
359 self.data.node_weight_mut(index)
360 }
361
362 pub fn outgoing_edges(
364 &self,
365 source: NodeIndex,
366 ) -> impl Iterator<Item = (NodeIndex, FlowEdge)> + '_ {
367 self.data
368 .edges(source)
369 .map(|edge| (edge.target(), edge.weight().clone()))
370 }
371
372 pub fn num_blocks(&self) -> usize {
374 self.data.node_indices().count()
375 }
376
377 pub fn num_qubits(&self) -> usize {
379 self.qubits.len()
380 }
381
382 pub fn qubits(&self) -> Vec<Qubit> {
384 self.qubits.iter().cloned().collect()
385 }
386
387 pub fn classical_vars(&self) -> &[ClassicalType] {
389 &self.classical_vars
390 }
391
392 pub fn classical_values(&self) -> &[ClassicalType] {
394 &self.classical_values
395 }
396
397 pub fn from_circuit(circuit: &Circuit) -> Result<Self, CircuitError> {
404 let mut cfg = Self::from_qubits(circuit.qubits());
405 cfg.symbols = circuit.symbols().clone();
406 cfg.parameters = circuit.parameters().clone();
407 cfg.classical_vars = circuit.classical_vars().to_vec();
408 cfg.classical_values = circuit.classical_values().to_vec();
409 cfg.global_phase = circuit.global_phase_param().clone();
410
411 let entry = cfg.add_block(BasicBlock::new().with_label("entry"));
412 cfg.set_entry_block(entry);
413
414 match process_operations(
415 circuit.operations(),
416 &mut cfg,
417 entry,
418 ControlContext::default(),
419 )? {
420 ProcessExit::Reachable(block) => {
421 if !cfg.data[block].has_terminator() {
422 cfg.data[block].set_terminator(Terminator::Return);
423 }
424 }
425 ProcessExit::Terminated => {}
426 }
427
428 cfg.validate()?;
429 Ok(cfg)
430 }
431
432 pub fn validate(&self) -> Result<(), CircuitError> {
440 let entry = self.entry_block.ok_or_else(|| {
441 CircuitError::InvalidControlFlow("CFG does not define an entry block".to_string())
442 })?;
443 self.require_block(entry, "entry block")?;
444 self.validate_param(&self.global_phase, "global phase")?;
445
446 for (node, block) in self.blocks() {
447 for (index, operation) in block.operations.iter().enumerate() {
448 self.validate_operation(
449 operation,
450 &format!("block {:?} operation {}", node, index),
451 )?;
452 if matches!(operation.instruction, Instruction::ClassicalControl(_)) {
453 return Err(CircuitError::InvalidControlFlow(format!(
454 "block {:?} contains an unexpanded classical control operation",
455 node
456 )));
457 }
458 }
459
460 let terminator = block.terminator.as_ref().ok_or_else(|| {
461 CircuitError::InvalidControlFlow(format!(
462 "Block '{}' (index {:?}) is missing a terminator",
463 block.label().unwrap_or("<unlabeled>"),
464 node
465 ))
466 })?;
467 self.validate_terminator(node, block, terminator)?;
468 }
469
470 for region_node in self.control_flow_regions.keys() {
471 self.require_block(*region_node, "structured region owner")?;
472 }
473
474 let mut visited = HashSet::new();
475 self.parse_subgraph(entry, None, &mut visited)?;
476 if visited.len() != self.num_blocks() {
477 return Err(CircuitError::InvalidControlFlow(format!(
478 "CFG contains {} unreachable or unconsumed block(s)",
479 self.num_blocks() - visited.len()
480 )));
481 }
482
483 Ok(())
484 }
485
486 pub fn to_circuit(&self) -> Result<Circuit, CircuitError> {
493 self.validate()?;
494 let entry = self.entry_block.expect("validated CFG must define entry");
495 let mut visited = HashSet::new();
496 let ops = self.parse_subgraph(entry, None, &mut visited)?;
497
498 let value_ops = ops
499 .into_iter()
500 .map(|operation| self.value_operation(operation))
501 .collect::<Result<Vec<_>, _>>()?;
502 let mut circuit = Circuit::from_operations(
503 self.qubits.iter().copied().collect(),
504 value_ops,
505 Some(self.classical_vars.clone()),
506 Some(self.classical_values.clone()),
507 )?;
508 circuit.set_global_phase(self.global_phase_parameter()?);
509 Ok(circuit)
510 }
511
512 fn validate_terminator(
513 &self,
514 node: NodeIndex,
515 block: &BasicBlock,
516 terminator: &Terminator,
517 ) -> Result<(), CircuitError> {
518 let outgoing: Vec<_> = self.data.edges(node).collect();
519 match terminator {
520 Terminator::Return => {
521 if !outgoing.is_empty() {
522 return Err(self.invalid_block(
523 block,
524 node,
525 "has Return terminator with outgoing edges",
526 ));
527 }
528 }
529 Terminator::Jump(target) => {
530 self.require_block(*target, "jump target")?;
531 if outgoing.len() != 1
532 || !matches!(outgoing[0].weight(), FlowEdge::Unconditional)
533 || outgoing[0].target() != *target
534 {
535 return Err(self.invalid_block(block, node, "has invalid Jump edge"));
536 }
537 }
538 Terminator::Break(target) => {
539 self.require_block(*target, "break target")?;
540 if outgoing.len() != 1
541 || !matches!(outgoing[0].weight(), FlowEdge::Break)
542 || outgoing[0].target() != *target
543 {
544 return Err(self.invalid_block(block, node, "has invalid Break edge"));
545 }
546 }
547 Terminator::Continue(target) => {
548 self.require_block(*target, "continue target")?;
549 if outgoing.len() != 1
550 || !matches!(outgoing[0].weight(), FlowEdge::Continue)
551 || outgoing[0].target() != *target
552 {
553 return Err(self.invalid_block(block, node, "has invalid Continue edge"));
554 }
555 }
556 Terminator::Branch(condition) => {
557 if condition.ty() != ClassicalType::Bool {
558 return Err(CircuitError::InvalidControlFlow(format!(
559 "Branch condition in block {:?} must be Bool, got {:?}",
560 node,
561 condition.ty()
562 )));
563 }
564 self.validate_expr(condition, "branch condition")?;
565 let (true_target, false_target) = self.branch_targets(node, block)?;
566 match self.control_flow_region(node).ok_or_else(|| {
567 self.invalid_block(
568 block,
569 node,
570 "has Branch terminator but no structured region",
571 )
572 })? {
573 ControlFlowRegion::If {
574 then_entry,
575 else_entry,
576 merge_block,
577 outer,
578 ..
579 } => {
580 if *then_entry != true_target || *else_entry != false_target {
581 return Err(self.invalid_block(
582 block,
583 node,
584 "has If region that does not match branch edges",
585 ));
586 }
587 self.require_block(*merge_block, "if merge block")?;
588 self.validate_outer_fields(outer, node)?;
589 }
590 ControlFlowRegion::While {
591 body_entry,
592 exit_block,
593 outer,
594 } => {
595 if *body_entry != true_target || *exit_block != false_target {
596 return Err(self.invalid_block(
597 block,
598 node,
599 "has While region that does not match branch edges",
600 ));
601 }
602 self.validate_outer_fields(outer, node)?;
603 }
604 _ => {
605 return Err(self.invalid_block(
606 block,
607 node,
608 "has Branch terminator with incompatible region",
609 ));
610 }
611 }
612 }
613 Terminator::ForLoop {
614 var,
615 start,
616 stop,
617 step,
618 } => {
619 self.validate_classical_var(*var, "for loop variable")?;
620 self.validate_expr(start, "for start")?;
621 self.validate_expr(stop, "for stop")?;
622 self.validate_expr(step, "for step")?;
623 let (body_target, exit_target) = self.branch_targets(node, block)?;
624 match self.control_flow_region(node).ok_or_else(|| {
625 self.invalid_block(block, node, "has For terminator but no structured region")
626 })? {
627 ControlFlowRegion::For {
628 body_entry,
629 exit_block,
630 outer,
631 } => {
632 if *body_entry != body_target || *exit_block != exit_target {
633 return Err(self.invalid_block(
634 block,
635 node,
636 "has For region that does not match branch edges",
637 ));
638 }
639 self.validate_outer_fields(outer, node)?;
640 }
641 _ => {
642 return Err(self.invalid_block(
643 block,
644 node,
645 "has For terminator with incompatible region",
646 ));
647 }
648 }
649 }
650 Terminator::Switch(target) => {
651 if !matches!(target.ty(), ClassicalType::UInt(_)) {
652 return Err(CircuitError::InvalidControlFlow(format!(
653 "Switch target in block {:?} must be UInt, got {:?}",
654 node,
655 target.ty()
656 )));
657 }
658 self.validate_expr(target, "switch target")?;
659 match self.control_flow_region(node).ok_or_else(|| {
660 self.invalid_block(
661 block,
662 node,
663 "has Switch terminator but no structured region",
664 )
665 })? {
666 ControlFlowRegion::Switch {
667 cases,
668 default_entry,
669 merge_block,
670 outer,
671 ..
672 } => {
673 self.require_block(*default_entry, "switch default block")?;
674 self.require_block(*merge_block, "switch merge block")?;
675 self.validate_outer_fields(outer, node)?;
676 for case in cases {
677 self.require_block(case.entry, "switch case block")?;
678 let found = outgoing.iter().any(|edge| {
679 edge.target() == case.entry
680 && matches!(edge.weight(), FlowEdge::Case(value) if *value == case.value)
681 });
682 if !found {
683 return Err(self.invalid_block(
684 block,
685 node,
686 "is missing a switch case edge",
687 ));
688 }
689 }
690 let has_default = outgoing.iter().any(|edge| {
691 edge.target() == *default_entry
692 && matches!(edge.weight(), FlowEdge::DefaultCase)
693 });
694 if !has_default || outgoing.len() != cases.len() + 1 {
695 return Err(self.invalid_block(
696 block,
697 node,
698 "has invalid switch outgoing edges",
699 ));
700 }
701 }
702 _ => {
703 return Err(self.invalid_block(
704 block,
705 node,
706 "has Switch terminator with incompatible region",
707 ));
708 }
709 }
710 }
711 }
712 Ok(())
713 }
714
715 fn parse_subgraph(
716 &self,
717 start_node: NodeIndex,
718 stop_node: Option<NodeIndex>,
719 visited: &mut HashSet<NodeIndex>,
720 ) -> Result<Vec<Operation>, CircuitError> {
721 let mut ops = Vec::new();
722 let mut current = Some(start_node);
723
724 while let Some(node) = current {
725 if Some(node) == stop_node {
726 return Ok(ops);
727 }
728 if !visited.insert(node) {
729 return Err(CircuitError::InvalidControlFlow(format!(
730 "control flow visits block {:?} more than once outside a structured boundary",
731 node
732 )));
733 }
734
735 let block = &self.data[node];
736 ops.extend(block.operations.clone());
737 match block.terminator.as_ref() {
738 Some(Terminator::Return) => return Ok(ops),
739 Some(Terminator::Jump(target)) => current = Some(*target),
740 Some(Terminator::Break(_)) => {
741 ops.push(control_operation(ClassicalControlOp::Break));
742 return Ok(ops);
743 }
744 Some(Terminator::Continue(_)) => {
745 ops.push(control_operation(ClassicalControlOp::Continue));
746 return Ok(ops);
747 }
748 Some(Terminator::Branch(condition)) => match self
749 .control_flow_region(node)
750 .ok_or_else(|| {
751 CircuitError::InvalidControlFlow(format!(
752 "Branch block {:?} is missing structured region",
753 node
754 ))
755 })? {
756 ControlFlowRegion::If {
757 then_entry,
758 else_entry,
759 merge_block,
760 has_else,
761 outer,
762 } => {
763 let then_ops =
764 self.parse_subgraph(*then_entry, Some(*merge_block), visited)?;
765 let else_ops =
766 self.parse_subgraph(*else_entry, Some(*merge_block), visited)?;
767 let op = IfOp::new(
768 condition.clone(),
769 ControlBody::new(then_ops),
770 has_else.then_some(ControlBody::new(else_ops)),
771 )?;
772 ops.push(Operation {
773 instruction: Instruction::ClassicalControl(ClassicalControlOp::If(op)),
774 qubits: outer.qubits.clone(),
775 params: outer.params.clone(),
776 label: outer.label.clone(),
777 });
778 current = Some(*merge_block);
779 }
780 ControlFlowRegion::While {
781 body_entry,
782 exit_block,
783 outer,
784 } => {
785 let body_ops = self.parse_subgraph(*body_entry, Some(node), visited)?;
786 let op = WhileOp::new(condition.clone(), ControlBody::new(body_ops))?;
787 ops.push(Operation {
788 instruction: Instruction::ClassicalControl(ClassicalControlOp::While(
789 op,
790 )),
791 qubits: outer.qubits.clone(),
792 params: outer.params.clone(),
793 label: outer.label.clone(),
794 });
795 current = Some(*exit_block);
796 }
797 _ => unreachable!("validation rejects incompatible branch regions"),
798 },
799 Some(Terminator::ForLoop {
800 var,
801 start,
802 stop,
803 step,
804 }) => match self.control_flow_region(node).ok_or_else(|| {
805 CircuitError::InvalidControlFlow(format!(
806 "For block {:?} is missing structured region",
807 node
808 ))
809 })? {
810 ControlFlowRegion::For {
811 body_entry,
812 exit_block,
813 outer,
814 } => {
815 let body_ops = self.parse_subgraph(*body_entry, Some(node), visited)?;
816 let op = ForOp::new(
817 *var,
818 start.clone(),
819 stop.clone(),
820 step.clone(),
821 ControlBody::new(body_ops),
822 )?;
823 ops.push(Operation {
824 instruction: Instruction::ClassicalControl(ClassicalControlOp::For(op)),
825 qubits: outer.qubits.clone(),
826 params: outer.params.clone(),
827 label: outer.label.clone(),
828 });
829 current = Some(*exit_block);
830 }
831 _ => unreachable!("validation rejects incompatible for regions"),
832 },
833 Some(Terminator::Switch(target)) => {
834 match self.control_flow_region(node).ok_or_else(|| {
835 CircuitError::InvalidControlFlow(format!(
836 "Switch block {:?} is missing structured region",
837 node
838 ))
839 })? {
840 ControlFlowRegion::Switch {
841 cases,
842 default_entry,
843 merge_block,
844 has_default,
845 outer,
846 } => {
847 let mut rebuilt_cases = Vec::with_capacity(cases.len());
848 for case in cases {
849 let body_ops =
850 self.parse_subgraph(case.entry, Some(*merge_block), visited)?;
851 rebuilt_cases
852 .push(SwitchCase::new(case.value, ControlBody::new(body_ops)));
853 }
854 let default = if *has_default {
855 Some(ControlBody::new(self.parse_subgraph(
856 *default_entry,
857 Some(*merge_block),
858 visited,
859 )?))
860 } else {
861 self.parse_subgraph(*default_entry, Some(*merge_block), visited)?;
862 None
863 };
864 let op = SwitchOp::new(target.clone(), rebuilt_cases, default)?;
865 ops.push(Operation {
866 instruction: Instruction::ClassicalControl(
867 ClassicalControlOp::Switch(op),
868 ),
869 qubits: outer.qubits.clone(),
870 params: outer.params.clone(),
871 label: outer.label.clone(),
872 });
873 current = Some(*merge_block);
874 }
875 _ => unreachable!("validation rejects incompatible switch regions"),
876 }
877 }
878 None => unreachable!("validation rejects unterminated blocks"),
879 }
880 }
881
882 Err(CircuitError::InvalidControlFlow(
883 "structured CFG traversal ended without a terminator".to_string(),
884 ))
885 }
886
887 fn value_operation(&self, operation: Operation) -> Result<ValueOperation, CircuitError> {
888 storage_operation_to_value(operation, &|param| self.parameter_value(param))
889 }
890
891 fn parameter_value(&self, parameter: &CircuitParam) -> Result<ParameterValue, CircuitError> {
892 match parameter {
893 CircuitParam::Fixed(value) => Ok(ParameterValue::Fixed(*value)),
894 CircuitParam::Index(index) => self
895 .parameters
896 .get_index(*index as usize)
897 .cloned()
898 .map(ParameterValue::Param)
899 .ok_or(CircuitError::InvalidParameterIndex(*index)),
900 }
901 }
902
903 fn global_phase_parameter(&self) -> Result<Parameter, CircuitError> {
904 match self.global_phase {
905 CircuitParam::Fixed(value) => Ok(Parameter::from(value)),
906 CircuitParam::Index(index) => self
907 .parameters
908 .get_index(index as usize)
909 .cloned()
910 .ok_or(CircuitError::InvalidParameterIndex(index)),
911 }
912 }
913
914 fn validate_operation(&self, operation: &Operation, context: &str) -> Result<(), CircuitError> {
915 for qubit in &operation.qubits {
916 if !self.qubits.contains(qubit) {
917 return Err(CircuitError::InvalidControlFlow(format!(
918 "{} references unknown qubit {}",
919 context,
920 qubit.id()
921 )));
922 }
923 }
924 for parameter in &operation.params {
925 self.validate_param(parameter, context)?;
926 }
927 if let Instruction::ClassicalData(op) = &operation.instruction {
928 self.validate_classical_data_op(op, operation.qubits.len())?;
929 }
930 Ok(())
931 }
932
933 fn validate_classical_data_op(
934 &self,
935 op: &ClassicalDataOp,
936 qubit_count: usize,
937 ) -> Result<(), CircuitError> {
938 match op {
939 ClassicalDataOp::Store { target, value } => {
940 self.validate_classical_var(*target, "store target")?;
941 if qubit_count != 0 {
942 return Err(CircuitError::InvalidControlFlow(format!(
943 "store operation has {} qubits",
944 qubit_count
945 )));
946 }
947 self.validate_expr(value, "store value")?;
948 if target.ty() != value.ty() {
949 return Err(CircuitError::InvalidControlFlow(format!(
950 "store target type {:?} does not match value type {:?}",
951 target.ty(),
952 value.ty()
953 )));
954 }
955 }
956 ClassicalDataOp::MeasureBit { result } => {
957 self.validate_classical_value(*result, "measure_bit result")?;
958 if qubit_count != 1 {
959 return Err(CircuitError::InvalidControlFlow(format!(
960 "measure_bit operation has {} qubits",
961 qubit_count
962 )));
963 }
964 }
965 ClassicalDataOp::MeasureBits { result } => {
966 self.validate_classical_value(*result, "measure_bits result")?;
967 if result.ty().measurement_width() != Some(qubit_count as u32) {
968 return Err(CircuitError::InvalidControlFlow(format!(
969 "measure_bits result type {:?} does not match {} qubits",
970 result.ty(),
971 qubit_count
972 )));
973 }
974 }
975 }
976 Ok(())
977 }
978
979 fn validate_outer_fields(
980 &self,
981 outer: &OperationMetadata,
982 node: NodeIndex,
983 ) -> Result<(), CircuitError> {
984 for qubit in &outer.qubits {
985 if !self.qubits.contains(qubit) {
986 return Err(CircuitError::InvalidControlFlow(format!(
987 "control operation at block {:?} references unknown qubit {}",
988 node,
989 qubit.id()
990 )));
991 }
992 }
993 for param in &outer.params {
994 self.validate_param(param, &format!("control operation at block {:?}", node))?;
995 }
996 Ok(())
997 }
998
999 fn validate_expr(&self, expr: &ClassicalExpr, context: &str) -> Result<(), CircuitError> {
1000 for var in expr.vars() {
1001 self.validate_classical_var(var, context)?;
1002 }
1003 for value in expr.values() {
1004 self.validate_classical_value(value, context)?;
1005 }
1006 Ok(())
1007 }
1008
1009 fn validate_classical_var(&self, var: ClassicalVar, context: &str) -> Result<(), CircuitError> {
1010 match self.classical_vars.get(var.id() as usize) {
1011 Some(ty) if *ty == var.ty() => Ok(()),
1012 Some(ty) => Err(CircuitError::InvalidControlFlow(format!(
1013 "{} references classical var {} with type {:?}, got {:?}",
1014 context,
1015 var.id(),
1016 ty,
1017 var.ty()
1018 ))),
1019 None => Err(CircuitError::InvalidControlFlow(format!(
1020 "{} references unknown classical var {}",
1021 context,
1022 var.id()
1023 ))),
1024 }
1025 }
1026
1027 fn validate_classical_value(
1028 &self,
1029 value: crate::circuit::ClassicalValue,
1030 context: &str,
1031 ) -> Result<(), CircuitError> {
1032 match self.classical_values.get(value.index() as usize) {
1033 Some(ty) if *ty == value.ty() => Ok(()),
1034 Some(ty) => Err(CircuitError::InvalidControlFlow(format!(
1035 "{} references classical value {} with type {:?}, got {:?}",
1036 context,
1037 value.index(),
1038 ty,
1039 value.ty()
1040 ))),
1041 None => Err(CircuitError::InvalidControlFlow(format!(
1042 "{} references unknown classical value {}",
1043 context,
1044 value.index()
1045 ))),
1046 }
1047 }
1048
1049 fn validate_param(&self, parameter: &CircuitParam, context: &str) -> Result<(), CircuitError> {
1050 if let CircuitParam::Index(index) = parameter
1051 && self.parameters.get_index(*index as usize).is_none()
1052 {
1053 return Err(CircuitError::InvalidControlFlow(format!(
1054 "{} references missing parameter index {}",
1055 context, index
1056 )));
1057 }
1058 Ok(())
1059 }
1060
1061 fn require_block(&self, node: NodeIndex, context: &str) -> Result<(), CircuitError> {
1062 if self.data.node_weight(node).is_none() {
1063 return Err(CircuitError::InvalidControlFlow(format!(
1064 "{} {:?} does not exist in the CFG",
1065 context, node
1066 )));
1067 }
1068 Ok(())
1069 }
1070
1071 fn branch_targets(
1072 &self,
1073 node: NodeIndex,
1074 block: &BasicBlock,
1075 ) -> Result<(NodeIndex, NodeIndex), CircuitError> {
1076 let outgoing: Vec<_> = self.data.edges(node).collect();
1077 let true_targets: Vec<_> = outgoing
1078 .iter()
1079 .filter(|edge| matches!(edge.weight(), FlowEdge::TrueBranch))
1080 .map(|edge| edge.target())
1081 .collect();
1082 let false_targets: Vec<_> = outgoing
1083 .iter()
1084 .filter(|edge| matches!(edge.weight(), FlowEdge::FalseBranch))
1085 .map(|edge| edge.target())
1086 .collect();
1087 if true_targets.is_empty() {
1088 return Err(self.invalid_block(block, node, "is missing a TrueBranch edge"));
1089 }
1090 if false_targets.is_empty() {
1091 return Err(self.invalid_block(block, node, "is missing a FalseBranch edge"));
1092 }
1093 if true_targets.len() != 1 || false_targets.len() != 1 || outgoing.len() != 2 {
1094 return Err(self.invalid_block(
1095 block,
1096 node,
1097 "must have exactly one TrueBranch edge and one FalseBranch edge",
1098 ));
1099 }
1100 Ok((true_targets[0], false_targets[0]))
1101 }
1102
1103 fn invalid_block(&self, block: &BasicBlock, node: NodeIndex, message: &str) -> CircuitError {
1104 CircuitError::InvalidControlFlow(format!(
1105 "Block '{}' (index {:?}) {}",
1106 block.label().unwrap_or("<unlabeled>"),
1107 node,
1108 message
1109 ))
1110 }
1111}
1112
1113#[derive(Clone, Copy, Default)]
1114struct ControlContext {
1115 break_target: Option<NodeIndex>,
1116 continue_target: Option<NodeIndex>,
1117}
1118
1119#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1120enum ProcessExit {
1121 Reachable(NodeIndex),
1122 Terminated,
1123}
1124
1125fn process_operations(
1126 operations: &[Operation],
1127 cfg: &mut CircuitCFG,
1128 mut current_block: NodeIndex,
1129 context: ControlContext,
1130) -> Result<ProcessExit, CircuitError> {
1131 for (idx, op) in operations.iter().enumerate() {
1132 match &op.instruction {
1133 Instruction::ClassicalControl(ClassicalControlOp::If(if_op)) => {
1134 let then_entry =
1135 cfg.add_block(BasicBlock::new().with_label(format!("if_then_{idx}")));
1136 let else_entry =
1137 cfg.add_block(BasicBlock::new().with_label(format!("if_else_{idx}")));
1138 let merge_block =
1139 cfg.add_block(BasicBlock::new().with_label(format!("if_merge_{idx}")));
1140
1141 cfg.data[current_block]
1142 .set_terminator(Terminator::Branch(if_op.condition().clone()));
1143 cfg.add_edge(current_block, then_entry, FlowEdge::TrueBranch);
1144 cfg.add_edge(current_block, else_entry, FlowEdge::FalseBranch);
1145
1146 let then_exit =
1147 process_operations(if_op.then_body().operations(), cfg, then_entry, context)?;
1148 let else_exit = if let Some(else_body) = if_op.else_body() {
1149 process_operations(else_body.operations(), cfg, else_entry, context)?
1150 } else {
1151 ProcessExit::Reachable(else_entry)
1152 };
1153
1154 connect_to_merge(cfg, then_exit, merge_block)?;
1155 connect_to_merge(cfg, else_exit, merge_block)?;
1156 cfg.set_control_flow_region(
1157 current_block,
1158 ControlFlowRegion::If {
1159 then_entry,
1160 else_entry,
1161 merge_block,
1162 has_else: if_op.else_body().is_some(),
1163 outer: OperationMetadata::from_operation(op),
1164 },
1165 );
1166 current_block = merge_block;
1167 }
1168 Instruction::ClassicalControl(ClassicalControlOp::While(while_op)) => {
1169 let header =
1170 cfg.add_block(BasicBlock::new().with_label(format!("while_cond_{idx}")));
1171 cfg.data[current_block].set_terminator(Terminator::Jump(header));
1172 cfg.add_edge(current_block, header, FlowEdge::Unconditional);
1173
1174 let body_entry =
1175 cfg.add_block(BasicBlock::new().with_label(format!("while_body_{idx}")));
1176 let exit_block =
1177 cfg.add_block(BasicBlock::new().with_label(format!("while_exit_{idx}")));
1178 cfg.data[header].set_terminator(Terminator::Branch(while_op.condition().clone()));
1179 cfg.add_edge(header, body_entry, FlowEdge::TrueBranch);
1180 cfg.add_edge(header, exit_block, FlowEdge::FalseBranch);
1181
1182 let body_exit = process_operations(
1183 while_op.body().operations(),
1184 cfg,
1185 body_entry,
1186 ControlContext {
1187 break_target: Some(exit_block),
1188 continue_target: Some(header),
1189 },
1190 )?;
1191 connect_to_merge(cfg, body_exit, header)?;
1192 cfg.set_control_flow_region(
1193 header,
1194 ControlFlowRegion::While {
1195 body_entry,
1196 exit_block,
1197 outer: OperationMetadata::from_operation(op),
1198 },
1199 );
1200 current_block = exit_block;
1201 }
1202 Instruction::ClassicalControl(ClassicalControlOp::For(for_op)) => {
1203 let header =
1204 cfg.add_block(BasicBlock::new().with_label(format!("for_header_{idx}")));
1205 cfg.data[current_block].set_terminator(Terminator::Jump(header));
1206 cfg.add_edge(current_block, header, FlowEdge::Unconditional);
1207
1208 let body_entry =
1209 cfg.add_block(BasicBlock::new().with_label(format!("for_body_{idx}")));
1210 let exit_block =
1211 cfg.add_block(BasicBlock::new().with_label(format!("for_exit_{idx}")));
1212 cfg.data[header].set_terminator(Terminator::ForLoop {
1213 var: for_op.var(),
1214 start: for_op.start().clone(),
1215 stop: for_op.stop().clone(),
1216 step: for_op.step().clone(),
1217 });
1218 cfg.add_edge(header, body_entry, FlowEdge::TrueBranch);
1219 cfg.add_edge(header, exit_block, FlowEdge::FalseBranch);
1220
1221 let body_exit = process_operations(
1222 for_op.body().operations(),
1223 cfg,
1224 body_entry,
1225 ControlContext {
1226 break_target: Some(exit_block),
1227 continue_target: Some(header),
1228 },
1229 )?;
1230 connect_to_merge(cfg, body_exit, header)?;
1231 cfg.set_control_flow_region(
1232 header,
1233 ControlFlowRegion::For {
1234 body_entry,
1235 exit_block,
1236 outer: OperationMetadata::from_operation(op),
1237 },
1238 );
1239 current_block = exit_block;
1240 }
1241 Instruction::ClassicalControl(ClassicalControlOp::Switch(switch_op)) => {
1242 let merge_block =
1243 cfg.add_block(BasicBlock::new().with_label(format!("switch_merge_{idx}")));
1244 cfg.data[current_block]
1245 .set_terminator(Terminator::Switch(switch_op.target().clone()));
1246
1247 let mut cases = Vec::with_capacity(switch_op.cases().len());
1248 let switch_context = ControlContext {
1249 break_target: Some(merge_block),
1250 continue_target: context.continue_target,
1251 };
1252 for case in switch_op.cases() {
1253 let entry = cfg.add_block(BasicBlock::new().with_label(format!(
1254 "switch_case_{}_{}",
1255 idx,
1256 case.value()
1257 )));
1258 cfg.add_edge(current_block, entry, FlowEdge::Case(case.value()));
1259 let case_exit =
1260 process_operations(case.body().operations(), cfg, entry, switch_context)?;
1261 connect_to_merge(cfg, case_exit, merge_block)?;
1262 cases.push(SwitchRegionCase {
1263 value: case.value(),
1264 entry,
1265 });
1266 }
1267
1268 let default_entry =
1269 cfg.add_block(BasicBlock::new().with_label(format!("switch_default_{idx}")));
1270 cfg.add_edge(current_block, default_entry, FlowEdge::DefaultCase);
1271 let has_default = switch_op.default().is_some();
1272 let default_exit = if let Some(default) = switch_op.default() {
1273 process_operations(default.operations(), cfg, default_entry, switch_context)?
1274 } else {
1275 ProcessExit::Reachable(default_entry)
1276 };
1277 connect_to_merge(cfg, default_exit, merge_block)?;
1278 cfg.set_control_flow_region(
1279 current_block,
1280 ControlFlowRegion::Switch {
1281 cases,
1282 default_entry,
1283 merge_block,
1284 has_default,
1285 outer: OperationMetadata::from_operation(op),
1286 },
1287 );
1288 current_block = merge_block;
1289 }
1290 Instruction::ClassicalControl(ClassicalControlOp::Break) => {
1291 let target = context.break_target.ok_or_else(|| {
1292 CircuitError::InvalidControlFlow(
1293 "break appears outside a loop or switch".to_string(),
1294 )
1295 })?;
1296 cfg.data[current_block].set_terminator(Terminator::Break(target));
1297 cfg.add_edge(current_block, target, FlowEdge::Break);
1298 return require_terminal_control(idx, operations.len());
1299 }
1300 Instruction::ClassicalControl(ClassicalControlOp::Continue) => {
1301 let target = context.continue_target.ok_or_else(|| {
1302 CircuitError::InvalidControlFlow("continue appears outside a loop".to_string())
1303 })?;
1304 cfg.data[current_block].set_terminator(Terminator::Continue(target));
1305 cfg.add_edge(current_block, target, FlowEdge::Continue);
1306 return require_terminal_control(idx, operations.len());
1307 }
1308 _ => cfg.data[current_block].push_operation(op.clone()),
1309 }
1310 }
1311 Ok(ProcessExit::Reachable(current_block))
1312}
1313
1314fn connect_to_merge(
1315 cfg: &mut CircuitCFG,
1316 source: ProcessExit,
1317 target: NodeIndex,
1318) -> Result<(), CircuitError> {
1319 if let ProcessExit::Reachable(source) = source {
1320 cfg.data[source].set_terminator(Terminator::Jump(target));
1321 cfg.add_edge(source, target, FlowEdge::Unconditional);
1322 }
1323 Ok(())
1324}
1325
1326fn require_terminal_control(
1327 index: usize,
1328 operation_count: usize,
1329) -> Result<ProcessExit, CircuitError> {
1330 if index + 1 != operation_count {
1331 return Err(CircuitError::InvalidControlFlow(
1332 "break/continue must be the final operation in its control-flow body".to_string(),
1333 ));
1334 }
1335 Ok(ProcessExit::Terminated)
1336}
1337
1338fn control_operation(op: ClassicalControlOp) -> Operation {
1339 Operation {
1340 instruction: Instruction::ClassicalControl(op),
1341 qubits: SmallVec::new(),
1342 params: SmallVec::new(),
1343 label: None,
1344 }
1345}
1346
1347impl TryFrom<&Circuit> for CircuitCFG {
1348 type Error = CircuitError;
1349
1350 fn try_from(circuit: &Circuit) -> Result<Self, Self::Error> {
1351 Self::from_circuit(circuit)
1352 }
1353}
1354
1355impl TryFrom<&CircuitCFG> for Circuit {
1356 type Error = CircuitError;
1357
1358 fn try_from(cfg: &CircuitCFG) -> Result<Self, Self::Error> {
1359 cfg.to_circuit()
1360 }
1361}
1362
1363#[cfg(test)]
1364#[path = "./cfg_test.rs"]
1365mod cfg_test;