1use crate::arm_semantics::{ArmSemantics, ArmState};
22use crate::solver::{CheckOutcome, new_solver};
23use crate::term::{BV, Bool};
24use crate::wasm_semantics::WasmSemantics;
25use synth_core::WasmOp;
26use synth_synthesis::{ArmOp, Reg, SynthesisRule};
27use thiserror::Error;
28
29#[derive(Debug, Error)]
31pub enum VerificationError {
32 #[error("Translation is incorrect: counterexample found")]
33 CounterexampleFound {
34 wasm_result: String,
35 arm_result: String,
36 inputs: Vec<String>,
37 },
38
39 #[error("Verification timeout after {0}ms")]
40 Timeout(u64),
41
42 #[error("Unsupported operation: {0}")]
43 UnsupportedOperation(String),
44
45 #[error("SMT solver error: {0}")]
46 SolverError(String),
47
48 #[error("Invalid synthesis rule: {0}")]
49 InvalidRule(String),
50}
51
52#[derive(Debug, Clone, PartialEq)]
54pub enum ValidationResult {
55 Verified,
57
58 Invalid { counterexample: Vec<(String, i64)> },
60
61 Unknown { reason: String },
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct CallIndirectSpec {
71 pub table_size: u32,
73 pub may_have_null_slot: bool,
76 pub heterogeneous_expected_type: Option<u32>,
80}
81
82pub struct TranslationValidator {
86 wasm_encoder: WasmSemantics,
87 arm_encoder: ArmSemantics,
88 timeout_ms: u64,
89}
90
91impl Default for TranslationValidator {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97impl TranslationValidator {
98 pub fn new() -> Self {
100 Self {
101 wasm_encoder: WasmSemantics::new(),
102 arm_encoder: ArmSemantics::new(),
103 timeout_ms: 30000, }
105 }
106
107 pub fn set_timeout(&mut self, timeout_ms: u64) {
109 self.timeout_ms = timeout_ms;
110 }
111
112 pub fn verify_rule(&self, rule: &SynthesisRule) -> Result<ValidationResult, VerificationError> {
117 let wasm_op = match &rule.pattern {
119 synth_synthesis::Pattern::WasmInstr(op) => op,
120 _ => {
121 return Err(VerificationError::UnsupportedOperation(
122 "Only single WASM instruction patterns are supported".to_string(),
123 ));
124 }
125 };
126
127 let arm_ops = match &rule.replacement {
129 synth_synthesis::Replacement::ArmInstr(op) => vec![op.clone()],
130 synth_synthesis::Replacement::ArmSequence(ops) => ops.clone(),
131 _ => {
132 return Err(VerificationError::UnsupportedOperation(
133 "Only ARM instruction replacements are supported".to_string(),
134 ));
135 }
136 };
137
138 if Self::is_trap_gated_op(wasm_op) {
145 return self.verify_trap_preservation(wasm_op, &arm_ops);
146 }
147
148 self.verify_equivalence(wasm_op, &arm_ops)
149 }
150
151 fn is_trap_gated_op(wasm_op: &WasmOp) -> bool {
154 matches!(
155 wasm_op,
156 WasmOp::I32DivS
157 | WasmOp::I32DivU
158 | WasmOp::I32RemS
159 | WasmOp::I32RemU
160 | WasmOp::Unreachable
161 | WasmOp::I32Load { .. }
162 | WasmOp::I32Load8S { .. }
163 | WasmOp::I32Load8U { .. }
164 | WasmOp::I32Load16S { .. }
165 | WasmOp::I32Load16U { .. }
166 | WasmOp::I32Store { .. }
167 | WasmOp::I32Store8 { .. }
168 | WasmOp::I32Store16 { .. }
169 | WasmOp::I32TruncF32S
170 | WasmOp::I32TruncF32U
171 )
172 }
173
174 pub fn verify_equivalence(
176 &self,
177 wasm_op: &WasmOp,
178 arm_ops: &[ArmOp],
179 ) -> Result<ValidationResult, VerificationError> {
180 self.verify_equivalence_parameterized(wasm_op, arm_ops, &[])
181 }
182
183 pub fn verify_equivalence_parameterized(
185 &self,
186 wasm_op: &WasmOp,
187 arm_ops: &[ArmOp],
188 concrete_params: &[(usize, i64)],
189 ) -> Result<ValidationResult, VerificationError> {
190 let mut solver = new_solver();
191
192 let num_inputs = self.get_num_inputs(wasm_op);
194 let mut inputs: Vec<BV> = Vec::new();
195
196 for i in 0..num_inputs {
197 let input = if let Some((_, value)) = concrete_params.iter().find(|(idx, _)| *idx == i)
198 {
199 BV::from_i64(*value, 32)
201 } else {
202 BV::new_const(format!("input_{}", i), 32)
204 };
205 inputs.push(input);
206 }
207
208 let wasm_result = self.wasm_encoder.encode_op(wasm_op, &inputs);
210
211 let arm_result = self.encode_arm_sequence(arm_ops, &inputs)?;
213
214 solver.assert(&wasm_result.eq(&arm_result).not());
218
219 match solver.check() {
220 CheckOutcome::Unsat => {
221 Ok(ValidationResult::Verified)
223 }
224
225 CheckOutcome::Sat => {
226 let mut counterexample = Vec::new();
230 for (i, input) in inputs.iter().enumerate() {
231 if let Some(value) = solver.value(input)
232 && let Ok(int_val) = i64::try_from(value)
233 {
234 counterexample.push((format!("input_{}", i), int_val));
235 }
236 }
237
238 Ok(ValidationResult::Invalid { counterexample })
239 }
240
241 CheckOutcome::Unknown(reason) => {
242 Ok(ValidationResult::Unknown {
244 reason: format!("SMT solver returned unknown: {reason}"),
245 })
246 }
247 }
248 }
249
250 fn encode_arm_sequence(
252 &self,
253 arm_ops: &[ArmOp],
254 inputs: &[BV],
255 ) -> Result<BV, VerificationError> {
256 let mut state = ArmState::new_symbolic();
257
258 for (i, input) in inputs.iter().enumerate() {
260 let reg = match i {
261 0 => Reg::R0,
262 1 => Reg::R1,
263 2 => Reg::R2,
264 _ => {
265 return Err(VerificationError::UnsupportedOperation(format!(
266 "Too many inputs: {}",
267 inputs.len()
268 )));
269 }
270 };
271 state.set_reg(®, input.clone());
272 }
273
274 for arm_op in arm_ops {
276 self.arm_encoder.encode_op(arm_op, &mut state);
277 }
278
279 Ok(self.arm_encoder.extract_result(&state, &Reg::R0))
281 }
282
283 pub fn verify_parameterized_range<F>(
285 &self,
286 wasm_op: &WasmOp,
287 create_arm_ops: F,
288 param_index: usize,
289 range: std::ops::Range<i64>,
290 ) -> Result<ValidationResult, VerificationError>
291 where
292 F: Fn(i64) -> Vec<ArmOp>,
293 {
294 for value in range {
295 let arm_ops = create_arm_ops(value);
296 let result =
297 self.verify_equivalence_parameterized(wasm_op, &arm_ops, &[(param_index, value)])?;
298
299 match result {
300 ValidationResult::Verified => continue,
301 ValidationResult::Invalid { counterexample } => {
302 return Ok(ValidationResult::Invalid {
303 counterexample: counterexample
304 .into_iter()
305 .map(|(k, v)| (format!("{} (param={})", k, value), v))
306 .collect(),
307 });
308 }
309 ValidationResult::Unknown { reason } => {
310 return Ok(ValidationResult::Unknown {
311 reason: format!("Failed at param={}: {}", value, reason),
312 });
313 }
314 }
315 }
316
317 Ok(ValidationResult::Verified)
318 }
319
320 pub fn verify_trap_preservation(
350 &self,
351 wasm_op: &WasmOp,
352 arm_ops: &[ArmOp],
353 ) -> Result<ValidationResult, VerificationError> {
354 match wasm_op {
355 WasmOp::I32DivS | WasmOp::I32DivU | WasmOp::I32RemS | WasmOp::I32RemU => {
356 self.verify_div_rem_trap_preservation(wasm_op, arm_ops)
357 }
358 WasmOp::Unreachable => {
359 let (_state, arm_trap) = self.derive_arm_state(arm_ops, &[], None)?;
360 Ok(Self::condition_verdict(
361 &crate::trap::trap_always(),
362 &arm_trap,
363 ))
364 }
365 WasmOp::I32Load { offset, .. }
366 | WasmOp::I32Load8S { offset, .. }
367 | WasmOp::I32Load8U { offset, .. }
368 | WasmOp::I32Load16S { offset, .. }
369 | WasmOp::I32Load16U { offset, .. }
370 | WasmOp::I32Store { offset, .. }
371 | WasmOp::I32Store8 { offset, .. }
372 | WasmOp::I32Store16 { offset, .. } => {
373 let size: u64 = match wasm_op {
374 WasmOp::I32Load8S { .. }
375 | WasmOp::I32Load8U { .. }
376 | WasmOp::I32Store8 { .. } => 1,
377 WasmOp::I32Load16S { .. }
378 | WasmOp::I32Load16U { .. }
379 | WasmOp::I32Store16 { .. } => 2,
380 _ => 4,
381 };
382 self.verify_mem_trap_preservation(arm_ops, *offset, size)
383 }
384 WasmOp::I32TruncF32S | WasmOp::I32TruncF32U => {
385 let signed = matches!(wasm_op, WasmOp::I32TruncF32S);
386 self.verify_trunc_f32_trap_preservation(arm_ops, signed)
387 }
388 other => Err(VerificationError::UnsupportedOperation(format!(
389 "trap-preservation gate does not cover {other:?} \
390 (i64 div/rem and trunc_f64 are unit-gated — see method docs)"
391 ))),
392 }
393 }
394
395 pub fn verify_div_rem_trap_preservation(
413 &self,
414 wasm_op: &WasmOp,
415 arm_ops: &[ArmOp],
416 ) -> Result<ValidationResult, VerificationError> {
417 let Some(div_op) = crate::trap::div_op(wasm_op) else {
418 return Err(VerificationError::UnsupportedOperation(format!(
419 "trap-preservation gate applies to div/rem only, got {wasm_op:?}"
420 )));
421 };
422 if !matches!(
426 wasm_op,
427 WasmOp::I32DivS | WasmOp::I32DivU | WasmOp::I32RemS | WasmOp::I32RemU
428 ) {
429 return Err(VerificationError::UnsupportedOperation(format!(
430 "trap-preservation gate currently supports i32 div/rem only, got {wasm_op:?}"
431 )));
432 }
433
434 let dividend = BV::new_const("input_0", 32);
437 let divisor = BV::new_const("input_1", 32);
438 let inputs = vec![dividend.clone(), divisor.clone()];
439
440 let wasm_value = self.wasm_encoder.encode_op(wasm_op, &inputs);
441 let (state, arm_may_trap) = self.derive_arm_state(arm_ops, &inputs, None)?;
442 let arm_value = if ArmSemantics::branch_spans_are_value_dead(arm_ops) {
443 let mut vstate = ArmState::new_symbolic();
446 Self::seed_inputs(&mut vstate, &inputs)?;
447 self.arm_encoder
448 .encode_sequence_value_straightline(arm_ops, &mut vstate)
449 .map_err(VerificationError::UnsupportedOperation)?;
450 self.arm_encoder.extract_result(&vstate, &Reg::R0)
451 } else {
452 self.arm_encoder.extract_result(&state, &Reg::R0)
453 };
454
455 let orig = crate::trap::DefineOrTrap {
456 value: wasm_value,
457 may_trap: crate::trap::trap_div(div_op, ÷nd, &divisor),
458 };
459 let opt = crate::trap::DefineOrTrap {
460 value: arm_value,
461 may_trap: arm_may_trap,
462 };
463
464 Ok(Self::trap_verdict_to_result(
465 crate::trap::prove_trap_equivalence(&orig, &opt),
466 ))
467 }
468
469 pub fn verify_mem_trap_preservation(
477 &self,
478 arm_ops: &[ArmOp],
479 offset: u32,
480 access_size: u64,
481 ) -> Result<ValidationResult, VerificationError> {
482 let addr = BV::new_const("input_0", 32);
483 let value = BV::new_const("input_1", 32);
484 let inputs = vec![addr.clone(), value];
485
486 let mut state = ArmState::new_symbolic();
487 let mem_bound = state.get_reg(&Reg::R10).clone();
490 Self::seed_inputs(&mut state, &inputs)?;
491 self.arm_encoder
492 .encode_sequence_br(arm_ops, &mut state)
493 .map_err(VerificationError::UnsupportedOperation)?;
494 let arm_trap = state.may_trap.clone();
495
496 let static_bytes = offset as u64 + access_size;
500 let wasm_trap = if static_bytes > u32::MAX as u64 {
501 crate::trap::trap_always()
503 } else {
504 crate::trap::trap_mem_oob(&addr, &BV::from_u64(static_bytes, 32), &mem_bound)
505 };
506
507 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
508 }
509
510 pub fn verify_trunc_f32_trap_preservation(
518 &self,
519 arm_ops: &[ArmOp],
520 signed: bool,
521 ) -> Result<ValidationResult, VerificationError> {
522 use synth_synthesis::rules::VfpReg;
523 let bits = BV::new_const("input_0", 32);
524
525 let mut state = ArmState::new_symbolic();
526 state.set_vfp_reg(&VfpReg::S0, bits.clone());
527 self.arm_encoder
528 .encode_sequence_br(arm_ops, &mut state)
529 .map_err(VerificationError::UnsupportedOperation)?;
530 let arm_trap = state.may_trap.clone();
531
532 let wasm_trap = crate::trap::trap_trunc(
533 &bits,
534 crate::trap::FpFmt::F32,
535 crate::trap::IntTarget::I32,
536 signed,
537 );
538
539 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
540 }
541
542 pub fn verify_call_indirect_trap_preservation(
560 &self,
561 arm_op: &ArmOp,
562 spec: &CallIndirectSpec,
563 ) -> Result<ValidationResult, VerificationError> {
564 let ArmOp::CallIndirect {
565 table_size,
566 null_check,
567 type_check,
568 ..
569 } = arm_op
570 else {
571 return Err(VerificationError::UnsupportedOperation(format!(
572 "call_indirect trap gate needs the CallIndirect pseudo-op, got {arm_op:?}"
573 )));
574 };
575
576 let index = BV::new_const("input_0", 32);
577 let slot = BV::new_const("slot_ptr", 32);
578 let nonnull_slot = slot.bvor(BV::from_u64(1, 32));
579 let actual_ty = BV::new_const("slot_type_id", 32);
580
581 let build = |size: u32, may_null: bool, expected: Option<u32>| {
582 let expected_bv = expected.map(|e| BV::from_u64(e as u64, 32));
583 let size_bv = BV::from_u64(size as u64, 32);
584 let slot_term = if may_null { &slot } else { &nonnull_slot };
585 let type_trap = match &expected_bv {
586 Some(e) => crate::trap::TypeTrap::Runtime {
587 actual_type_id: &actual_ty,
588 expected_id: e,
589 },
590 None => crate::trap::TypeTrap::StaticallyDischarged,
591 };
592 crate::trap::trap_call_indirect(&crate::trap::CallIndirect {
593 index: &index,
594 table_size: &size_bv,
595 slot_ptr: slot_term,
596 type_trap,
597 })
598 };
599
600 let wasm_trap = build(
601 spec.table_size,
602 spec.may_have_null_slot,
603 spec.heterogeneous_expected_type,
604 );
605 let arm_trap = build(
606 *table_size,
607 *null_check,
608 type_check.as_ref().map(|(expected, _)| *expected),
609 );
610
611 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
612 }
613
614 fn derive_arm_state(
617 &self,
618 arm_ops: &[ArmOp],
619 inputs: &[BV],
620 vfp_s0: Option<&BV>,
621 ) -> Result<(ArmState, Bool), VerificationError> {
622 let mut state = ArmState::new_symbolic();
623 Self::seed_inputs(&mut state, inputs)?;
624 if let Some(bits) = vfp_s0 {
625 state.set_vfp_reg(&synth_synthesis::rules::VfpReg::S0, bits.clone());
626 }
627 self.arm_encoder
628 .encode_sequence_br(arm_ops, &mut state)
629 .map_err(VerificationError::UnsupportedOperation)?;
630 let trap = state.may_trap.clone();
631 Ok((state, trap))
632 }
633
634 fn seed_inputs(state: &mut ArmState, inputs: &[BV]) -> Result<(), VerificationError> {
635 for (i, input) in inputs.iter().enumerate() {
636 let reg = match i {
637 0 => Reg::R0,
638 1 => Reg::R1,
639 2 => Reg::R2,
640 _ => {
641 return Err(VerificationError::UnsupportedOperation(format!(
642 "Too many inputs: {}",
643 inputs.len()
644 )));
645 }
646 };
647 state.set_reg(®, input.clone());
648 }
649 Ok(())
650 }
651
652 fn condition_verdict(wasm_trap: &Bool, arm_trap: &Bool) -> ValidationResult {
654 Self::trap_verdict_to_result(crate::trap::prove_trap_condition_equivalence(
655 wasm_trap, arm_trap,
656 ))
657 }
658
659 fn trap_verdict_to_result(verdict: crate::trap::TrapVerdict) -> ValidationResult {
660 match verdict {
661 crate::trap::TrapVerdict::Preserved => ValidationResult::Verified,
662 crate::trap::TrapVerdict::Dropped(model) => ValidationResult::Invalid {
663 counterexample: model
664 .into_iter()
665 .filter_map(|(n, v)| i64::try_from(v).ok().map(|x| (n, x)))
666 .collect(),
667 },
668 crate::trap::TrapVerdict::Unknown => ValidationResult::Unknown {
669 reason: "trap-preservation VC returned Unknown (conservative reject)".to_string(),
670 },
671 }
672 }
673
674 fn get_num_inputs(&self, wasm_op: &WasmOp) -> usize {
676 use WasmOp::*;
677 match wasm_op {
678 I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or
680 | I32Xor | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr | I32Eq | I32Ne | I32LtS
681 | I32LtU | I32LeS | I32LeU | I32GtS | I32GtU | I32GeS | I32GeU => 2,
682
683 I32Clz | I32Ctz | I32Popcnt | I32Eqz => 1,
685
686 I32Const(_) => 0,
688
689 I32Load { .. } => 1, I32Store { .. } => 2, LocalGet(_) | GlobalGet(_) => 0,
695 LocalSet(_) | GlobalSet(_) | LocalTee(_) => 1,
696 Br(_) | BrIf(_) | Return => 0,
697
698 Drop => 1,
700 Select => 3, Nop | Unreachable | Block | Loop | If | Else | End => 0,
702
703 _ => 0,
705 }
706 }
707
708 pub fn verify_rules(
710 &self,
711 rules: &[SynthesisRule],
712 ) -> Vec<(String, Result<ValidationResult, VerificationError>)> {
713 rules
714 .iter()
715 .map(|rule| {
716 let result = self.verify_rule(rule);
717 (rule.name.clone(), result)
718 })
719 .collect()
720 }
721}
722
723#[cfg(test)]
724mod tests {
725 use super::*;
726 use crate::with_verification_context;
727 use synth_synthesis::rules::Condition;
728 use synth_synthesis::{Cost, Operand2, Pattern, Replacement};
729
730 fn create_test_rule(wasm_op: WasmOp, arm_op: ArmOp) -> SynthesisRule {
731 SynthesisRule {
732 name: format!("{:?}", wasm_op),
733 priority: 0,
734 pattern: Pattern::WasmInstr(wasm_op),
735 replacement: Replacement::ArmInstr(arm_op),
736 cost: Cost {
737 cycles: 1,
738 code_size: 4,
739 registers: 2,
740 },
741 }
742 }
743
744 #[test]
747 fn div_lowering_without_guard_is_rejected_as_trap_drop() {
748 with_verification_context(|| {
749 let validator = TranslationValidator::new();
750 let arm_ops = [ArmOp::Udiv {
753 rd: Reg::R0,
754 rn: Reg::R0,
755 rm: Reg::R1,
756 }];
757 let result = validator
758 .verify_div_rem_trap_preservation(&WasmOp::I32DivU, &arm_ops)
759 .unwrap();
760 match result {
761 ValidationResult::Invalid { counterexample } => {
762 let divisor = counterexample.iter().find(|(n, _)| n == "input_1");
764 assert_eq!(
765 divisor.map(|(_, v)| *v),
766 Some(0),
767 "trap-drop counterexample must set the divisor to 0"
768 );
769 }
770 other => panic!("unguarded div must be Invalid, got {other:?}"),
771 }
772 });
773 }
774
775 fn shipped_divu_guard() -> Vec<ArmOp> {
779 vec![
780 ArmOp::Cmp {
781 rn: Reg::R1,
782 op2: Operand2::Imm(0),
783 },
784 ArmOp::BCondOffset {
785 cond: Condition::NE,
786 offset: 0,
787 },
788 ArmOp::Udf { imm: 0 },
789 ArmOp::Udiv {
790 rd: Reg::R0,
791 rn: Reg::R0,
792 rm: Reg::R1,
793 },
794 ]
795 }
796
797 fn shipped_divs_double_guard() -> Vec<ArmOp> {
801 vec![
802 ArmOp::Cmp {
803 rn: Reg::R1,
804 op2: Operand2::Imm(0),
805 },
806 ArmOp::BCondOffset {
807 cond: Condition::NE,
808 offset: 0,
809 },
810 ArmOp::Udf { imm: 0 },
811 ArmOp::Movw {
812 rd: Reg::R12,
813 imm16: 0,
814 },
815 ArmOp::Movt {
816 rd: Reg::R12,
817 imm16: 0x8000,
818 },
819 ArmOp::Cmp {
820 rn: Reg::R0,
821 op2: Operand2::Reg(Reg::R12),
822 },
823 ArmOp::BCondOffset {
824 cond: Condition::NE,
825 offset: 3,
826 },
827 ArmOp::Cmn {
828 rn: Reg::R1,
829 op2: Operand2::Imm(1),
830 },
831 ArmOp::BCondOffset {
832 cond: Condition::NE,
833 offset: 0,
834 },
835 ArmOp::Udf { imm: 1 },
836 ArmOp::Sdiv {
837 rd: Reg::R0,
838 rn: Reg::R0,
839 rm: Reg::R1,
840 },
841 ]
842 }
843
844 #[test]
845 fn div_lowering_with_guard_preserves_the_trap() {
846 with_verification_context(|| {
847 let validator = TranslationValidator::new();
848 let result = validator
849 .verify_div_rem_trap_preservation(&WasmOp::I32DivU, &shipped_divu_guard())
850 .unwrap();
851 assert_eq!(result, ValidationResult::Verified);
852 });
853 }
854
855 #[test]
861 fn div_guard_with_inverted_polarity_is_rejected() {
862 with_verification_context(|| {
863 let validator = TranslationValidator::new();
864 let mut arm_ops = shipped_divu_guard();
865 arm_ops[1] = ArmOp::BCondOffset {
866 cond: Condition::EQ,
867 offset: 0,
868 };
869 let result = validator
870 .verify_div_rem_trap_preservation(&WasmOp::I32DivU, &arm_ops)
871 .unwrap();
872 assert!(
873 matches!(result, ValidationResult::Invalid { .. }),
874 "inverted guard polarity must be Invalid, got {result:?}"
875 );
876 });
877 }
878
879 #[test]
880 fn signed_div_double_guard_preserves_both_zero_and_overflow_traps() {
881 with_verification_context(|| {
882 let validator = TranslationValidator::new();
883 let result = validator
884 .verify_div_rem_trap_preservation(&WasmOp::I32DivS, &shipped_divs_double_guard())
885 .unwrap();
886 assert_eq!(result, ValidationResult::Verified);
887 });
888 }
889
890 #[test]
896 fn signed_div_with_overflow_guard_stripped_is_rejected() {
897 with_verification_context(|| {
898 let validator = TranslationValidator::new();
899 let arm_ops = [
900 ArmOp::Cmp {
901 rn: Reg::R1,
902 op2: Operand2::Imm(0),
903 },
904 ArmOp::BCondOffset {
905 cond: Condition::NE,
906 offset: 0,
907 },
908 ArmOp::Udf { imm: 0 },
909 ArmOp::Sdiv {
910 rd: Reg::R0,
911 rn: Reg::R0,
912 rm: Reg::R1,
913 },
914 ];
915 let result = validator
916 .verify_div_rem_trap_preservation(&WasmOp::I32DivS, &arm_ops)
917 .unwrap();
918 match result {
919 ValidationResult::Invalid { counterexample } => {
920 let get = |n: &str| {
921 counterexample
922 .iter()
923 .find(|(name, _)| name == n)
924 .map(|(_, v)| *v)
925 };
926 assert_eq!(
927 get("input_0"),
928 Some(i32::MIN as u32 as i64),
929 "dropped overflow trap must exhibit dividend INT_MIN: {counterexample:?}"
930 );
931 assert_eq!(
932 get("input_1"),
933 Some(u32::MAX as i64),
934 "dropped overflow trap must exhibit divisor -1: {counterexample:?}"
935 );
936 }
937 other => panic!("overflow-guard-stripped div_s must be Invalid, got {other:?}"),
938 }
939 });
940 }
941
942 #[test]
945 fn rems_single_zero_guard_is_exactly_right() {
946 with_verification_context(|| {
947 let validator = TranslationValidator::new();
948 let arm_ops = [
949 ArmOp::Cmp {
950 rn: Reg::R1,
951 op2: Operand2::Imm(0),
952 },
953 ArmOp::BCondOffset {
954 cond: Condition::NE,
955 offset: 0,
956 },
957 ArmOp::Udf { imm: 0 },
958 ArmOp::Sdiv {
959 rd: Reg::R2,
960 rn: Reg::R0,
961 rm: Reg::R1,
962 },
963 ArmOp::Mls {
964 rd: Reg::R0,
965 rn: Reg::R2,
966 rm: Reg::R1,
967 ra: Reg::R0,
968 },
969 ];
970 let result = validator
971 .verify_div_rem_trap_preservation(&WasmOp::I32RemS, &arm_ops)
972 .unwrap();
973 assert_eq!(result, ValidationResult::Verified);
974 });
975 }
976
977 #[test]
980 fn unreachable_udf_lowering_preserves_the_trap() {
981 with_verification_context(|| {
982 let validator = TranslationValidator::new();
983 let result = validator
984 .verify_trap_preservation(&WasmOp::Unreachable, &[ArmOp::Udf { imm: 0 }])
985 .unwrap();
986 assert_eq!(result, ValidationResult::Verified);
987 });
988 }
989
990 #[test]
991 fn unreachable_lowered_to_nop_is_rejected() {
992 with_verification_context(|| {
993 let validator = TranslationValidator::new();
994 let result = validator
996 .verify_trap_preservation(&WasmOp::Unreachable, &[ArmOp::Nop])
997 .unwrap();
998 assert!(
999 matches!(result, ValidationResult::Invalid { .. }),
1000 "trap-dropping unreachable lowering must be Invalid, got {result:?}"
1001 );
1002 });
1003 }
1004
1005 fn shipped_software_bounds_ops(wasm_op: &WasmOp) -> Vec<ArmOp> {
1018 use synth_synthesis::instruction_selector::InstructionSelector;
1019 use synth_synthesis::rules::MemAddr;
1020 let (offset, size) = match wasm_op {
1021 WasmOp::I32Load { offset, .. } | WasmOp::I32Store { offset, .. } => (*offset, 4u32),
1022 WasmOp::I32Load16S { offset, .. }
1023 | WasmOp::I32Load16U { offset, .. }
1024 | WasmOp::I32Store16 { offset, .. } => (*offset, 2),
1025 WasmOp::I32Load8S { offset, .. }
1026 | WasmOp::I32Load8U { offset, .. }
1027 | WasmOp::I32Store8 { offset, .. } => (*offset, 1),
1028 other => panic!("not a guarded i32 access: {other:?}"),
1029 };
1030 let addr = MemAddr::reg_imm(Reg::R11, Reg::R0, offset as i32);
1031 let access = match wasm_op {
1032 WasmOp::I32Load { .. } => ArmOp::Ldr { rd: Reg::R0, addr },
1033 WasmOp::I32Load8S { .. } => ArmOp::Ldrsb { rd: Reg::R0, addr },
1034 WasmOp::I32Load8U { .. } => ArmOp::Ldrb { rd: Reg::R0, addr },
1035 WasmOp::I32Load16S { .. } => ArmOp::Ldrsh { rd: Reg::R0, addr },
1036 WasmOp::I32Load16U { .. } => ArmOp::Ldrh { rd: Reg::R0, addr },
1037 WasmOp::I32Store { .. } => ArmOp::Str { rd: Reg::R1, addr },
1038 WasmOp::I32Store8 { .. } => ArmOp::Strb { rd: Reg::R1, addr },
1039 WasmOp::I32Store16 { .. } => ArmOp::Strh { rd: Reg::R1, addr },
1040 other => panic!("not a guarded i32 access: {other:?}"),
1041 };
1042 let mut ops = InstructionSelector::software_bounds_guard(Reg::R0, offset as i32, size);
1043 ops.push(access);
1044 ops
1045 }
1046
1047 #[test]
1050 fn load_without_bounds_guard_is_rejected() {
1051 use synth_synthesis::rules::MemAddr;
1052 with_verification_context(|| {
1053 let validator = TranslationValidator::new();
1054 let arm_ops = [ArmOp::Ldr {
1055 rd: Reg::R0,
1056 addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 0),
1057 }];
1058 let result = validator
1059 .verify_trap_preservation(
1060 &WasmOp::I32Load {
1061 offset: 0,
1062 align: 2,
1063 },
1064 &arm_ops,
1065 )
1066 .unwrap();
1067 assert!(
1068 matches!(result, ValidationResult::Invalid { .. }),
1069 "guard-stripped load must be Invalid, got {result:?}"
1070 );
1071 });
1072 }
1073
1074 #[test]
1078 fn byte_load_software_bounds_guard_preserves_the_trap() {
1079 with_verification_context(|| {
1080 let validator = TranslationValidator::new();
1081 let result = validator
1082 .verify_trap_preservation(
1083 &WasmOp::I32Load8U {
1084 offset: 0,
1085 align: 0,
1086 },
1087 &shipped_software_bounds_ops(&WasmOp::I32Load8U {
1088 offset: 0,
1089 align: 0,
1090 }),
1091 )
1092 .unwrap();
1093 assert_eq!(result, ValidationResult::Verified);
1094 });
1095 }
1096
1097 #[test]
1108 fn word_load_software_bounds_guard_survives_the_address_top_752() {
1109 with_verification_context(|| {
1110 let validator = TranslationValidator::new();
1111 let result = validator
1112 .verify_trap_preservation(
1113 &WasmOp::I32Load {
1114 offset: 0,
1115 align: 2,
1116 },
1117 &shipped_software_bounds_ops(&WasmOp::I32Load {
1118 offset: 0,
1119 align: 2,
1120 }),
1121 )
1122 .unwrap();
1123 assert_eq!(
1124 result,
1125 ValidationResult::Verified,
1126 "the #752 wraparound divergence must be closed for every addr"
1127 );
1128 });
1129 }
1130
1131 #[test]
1135 fn all_load_widths_software_bounds_guard_verify_752() {
1136 let cases: Vec<WasmOp> = vec![
1137 WasmOp::I32Load {
1138 offset: 4,
1139 align: 2,
1140 },
1141 WasmOp::I32Load8S {
1142 offset: 3,
1143 align: 0,
1144 },
1145 WasmOp::I32Load8U {
1146 offset: 1,
1147 align: 0,
1148 },
1149 WasmOp::I32Load16S {
1150 offset: 2,
1151 align: 1,
1152 },
1153 WasmOp::I32Load16U {
1154 offset: 0,
1155 align: 1,
1156 },
1157 ];
1158 with_verification_context(|| {
1159 let validator = TranslationValidator::new();
1160 for wasm_op in &cases {
1161 let result = validator
1162 .verify_trap_preservation(wasm_op, &shipped_software_bounds_ops(wasm_op))
1163 .unwrap();
1164 assert_eq!(result, ValidationResult::Verified, "{wasm_op:?}");
1165 }
1166 });
1167 }
1168
1169 #[test]
1172 fn store_software_bounds_guard_verifies_752() {
1173 let cases: Vec<WasmOp> = vec![
1174 WasmOp::I32Store {
1175 offset: 0,
1176 align: 2,
1177 },
1178 WasmOp::I32Store8 {
1179 offset: 5,
1180 align: 0,
1181 },
1182 WasmOp::I32Store16 {
1183 offset: 3,
1184 align: 1,
1185 },
1186 ];
1187 with_verification_context(|| {
1188 let validator = TranslationValidator::new();
1189 for wasm_op in &cases {
1190 let result = validator
1191 .verify_trap_preservation(wasm_op, &shipped_software_bounds_ops(wasm_op))
1192 .unwrap();
1193 assert_eq!(result, ValidationResult::Verified, "{wasm_op:?}");
1194 }
1195 });
1196 }
1197
1198 #[test]
1202 fn large_offset_software_bounds_guard_verifies_752() {
1203 let wasm_op = WasmOp::I32Load {
1204 offset: 0x2000,
1205 align: 2,
1206 };
1207 with_verification_context(|| {
1208 let validator = TranslationValidator::new();
1209 let result = validator
1210 .verify_trap_preservation(&wasm_op, &shipped_software_bounds_ops(&wasm_op))
1211 .unwrap();
1212 assert_eq!(result, ValidationResult::Verified);
1213 });
1214 }
1215
1216 #[test]
1220 fn offset_overflow_software_bounds_guard_always_traps_752() {
1221 let wasm_op = WasmOp::I32Load {
1222 offset: u32::MAX,
1223 align: 2,
1224 };
1225 with_verification_context(|| {
1226 let validator = TranslationValidator::new();
1227 let result = validator
1228 .verify_trap_preservation(&wasm_op, &shipped_software_bounds_ops(&wasm_op))
1229 .unwrap();
1230 assert_eq!(result, ValidationResult::Verified);
1231 });
1232 }
1233
1234 #[test]
1239 fn retired_add_computed_guard_stays_invalid_at_the_address_top_752() {
1240 use synth_synthesis::rules::{Condition, MemAddr, Operand2};
1241 with_verification_context(|| {
1242 let validator = TranslationValidator::new();
1243 let arm_ops = [
1244 ArmOp::Add {
1245 rd: Reg::R12,
1246 rn: Reg::R0,
1247 op2: Operand2::Imm(3), },
1249 ArmOp::Cmp {
1250 rn: Reg::R12,
1251 op2: Operand2::Reg(Reg::R10),
1252 },
1253 ArmOp::BCondOffset {
1254 cond: Condition::LO,
1255 offset: 0,
1256 },
1257 ArmOp::Udf { imm: 0 },
1258 ArmOp::Ldr {
1259 rd: Reg::R0,
1260 addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 0),
1261 },
1262 ];
1263 let result = validator
1264 .verify_trap_preservation(
1265 &WasmOp::I32Load {
1266 offset: 0,
1267 align: 2,
1268 },
1269 &arm_ops,
1270 )
1271 .unwrap();
1272 match result {
1273 ValidationResult::Invalid { counterexample } => {
1274 let addr = counterexample
1275 .iter()
1276 .find(|(n, _)| n == "input_0")
1277 .map(|(_, v)| *v)
1278 .expect("counterexample must assign the address");
1279 assert!(
1280 addr >= 0xFFFF_FFFD,
1281 "the divergence is the 32-bit end-address wrap at the top \
1282 of the address space, got addr {addr:#x}"
1283 );
1284 }
1285 other => panic!("the retired wrapping guard must stay Invalid, got {other:?}"),
1286 }
1287 });
1288 }
1289
1290 #[test]
1295 fn wraparound_safe_bounds_guard_verifies() {
1296 use synth_synthesis::rules::MemAddr;
1297 with_verification_context(|| {
1298 let validator = TranslationValidator::new();
1299 let k = 4; let arm_ops = [
1301 ArmOp::Cmp {
1303 rn: Reg::R10,
1304 op2: Operand2::Imm(k),
1305 },
1306 ArmOp::BCondOffset {
1307 cond: Condition::HS,
1308 offset: 0,
1309 },
1310 ArmOp::Udf { imm: 0 },
1311 ArmOp::Sub {
1314 rd: Reg::R12,
1315 rn: Reg::R10,
1316 op2: Operand2::Imm(k),
1317 },
1318 ArmOp::Cmp {
1319 rn: Reg::R0,
1320 op2: Operand2::Reg(Reg::R12),
1321 },
1322 ArmOp::BCondOffset {
1323 cond: Condition::LS,
1324 offset: 0,
1325 },
1326 ArmOp::Udf { imm: 0 },
1327 ArmOp::Ldr {
1328 rd: Reg::R0,
1329 addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 0),
1330 },
1331 ];
1332 let result = validator
1333 .verify_trap_preservation(
1334 &WasmOp::I32Load {
1335 offset: 0,
1336 align: 2,
1337 },
1338 &arm_ops,
1339 )
1340 .unwrap();
1341 assert_eq!(result, ValidationResult::Verified);
1342 });
1343 }
1344
1345 fn shipped_trunc_f32_guard(signed: bool) -> Vec<ArmOp> {
1352 use synth_synthesis::rules::VfpReg;
1353 let (hi, lo) = if signed {
1354 (2147483648.0_f32, -2147483648.0_f32)
1355 } else {
1356 (4294967296.0_f32, -1.0_f32)
1357 };
1358 let mut ops = Vec::new();
1359 let guard = |ops: &mut Vec<ArmOp>, bound: f32, upper: bool| {
1360 ops.push(ArmOp::F32Const {
1361 sd: VfpReg::S1,
1362 value: bound,
1363 });
1364 let cmp = if upper {
1365 ArmOp::F32Lt {
1366 rd: Reg::R0,
1367 sn: VfpReg::S0,
1368 sm: VfpReg::S1,
1369 }
1370 } else if signed {
1371 ArmOp::F32Ge {
1372 rd: Reg::R0,
1373 sn: VfpReg::S0,
1374 sm: VfpReg::S1,
1375 }
1376 } else {
1377 ArmOp::F32Gt {
1378 rd: Reg::R0,
1379 sn: VfpReg::S0,
1380 sm: VfpReg::S1,
1381 }
1382 };
1383 ops.push(cmp);
1384 ops.push(ArmOp::Cmp {
1385 rn: Reg::R0,
1386 op2: Operand2::Imm(0),
1387 });
1388 ops.push(ArmOp::BCondOffset {
1389 cond: Condition::NE,
1390 offset: 0,
1391 });
1392 ops.push(ArmOp::Udf { imm: 0 });
1393 };
1394 guard(&mut ops, hi, true);
1395 guard(&mut ops, lo, false);
1396 if signed {
1397 ops.push(ArmOp::I32TruncF32S {
1398 rd: Reg::R0,
1399 sm: VfpReg::S0,
1400 });
1401 } else {
1402 ops.push(ArmOp::I32TruncF32U {
1403 rd: Reg::R0,
1404 sm: VfpReg::S0,
1405 });
1406 }
1407 ops
1408 }
1409
1410 #[test]
1411 fn trunc_f32_s_domain_guard_preserves_the_trap() {
1412 with_verification_context(|| {
1413 let validator = TranslationValidator::new();
1414 let result = validator
1415 .verify_trap_preservation(&WasmOp::I32TruncF32S, &shipped_trunc_f32_guard(true))
1416 .unwrap();
1417 assert_eq!(result, ValidationResult::Verified);
1418 });
1419 }
1420
1421 #[test]
1422 fn trunc_f32_u_domain_guard_preserves_the_trap() {
1423 with_verification_context(|| {
1424 let validator = TranslationValidator::new();
1425 let result = validator
1426 .verify_trap_preservation(&WasmOp::I32TruncF32U, &shipped_trunc_f32_guard(false))
1427 .unwrap();
1428 assert_eq!(result, ValidationResult::Verified);
1429 });
1430 }
1431
1432 #[test]
1435 fn trunc_f32_without_domain_guard_is_rejected() {
1436 use synth_synthesis::rules::VfpReg;
1437 with_verification_context(|| {
1438 let validator = TranslationValidator::new();
1439 let arm_ops = [ArmOp::I32TruncF32S {
1440 rd: Reg::R0,
1441 sm: VfpReg::S0,
1442 }];
1443 let result = validator
1444 .verify_trap_preservation(&WasmOp::I32TruncF32S, &arm_ops)
1445 .unwrap();
1446 assert!(
1447 matches!(result, ValidationResult::Invalid { .. }),
1448 "guard-stripped trunc must be Invalid, got {result:?}"
1449 );
1450 });
1451 }
1452
1453 #[test]
1455 fn trunc_f32_with_only_upper_guard_is_rejected() {
1456 with_verification_context(|| {
1457 let validator = TranslationValidator::new();
1458 let mut arm_ops = shipped_trunc_f32_guard(true);
1459 arm_ops.drain(5..10);
1461 let result = validator
1462 .verify_trap_preservation(&WasmOp::I32TruncF32S, &arm_ops)
1463 .unwrap();
1464 assert!(
1465 matches!(result, ValidationResult::Invalid { .. }),
1466 "upper-only trunc guard must be Invalid, got {result:?}"
1467 );
1468 });
1469 }
1470
1471 fn call_indirect_pseudo(
1474 table_size: u32,
1475 null_check: bool,
1476 type_check: Option<(u32, u32)>,
1477 ) -> ArmOp {
1478 ArmOp::CallIndirect {
1479 rd: Reg::R0,
1480 type_idx: 0,
1481 table_index_reg: Reg::R0,
1482 table_size,
1483 table_byte_offset: 0,
1484 null_check,
1485 type_check,
1486 }
1487 }
1488
1489 #[test]
1490 fn call_indirect_matching_guards_preserve_the_traps() {
1491 with_verification_context(|| {
1492 let validator = TranslationValidator::new();
1493 let result = validator
1495 .verify_call_indirect_trap_preservation(
1496 &call_indirect_pseudo(8, false, None),
1497 &CallIndirectSpec {
1498 table_size: 8,
1499 may_have_null_slot: false,
1500 heterogeneous_expected_type: None,
1501 },
1502 )
1503 .unwrap();
1504 assert_eq!(result, ValidationResult::Verified);
1505 let result = validator
1507 .verify_call_indirect_trap_preservation(
1508 &call_indirect_pseudo(8, true, Some((3, 32))),
1509 &CallIndirectSpec {
1510 table_size: 8,
1511 may_have_null_slot: true,
1512 heterogeneous_expected_type: Some(3),
1513 },
1514 )
1515 .unwrap();
1516 assert_eq!(result, ValidationResult::Verified);
1517 });
1518 }
1519
1520 #[test]
1523 fn call_indirect_dropped_null_check_is_rejected() {
1524 with_verification_context(|| {
1525 let validator = TranslationValidator::new();
1526 let result = validator
1527 .verify_call_indirect_trap_preservation(
1528 &call_indirect_pseudo(8, false, None),
1529 &CallIndirectSpec {
1530 table_size: 8,
1531 may_have_null_slot: true,
1532 heterogeneous_expected_type: None,
1533 },
1534 )
1535 .unwrap();
1536 assert!(
1537 matches!(result, ValidationResult::Invalid { .. }),
1538 "dropped null check must be Invalid, got {result:?}"
1539 );
1540 });
1541 }
1542
1543 #[test]
1546 fn call_indirect_wrong_table_size_is_rejected() {
1547 with_verification_context(|| {
1548 let validator = TranslationValidator::new();
1549 let result = validator
1550 .verify_call_indirect_trap_preservation(
1551 &call_indirect_pseudo(16, false, None),
1552 &CallIndirectSpec {
1553 table_size: 8,
1554 may_have_null_slot: false,
1555 heterogeneous_expected_type: None,
1556 },
1557 )
1558 .unwrap();
1559 assert!(
1560 matches!(result, ValidationResult::Invalid { .. }),
1561 "wrong bounds size must be Invalid, got {result:?}"
1562 );
1563 });
1564 }
1565
1566 #[test]
1569 fn call_indirect_dropped_type_check_is_rejected() {
1570 with_verification_context(|| {
1571 let validator = TranslationValidator::new();
1572 let result = validator
1573 .verify_call_indirect_trap_preservation(
1574 &call_indirect_pseudo(8, true, None),
1575 &CallIndirectSpec {
1576 table_size: 8,
1577 may_have_null_slot: true,
1578 heterogeneous_expected_type: Some(3),
1579 },
1580 )
1581 .unwrap();
1582 assert!(
1583 matches!(result, ValidationResult::Invalid { .. }),
1584 "dropped type check must be Invalid, got {result:?}"
1585 );
1586 });
1587 }
1588
1589 #[test]
1592 fn verify_rule_routes_partial_ops_through_the_trap_gate() {
1593 with_verification_context(|| {
1594 let validator = TranslationValidator::new();
1595 let rule = SynthesisRule {
1598 name: "i32.div_u → bare UDIV (trap-dropping)".into(),
1599 priority: 0,
1600 pattern: Pattern::WasmInstr(WasmOp::I32DivU),
1601 replacement: Replacement::ArmInstr(ArmOp::Udiv {
1602 rd: Reg::R0,
1603 rn: Reg::R0,
1604 rm: Reg::R1,
1605 }),
1606 cost: Cost {
1607 cycles: 1,
1608 code_size: 4,
1609 registers: 2,
1610 },
1611 };
1612 let result = validator.verify_rule(&rule).unwrap();
1613 assert!(
1614 matches!(result, ValidationResult::Invalid { .. }),
1615 "verify_rule must reject the trap-dropping div rule, got {result:?}"
1616 );
1617
1618 let rule = SynthesisRule {
1620 name: "i32.div_u → guarded UDIV".into(),
1621 priority: 0,
1622 pattern: Pattern::WasmInstr(WasmOp::I32DivU),
1623 replacement: Replacement::ArmSequence(shipped_divu_guard()),
1624 cost: Cost {
1625 cycles: 4,
1626 code_size: 10,
1627 registers: 2,
1628 },
1629 };
1630 assert_eq!(
1631 validator.verify_rule(&rule).unwrap(),
1632 ValidationResult::Verified
1633 );
1634 });
1635 }
1636
1637 #[test]
1638 fn trap_preservation_gate_rejects_non_div_ops() {
1639 with_verification_context(|| {
1640 let validator = TranslationValidator::new();
1641 let err = validator
1642 .verify_div_rem_trap_preservation(&WasmOp::I32Add, &[])
1643 .unwrap_err();
1644 assert!(matches!(err, VerificationError::UnsupportedOperation(_)));
1645 let err64 = validator
1648 .verify_div_rem_trap_preservation(&WasmOp::I64DivU, &[])
1649 .unwrap_err();
1650 assert!(matches!(err64, VerificationError::UnsupportedOperation(_)));
1651 });
1652 }
1653
1654 #[test]
1655 fn test_verify_add_correct() {
1656 with_verification_context(|| {
1657 let validator = TranslationValidator::new();
1658
1659 let rule = create_test_rule(
1660 WasmOp::I32Add,
1661 ArmOp::Add {
1662 rd: Reg::R0,
1663 rn: Reg::R0,
1664 op2: Operand2::Reg(Reg::R1),
1665 },
1666 );
1667
1668 let result = validator.verify_rule(&rule).unwrap();
1669 assert_eq!(result, ValidationResult::Verified);
1670 });
1671 }
1672
1673 #[test]
1674 fn test_verify_sub_correct() {
1675 with_verification_context(|| {
1676 let validator = TranslationValidator::new();
1677
1678 let rule = create_test_rule(
1679 WasmOp::I32Sub,
1680 ArmOp::Sub {
1681 rd: Reg::R0,
1682 rn: Reg::R0,
1683 op2: Operand2::Reg(Reg::R1),
1684 },
1685 );
1686
1687 let result = validator.verify_rule(&rule).unwrap();
1688 assert_eq!(result, ValidationResult::Verified);
1689 });
1690 }
1691
1692 #[test]
1693 fn test_verify_mul_correct() {
1694 with_verification_context(|| {
1695 let validator = TranslationValidator::new();
1696
1697 let rule = create_test_rule(
1698 WasmOp::I32Mul,
1699 ArmOp::Mul {
1700 rd: Reg::R0,
1701 rn: Reg::R0,
1702 rm: Reg::R1,
1703 },
1704 );
1705
1706 let result = validator.verify_rule(&rule).unwrap();
1707 assert_eq!(result, ValidationResult::Verified);
1708 });
1709 }
1710
1711 #[test]
1712 fn test_verify_and_correct() {
1713 with_verification_context(|| {
1714 let validator = TranslationValidator::new();
1715
1716 let rule = create_test_rule(
1717 WasmOp::I32And,
1718 ArmOp::And {
1719 rd: Reg::R0,
1720 rn: Reg::R0,
1721 op2: Operand2::Reg(Reg::R1),
1722 },
1723 );
1724
1725 let result = validator.verify_rule(&rule).unwrap();
1726 assert_eq!(result, ValidationResult::Verified);
1727 });
1728 }
1729
1730 #[test]
1731 fn test_verify_incorrect_rule() {
1732 with_verification_context(|| {
1733 let validator = TranslationValidator::new();
1734
1735 let rule = create_test_rule(
1737 WasmOp::I32Add,
1738 ArmOp::Sub {
1739 rd: Reg::R0,
1740 rn: Reg::R0,
1741 op2: Operand2::Reg(Reg::R1),
1742 },
1743 );
1744
1745 let result = validator.verify_rule(&rule).unwrap();
1746
1747 match result {
1748 ValidationResult::Invalid { counterexample } => {
1749 assert!(!counterexample.is_empty());
1750 }
1751 _ => panic!("Expected counterexample but got: {:?}", result),
1752 }
1753 });
1754 }
1755
1756 #[test]
1757 fn test_verify_bitwise_ops() {
1758 with_verification_context(|| {
1759 let validator = TranslationValidator::new();
1760
1761 let or_rule = create_test_rule(
1763 WasmOp::I32Or,
1764 ArmOp::Orr {
1765 rd: Reg::R0,
1766 rn: Reg::R0,
1767 op2: Operand2::Reg(Reg::R1),
1768 },
1769 );
1770 assert_eq!(
1771 validator.verify_rule(&or_rule).unwrap(),
1772 ValidationResult::Verified
1773 );
1774
1775 let xor_rule = create_test_rule(
1777 WasmOp::I32Xor,
1778 ArmOp::Eor {
1779 rd: Reg::R0,
1780 rn: Reg::R0,
1781 op2: Operand2::Reg(Reg::R1),
1782 },
1783 );
1784 assert_eq!(
1785 validator.verify_rule(&xor_rule).unwrap(),
1786 ValidationResult::Verified
1787 );
1788 });
1789 }
1790
1791 #[test]
1792 fn test_verify_shift_ops() {
1793 }
1798}