Skip to main content

cqlib_core/circuit/
cfg.rs

1// This code is part of Cqlib.
2//
3// (C) Copyright China Telecom Quantum Group 2026
4//
5// This code is licensed under the Apache License, Version 2.0. You may
6// obtain a copy of this license in the LICENSE.txt file in the root directory
7// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8//
9// Any modifications or derivative works of this code must retain this
10// copyright notice, and modified files need to carry a notice indicating
11// that they have been altered from the originals.
12
13//! Structured control-flow graph representation for circuits.
14//!
15//! `CircuitCFG` is a graph view of the expression-based dynamic-circuit IR.
16//! It is intentionally structured: every conditional, loop, and switch header
17//! has an accompanying [`ControlFlowRegion`] that records the body entry blocks,
18//! merge/exit block, and outer operation metadata needed for exact round-trip
19//! conversion back to [`Circuit`].
20//!
21//! The CFG does not accept arbitrary irreducible control flow. Non-structured
22//! cycles, missing region metadata, unreachable blocks, and mismatched branch
23//! edges are rejected by [`CircuitCFG::validate`]. This keeps the graph aligned
24//! with [`ClassicalControlOp`] rather than becoming a separate unstructured IR.
25
26use 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)]
41/// Semantic kind of an edge between two circuit basic blocks.
42pub enum FlowEdge {
43    /// True edge out of `if`, `while`, or `for` headers.
44    TrueBranch,
45    /// False edge out of `if`, `while`, or `for` headers.
46    FalseBranch,
47    /// Fallthrough edge for sequential jumps and structured merges.
48    Unconditional,
49    /// Exact-value switch case edge.
50    Case(u128),
51    /// Switch default edge. Present even when the source switch has no default
52    /// body, so the CFG always has an explicit non-matching path.
53    DefaultCase,
54    /// Edge taken by a structured `break`.
55    Break,
56    /// Edge taken by a structured `continue`.
57    Continue,
58}
59
60#[derive(Debug, Clone)]
61/// Control transfer that ends a [`BasicBlock`].
62///
63/// Every block in a valid [`CircuitCFG`] has exactly one terminator, and its
64/// outgoing edges must agree with that terminator.
65pub enum Terminator {
66    /// Boolean condition for `if` and `while` headers.
67    Branch(ClassicalExpr),
68    /// Runtime unsigned range loop header. This is kept distinct from
69    /// `Branch` because `ForOp` has no lossless lowering to the current
70    /// expression language.
71    ForLoop {
72        /// Loop variable updated for each iteration.
73        var: ClassicalVar,
74        /// Inclusive initial value.
75        start: ClassicalExpr,
76        /// Exclusive upper bound.
77        stop: ClassicalExpr,
78        /// Non-zero iteration increment.
79        step: ClassicalExpr,
80    },
81    /// Unsigned exact-value multi-way branch.
82    Switch(ClassicalExpr),
83    /// Unconditional structured fallthrough.
84    Jump(NodeIndex),
85    /// Exit the nearest loop or switch region.
86    Break(NodeIndex),
87    /// Advance the nearest loop region.
88    Continue(NodeIndex),
89    /// End of circuit execution.
90    Return,
91}
92
93/// Operation fields that live on the outer `Instruction::ClassicalControl`
94/// operation, not inside the expanded body blocks.
95#[derive(Debug, Clone)]
96pub struct OperationMetadata {
97    /// Qubits attached to the outer control-flow operation.
98    pub qubits: SmallVec<[Qubit; 3]>,
99    /// Parameters attached to the outer control-flow operation.
100    pub params: SmallVec<[CircuitParam; 1]>,
101    /// Optional diagnostic label of the outer operation.
102    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)]
116/// A switch match value and the entry block of its body.
117pub struct SwitchRegionCase {
118    /// Exact unsigned value matched by this case.
119    pub value: u128,
120    /// First block executed when the case matches.
121    pub entry: NodeIndex,
122}
123
124/// Structured region owned by a control-flow header block.
125///
126/// Labels are deliberately not part of this metadata. Labels are diagnostic
127/// text only; region metadata and graph edges define the control-flow shape.
128#[derive(Debug, Clone)]
129pub enum ControlFlowRegion {
130    /// Structured conditional region.
131    If {
132        /// Entry block of the then body.
133        then_entry: NodeIndex,
134        /// Entry block of the else path, or the merge block when absent.
135        else_entry: NodeIndex,
136        /// Block where both branches rejoin.
137        merge_block: NodeIndex,
138        /// Whether an explicit else body existed in the source circuit.
139        has_else: bool,
140        /// Metadata from the outer control-flow operation.
141        outer: OperationMetadata,
142    },
143    /// Structured while-loop region.
144    While {
145        /// Entry block of the loop body.
146        body_entry: NodeIndex,
147        /// First block executed after the loop.
148        exit_block: NodeIndex,
149        /// Metadata from the outer control-flow operation.
150        outer: OperationMetadata,
151    },
152    /// Structured unsigned range-loop region.
153    For {
154        /// Entry block of the loop body.
155        body_entry: NodeIndex,
156        /// First block executed after the loop.
157        exit_block: NodeIndex,
158        /// Metadata from the outer control-flow operation.
159        outer: OperationMetadata,
160    },
161    /// Structured exact-value switch region.
162    Switch {
163        /// Explicit cases in source order.
164        cases: Vec<SwitchRegionCase>,
165        /// Entry block of the default path, or the merge block when absent.
166        default_entry: NodeIndex,
167        /// Block where all switch paths rejoin.
168        merge_block: NodeIndex,
169        /// Whether an explicit default body existed in the source circuit.
170        has_default: bool,
171        /// Metadata from the outer control-flow operation.
172        outer: OperationMetadata,
173    },
174}
175
176/// Straight-line sequence of operations followed by one control transfer.
177#[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    /// Creates an empty, unlabeled block without a terminator.
186    pub fn new() -> Self {
187        Self {
188            operations: Vec::new(),
189            terminator: None,
190            label: None,
191        }
192    }
193
194    /// Sets a diagnostic label and returns the updated block.
195    pub fn with_label(mut self, label: impl Into<String>) -> Self {
196        self.label = Some(label.into());
197        self
198    }
199
200    /// Appends one non-control-flow storage operation.
201    pub fn push_operation(&mut self, op: Operation) {
202        self.operations.push(op);
203    }
204
205    /// Appends a sequence of non-control-flow storage operations.
206    pub fn extend_operations(&mut self, ops: impl IntoIterator<Item = Operation>) {
207        self.operations.extend(ops);
208    }
209
210    /// Replaces the block terminator.
211    pub fn set_terminator(&mut self, terminator: Terminator) {
212        self.terminator = Some(terminator);
213    }
214
215    /// Returns `true` when the block has neither operations nor a terminator.
216    pub fn is_empty(&self) -> bool {
217        self.operations.is_empty() && self.terminator.is_none()
218    }
219
220    /// Returns `true` when a terminator has been assigned.
221    pub fn has_terminator(&self) -> bool {
222        self.terminator.is_some()
223    }
224
225    /// Returns the number of ordinary operations, excluding the terminator.
226    pub fn len(&self) -> usize {
227        self.operations.len()
228    }
229
230    /// Returns the optional diagnostic label.
231    pub fn label(&self) -> Option<&str> {
232        self.label.as_deref()
233    }
234
235    /// Returns the block terminator, if assigned.
236    pub fn terminator(&self) -> Option<&Terminator> {
237        self.terminator.as_ref()
238    }
239
240    /// Returns the ordinary operations in execution order.
241    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/// Structured control-flow graph representation of a [`Circuit`].
253///
254/// This is not a general unstructured IR. Conditional, loop, and switch
255/// headers require matching [`ControlFlowRegion`] metadata so the graph can
256/// round-trip back to circuit control-flow operations.
257#[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    /// Creates an empty CFG with densely numbered qubits `0..num_qubits`.
272    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    /// Creates an empty CFG with the supplied logical qubits in insertion order.
288    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    /// Adds a block and returns its stable graph index.
303    pub fn add_block(&mut self, block: BasicBlock) -> NodeIndex {
304        self.data.add_node(block)
305    }
306
307    /// Adds a directed semantic edge between existing blocks.
308    ///
309    /// Returns `None` without mutation when either endpoint is unknown.
310    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    /// Returns the designated entry block.
323    pub fn entry_block(&self) -> Option<NodeIndex> {
324        self.entry_block
325    }
326
327    /// Sets the graph entry block.
328    ///
329    /// The index is checked by [`CircuitCFG::validate`], not by this setter.
330    pub fn set_entry_block(&mut self, index: NodeIndex) {
331        self.entry_block = Some(index);
332    }
333
334    /// Associates structured-region metadata with its header block.
335    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    /// Returns structured-region metadata owned by `branch_block`.
340    pub fn control_flow_region(&self, branch_block: NodeIndex) -> Option<&ControlFlowRegion> {
341        self.control_flow_regions.get(&branch_block)
342    }
343
344    /// Returns `true` when the block owns a while or for region.
345    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    /// Iterates over all stable block indices and immutable blocks.
353    pub fn blocks(&self) -> impl Iterator<Item = (NodeIndex, &BasicBlock)> {
354        self.data.node_indices().map(|i| (i, &self.data[i]))
355    }
356
357    /// Returns a mutable block by stable graph index.
358    pub fn block_mut(&mut self, index: NodeIndex) -> Option<&mut BasicBlock> {
359        self.data.node_weight_mut(index)
360    }
361
362    /// Iterates over outgoing target blocks and semantic edge kinds.
363    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    /// Returns the number of blocks currently stored in the graph.
373    pub fn num_blocks(&self) -> usize {
374        self.data.node_indices().count()
375    }
376
377    /// Returns the number of logical qubits known to the CFG.
378    pub fn num_qubits(&self) -> usize {
379        self.qubits.len()
380    }
381
382    /// Returns logical qubits in insertion order.
383    pub fn qubits(&self) -> Vec<Qubit> {
384        self.qubits.iter().cloned().collect()
385    }
386
387    /// Returns the static type table for mutable classical variables.
388    pub fn classical_vars(&self) -> &[ClassicalType] {
389        &self.classical_vars
390    }
391
392    /// Returns the static type table for immutable classical values.
393    pub fn classical_values(&self) -> &[ClassicalType] {
394        &self.classical_values
395    }
396
397    /// Expands a structured circuit into a validated CFG.
398    ///
399    /// # Errors
400    ///
401    /// Returns an error if operations cannot be expanded into the supported
402    /// structured representation or the generated graph fails validation.
403    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    /// Checks graph, operation, parameter, edge, terminator, and region invariants.
433    ///
434    /// # Errors
435    ///
436    /// Returns [`CircuitError::InvalidControlFlow`] for malformed or
437    /// unreachable graph structure and other [`CircuitError`] variants for
438    /// invalid operations or parameter references.
439    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    /// Reconstructs a structured circuit from this CFG.
487    ///
488    /// # Errors
489    ///
490    /// Returns an error when validation fails or stored operations and
491    /// parameters cannot be converted back to value-level circuit IR.
492    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;