1use crate::blocks::BlockKey;
2use crate::context::Context;
3use crate::dialect::DialectRegistry;
4use crate::operations::OpKey;
5use crate::values::ValueKey;
6use std::collections::HashSet;
7use thiserror::Error;
8
9#[derive(Debug, Error)]
10pub enum VerifyError {
11 #[error("SSA violation: value {0:?} used but not defined")]
12 UndefinedValue(ValueKey),
13
14 #[error("SSA violation: value {0:?} defined more than once")]
15 MultipleDefinition(ValueKey),
16
17 #[error("Dominance violation: value {0:?} used before definition in block {1:?}")]
18 DominanceViolation(ValueKey, BlockKey),
19
20 #[error("Type mismatch in operation {op:?}: expected {expected}, got {actual}")]
21 TypeMismatch {
22 op: OpKey,
23 expected: String,
24 actual: String,
25 },
26
27 #[error("Linearity violation: qubit value {0:?} consumed more than once")]
28 LinearityViolation(ValueKey),
29
30 #[error("Linearity violation: qubit {0:?} not consumed (leaked)")]
31 QubitLeaked(ValueKey),
32
33 #[error("Branch linearity: arms consume different qubit sets at block {0:?}")]
34 BranchLinearityMismatch(BlockKey),
35
36 #[error("Dangling reference: {0}")]
37 DanglingReference(String),
38
39 #[error("Empty block {0:?} has no terminator")]
40 MissingTerminator(BlockKey),
41
42 #[error("Operation {0:?} has no parent block")]
43 OrphanedOperation(OpKey),
44
45 #[error("Block {0:?} has no parent region")]
46 OrphanedBlock(BlockKey),
47
48 #[error("Invalid operation: {0}")]
49 InvalidOperation(String),
50
51 #[error("Semantic error in operation {op:?}: {message}")]
52 SemanticError { op: OpKey, message: String },
53}
54
55pub struct Verifier<'a> {
56 ctx: &'a Context,
57 errors: Vec<VerifyError>,
58 defined: HashSet<ValueKey>,
59 consumed_qubits: HashSet<ValueKey>,
60}
61
62impl<'a> Verifier<'a> {
63 pub fn new(ctx: &'a Context) -> Self {
64 Self {
65 ctx,
66 errors: Vec::new(),
67 defined: HashSet::new(),
68 consumed_qubits: HashSet::new(),
69 }
70 }
71
72 pub fn verify_all(&mut self) -> Result<(), Vec<VerifyError>> {
73 self.verify_ssa();
74 self.verify_well_formedness();
75 self.verify_linearity();
76
77 if self.errors.is_empty() {
78 Ok(())
79 } else {
80 Err(std::mem::take(&mut self.errors))
81 }
82 }
83
84 pub fn verify_all_with_dialects(
86 &mut self,
87 registry: &DialectRegistry,
88 ) -> Result<(), Vec<VerifyError>> {
89 self.verify_ssa();
90 self.verify_well_formedness();
91 self.verify_linearity();
92 self.verify_semantics(registry);
93
94 if self.errors.is_empty() {
95 Ok(())
96 } else {
97 Err(std::mem::take(&mut self.errors))
98 }
99 }
100
101 fn verify_semantics(&mut self, registry: &DialectRegistry) {
104 for (op_key, op) in &self.ctx.ops {
105 let dialect_name = self.ctx.strings.resolve(op.dialect);
106 let op_name = self.ctx.strings.resolve(op.name);
107
108 let dialect = match registry.get(dialect_name) {
109 Some(d) => d,
110 None => {
111 continue;
114 }
115 };
116
117 if let Err(msg) = dialect.verify_op(op_name, op.inputs.len(), op.results.len()) {
118 self.errors.push(VerifyError::SemanticError {
119 op: op_key,
120 message: msg,
121 });
122 }
123 }
124 }
125
126 fn verify_ssa(&mut self) {
127 let mut all_defined: HashSet<ValueKey> = HashSet::new();
128
129 for (block_key, block) in &self.ctx.blocks {
131 for &arg_key in &block.args {
132 if !all_defined.insert(arg_key) {
133 self.errors.push(VerifyError::MultipleDefinition(arg_key));
134 }
135 }
136 let _ = block_key;
137 }
138
139 for (_op_key, op) in &self.ctx.ops {
141 for &result_key in &op.results {
142 if !all_defined.insert(result_key) {
143 self.errors
144 .push(VerifyError::MultipleDefinition(result_key));
145 }
146 }
147 }
148
149 for (_op_key, op) in &self.ctx.ops {
151 for &input_key in &op.inputs {
152 if !all_defined.contains(&input_key) {
153 self.errors.push(VerifyError::UndefinedValue(input_key));
154 }
155 }
156 }
157
158 self.defined = all_defined;
159 }
160
161 fn verify_well_formedness(&mut self) {
162 for (op_key, op) in &self.ctx.ops {
164 for &input in &op.inputs {
165 if !self.ctx.values.contains_key(input) {
166 self.errors.push(VerifyError::DanglingReference(format!(
167 "Operation {:?} references non-existent value {:?}",
168 op_key, input
169 )));
170 }
171 }
172 for &result in &op.results {
173 if !self.ctx.values.contains_key(result) {
174 self.errors.push(VerifyError::DanglingReference(format!(
175 "Operation {:?} references non-existent result {:?}",
176 op_key, result
177 )));
178 }
179 }
180 for ®ion in &op.regions {
181 if !self.ctx.regions.contains_key(region) {
182 self.errors.push(VerifyError::DanglingReference(format!(
183 "Operation {:?} references non-existent region {:?}",
184 op_key, region
185 )));
186 }
187 }
188 }
189
190 for (block_key, block) in &self.ctx.blocks {
192 for &op in &block.ops {
193 if !self.ctx.ops.contains_key(op) {
194 self.errors.push(VerifyError::DanglingReference(format!(
195 "Block {:?} references non-existent operation {:?}",
196 block_key, op
197 )));
198 }
199 }
200 }
201
202 for (region_key, region) in &self.ctx.regions {
204 for &block in ®ion.blocks {
205 if !self.ctx.blocks.contains_key(block) {
206 self.errors.push(VerifyError::DanglingReference(format!(
207 "Region {:?} references non-existent block {:?}",
208 region_key, block
209 )));
210 }
211 }
212 }
213 }
214
215 fn verify_linearity(&mut self) {
216 let mut consumed: HashSet<ValueKey> = HashSet::new();
217 let mut all_qubits: HashSet<ValueKey> = HashSet::new();
218
219 for (val_key, val) in &self.ctx.values {
221 if self.ctx.is_qubit_type(val.ty) {
222 all_qubits.insert(val_key);
223 }
224 }
225
226 for (_op_key, op) in &self.ctx.ops {
228 let op_name = self.ctx.strings.resolve(op.name);
229
230 for &input in &op.inputs {
231 if let Some(val) = self.ctx.values.get(input) {
232 if self.ctx.is_qubit_type(val.ty) && !consumed.insert(input) {
233 self.errors.push(VerifyError::LinearityViolation(input));
234 }
235 }
236 }
237
238 if op_name == "quantum.measure" {
241 }
243 }
244
245 self.consumed_qubits = consumed;
246 }
247
248 pub fn errors(&self) -> &[VerifyError] {
249 &self.errors
250 }
251}
252
253pub fn verify(ctx: &Context) -> Result<(), Vec<VerifyError>> {
254 let mut verifier = Verifier::new(ctx);
255 verifier.verify_all()
256}
257
258pub fn verify_with_dialects(
260 ctx: &Context,
261 registry: &DialectRegistry,
262) -> Result<(), Vec<VerifyError>> {
263 let mut verifier = Verifier::new(ctx);
264 verifier.verify_all_with_dialects(registry)
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn test_empty_context_verifies() {
273 let ctx = Context::new();
274 assert!(verify(&ctx).is_ok());
275 }
276
277 #[test]
278 fn test_simple_ssa_valid() {
279 let mut ctx = Context::new();
280 let f32_ty = ctx.make_float_type(32);
281
282 let block = ctx.create_block();
283 let arg = ctx.create_block_arg(block, f32_ty);
284
285 let (op, _results) = ctx.create_op(
286 "tensor.relu",
287 "tensor",
288 vec![arg],
289 vec![f32_ty],
290 crate::attributes::Attributes::new(),
291 crate::location::Location::unknown(),
292 );
293 ctx.add_op_to_block(block, op);
294
295 assert!(verify(&ctx).is_ok());
296 }
297
298 #[test]
299 fn test_qubit_linearity_violation() {
300 let mut ctx = Context::new();
301 let qubit_ty = ctx.make_qubit_type();
302
303 let block = ctx.create_block();
304 let q0 = ctx.create_block_arg(block, qubit_ty);
305
306 let (op1, _) = ctx.create_op(
308 "quantum.x",
309 "quantum",
310 vec![q0],
311 vec![qubit_ty],
312 crate::attributes::Attributes::new(),
313 crate::location::Location::unknown(),
314 );
315 ctx.add_op_to_block(block, op1);
316
317 let (op2, _) = ctx.create_op(
319 "quantum.h",
320 "quantum",
321 vec![q0],
322 vec![qubit_ty],
323 crate::attributes::Attributes::new(),
324 crate::location::Location::unknown(),
325 );
326 ctx.add_op_to_block(block, op2);
327
328 let result = verify(&ctx);
329 assert!(result.is_err());
330 let errors = result.unwrap_err();
331 assert!(errors
332 .iter()
333 .any(|e| matches!(e, VerifyError::LinearityViolation(_))));
334 }
335
336 #[test]
337 fn test_semantic_verification_detects_wrong_input_count() {
338 use crate::dialect::Dialect;
339
340 #[derive(Debug)]
341 struct FakeTensorDialect;
342 impl Dialect for FakeTensorDialect {
343 fn name(&self) -> &str {
344 "tensor"
345 }
346 fn verify_op(
347 &self,
348 op_name: &str,
349 num_inputs: usize,
350 _num_results: usize,
351 ) -> Result<(), String> {
352 if op_name == "tensor.matmul" && num_inputs != 2 {
353 return Err(format!("matmul expects 2 inputs, got {}", num_inputs));
354 }
355 Ok(())
356 }
357 }
358
359 let mut registry = DialectRegistry::new();
360 registry.register(Box::new(FakeTensorDialect));
361
362 let mut ctx = Context::new();
363 let f32_ty = ctx.make_float_type(32);
364 let block = ctx.create_block();
365 let a = ctx.create_block_arg(block, f32_ty);
366 let b = ctx.create_block_arg(block, f32_ty);
367 let c = ctx.create_block_arg(block, f32_ty);
368
369 let (op, _) = ctx.create_op(
371 "tensor.matmul",
372 "tensor",
373 vec![a, b, c],
374 vec![f32_ty],
375 crate::attributes::Attributes::new(),
376 crate::location::Location::unknown(),
377 );
378 ctx.add_op_to_block(block, op);
379
380 let result = verify_with_dialects(&ctx, ®istry);
381 assert!(result.is_err());
382 let errors = result.unwrap_err();
383 assert!(errors
384 .iter()
385 .any(|e| matches!(e, VerifyError::SemanticError { .. })));
386 }
387}