Skip to main content

cqlib_core/circuit/
circuit_verify.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//! Validation of circuit classical-data and structured control-flow invariants.
14//!
15//! The verifier checks that classical handles belong to the circuit, immutable
16//! values have one dominating definition, expressions only read available
17//! values, branch-local values do not escape their region, and `break` and
18//! `continue` appear only in valid terminal positions. Quantum gate arity and
19//! qubit membership are validated when operations are appended, not here.
20
21use crate::circuit::gate::{ClassicalDataOp, Instruction};
22use crate::circuit::{
23    Circuit, CircuitError, ClassicalControlOp, ClassicalExpr, ClassicalType, ClassicalValue,
24    ControlBody, Operation,
25};
26use std::collections::{HashMap, HashSet};
27
28#[derive(Clone, Copy, Default)]
29struct ControlContext {
30    break_allowed: bool,
31    continue_allowed: bool,
32}
33
34impl ControlContext {
35    fn enter_loop(self) -> Self {
36        Self {
37            break_allowed: true,
38            continue_allowed: true,
39        }
40    }
41
42    fn enter_switch(self) -> Self {
43        Self {
44            break_allowed: true,
45            continue_allowed: self.continue_allowed,
46        }
47    }
48}
49
50struct ClassicalVerifier<'a> {
51    circuit: &'a Circuit,
52    definitions: HashMap<ClassicalValue, String>,
53}
54
55impl Circuit {
56    /// Validates classical handle ownership, measurement definitions, SSA use
57    /// availability, and structured control-flow value scope.
58    ///
59    /// Validation does not mutate the circuit and may be called after loading,
60    /// transforming, or manually constructing circuit IR.
61    ///
62    /// # Errors
63    ///
64    /// Returns a [`CircuitError`] describing the first ownership, definition,
65    /// type, scope, or control-flow placement violation encountered.
66    pub fn validate(&self) -> Result<(), CircuitError> {
67        self.validate_with_context(ControlContext::default(), true)
68    }
69
70    pub(super) fn validate_builder_state(&self) -> Result<(), CircuitError> {
71        let context = ControlContext {
72            break_allowed: self.control_scope_stack.iter().rev().any(|scope| {
73                matches!(
74                    scope,
75                    crate::circuit::circuit_classical::ControlScopeKind::Loop
76                        | crate::circuit::circuit_classical::ControlScopeKind::Switch
77                )
78            }),
79            continue_allowed: self.control_scope_stack.iter().rev().any(|scope| {
80                matches!(
81                    scope,
82                    crate::circuit::circuit_classical::ControlScopeKind::Loop
83                )
84            }),
85        };
86        self.validate_with_context(context, false)
87    }
88
89    fn validate_with_context(
90        &self,
91        context: ControlContext,
92        require_all_values_defined: bool,
93    ) -> Result<(), CircuitError> {
94        let mut verifier = ClassicalVerifier {
95            circuit: self,
96            definitions: HashMap::new(),
97        };
98        verifier.verify_operations(self.operations(), HashSet::new(), context, "circuit")?;
99
100        if require_all_values_defined {
101            for (index, ty) in self.classical_values.iter().copied().enumerate() {
102                let value = ClassicalValue::new(self.circuit_id, index as u32, ty);
103                if !verifier.definitions.contains_key(&value) {
104                    return Err(CircuitError::UndefinedClassicalValue {
105                        index: index as u32,
106                        context: "classical value table".to_string(),
107                    });
108                }
109            }
110        }
111        Ok(())
112    }
113}
114
115impl ClassicalVerifier<'_> {
116    fn verify_operations(
117        &mut self,
118        operations: &[Operation],
119        mut available: HashSet<ClassicalValue>,
120        context: ControlContext,
121        context_name: &str,
122    ) -> Result<HashSet<ClassicalValue>, CircuitError> {
123        for (index, operation) in operations.iter().enumerate() {
124            match &operation.instruction {
125                Instruction::ClassicalData(op) => {
126                    let operation_context = format!("{context_name} operation {index}");
127                    self.verify_data_op(op, operation, &mut available, &operation_context)?;
128                }
129                Instruction::ClassicalControl(op) => {
130                    let operation_context = format!("{context_name} operation {index}");
131                    self.verify_control_op(op, &available, context, &operation_context)?;
132                    match op {
133                        ClassicalControlOp::Break => {
134                            if !context.break_allowed {
135                                return Err(CircuitError::InvalidOperation(
136                                    "break can only be used inside a loop or switch body"
137                                        .to_string(),
138                                ));
139                            }
140                            if index + 1 != operations.len() {
141                                return Err(CircuitError::NonTerminalControlTransfer {
142                                    operation: "break",
143                                    context: context_name.to_string(),
144                                });
145                            }
146                        }
147                        ClassicalControlOp::Continue => {
148                            if !context.continue_allowed {
149                                return Err(CircuitError::InvalidOperation(
150                                    "continue can only be used inside a loop body".to_string(),
151                                ));
152                            }
153                            if index + 1 != operations.len() {
154                                return Err(CircuitError::NonTerminalControlTransfer {
155                                    operation: "continue",
156                                    context: context_name.to_string(),
157                                });
158                            }
159                        }
160                        _ => {}
161                    }
162                }
163                _ => {}
164            }
165        }
166        Ok(available)
167    }
168
169    fn verify_data_op(
170        &mut self,
171        op: &ClassicalDataOp,
172        operation: &Operation,
173        available: &mut HashSet<ClassicalValue>,
174        context: &str,
175    ) -> Result<(), CircuitError> {
176        match op {
177            ClassicalDataOp::Store { target, value } => {
178                self.circuit.validate_classical_var(*target)?;
179                self.verify_expr(value, available, context)?;
180                if target.ty() != value.ty() {
181                    return Err(CircuitError::InvalidOperation(format!(
182                        "store target type {:?} does not match value type {:?}",
183                        target.ty(),
184                        value.ty()
185                    )));
186                }
187            }
188            ClassicalDataOp::MeasureBit { result } => {
189                self.circuit.validate_classical_value(*result)?;
190                if result.ty() != ClassicalType::Bit || operation.qubits.len() != 1 {
191                    return Err(CircuitError::InvalidOperation(format!(
192                        "measure_bit result {} must be Bit with one qubit",
193                        result.index()
194                    )));
195                }
196                self.define(*result, available, context)?;
197            }
198            ClassicalDataOp::MeasureBits { result } => {
199                self.circuit.validate_classical_value(*result)?;
200                if result.ty().measurement_width() != Some(operation.qubits.len() as u32) {
201                    return Err(CircuitError::InvalidOperation(format!(
202                        "measure_bits result {} type {:?} does not match {} qubits",
203                        result.index(),
204                        result.ty(),
205                        operation.qubits.len()
206                    )));
207                }
208                self.define(*result, available, context)?;
209            }
210        }
211        Ok(())
212    }
213
214    fn define(
215        &mut self,
216        value: ClassicalValue,
217        available: &mut HashSet<ClassicalValue>,
218        context: &str,
219    ) -> Result<(), CircuitError> {
220        if let Some(first) = self.definitions.insert(value, context.to_string()) {
221            return Err(CircuitError::DuplicateClassicalValueDefinition {
222                index: value.index(),
223                first,
224                second: context.to_string(),
225            });
226        }
227        available.insert(value);
228        Ok(())
229    }
230
231    fn verify_expr(
232        &self,
233        expr: &ClassicalExpr,
234        available: &HashSet<ClassicalValue>,
235        context: &str,
236    ) -> Result<(), CircuitError> {
237        for var in expr.vars() {
238            self.circuit.validate_classical_var(var)?;
239        }
240        for value in expr.values() {
241            self.circuit.validate_classical_value(value)?;
242            if !available.contains(&value) {
243                if self.definitions.contains_key(&value) {
244                    return Err(CircuitError::ClassicalValueOutOfScope {
245                        index: value.index(),
246                        context: context.to_string(),
247                    });
248                }
249                return Err(CircuitError::UndefinedClassicalValue {
250                    index: value.index(),
251                    context: context.to_string(),
252                });
253            }
254        }
255        Ok(())
256    }
257
258    fn verify_body(
259        &mut self,
260        body: &ControlBody,
261        available: &HashSet<ClassicalValue>,
262        context: ControlContext,
263        name: &str,
264    ) -> Result<(), CircuitError> {
265        self.verify_operations(body.operations(), available.clone(), context, name)?;
266        Ok(())
267    }
268
269    fn verify_control_op(
270        &mut self,
271        op: &ClassicalControlOp,
272        available: &HashSet<ClassicalValue>,
273        context: ControlContext,
274        operation_context: &str,
275    ) -> Result<(), CircuitError> {
276        match op {
277            ClassicalControlOp::If(op) => {
278                self.verify_expr(op.condition(), available, operation_context)?;
279                self.verify_body(
280                    op.then_body(),
281                    available,
282                    context,
283                    &format!("{operation_context} then body"),
284                )?;
285                if let Some(body) = op.else_body() {
286                    self.verify_body(
287                        body,
288                        available,
289                        context,
290                        &format!("{operation_context} else body"),
291                    )?;
292                }
293            }
294            ClassicalControlOp::While(op) => {
295                self.verify_expr(op.condition(), available, operation_context)?;
296                self.verify_body(
297                    op.body(),
298                    available,
299                    context.enter_loop(),
300                    &format!("{operation_context} while body"),
301                )?;
302            }
303            ClassicalControlOp::For(op) => {
304                self.circuit.validate_classical_var(op.var())?;
305                self.verify_expr(op.start(), available, operation_context)?;
306                self.verify_expr(op.stop(), available, operation_context)?;
307                self.verify_expr(op.step(), available, operation_context)?;
308                self.verify_body(
309                    op.body(),
310                    available,
311                    context.enter_loop(),
312                    &format!("{operation_context} for body"),
313                )?;
314            }
315            ClassicalControlOp::Switch(op) => {
316                self.verify_expr(op.target(), available, operation_context)?;
317                let body_context = context.enter_switch();
318                for case in op.cases() {
319                    self.verify_body(
320                        case.body(),
321                        available,
322                        body_context,
323                        &format!("{operation_context} switch case {}", case.value()),
324                    )?;
325                }
326                if let Some(body) = op.default() {
327                    self.verify_body(
328                        body,
329                        available,
330                        body_context,
331                        &format!("{operation_context} switch default"),
332                    )?;
333                }
334            }
335            ClassicalControlOp::Break | ClassicalControlOp::Continue => {}
336        }
337        Ok(())
338    }
339}