bloqade_lanes_bytecode_core/bytecode/
validate.rs

1//! Bytecode program validation: structural checks, address validation,
2//! and stack-type simulation.
3//!
4//! Validation runs in layers:
5//! - **Structural** (always): operand bounds, arity limits, instruction ordering
6//! - **Architecture** (when arch spec provided): address validity
7//! - **Stack simulation** (when enabled): type checking via abstract interpretation
8
9use std::collections::HashSet;
10use std::fmt;
11
12use super::instruction::{
13    ArrayInstruction, AtomArrangementInstruction, CpuInstruction, DetectorObservableInstruction,
14    Instruction, LaneConstInstruction, MeasurementInstruction, QuantumGateInstruction,
15};
16use super::program::Program;
17use super::value::{
18    TAG_ARRAY_REF, TAG_DETECTOR_REF, TAG_FLOAT, TAG_INT, TAG_LANE, TAG_LOCATION,
19    TAG_MEASURE_FUTURE, TAG_OBSERVABLE_REF, TAG_ZONE,
20};
21use crate::arch::addr::{LaneAddr, LocationAddr, ZoneAddr};
22use crate::arch::query::{LaneGroupError, LocationGroupError};
23use crate::arch::types::ArchSpec;
24
25/// Maximum valid type tag (TAG_OBSERVABLE_REF = 0x8).
26const MAX_TYPE_TAG: u8 = 0x8;
27
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum ValidationError {
30    /// NewArray dim0 must be > 0.
31    NewArrayZeroDim0 { pc: usize },
32    /// NewArray type_tag is invalid.
33    NewArrayInvalidTypeTag { pc: usize, type_tag: u8 },
34    /// InitialFill is not the first non-constant instruction.
35    InitialFillNotFirst { pc: usize },
36    /// Stack underflow at the given program counter.
37    StackUnderflow { pc: usize },
38    /// Type mismatch on stack.
39    TypeMismatch { pc: usize, expected: u8, got: u8 },
40    /// Invalid zone address per arch spec.
41    InvalidZone { pc: usize, zone_id: u32 },
42    /// Location group validation error (invalid address, duplicate, etc.).
43    LocationGroupValidation {
44        pc: usize,
45        error: LocationGroupError,
46    },
47    /// Lane group validation error (invalid address, duplicate, inconsistent, AOD constraint, etc.).
48    LaneGroupValidation { pc: usize, error: LaneGroupError },
49    /// Multiple measure instructions when feed_forward is disabled.
50    FeedForwardNotSupported { pc: usize },
51    /// Fill instruction when atom_reloading is disabled.
52    AtomReloadingNotSupported { pc: usize },
53}
54
55impl fmt::Display for ValidationError {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        match self {
58            ValidationError::NewArrayZeroDim0 { pc } => {
59                write!(f, "pc {}: new_array dim0 must be > 0", pc)
60            }
61            ValidationError::NewArrayInvalidTypeTag { pc, type_tag } => {
62                write!(f, "pc {}: invalid type tag 0x{:x}", pc, type_tag)
63            }
64            ValidationError::InitialFillNotFirst { pc } => {
65                write!(
66                    f,
67                    "pc {}: initial_fill must be the first non-constant instruction",
68                    pc
69                )
70            }
71            ValidationError::StackUnderflow { pc } => {
72                write!(f, "pc {}: stack underflow", pc)
73            }
74            ValidationError::TypeMismatch { pc, expected, got } => {
75                write!(
76                    f,
77                    "pc {}: type mismatch: expected tag 0x{:x}, got 0x{:x}",
78                    pc, expected, got
79                )
80            }
81            ValidationError::InvalidZone { pc, zone_id } => {
82                write!(f, "pc {}: invalid zone_id={}", pc, zone_id)
83            }
84            ValidationError::LocationGroupValidation { pc, error } => {
85                write!(f, "pc {}: {}", pc, error)
86            }
87            ValidationError::LaneGroupValidation { pc, error } => {
88                write!(f, "pc {}: {}", pc, error)
89            }
90            ValidationError::FeedForwardNotSupported { pc } => {
91                write!(
92                    f,
93                    "pc {}: multiple measure instructions require feed_forward capability",
94                    pc
95                )
96            }
97            ValidationError::AtomReloadingNotSupported { pc } => {
98                write!(
99                    f,
100                    "pc {}: fill instruction requires atom_reloading capability",
101                    pc
102                )
103            }
104        }
105    }
106}
107
108impl std::error::Error for ValidationError {}
109
110/// Run structural validation on a program. Returns collected errors.
111pub fn validate_structure(program: &Program) -> Vec<ValidationError> {
112    let mut errors = Vec::new();
113    let mut seen_non_constant = false;
114
115    for (pc, instr) in program.instructions.iter().enumerate() {
116        match instr {
117            Instruction::Array(ArrayInstruction::NewArray {
118                type_tag,
119                dim0,
120                dim1: _,
121            }) => {
122                if *dim0 == 0 {
123                    errors.push(ValidationError::NewArrayZeroDim0 { pc });
124                }
125                if *type_tag > MAX_TYPE_TAG {
126                    errors.push(ValidationError::NewArrayInvalidTypeTag {
127                        pc,
128                        type_tag: *type_tag,
129                    });
130                }
131            }
132            Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { .. }) => {
133                if seen_non_constant {
134                    errors.push(ValidationError::InitialFillNotFirst { pc });
135                }
136                seen_non_constant = true;
137            }
138            Instruction::AtomArrangement(AtomArrangementInstruction::Fill { .. })
139            | Instruction::AtomArrangement(AtomArrangementInstruction::Move { .. })
140            | Instruction::QuantumGate(QuantumGateInstruction::LocalR { .. })
141            | Instruction::QuantumGate(QuantumGateInstruction::LocalRz { .. })
142            | Instruction::Measurement(MeasurementInstruction::Measure { .. }) => {
143                seen_non_constant = true;
144            }
145            // Constants don't set seen_non_constant
146            Instruction::Cpu(CpuInstruction::ConstFloat(_))
147            | Instruction::Cpu(CpuInstruction::ConstInt(_))
148            | Instruction::LaneConst(LaneConstInstruction::ConstLoc(_))
149            | Instruction::LaneConst(LaneConstInstruction::ConstLane(_, _))
150            | Instruction::LaneConst(LaneConstInstruction::ConstZone(_)) => {}
151            // All other instructions are non-constant
152            _ => {
153                seen_non_constant = true;
154            }
155        }
156    }
157
158    errors
159}
160
161/// Validate addresses in the program against an architecture specification.
162/// Checks each constant location, lane, and zone instruction to ensure the
163/// encoded address refers to valid entries in the arch spec.
164pub fn validate_addresses(program: &Program, arch: &ArchSpec) -> Vec<ValidationError> {
165    let mut errors = Vec::new();
166
167    for (pc, instr) in program.instructions.iter().enumerate() {
168        match instr {
169            Instruction::LaneConst(LaneConstInstruction::ConstLoc(bits)) => {
170                let addr = LocationAddr::decode(*bits);
171                if arch.check_location(&addr).is_some() {
172                    errors.push(ValidationError::LocationGroupValidation {
173                        pc,
174                        error: LocationGroupError::InvalidAddress {
175                            word_id: addr.word_id,
176                            site_id: addr.site_id,
177                        },
178                    });
179                }
180            }
181            Instruction::LaneConst(LaneConstInstruction::ConstLane(d0, d1)) => {
182                let addr = LaneAddr::decode(*d0, *d1);
183                for msg in arch.check_lane(&addr) {
184                    errors.push(ValidationError::LaneGroupValidation {
185                        pc,
186                        error: LaneGroupError::InvalidLane { message: msg },
187                    });
188                }
189            }
190            Instruction::LaneConst(LaneConstInstruction::ConstZone(bits)) => {
191                let addr = ZoneAddr::decode(*bits);
192                if arch.check_zone(&addr).is_some() {
193                    errors.push(ValidationError::InvalidZone {
194                        pc,
195                        zone_id: addr.zone_id,
196                    });
197                }
198            }
199            _ => {}
200        }
201    }
202
203    errors
204}
205
206/// Validate device capability constraints against the program.
207///
208/// Checks:
209/// - If `feed_forward` is false, at most one `measure` instruction is allowed.
210/// - If `atom_reloading` is false, no `fill` instruction is allowed.
211pub fn validate_capabilities(program: &Program, arch: &ArchSpec) -> Vec<ValidationError> {
212    let mut errors = Vec::new();
213    let mut measure_count = 0u32;
214
215    for (pc, instr) in program.instructions.iter().enumerate() {
216        match instr {
217            Instruction::Measurement(MeasurementInstruction::Measure { .. }) => {
218                measure_count += 1;
219                if !arch.feed_forward && measure_count > 1 {
220                    errors.push(ValidationError::FeedForwardNotSupported { pc });
221                }
222            }
223            Instruction::AtomArrangement(AtomArrangementInstruction::Fill { .. }) => {
224                if !arch.atom_reloading {
225                    errors.push(ValidationError::AtomReloadingNotSupported { pc });
226                }
227            }
228            _ => {}
229        }
230    }
231
232    errors
233}
234
235/// Validate all architecture-dependent constraints (addresses + capabilities).
236///
237/// Convenience helper that runs both [`validate_addresses`] and
238/// [`validate_capabilities`] so callers get consistent architecture
239/// validation in a single call.
240pub fn validate_arch_constraints(program: &Program, arch: &ArchSpec) -> Vec<ValidationError> {
241    let mut errors = validate_addresses(program, arch);
242    errors.extend(validate_capabilities(program, arch));
243    errors
244}
245
246/// Stack simulation entry — tracks type tag and optionally the concrete value.
247#[derive(Debug, Clone)]
248struct SimEntry {
249    tag: u8,
250    #[allow(dead_code)]
251    value: Option<u64>,
252}
253
254/// Type-level stack simulator that walks an instruction stream, tracking types
255/// and concrete values. Validates stack discipline (underflow, type mismatches)
256/// and, when given an `ArchSpec`, validates lane groups at `Move` instructions.
257struct StackSimulator<'a> {
258    stack: Vec<SimEntry>,
259    errors: Vec<ValidationError>,
260    arch: Option<&'a ArchSpec>,
261    pc: usize,
262}
263
264impl<'a> StackSimulator<'a> {
265    fn new(arch: Option<&'a ArchSpec>) -> Self {
266        Self {
267            stack: Vec::new(),
268            errors: Vec::new(),
269            arch,
270            pc: 0,
271        }
272    }
273
274    // --- Primitives ---
275
276    /// Pop one value of any type. Records `StackUnderflow` if the stack is empty.
277    fn pop_any(&mut self) {
278        if self.stack.pop().is_none() {
279            self.errors
280                .push(ValidationError::StackUnderflow { pc: self.pc });
281        }
282    }
283
284    /// Pop one value and check its type tag. Records `StackUnderflow` or `TypeMismatch`.
285    fn pop_typed(&mut self, expected_tag: u8) {
286        match self.stack.pop() {
287            Some(entry) if entry.tag != expected_tag => {
288                self.errors.push(ValidationError::TypeMismatch {
289                    pc: self.pc,
290                    expected: expected_tag,
291                    got: entry.tag,
292                });
293            }
294            Some(_) => {}
295            None => {
296                self.errors
297                    .push(ValidationError::StackUnderflow { pc: self.pc });
298            }
299        }
300    }
301
302    /// Pop `count` values, each checked against `expected_tag`.
303    fn pop_typed_n(&mut self, expected_tag: u8, count: u32) {
304        for _ in 0..count {
305            self.pop_typed(expected_tag);
306        }
307    }
308
309    /// Pop one lane-typed value, returning the concrete bits if valid.
310    fn pop_lane(&mut self) -> Option<u64> {
311        match self.stack.pop() {
312            Some(entry) => {
313                if entry.tag != TAG_LANE {
314                    self.errors.push(ValidationError::TypeMismatch {
315                        pc: self.pc,
316                        expected: TAG_LANE,
317                        got: entry.tag,
318                    });
319                    None
320                } else {
321                    entry.value
322                }
323            }
324            None => {
325                self.errors
326                    .push(ValidationError::StackUnderflow { pc: self.pc });
327                None
328            }
329        }
330    }
331
332    /// Pop one location-typed value, returning the concrete bits if valid.
333    fn pop_location(&mut self) -> Option<u64> {
334        match self.stack.pop() {
335            Some(entry) => {
336                if entry.tag != TAG_LOCATION {
337                    self.errors.push(ValidationError::TypeMismatch {
338                        pc: self.pc,
339                        expected: TAG_LOCATION,
340                        got: entry.tag,
341                    });
342                    None
343                } else {
344                    entry.value
345                }
346            }
347            None => {
348                self.errors
349                    .push(ValidationError::StackUnderflow { pc: self.pc });
350                None
351            }
352        }
353    }
354
355    /// Wrap `LocationGroupError`s as `ValidationError`s with the current `pc`.
356    fn push_location_group_errors(&mut self, errors: Vec<LocationGroupError>) {
357        let pc = self.pc;
358        for error in errors {
359            self.errors
360                .push(ValidationError::LocationGroupValidation { pc, error });
361        }
362    }
363
364    /// Wrap `LaneGroupError`s as `ValidationError`s with the current `pc`.
365    fn push_lane_group_errors(&mut self, errors: Vec<LaneGroupError>) {
366        let pc = self.pc;
367        for error in errors {
368            self.errors
369                .push(ValidationError::LaneGroupValidation { pc, error });
370        }
371    }
372
373    /// Check for duplicate locations without an arch spec (standalone duplicate check).
374    /// Reports each unique duplicated address once.
375    fn check_duplicate_locations_standalone(&mut self, locations: &[LocationAddr]) {
376        let mut seen = HashSet::new();
377        let mut reported = HashSet::new();
378        for loc in locations {
379            let bits = loc.encode();
380            if !seen.insert(bits) && reported.insert(bits) {
381                self.errors.push(ValidationError::LocationGroupValidation {
382                    pc: self.pc,
383                    error: LocationGroupError::DuplicateAddress { address: bits },
384                });
385            }
386        }
387    }
388
389    /// Check for duplicate lanes without an arch spec (standalone duplicate check).
390    /// Reports each unique duplicated address once.
391    fn check_duplicate_lanes_standalone(&mut self, lanes: &[LaneAddr]) {
392        let mut seen = HashSet::new();
393        let mut reported = HashSet::new();
394        for lane in lanes {
395            let (d0, d1) = lane.encode();
396            let combined = (d0 as u64) | ((d1 as u64) << 32);
397            if !seen.insert(combined) && reported.insert(combined) {
398                self.errors.push(ValidationError::LaneGroupValidation {
399                    pc: self.pc,
400                    error: LaneGroupError::DuplicateAddress { address: (d0, d1) },
401                });
402            }
403        }
404    }
405
406    // --- Instruction handlers ---
407
408    /// Push a typed constant value onto the simulation stack.
409    fn push_const(&mut self, tag: u8, bits: u64) {
410        self.stack.push(SimEntry {
411            tag,
412            value: Some(bits),
413        });
414    }
415
416    /// Duplicate the top stack entry.
417    fn sim_dup(&mut self) {
418        if let Some(top) = self.stack.last().cloned() {
419            self.stack.push(top);
420        } else {
421            self.errors
422                .push(ValidationError::StackUnderflow { pc: self.pc });
423        }
424    }
425
426    /// Swap the top two stack entries.
427    fn sim_swap(&mut self) {
428        let len = self.stack.len();
429        if len >= 2 {
430            self.stack.swap(len - 1, len - 2);
431        } else {
432            self.errors
433                .push(ValidationError::StackUnderflow { pc: self.pc });
434        }
435    }
436
437    /// Pop `arity` location-typed values and validate the group
438    /// (duplicates + arch spec checks if available).
439    fn pop_and_validate_locations(&mut self, arity: u32) {
440        let loc_values: Vec<Option<u64>> = (0..arity).map(|_| self.pop_location()).collect();
441        let locations: Vec<LocationAddr> = loc_values
442            .iter()
443            .filter_map(|v| v.map(|bits| LocationAddr::decode(bits as u32)))
444            .collect();
445        if let Some(arch) = self.arch {
446            self.push_location_group_errors(arch.check_locations(&locations));
447        } else {
448            self.check_duplicate_locations_standalone(&locations);
449        }
450    }
451
452    /// Simulate `initial_fill` or `fill`: pop `arity` location-typed values and validate.
453    fn sim_fill(&mut self, arity: u32) {
454        self.pop_and_validate_locations(arity);
455    }
456
457    /// Simulate `move`: pop `arity` lane-typed values. When an `ArchSpec` is
458    /// available, validates duplicates, consistency, membership, and AOD constraints
459    /// via the unified `check_lanes`. Without arch, only checks duplicates.
460    fn sim_move(&mut self, arity: u32) {
461        let lane_values: Vec<Option<u64>> = (0..arity).map(|_| self.pop_lane()).collect();
462        let lanes: Vec<LaneAddr> = lane_values
463            .iter()
464            .filter_map(|v| {
465                v.map(|bits| {
466                    let d0 = bits as u32;
467                    let d1 = (bits >> 32) as u32;
468                    LaneAddr::decode(d0, d1)
469                })
470            })
471            .collect();
472        if let Some(arch) = self.arch {
473            self.push_lane_group_errors(arch.check_lanes(&lanes));
474        } else {
475            self.check_duplicate_lanes_standalone(&lanes);
476        }
477    }
478
479    /// Simulate `local_r`: pop 2 float parameters (axis_angle, then rotation_angle) then validate locations.
480    fn sim_local_r(&mut self, arity: u32) {
481        self.pop_typed_n(TAG_FLOAT, 2);
482        self.pop_and_validate_locations(arity);
483    }
484
485    /// Simulate `local_rz`: pop 1 float parameter (rotation_angle) then validate locations.
486    fn sim_local_rz(&mut self, arity: u32) {
487        self.pop_typed_n(TAG_FLOAT, 1);
488        self.pop_and_validate_locations(arity);
489    }
490
491    /// Simulate `global_r`: pop 2 float parameters (axis_angle, then rotation_angle) for a global rotation.
492    fn sim_global_r(&mut self) {
493        self.pop_typed_n(TAG_FLOAT, 2);
494    }
495
496    /// Simulate `global_rz`: pop 1 float parameter (rotation_angle) for a global Z-rotation.
497    fn sim_global_rz(&mut self) {
498        self.pop_typed_n(TAG_FLOAT, 1);
499    }
500
501    /// Simulate `cz`: pop 1 zone value for a controlled-Z gate.
502    fn sim_cz(&mut self) {
503        self.pop_typed(TAG_ZONE);
504    }
505
506    /// Simulate `measure`: pop `arity` zone values and push `arity` measure futures.
507    fn sim_measure(&mut self, arity: u32) {
508        self.pop_typed_n(TAG_ZONE, arity);
509        for _ in 0..arity {
510            self.stack.push(SimEntry {
511                tag: TAG_MEASURE_FUTURE,
512                value: None,
513            });
514        }
515    }
516
517    /// Simulate `await_measure`: pop 1 measure future and push an array reference.
518    fn sim_await_measure(&mut self) {
519        self.pop_typed(TAG_MEASURE_FUTURE);
520        self.stack.push(SimEntry {
521            tag: TAG_ARRAY_REF,
522            value: None,
523        });
524    }
525
526    /// Simulate `new_array`: pop `dim0 * max(dim1, 1)` values of any type and push an array reference.
527    fn sim_new_array(&mut self, dim0: u16, dim1: u16) {
528        let count = dim0 * if dim1 == 0 { 1 } else { dim1 };
529        for _ in 0..count {
530            self.pop_any();
531        }
532        self.stack.push(SimEntry {
533            tag: TAG_ARRAY_REF,
534            value: None,
535        });
536    }
537
538    /// Simulate `get_item`: pop `ndims` int indices and 1 array reference, then push the
539    /// element value (assumed float since element type is not tracked).
540    fn sim_get_item(&mut self, ndims: u16) {
541        self.pop_typed_n(TAG_INT, ndims.into());
542        self.pop_typed(TAG_ARRAY_REF);
543        self.stack.push(SimEntry {
544            tag: TAG_FLOAT,
545            value: None,
546        });
547    }
548
549    /// Simulate `set_detector` or `set_observable`: pop 1 array reference and push a
550    /// typed reference (detector or observable) identified by `out_tag`.
551    fn sim_set_ref(&mut self, out_tag: u8) {
552        self.pop_typed(TAG_ARRAY_REF);
553        self.stack.push(SimEntry {
554            tag: out_tag,
555            value: None,
556        });
557    }
558
559    /// Simulate `return`: pop 1 value of any type as the return value.
560    fn sim_return(&mut self) {
561        self.pop_any();
562    }
563
564    // --- Main simulation loop ---
565
566    /// Dispatch a single instruction to the appropriate handler.
567    fn dispatch(&mut self, inst: &Instruction) {
568        match inst {
569            Instruction::Cpu(CpuInstruction::ConstFloat(v)) => {
570                self.push_const(TAG_FLOAT, v.to_bits());
571            }
572            Instruction::Cpu(CpuInstruction::ConstInt(v)) => {
573                self.push_const(TAG_INT, *v as u64);
574            }
575            Instruction::LaneConst(LaneConstInstruction::ConstLoc(v)) => {
576                self.push_const(TAG_LOCATION, *v as u64);
577            }
578            Instruction::LaneConst(LaneConstInstruction::ConstLane(d0, d1)) => {
579                let combined = (*d0 as u64) | ((*d1 as u64) << 32);
580                self.push_const(TAG_LANE, combined);
581            }
582            Instruction::LaneConst(LaneConstInstruction::ConstZone(v)) => {
583                self.push_const(TAG_ZONE, *v as u64);
584            }
585
586            Instruction::Cpu(CpuInstruction::Pop) => self.pop_any(),
587            Instruction::Cpu(CpuInstruction::Dup) => self.sim_dup(),
588            Instruction::Cpu(CpuInstruction::Swap) => self.sim_swap(),
589
590            Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity })
591            | Instruction::AtomArrangement(AtomArrangementInstruction::Fill { arity }) => {
592                self.sim_fill(*arity);
593            }
594            Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity }) => {
595                self.sim_move(*arity);
596            }
597
598            Instruction::QuantumGate(QuantumGateInstruction::LocalR { arity }) => {
599                self.sim_local_r(*arity);
600            }
601            Instruction::QuantumGate(QuantumGateInstruction::LocalRz { arity }) => {
602                self.sim_local_rz(*arity);
603            }
604            Instruction::QuantumGate(QuantumGateInstruction::GlobalR) => self.sim_global_r(),
605            Instruction::QuantumGate(QuantumGateInstruction::GlobalRz) => self.sim_global_rz(),
606            Instruction::QuantumGate(QuantumGateInstruction::CZ) => self.sim_cz(),
607
608            Instruction::Measurement(MeasurementInstruction::Measure { arity }) => {
609                self.sim_measure(*arity);
610            }
611            Instruction::Measurement(MeasurementInstruction::AwaitMeasure) => {
612                self.sim_await_measure()
613            }
614
615            Instruction::Array(ArrayInstruction::NewArray {
616                type_tag: _,
617                dim0,
618                dim1,
619            }) => {
620                self.sim_new_array(*dim0, *dim1);
621            }
622            Instruction::Array(ArrayInstruction::GetItem { ndims }) => {
623                self.sim_get_item(*ndims);
624            }
625            Instruction::DetectorObservable(DetectorObservableInstruction::SetDetector) => {
626                self.sim_set_ref(TAG_DETECTOR_REF);
627            }
628            Instruction::DetectorObservable(DetectorObservableInstruction::SetObservable) => {
629                self.sim_set_ref(TAG_OBSERVABLE_REF);
630            }
631
632            Instruction::Cpu(CpuInstruction::Return) => self.sim_return(),
633            Instruction::Cpu(CpuInstruction::Halt) => {}
634        }
635    }
636
637    /// Run the stack simulation over the entire instruction stream.
638    /// Collects type errors, stack underflow errors, and (when an `ArchSpec` is
639    /// available) lane group validation errors.
640    fn run(mut self, program: &Program) -> Vec<ValidationError> {
641        for (pc, inst) in program.instructions.iter().enumerate() {
642            self.pc = pc;
643            self.dispatch(inst);
644        }
645        self.errors
646    }
647}
648
649/// Simulate the type-level stack through the instruction stream.
650/// Collects type errors and stack underflow errors.
651/// If an `ArchSpec` is provided, also validates lane groups at `Move` instructions.
652pub fn simulate_stack(program: &Program, arch: Option<&ArchSpec>) -> Vec<ValidationError> {
653    let sim = StackSimulator::new(arch);
654    sim.run(program)
655}
656
657#[cfg(test)]
658mod tests {
659    use super::*;
660    use crate::version::Version;
661
662    // --- Structural validation tests ---
663
664    #[test]
665    fn test_valid_program_no_errors() {
666        let program = Program {
667            version: Version::new(1, 0),
668            instructions: vec![
669                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0)),
670                Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity: 1 }),
671                Instruction::Cpu(CpuInstruction::Halt),
672            ],
673        };
674        assert!(validate_structure(&program).is_empty());
675    }
676
677    #[test]
678    fn test_initial_fill_not_first() {
679        let program = Program {
680            version: Version::new(1, 0),
681            instructions: vec![
682                Instruction::Cpu(CpuInstruction::Halt),
683                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0)),
684                Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity: 1 }),
685            ],
686        };
687        let errors = validate_structure(&program);
688        assert_eq!(errors.len(), 1);
689        assert!(matches!(
690            errors[0],
691            ValidationError::InitialFillNotFirst { pc: 2 }
692        ));
693    }
694
695    #[test]
696    fn test_initial_fill_after_constants_ok() {
697        let program = Program {
698            version: Version::new(1, 0),
699            instructions: vec![
700                Instruction::Cpu(CpuInstruction::ConstFloat(1.0)),
701                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0)),
702                Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity: 1 }),
703            ],
704        };
705        assert!(validate_structure(&program).is_empty());
706    }
707
708    #[test]
709    fn test_new_array_zero_dim0() {
710        let program = Program {
711            version: Version::new(1, 0),
712            instructions: vec![Instruction::Array(ArrayInstruction::NewArray {
713                type_tag: 0,
714                dim0: 0,
715                dim1: 0,
716            })],
717        };
718        let errors = validate_structure(&program);
719        assert!(
720            errors
721                .iter()
722                .any(|e| matches!(e, ValidationError::NewArrayZeroDim0 { pc: 0 }))
723        );
724    }
725
726    #[test]
727    fn test_new_array_invalid_type_tag() {
728        let program = Program {
729            version: Version::new(1, 0),
730            instructions: vec![Instruction::Array(ArrayInstruction::NewArray {
731                type_tag: 0xF,
732                dim0: 1,
733                dim1: 0,
734            })],
735        };
736        let errors = validate_structure(&program);
737        assert!(errors.iter().any(|e| matches!(
738            e,
739            ValidationError::NewArrayInvalidTypeTag {
740                pc: 0,
741                type_tag: 0xF
742            }
743        )));
744    }
745
746    // --- Stack simulation tests ---
747
748    #[test]
749    fn test_stack_sim_valid_program() {
750        let program = Program {
751            version: Version::new(1, 0),
752            instructions: vec![
753                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0)),
754                Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity: 1 }),
755                Instruction::Cpu(CpuInstruction::Halt),
756            ],
757        };
758        assert!(simulate_stack(&program, None).is_empty());
759    }
760
761    #[test]
762    fn test_stack_sim_underflow() {
763        let program = Program {
764            version: Version::new(1, 0),
765            instructions: vec![Instruction::Cpu(CpuInstruction::Pop)],
766        };
767        let errors = simulate_stack(&program, None);
768        assert_eq!(errors.len(), 1);
769        assert!(matches!(
770            errors[0],
771            ValidationError::StackUnderflow { pc: 0 }
772        ));
773    }
774
775    #[test]
776    fn test_stack_sim_type_mismatch() {
777        let program = Program {
778            version: Version::new(1, 0),
779            instructions: vec![
780                // Push a float, try to use it as a location for InitialFill
781                Instruction::Cpu(CpuInstruction::ConstFloat(1.0)),
782                Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity: 1 }),
783            ],
784        };
785        let errors = simulate_stack(&program, None);
786        assert_eq!(errors.len(), 1);
787        assert!(matches!(
788            errors[0],
789            ValidationError::TypeMismatch {
790                pc: 1,
791                expected,
792                got
793            } if expected == TAG_LOCATION && got == TAG_FLOAT
794        ));
795    }
796
797    #[test]
798    fn test_stack_sim_move() {
799        let program = Program {
800            version: Version::new(1, 0),
801            instructions: vec![
802                Instruction::LaneConst(LaneConstInstruction::ConstLane(0x100, 0)),
803                Instruction::LaneConst(LaneConstInstruction::ConstLane(0x200, 0)),
804                Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 2 }),
805            ],
806        };
807        assert!(simulate_stack(&program, None).is_empty());
808    }
809
810    #[test]
811    fn test_stack_sim_local_r() {
812        let program = Program {
813            version: Version::new(1, 0),
814            instructions: vec![
815                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0)),
816                Instruction::Cpu(CpuInstruction::ConstFloat(1.0)), // rotation_angle (pushed first)
817                Instruction::Cpu(CpuInstruction::ConstFloat(0.5)), // axis_angle (pushed last, popped first)
818                Instruction::QuantumGate(QuantumGateInstruction::LocalR { arity: 1 }),
819            ],
820        };
821        assert!(simulate_stack(&program, None).is_empty());
822    }
823
824    #[test]
825    fn test_stack_sim_measure_and_await() {
826        let program = Program {
827            version: Version::new(1, 0),
828            instructions: vec![
829                Instruction::LaneConst(LaneConstInstruction::ConstZone(0)),
830                Instruction::Measurement(MeasurementInstruction::Measure { arity: 1 }),
831                Instruction::Measurement(MeasurementInstruction::AwaitMeasure),
832                Instruction::Cpu(CpuInstruction::Return),
833            ],
834        };
835        assert!(simulate_stack(&program, None).is_empty());
836    }
837
838    #[test]
839    fn test_stack_sim_dup_and_swap() {
840        let program = Program {
841            version: Version::new(1, 0),
842            instructions: vec![
843                Instruction::Cpu(CpuInstruction::ConstFloat(1.0)),
844                Instruction::Cpu(CpuInstruction::Dup),
845                Instruction::QuantumGate(QuantumGateInstruction::GlobalR),
846            ],
847        };
848        assert!(simulate_stack(&program, None).is_empty());
849    }
850
851    #[test]
852    fn test_stack_sim_cz_type_mismatch() {
853        let program = Program {
854            version: Version::new(1, 0),
855            instructions: vec![
856                Instruction::Cpu(CpuInstruction::ConstFloat(1.0)),
857                Instruction::QuantumGate(QuantumGateInstruction::CZ),
858            ],
859        };
860        let errors = simulate_stack(&program, None);
861        assert_eq!(errors.len(), 1);
862        assert!(matches!(
863            errors[0],
864            ValidationError::TypeMismatch {
865                pc: 1,
866                expected,
867                got
868            } if expected == TAG_ZONE && got == TAG_FLOAT
869        ));
870    }
871
872    #[test]
873    fn test_stack_sim_set_detector() {
874        let program = Program {
875            version: Version::new(1, 0),
876            instructions: vec![
877                Instruction::LaneConst(LaneConstInstruction::ConstZone(0)),
878                Instruction::Measurement(MeasurementInstruction::Measure { arity: 1 }),
879                Instruction::Measurement(MeasurementInstruction::AwaitMeasure),
880                Instruction::DetectorObservable(DetectorObservableInstruction::SetDetector),
881                Instruction::Cpu(CpuInstruction::Return),
882            ],
883        };
884        assert!(simulate_stack(&program, None).is_empty());
885    }
886
887    // --- Address validation tests ---
888
889    fn test_arch_spec() -> ArchSpec {
890        let json = r#"{
891            "version": "1.0",
892            "geometry": {
893                "sites_per_word": 2,
894                "words": [
895                    {
896                        "positions": { "x_start": 1.0, "y_start": 2.5, "x_spacing": [2.0], "y_spacing": [] },
897                        "site_indices": [[0, 0], [1, 0]]
898                    }
899                ]
900            },
901            "buses": {
902                "site_buses": [
903                    { "src": [0], "dst": [1] }
904                ],
905                "word_buses": []
906            },
907            "words_with_site_buses": [0],
908            "sites_with_word_buses": [],
909            "zones": [
910                { "words": [0] }
911            ],
912            "entangling_zones": [0],
913            "measurement_mode_zones": [0]
914        }"#;
915        ArchSpec::from_json(json).unwrap()
916    }
917
918    #[test]
919    fn test_addr_valid_location() {
920        let arch = test_arch_spec();
921        let program = Program {
922            version: Version::new(1, 0),
923            instructions: vec![Instruction::LaneConst(LaneConstInstruction::ConstLoc(
924                0x0001,
925            ))],
926        };
927        assert!(validate_addresses(&program, &arch).is_empty());
928    }
929
930    #[test]
931    fn test_addr_invalid_location() {
932        let arch = test_arch_spec();
933        let program = Program {
934            version: Version::new(1, 0),
935            instructions: vec![Instruction::LaneConst(LaneConstInstruction::ConstLoc(
936                0x0005,
937            ))],
938        };
939        let errors = validate_addresses(&program, &arch);
940        assert_eq!(errors.len(), 1);
941        assert!(matches!(
942            errors[0],
943            ValidationError::LocationGroupValidation {
944                pc: 0,
945                error: LocationGroupError::InvalidAddress {
946                    word_id: 0,
947                    site_id: 5
948                }
949            }
950        ));
951    }
952
953    #[test]
954    fn test_addr_invalid_zone() {
955        let arch = test_arch_spec();
956        let program = Program {
957            version: Version::new(1, 0),
958            instructions: vec![Instruction::LaneConst(LaneConstInstruction::ConstZone(99))],
959        };
960        let errors = validate_addresses(&program, &arch);
961        assert_eq!(errors.len(), 1);
962        assert!(matches!(
963            errors[0],
964            ValidationError::InvalidZone { pc: 0, zone_id: 99 }
965        ));
966    }
967
968    #[test]
969    fn test_addr_valid_lane() {
970        let arch = test_arch_spec();
971        let program = Program {
972            version: Version::new(1, 0),
973            instructions: vec![Instruction::LaneConst(LaneConstInstruction::ConstLane(
974                0x00000000, 0x00000000,
975            ))],
976        };
977        assert!(validate_addresses(&program, &arch).is_empty());
978    }
979
980    #[test]
981    fn test_addr_invalid_lane_bus() {
982        let arch = test_arch_spec();
983        // data0=0x00000000 (word_id=0, site_id=0), data1=0x00000005 (bus_id=5)
984        let program = Program {
985            version: Version::new(1, 0),
986            instructions: vec![Instruction::LaneConst(LaneConstInstruction::ConstLane(
987                0x00000000, 0x00000005,
988            ))],
989        };
990        let errors = validate_addresses(&program, &arch);
991        assert!(!errors.is_empty());
992    }
993
994    // --- Lane group validation tests ---
995
996    use crate::arch::addr::{Direction, LaneAddr, MoveType};
997
998    fn lane_group_arch_spec() -> ArchSpec {
999        crate::arch::example_arch_spec()
1000    }
1001
1002    fn make_lane(
1003        dir: Direction,
1004        mt: MoveType,
1005        word_id: u32,
1006        site_id: u32,
1007        bus_id: u32,
1008    ) -> (u32, u32) {
1009        LaneAddr {
1010            direction: dir,
1011            move_type: mt,
1012            word_id,
1013            site_id,
1014            bus_id,
1015        }
1016        .encode()
1017    }
1018
1019    #[test]
1020    fn test_lane_group_consistent_passes() {
1021        let arch = lane_group_arch_spec();
1022        let lane0 = make_lane(Direction::Forward, MoveType::SiteBus, 0, 0, 0);
1023        let lane1 = make_lane(Direction::Forward, MoveType::SiteBus, 0, 1, 0);
1024        let program = Program {
1025            version: Version::new(1, 0),
1026            instructions: vec![
1027                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane0.0, lane0.1)),
1028                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane1.0, lane1.1)),
1029                Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 2 }),
1030            ],
1031        };
1032        let errors = simulate_stack(&program, Some(&arch));
1033        assert!(errors.is_empty(), "expected no errors, got: {:?}", errors);
1034    }
1035
1036    #[test]
1037    fn test_lane_group_inconsistent_bus_id() {
1038        let arch = lane_group_arch_spec();
1039        let lane0 = make_lane(Direction::Forward, MoveType::SiteBus, 0, 0, 0);
1040        let lane1 = make_lane(Direction::Forward, MoveType::SiteBus, 0, 1, 1);
1041        let program = Program {
1042            version: Version::new(1, 0),
1043            instructions: vec![
1044                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane0.0, lane0.1)),
1045                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane1.0, lane1.1)),
1046                Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 2 }),
1047            ],
1048        };
1049        let errors = simulate_stack(&program, Some(&arch));
1050        assert!(errors.iter().any(|e| matches!(
1051            e,
1052            ValidationError::LaneGroupValidation {
1053                pc: 2,
1054                error: LaneGroupError::Inconsistent { .. }
1055            }
1056        )));
1057    }
1058
1059    #[test]
1060    fn test_lane_group_inconsistent_direction() {
1061        let arch = lane_group_arch_spec();
1062        let lane0 = make_lane(Direction::Forward, MoveType::SiteBus, 0, 0, 0);
1063        let lane1 = make_lane(Direction::Backward, MoveType::SiteBus, 0, 1, 0);
1064        let program = Program {
1065            version: Version::new(1, 0),
1066            instructions: vec![
1067                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane0.0, lane0.1)),
1068                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane1.0, lane1.1)),
1069                Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 2 }),
1070            ],
1071        };
1072        let errors = simulate_stack(&program, Some(&arch));
1073        assert!(errors.iter().any(|e| matches!(
1074            e,
1075            ValidationError::LaneGroupValidation {
1076                pc: 2,
1077                error: LaneGroupError::Inconsistent { .. }
1078            }
1079        )));
1080    }
1081
1082    #[test]
1083    fn test_lane_group_word_not_in_site_bus_list() {
1084        let json = r#"{
1085            "version": "1.0",
1086            "geometry": {
1087                "sites_per_word": 2,
1088                "words": [
1089                    {
1090                        "positions": { "x_start": 1.0, "y_start": 2.5, "x_spacing": [2.0], "y_spacing": [] },
1091                        "site_indices": [[0, 0], [1, 0]]
1092                    },
1093                    {
1094                        "positions": { "x_start": 1.0, "y_start": 2.5, "x_spacing": [2.0], "y_spacing": [] },
1095                        "site_indices": [[0, 0], [1, 0]]
1096                    }
1097                ]
1098            },
1099            "buses": {
1100                "site_buses": [{ "src": [0], "dst": [1] }],
1101                "word_buses": []
1102            },
1103            "words_with_site_buses": [0],
1104            "sites_with_word_buses": [],
1105            "zones": [{ "words": [0, 1] }],
1106            "entangling_zones": [0],
1107            "measurement_mode_zones": [0]
1108        }"#;
1109        let arch = ArchSpec::from_json(json).unwrap();
1110
1111        let lane0 = make_lane(Direction::Forward, MoveType::SiteBus, 1, 0, 0);
1112        let lane1 = make_lane(Direction::Forward, MoveType::SiteBus, 1, 1, 0);
1113        let program = Program {
1114            version: Version::new(1, 0),
1115            instructions: vec![
1116                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane0.0, lane0.1)),
1117                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane1.0, lane1.1)),
1118                Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 2 }),
1119            ],
1120        };
1121        let errors = simulate_stack(&program, Some(&arch));
1122        assert!(errors.iter().any(|e| matches!(
1123            e,
1124            ValidationError::LaneGroupValidation {
1125                pc: 2,
1126                error: LaneGroupError::WordNotInSiteBusList { word_id: 1 }
1127            }
1128        )));
1129    }
1130
1131    #[test]
1132    fn test_lane_group_aod_constraint_rectangle_passes() {
1133        let arch = lane_group_arch_spec();
1134        // Use valid forward sources on two words to form a 2x2 grid:
1135        // Word 0, Site 0: (1.0, 2.5)
1136        // Word 0, Site 1: (3.0, 2.5)
1137        // Word 1, Site 0: (1.0, 12.5)
1138        // Word 1, Site 1: (3.0, 12.5)
1139        let lanes: Vec<(u32, u32)> = vec![
1140            make_lane(Direction::Forward, MoveType::SiteBus, 0, 0, 0),
1141            make_lane(Direction::Forward, MoveType::SiteBus, 0, 1, 0),
1142            make_lane(Direction::Forward, MoveType::SiteBus, 1, 0, 0),
1143            make_lane(Direction::Forward, MoveType::SiteBus, 1, 1, 0),
1144        ];
1145        let program = Program {
1146            version: Version::new(1, 0),
1147            instructions: vec![
1148                Instruction::LaneConst(LaneConstInstruction::ConstLane(lanes[0].0, lanes[0].1)),
1149                Instruction::LaneConst(LaneConstInstruction::ConstLane(lanes[1].0, lanes[1].1)),
1150                Instruction::LaneConst(LaneConstInstruction::ConstLane(lanes[2].0, lanes[2].1)),
1151                Instruction::LaneConst(LaneConstInstruction::ConstLane(lanes[3].0, lanes[3].1)),
1152                Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 4 }),
1153            ],
1154        };
1155        let errors = simulate_stack(&program, Some(&arch));
1156        assert!(errors.is_empty(), "expected no errors, got: {:?}", errors);
1157    }
1158
1159    #[test]
1160    fn test_lane_group_aod_constraint_not_rectangle() {
1161        let arch = lane_group_arch_spec();
1162        // 3 corners of a grid (missing word 1, site 1) — not a complete grid
1163        // Word 0, Site 0: (1.0, 2.5)
1164        // Word 0, Site 1: (3.0, 2.5)
1165        // Word 1, Site 0: (1.0, 12.5)
1166        let lanes: Vec<(u32, u32)> = vec![
1167            make_lane(Direction::Forward, MoveType::SiteBus, 0, 0, 0),
1168            make_lane(Direction::Forward, MoveType::SiteBus, 0, 1, 0),
1169            make_lane(Direction::Forward, MoveType::SiteBus, 1, 0, 0),
1170        ];
1171        let program = Program {
1172            version: Version::new(1, 0),
1173            instructions: vec![
1174                Instruction::LaneConst(LaneConstInstruction::ConstLane(lanes[0].0, lanes[0].1)),
1175                Instruction::LaneConst(LaneConstInstruction::ConstLane(lanes[1].0, lanes[1].1)),
1176                Instruction::LaneConst(LaneConstInstruction::ConstLane(lanes[2].0, lanes[2].1)),
1177                Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 3 }),
1178            ],
1179        };
1180        let errors = simulate_stack(&program, Some(&arch));
1181        assert!(
1182            errors.iter().any(|e| matches!(
1183                e,
1184                ValidationError::LaneGroupValidation {
1185                    pc: 3,
1186                    error: LaneGroupError::AODConstraintViolation { .. }
1187                }
1188            )),
1189            "expected AOD constraint violation error, got: {:?}",
1190            errors
1191        );
1192    }
1193
1194    #[test]
1195    fn test_lane_group_no_arch_skips_validation() {
1196        let lane0 = make_lane(Direction::Forward, MoveType::SiteBus, 0, 0, 0);
1197        let lane1 = make_lane(Direction::Backward, MoveType::WordBus, 1, 1, 5);
1198        let program = Program {
1199            version: Version::new(1, 0),
1200            instructions: vec![
1201                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane0.0, lane0.1)),
1202                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane1.0, lane1.1)),
1203                Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 2 }),
1204            ],
1205        };
1206        let errors = simulate_stack(&program, None);
1207        assert!(errors.is_empty());
1208    }
1209
1210    // --- Duplicate address validation tests ---
1211
1212    #[test]
1213    fn test_duplicate_location_in_initial_fill() {
1214        let addr = LocationAddr {
1215            word_id: 0,
1216            site_id: 9,
1217        }
1218        .encode();
1219        let program = Program {
1220            version: Version::new(1, 0),
1221            instructions: vec![
1222                Instruction::LaneConst(LaneConstInstruction::ConstLoc(addr)),
1223                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0x0007)),
1224                Instruction::LaneConst(LaneConstInstruction::ConstLoc(addr)),
1225                Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity: 3 }),
1226                Instruction::Cpu(CpuInstruction::Halt),
1227            ],
1228        };
1229        let errors = simulate_stack(&program, None);
1230        assert!(
1231            errors
1232                .iter()
1233                .any(|e| matches!(e, ValidationError::LocationGroupValidation { pc: 3, error: LocationGroupError::DuplicateAddress { address } } if *address == addr)),
1234            "expected DuplicateLocationAddress, got: {:?}",
1235            errors
1236        );
1237    }
1238
1239    #[test]
1240    fn test_duplicate_location_in_fill() {
1241        let addr = LocationAddr {
1242            word_id: 1,
1243            site_id: 2,
1244        }
1245        .encode();
1246        let program = Program {
1247            version: Version::new(1, 0),
1248            instructions: vec![
1249                Instruction::LaneConst(LaneConstInstruction::ConstLoc(addr)),
1250                Instruction::LaneConst(LaneConstInstruction::ConstLoc(addr)),
1251                Instruction::AtomArrangement(AtomArrangementInstruction::Fill { arity: 2 }),
1252                Instruction::Cpu(CpuInstruction::Halt),
1253            ],
1254        };
1255        let errors = simulate_stack(&program, None);
1256        assert!(
1257            errors.iter().any(|e| matches!(
1258                e,
1259                ValidationError::LocationGroupValidation {
1260                    pc: 2,
1261                    error: LocationGroupError::DuplicateAddress { .. }
1262                }
1263            )),
1264            "expected DuplicateLocationAddress, got: {:?}",
1265            errors
1266        );
1267    }
1268
1269    #[test]
1270    fn test_duplicate_location_in_local_r() {
1271        let addr = LocationAddr {
1272            word_id: 0,
1273            site_id: 3,
1274        }
1275        .encode();
1276        let program = Program {
1277            version: Version::new(1, 0),
1278            instructions: vec![
1279                Instruction::LaneConst(LaneConstInstruction::ConstLoc(addr)),
1280                Instruction::LaneConst(LaneConstInstruction::ConstLoc(addr)),
1281                Instruction::Cpu(CpuInstruction::ConstFloat(0.5)),
1282                Instruction::Cpu(CpuInstruction::ConstFloat(1.0)),
1283                Instruction::QuantumGate(QuantumGateInstruction::LocalR { arity: 2 }),
1284            ],
1285        };
1286        let errors = simulate_stack(&program, None);
1287        assert!(
1288            errors.iter().any(|e| matches!(
1289                e,
1290                ValidationError::LocationGroupValidation {
1291                    pc: 4,
1292                    error: LocationGroupError::DuplicateAddress { .. }
1293                }
1294            )),
1295            "expected DuplicateLocationAddress, got: {:?}",
1296            errors
1297        );
1298    }
1299
1300    #[test]
1301    fn test_duplicate_location_in_local_rz() {
1302        let addr = LocationAddr {
1303            word_id: 0,
1304            site_id: 5,
1305        }
1306        .encode();
1307        let program = Program {
1308            version: Version::new(1, 0),
1309            instructions: vec![
1310                Instruction::LaneConst(LaneConstInstruction::ConstLoc(addr)),
1311                Instruction::LaneConst(LaneConstInstruction::ConstLoc(addr)),
1312                Instruction::Cpu(CpuInstruction::ConstFloat(0.5)),
1313                Instruction::QuantumGate(QuantumGateInstruction::LocalRz { arity: 2 }),
1314            ],
1315        };
1316        let errors = simulate_stack(&program, None);
1317        assert!(
1318            errors.iter().any(|e| matches!(
1319                e,
1320                ValidationError::LocationGroupValidation {
1321                    pc: 3,
1322                    error: LocationGroupError::DuplicateAddress { .. }
1323                }
1324            )),
1325            "expected DuplicateLocationAddress, got: {:?}",
1326            errors
1327        );
1328    }
1329
1330    #[test]
1331    fn test_duplicate_lane_in_move() {
1332        let lane = make_lane(Direction::Forward, MoveType::SiteBus, 0, 9, 0);
1333        let lane_other = make_lane(Direction::Forward, MoveType::SiteBus, 0, 7, 0);
1334        let program = Program {
1335            version: Version::new(1, 0),
1336            instructions: vec![
1337                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane.0, lane.1)),
1338                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane_other.0, lane_other.1)),
1339                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane.0, lane.1)),
1340                Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 3 }),
1341            ],
1342        };
1343        let errors = simulate_stack(&program, None);
1344        assert!(
1345            errors
1346                .iter()
1347                .any(|e| matches!(e, ValidationError::LaneGroupValidation { pc: 3, error: LaneGroupError::DuplicateAddress { address } } if *address == lane)),
1348
1349            "expected DuplicateLaneAddress, got: {:?}",
1350            errors
1351        );
1352    }
1353
1354    #[test]
1355    fn test_duplicate_location_reported_once() {
1356        let addr = LocationAddr {
1357            word_id: 1,
1358            site_id: 2,
1359        }
1360        .encode();
1361        let program = Program {
1362            version: Version::new(1, 0),
1363            instructions: vec![
1364                Instruction::LaneConst(LaneConstInstruction::ConstLoc(addr)),
1365                Instruction::LaneConst(LaneConstInstruction::ConstLoc(addr)),
1366                Instruction::LaneConst(LaneConstInstruction::ConstLoc(addr)),
1367                Instruction::AtomArrangement(AtomArrangementInstruction::Fill { arity: 3 }),
1368                Instruction::Cpu(CpuInstruction::Halt),
1369            ],
1370        };
1371        let errors = simulate_stack(&program, None);
1372        let dup_count = errors
1373            .iter()
1374            .filter(|e| {
1375                matches!(
1376                    e,
1377                    ValidationError::LocationGroupValidation {
1378                        error: LocationGroupError::DuplicateAddress { .. },
1379                        ..
1380                    }
1381                )
1382            })
1383            .count();
1384        assert_eq!(
1385            dup_count, 1,
1386            "expected exactly 1 DuplicateAddress, got: {:?}",
1387            errors
1388        );
1389    }
1390
1391    #[test]
1392    fn test_duplicate_lane_reported_once() {
1393        let lane = make_lane(Direction::Forward, MoveType::SiteBus, 0, 9, 0);
1394        let program = Program {
1395            version: Version::new(1, 0),
1396            instructions: vec![
1397                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane.0, lane.1)),
1398                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane.0, lane.1)),
1399                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane.0, lane.1)),
1400                Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 3 }),
1401            ],
1402        };
1403        let errors = simulate_stack(&program, None);
1404        let dup_count = errors
1405            .iter()
1406            .filter(|e| {
1407                matches!(
1408                    e,
1409                    ValidationError::LaneGroupValidation {
1410                        error: LaneGroupError::DuplicateAddress { .. },
1411                        ..
1412                    }
1413                )
1414            })
1415            .count();
1416        assert_eq!(
1417            dup_count, 1,
1418            "expected exactly 1 DuplicateAddress, got: {:?}",
1419            errors
1420        );
1421    }
1422
1423    #[test]
1424    fn test_no_duplicate_location_passes() {
1425        let program = Program {
1426            version: Version::new(1, 0),
1427            instructions: vec![
1428                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0x0000)),
1429                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0x0001)),
1430                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0x0002)),
1431                Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity: 3 }),
1432                Instruction::Cpu(CpuInstruction::Halt),
1433            ],
1434        };
1435        let errors = simulate_stack(&program, None);
1436        assert!(errors.is_empty(), "expected no errors, got: {:?}", errors);
1437    }
1438
1439    // --- Capability validation tests ---
1440
1441    fn test_arch_with_caps(feed_forward: bool, atom_reloading: bool) -> ArchSpec {
1442        let mut arch = test_arch_spec();
1443        arch.feed_forward = feed_forward;
1444        arch.atom_reloading = atom_reloading;
1445        arch
1446    }
1447
1448    #[test]
1449    fn test_cap_single_measure_allowed() {
1450        let arch = test_arch_with_caps(false, false);
1451        let program = Program {
1452            version: Version::new(1, 0),
1453            instructions: vec![
1454                Instruction::LaneConst(LaneConstInstruction::ConstZone(0)),
1455                Instruction::Measurement(MeasurementInstruction::Measure { arity: 1 }),
1456            ],
1457        };
1458        assert!(validate_capabilities(&program, &arch).is_empty());
1459    }
1460
1461    #[test]
1462    fn test_cap_multiple_measure_rejected() {
1463        let arch = test_arch_with_caps(false, false);
1464        let program = Program {
1465            version: Version::new(1, 0),
1466            instructions: vec![
1467                Instruction::LaneConst(LaneConstInstruction::ConstZone(0)),
1468                Instruction::Measurement(MeasurementInstruction::Measure { arity: 1 }),
1469                Instruction::LaneConst(LaneConstInstruction::ConstZone(0)),
1470                Instruction::Measurement(MeasurementInstruction::Measure { arity: 1 }),
1471            ],
1472        };
1473        let errors = validate_capabilities(&program, &arch);
1474        assert_eq!(errors.len(), 1);
1475        assert!(matches!(
1476            errors[0],
1477            ValidationError::FeedForwardNotSupported { pc: 3 }
1478        ));
1479    }
1480
1481    #[test]
1482    fn test_cap_multiple_measure_with_feed_forward() {
1483        let arch = test_arch_with_caps(true, false);
1484        let program = Program {
1485            version: Version::new(1, 0),
1486            instructions: vec![
1487                Instruction::LaneConst(LaneConstInstruction::ConstZone(0)),
1488                Instruction::Measurement(MeasurementInstruction::Measure { arity: 1 }),
1489                Instruction::LaneConst(LaneConstInstruction::ConstZone(0)),
1490                Instruction::Measurement(MeasurementInstruction::Measure { arity: 1 }),
1491            ],
1492        };
1493        assert!(validate_capabilities(&program, &arch).is_empty());
1494    }
1495
1496    #[test]
1497    fn test_cap_fill_rejected() {
1498        let arch = test_arch_with_caps(false, false);
1499        let program = Program {
1500            version: Version::new(1, 0),
1501            instructions: vec![
1502                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0)),
1503                Instruction::AtomArrangement(AtomArrangementInstruction::Fill { arity: 1 }),
1504            ],
1505        };
1506        let errors = validate_capabilities(&program, &arch);
1507        assert_eq!(errors.len(), 1);
1508        assert!(matches!(
1509            errors[0],
1510            ValidationError::AtomReloadingNotSupported { pc: 1 }
1511        ));
1512    }
1513
1514    #[test]
1515    fn test_cap_fill_with_atom_reloading() {
1516        let arch = test_arch_with_caps(false, true);
1517        let program = Program {
1518            version: Version::new(1, 0),
1519            instructions: vec![
1520                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0)),
1521                Instruction::AtomArrangement(AtomArrangementInstruction::Fill { arity: 1 }),
1522            ],
1523        };
1524        assert!(validate_capabilities(&program, &arch).is_empty());
1525    }
1526
1527    #[test]
1528    fn test_cap_initial_fill_always_allowed() {
1529        let arch = test_arch_with_caps(false, false);
1530        let program = Program {
1531            version: Version::new(1, 0),
1532            instructions: vec![
1533                Instruction::LaneConst(LaneConstInstruction::ConstLoc(0)),
1534                Instruction::AtomArrangement(AtomArrangementInstruction::InitialFill { arity: 1 }),
1535            ],
1536        };
1537        assert!(validate_capabilities(&program, &arch).is_empty());
1538    }
1539
1540    #[test]
1541    fn test_no_duplicate_lane_passes() {
1542        let lane0 = make_lane(Direction::Forward, MoveType::SiteBus, 0, 0, 0);
1543        let lane1 = make_lane(Direction::Forward, MoveType::SiteBus, 0, 1, 0);
1544        let lane2 = make_lane(Direction::Forward, MoveType::SiteBus, 0, 2, 0);
1545        let program = Program {
1546            version: Version::new(1, 0),
1547            instructions: vec![
1548                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane0.0, lane0.1)),
1549                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane1.0, lane1.1)),
1550                Instruction::LaneConst(LaneConstInstruction::ConstLane(lane2.0, lane2.1)),
1551                Instruction::AtomArrangement(AtomArrangementInstruction::Move { arity: 3 }),
1552            ],
1553        };
1554        let errors = simulate_stack(&program, None);
1555        assert!(errors.is_empty(), "expected no errors, got: {:?}", errors);
1556    }
1557}