Skip to main content

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