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_dominance();
75 self.verify_well_formedness();
76 self.verify_linearity();
77
78 if self.errors.is_empty() {
79 Ok(())
80 } else {
81 Err(std::mem::take(&mut self.errors))
82 }
83 }
84
85 pub fn verify_all_with_dialects(
87 &mut self,
88 registry: &DialectRegistry,
89 ) -> Result<(), Vec<VerifyError>> {
90 self.verify_ssa();
91 self.verify_dominance();
92 self.verify_well_formedness();
93 self.verify_linearity();
94 self.verify_semantics(registry);
95
96 if self.errors.is_empty() {
97 Ok(())
98 } else {
99 Err(std::mem::take(&mut self.errors))
100 }
101 }
102
103 fn verify_semantics(&mut self, registry: &DialectRegistry) {
106 for (op_key, op) in &self.ctx.ops {
107 let dialect_name = self.ctx.strings.resolve(op.dialect);
108 let op_name = self.ctx.strings.resolve(op.name);
109
110 let dialect = match registry.get(dialect_name) {
111 Some(d) => d,
112 None => {
113 continue;
116 }
117 };
118
119 if let Err(msg) = dialect.verify_op(op_name, op.inputs.len(), op.results.len()) {
120 self.errors.push(VerifyError::SemanticError {
121 op: op_key,
122 message: msg,
123 });
124 }
125 }
126 }
127
128 fn verify_ssa(&mut self) {
129 let mut all_defined: HashSet<ValueKey> = HashSet::new();
130
131 for (block_key, block) in &self.ctx.blocks {
133 for &arg_key in &block.args {
134 if !all_defined.insert(arg_key) {
135 self.errors.push(VerifyError::MultipleDefinition(arg_key));
136 }
137 }
138 let _ = block_key;
139 }
140
141 for (_op_key, op) in &self.ctx.ops {
143 for &result_key in &op.results {
144 if !all_defined.insert(result_key) {
145 self.errors
146 .push(VerifyError::MultipleDefinition(result_key));
147 }
148 }
149 }
150
151 for (_op_key, op) in &self.ctx.ops {
153 for &input_key in &op.inputs {
154 if !all_defined.contains(&input_key) {
155 self.errors.push(VerifyError::UndefinedValue(input_key));
156 }
157 }
158 }
159
160 self.defined = all_defined;
161 }
162
163 fn verify_dominance(&mut self) {
177 use std::collections::HashMap;
178
179 let mut owner_block: HashMap<ValueKey, BlockKey> = HashMap::new();
180 for (block_key, block) in &self.ctx.blocks {
181 for &arg in &block.args {
182 owner_block.insert(arg, block_key);
183 }
184 for &op_key in &block.ops {
185 if let Some(op) = self.ctx.ops.get(op_key) {
186 for &result in &op.results {
187 owner_block.insert(result, block_key);
188 }
189 }
190 }
191 }
192
193 for (block_key, block) in &self.ctx.blocks {
194 let mut visible: HashSet<ValueKey> = block.args.iter().copied().collect();
195 for &op_key in &block.ops {
196 let Some(op) = self.ctx.ops.get(op_key) else {
197 continue;
198 };
199 for &input in &op.inputs {
200 if owner_block.get(&input) == Some(&block_key) && !visible.contains(&input) {
201 self.errors
202 .push(VerifyError::DominanceViolation(input, block_key));
203 }
204 }
205 for &result in &op.results {
206 visible.insert(result);
207 }
208 }
209 }
210 }
211
212 fn verify_well_formedness(&mut self) {
213 for (op_key, op) in &self.ctx.ops {
215 for &input in &op.inputs {
216 if !self.ctx.values.contains_key(input) {
217 self.errors.push(VerifyError::DanglingReference(format!(
218 "Operation {:?} references non-existent value {:?}",
219 op_key, input
220 )));
221 }
222 }
223 for &result in &op.results {
224 if !self.ctx.values.contains_key(result) {
225 self.errors.push(VerifyError::DanglingReference(format!(
226 "Operation {:?} references non-existent result {:?}",
227 op_key, result
228 )));
229 }
230 }
231 for ®ion in &op.regions {
232 if !self.ctx.regions.contains_key(region) {
233 self.errors.push(VerifyError::DanglingReference(format!(
234 "Operation {:?} references non-existent region {:?}",
235 op_key, region
236 )));
237 }
238 }
239 }
240
241 for (block_key, block) in &self.ctx.blocks {
243 for &op in &block.ops {
244 if !self.ctx.ops.contains_key(op) {
245 self.errors.push(VerifyError::DanglingReference(format!(
246 "Block {:?} references non-existent operation {:?}",
247 block_key, op
248 )));
249 }
250 }
251 }
252
253 for (region_key, region) in &self.ctx.regions {
255 for &block in ®ion.blocks {
256 if !self.ctx.blocks.contains_key(block) {
257 self.errors.push(VerifyError::DanglingReference(format!(
258 "Region {:?} references non-existent block {:?}",
259 region_key, block
260 )));
261 }
262 }
263 }
264 }
265
266 fn verify_linearity(&mut self) {
267 let mut consumed: HashSet<ValueKey> = HashSet::new();
268 let mut all_qubits: HashSet<ValueKey> = HashSet::new();
269
270 for (val_key, val) in &self.ctx.values {
272 if self.ctx.is_qubit_type(val.ty) {
273 all_qubits.insert(val_key);
274 }
275 }
276
277 for (_op_key, op) in &self.ctx.ops {
279 let op_name = self.ctx.strings.resolve(op.name);
280
281 for &input in &op.inputs {
282 if let Some(val) = self.ctx.values.get(input) {
283 if self.ctx.is_qubit_type(val.ty) && !consumed.insert(input) {
284 self.errors.push(VerifyError::LinearityViolation(input));
285 }
286 }
287 }
288
289 if op_name == "quantum.measure" {
292 }
294 }
295
296 self.consumed_qubits = consumed;
297 }
298
299 pub fn errors(&self) -> &[VerifyError] {
300 &self.errors
301 }
302}
303
304pub fn verify(ctx: &Context) -> Result<(), Vec<VerifyError>> {
305 let mut verifier = Verifier::new(ctx);
306 verifier.verify_all()
307}
308
309pub fn verify_with_dialects(
311 ctx: &Context,
312 registry: &DialectRegistry,
313) -> Result<(), Vec<VerifyError>> {
314 let mut verifier = Verifier::new(ctx);
315 verifier.verify_all_with_dialects(registry)
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321
322 #[test]
323 fn test_empty_context_verifies() {
324 let ctx = Context::new();
325 assert!(verify(&ctx).is_ok());
326 }
327
328 #[test]
329 fn test_simple_ssa_valid() {
330 let mut ctx = Context::new();
331 let f32_ty = ctx.make_float_type(32);
332
333 let block = ctx.create_block();
334 let arg = ctx.create_block_arg(block, f32_ty);
335
336 let (op, _results) = ctx.create_op(
337 "tensor.relu",
338 "tensor",
339 vec![arg],
340 vec![f32_ty],
341 crate::attributes::Attributes::new(),
342 crate::location::Location::unknown(),
343 );
344 ctx.add_op_to_block(block, op);
345
346 assert!(verify(&ctx).is_ok());
347 }
348
349 #[test]
355 fn test_use_before_def_is_a_dominance_violation() {
356 let mut ctx = Context::new();
357 let f32_ty = ctx.make_float_type(32);
358 let block = ctx.create_block();
359 let arg = ctx.create_block_arg(block, f32_ty);
360
361 let (producer, producer_results) = ctx.create_op(
365 "tensor.relu",
366 "tensor",
367 vec![arg],
368 vec![f32_ty],
369 crate::attributes::Attributes::new(),
370 crate::location::Location::unknown(),
371 );
372 let (consumer, _) = ctx.create_op(
373 "tensor.relu",
374 "tensor",
375 vec![producer_results[0]],
376 vec![f32_ty],
377 crate::attributes::Attributes::new(),
378 crate::location::Location::unknown(),
379 );
380
381 ctx.add_op_to_block(block, consumer);
384 ctx.add_op_to_block(block, producer);
385
386 let result = verify(&ctx);
387 assert!(result.is_err(), "use-before-def must fail verification");
388 let errors = result.unwrap_err();
389 assert!(
390 errors
391 .iter()
392 .any(|e| matches!(e, VerifyError::DominanceViolation(_, _))),
393 "{:?}",
394 errors
395 );
396 }
397
398 #[test]
399 fn test_qubit_linearity_violation() {
400 let mut ctx = Context::new();
401 let qubit_ty = ctx.make_qubit_type();
402
403 let block = ctx.create_block();
404 let q0 = ctx.create_block_arg(block, qubit_ty);
405
406 let (op1, _) = ctx.create_op(
408 "quantum.x",
409 "quantum",
410 vec![q0],
411 vec![qubit_ty],
412 crate::attributes::Attributes::new(),
413 crate::location::Location::unknown(),
414 );
415 ctx.add_op_to_block(block, op1);
416
417 let (op2, _) = ctx.create_op(
419 "quantum.h",
420 "quantum",
421 vec![q0],
422 vec![qubit_ty],
423 crate::attributes::Attributes::new(),
424 crate::location::Location::unknown(),
425 );
426 ctx.add_op_to_block(block, op2);
427
428 let result = verify(&ctx);
429 assert!(result.is_err());
430 let errors = result.unwrap_err();
431 assert!(errors
432 .iter()
433 .any(|e| matches!(e, VerifyError::LinearityViolation(_))));
434 }
435
436 #[test]
437 fn test_semantic_verification_detects_wrong_input_count() {
438 use crate::dialect::Dialect;
439
440 #[derive(Debug)]
441 struct FakeTensorDialect;
442 impl Dialect for FakeTensorDialect {
443 fn name(&self) -> &str {
444 "tensor"
445 }
446 fn verify_op(
447 &self,
448 op_name: &str,
449 num_inputs: usize,
450 _num_results: usize,
451 ) -> Result<(), String> {
452 if op_name == "tensor.matmul" && num_inputs != 2 {
453 return Err(format!("matmul expects 2 inputs, got {}", num_inputs));
454 }
455 Ok(())
456 }
457 }
458
459 let mut registry = DialectRegistry::new();
460 registry.register(Box::new(FakeTensorDialect));
461
462 let mut ctx = Context::new();
463 let f32_ty = ctx.make_float_type(32);
464 let block = ctx.create_block();
465 let a = ctx.create_block_arg(block, f32_ty);
466 let b = ctx.create_block_arg(block, f32_ty);
467 let c = ctx.create_block_arg(block, f32_ty);
468
469 let (op, _) = ctx.create_op(
471 "tensor.matmul",
472 "tensor",
473 vec![a, b, c],
474 vec![f32_ty],
475 crate::attributes::Attributes::new(),
476 crate::location::Location::unknown(),
477 );
478 ctx.add_op_to_block(block, op);
479
480 let result = verify_with_dialects(&ctx, ®istry);
481 assert!(result.is_err());
482 let errors = result.unwrap_err();
483 assert!(errors
484 .iter()
485 .any(|e| matches!(e, VerifyError::SemanticError { .. })));
486 }
487}