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::I64DivS
164 | WasmOp::I64DivU
165 | WasmOp::I64RemS
166 | WasmOp::I64RemU
167 | WasmOp::Unreachable
168 | WasmOp::I32Load { .. }
169 | WasmOp::I32Load8S { .. }
170 | WasmOp::I32Load8U { .. }
171 | WasmOp::I32Load16S { .. }
172 | WasmOp::I32Load16U { .. }
173 | WasmOp::I32Store { .. }
174 | WasmOp::I32Store8 { .. }
175 | WasmOp::I32Store16 { .. }
176 | WasmOp::I32TruncF32S
177 | WasmOp::I32TruncF32U
178 | WasmOp::I32TruncF64S
190 | WasmOp::I32TruncF64U
191 )
192 }
193
194 pub fn verify_equivalence(
196 &self,
197 wasm_op: &WasmOp,
198 arm_ops: &[ArmOp],
199 ) -> Result<ValidationResult, VerificationError> {
200 self.verify_equivalence_parameterized(wasm_op, arm_ops, &[])
201 }
202
203 pub fn verify_equivalence_parameterized(
205 &self,
206 wasm_op: &WasmOp,
207 arm_ops: &[ArmOp],
208 concrete_params: &[(usize, i64)],
209 ) -> Result<ValidationResult, VerificationError> {
210 let mut solver = new_solver();
211
212 let num_inputs = self.get_num_inputs(wasm_op);
214 let mut inputs: Vec<BV> = Vec::new();
215
216 for i in 0..num_inputs {
217 let input = if let Some((_, value)) = concrete_params.iter().find(|(idx, _)| *idx == i)
218 {
219 BV::from_i64(*value, 32)
221 } else {
222 BV::new_const(format!("input_{}", i), 32)
224 };
225 inputs.push(input);
226 }
227
228 let wasm_result = self.wasm_encoder.encode_op(wasm_op, &inputs);
230
231 let arm_result = self.encode_arm_sequence(arm_ops, &inputs)?;
233
234 solver.assert(&wasm_result.eq(&arm_result).not());
238
239 match solver.check() {
240 CheckOutcome::Unsat => {
241 Ok(ValidationResult::Verified)
243 }
244
245 CheckOutcome::Sat => {
246 let mut counterexample = Vec::new();
250 for (i, input) in inputs.iter().enumerate() {
251 if let Some(value) = solver.value(input)
252 && let Ok(int_val) = i64::try_from(value)
253 {
254 counterexample.push((format!("input_{}", i), int_val));
255 }
256 }
257
258 Ok(ValidationResult::Invalid { counterexample })
259 }
260
261 CheckOutcome::Unknown(reason) => {
262 Ok(ValidationResult::Unknown {
264 reason: format!("SMT solver returned unknown: {reason}"),
265 })
266 }
267 }
268 }
269
270 fn encode_arm_sequence(
272 &self,
273 arm_ops: &[ArmOp],
274 inputs: &[BV],
275 ) -> Result<BV, VerificationError> {
276 let mut state = ArmState::new_symbolic();
277
278 for (i, input) in inputs.iter().enumerate() {
280 let reg = match i {
281 0 => Reg::R0,
282 1 => Reg::R1,
283 2 => Reg::R2,
284 _ => {
285 return Err(VerificationError::UnsupportedOperation(format!(
286 "Too many inputs: {}",
287 inputs.len()
288 )));
289 }
290 };
291 state.set_reg(®, input.clone());
292 }
293
294 for arm_op in arm_ops {
306 self.arm_encoder.encode_op(arm_op, &mut state);
307 if let Some(name) = &state.unmodeled {
308 return Err(VerificationError::UnsupportedOperation(format!(
309 "ARM op `{name}` has no semantics in ArmSemantics::encode_op — \
310 declining rather than verifying a partially-executed sequence (#923)"
311 )));
312 }
313 }
314
315 Ok(self.arm_encoder.extract_result(&state, &Reg::R0))
317 }
318
319 pub fn verify_parameterized_range<F>(
321 &self,
322 wasm_op: &WasmOp,
323 create_arm_ops: F,
324 param_index: usize,
325 range: std::ops::Range<i64>,
326 ) -> Result<ValidationResult, VerificationError>
327 where
328 F: Fn(i64) -> Vec<ArmOp>,
329 {
330 for value in range {
331 let arm_ops = create_arm_ops(value);
332 let result =
333 self.verify_equivalence_parameterized(wasm_op, &arm_ops, &[(param_index, value)])?;
334
335 match result {
336 ValidationResult::Verified => continue,
337 ValidationResult::Invalid { counterexample } => {
338 return Ok(ValidationResult::Invalid {
339 counterexample: counterexample
340 .into_iter()
341 .map(|(k, v)| (format!("{} (param={})", k, value), v))
342 .collect(),
343 });
344 }
345 ValidationResult::Unknown { reason } => {
346 return Ok(ValidationResult::Unknown {
347 reason: format!("Failed at param={}: {}", value, reason),
348 });
349 }
350 }
351 }
352
353 Ok(ValidationResult::Verified)
354 }
355
356 pub fn verify_trap_preservation(
400 &self,
401 wasm_op: &WasmOp,
402 arm_ops: &[ArmOp],
403 ) -> Result<ValidationResult, VerificationError> {
404 match wasm_op {
405 WasmOp::I32DivS | WasmOp::I32DivU | WasmOp::I32RemS | WasmOp::I32RemU => {
406 self.verify_div_rem_trap_preservation(wasm_op, arm_ops)
407 }
408 WasmOp::I64RemU | WasmOp::I64RemS => {
416 self.verify_i64_rem_value_preservation(wasm_op, arm_ops)
417 }
418 WasmOp::I64DivS | WasmOp::I64DivU => {
419 self.verify_i64_div_rem_trap_preservation(wasm_op, arm_ops)
420 }
421 WasmOp::Unreachable => {
422 let (_state, arm_trap) = self.derive_arm_state(arm_ops, &[], None)?;
423 Ok(Self::condition_verdict(
424 &crate::trap::trap_always(),
425 &arm_trap,
426 ))
427 }
428 WasmOp::I32Load { offset, .. }
429 | WasmOp::I32Load8S { offset, .. }
430 | WasmOp::I32Load8U { offset, .. }
431 | WasmOp::I32Load16S { offset, .. }
432 | WasmOp::I32Load16U { offset, .. }
433 | WasmOp::I32Store { offset, .. }
434 | WasmOp::I32Store8 { offset, .. }
435 | WasmOp::I32Store16 { offset, .. } => {
436 let size: u64 = match wasm_op {
437 WasmOp::I32Load8S { .. }
438 | WasmOp::I32Load8U { .. }
439 | WasmOp::I32Store8 { .. } => 1,
440 WasmOp::I32Load16S { .. }
441 | WasmOp::I32Load16U { .. }
442 | WasmOp::I32Store16 { .. } => 2,
443 _ => 4,
444 };
445 self.verify_mem_trap_preservation(arm_ops, *offset, size)
446 }
447 WasmOp::I32TruncF32S | WasmOp::I32TruncF32U => {
448 let signed = matches!(wasm_op, WasmOp::I32TruncF32S);
449 self.verify_trunc_f32_trap_preservation(arm_ops, signed)
450 }
451 WasmOp::I32TruncF64S | WasmOp::I32TruncF64U => {
452 let signed = matches!(wasm_op, WasmOp::I32TruncF64S);
453 self.verify_trunc_f64_trap_preservation(arm_ops, signed)
454 }
455 other => Err(VerificationError::UnsupportedOperation(format!(
456 "trap-preservation gate does not cover {other:?} \
457 (i64.trunc_f64 has no shipped lowering — the selector declines \
458 it; its classifier is unit-gated — see method docs)"
459 ))),
460 }
461 }
462
463 pub fn verify_div_rem_trap_preservation(
481 &self,
482 wasm_op: &WasmOp,
483 arm_ops: &[ArmOp],
484 ) -> Result<ValidationResult, VerificationError> {
485 let Some(div_op) = crate::trap::div_op(wasm_op) else {
486 return Err(VerificationError::UnsupportedOperation(format!(
487 "trap-preservation gate applies to div/rem only, got {wasm_op:?}"
488 )));
489 };
490 if !matches!(
494 wasm_op,
495 WasmOp::I32DivS | WasmOp::I32DivU | WasmOp::I32RemS | WasmOp::I32RemU
496 ) {
497 return Err(VerificationError::UnsupportedOperation(format!(
498 "trap-preservation gate currently supports i32 div/rem only, got {wasm_op:?}"
499 )));
500 }
501
502 let dividend = BV::new_const("input_0", 32);
505 let divisor = BV::new_const("input_1", 32);
506 let inputs = vec![dividend.clone(), divisor.clone()];
507
508 let wasm_value = self.wasm_encoder.encode_op(wasm_op, &inputs);
509 let (state, arm_may_trap) = self.derive_arm_state(arm_ops, &inputs, None)?;
510 let arm_value = if ArmSemantics::branch_spans_are_value_dead(arm_ops) {
511 let mut vstate = ArmState::new_symbolic();
514 Self::seed_inputs(&mut vstate, &inputs)?;
515 self.arm_encoder
516 .encode_sequence_value_straightline(arm_ops, &mut vstate)
517 .map_err(VerificationError::UnsupportedOperation)?;
518 self.arm_encoder.extract_result(&vstate, &Reg::R0)
519 } else {
520 self.arm_encoder.extract_result(&state, &Reg::R0)
521 };
522
523 let orig = crate::trap::DefineOrTrap {
524 value: wasm_value,
525 may_trap: crate::trap::trap_div(div_op, ÷nd, &divisor),
526 };
527 let opt = crate::trap::DefineOrTrap {
528 value: arm_value,
529 may_trap: arm_may_trap,
530 };
531
532 Ok(Self::trap_verdict_to_result(
533 crate::trap::prove_trap_equivalence(&orig, &opt),
534 ))
535 }
536
537 pub fn verify_mem_trap_preservation(
545 &self,
546 arm_ops: &[ArmOp],
547 offset: u32,
548 access_size: u64,
549 ) -> Result<ValidationResult, VerificationError> {
550 let addr = BV::new_const("input_0", 32);
551 let value = BV::new_const("input_1", 32);
552 let inputs = vec![addr.clone(), value];
553
554 let mut state = ArmState::new_symbolic();
555 let mem_bound = state.get_reg(&Reg::R10).clone();
558 Self::seed_inputs(&mut state, &inputs)?;
559 self.arm_encoder
560 .encode_sequence_br(arm_ops, &mut state)
561 .map_err(VerificationError::UnsupportedOperation)?;
562 let arm_trap = state.may_trap.clone();
563
564 let static_bytes = offset as u64 + access_size;
568 let wasm_trap = if static_bytes > u32::MAX as u64 {
569 crate::trap::trap_always()
571 } else {
572 crate::trap::trap_mem_oob(&addr, &BV::from_u64(static_bytes, 32), &mem_bound)
573 };
574
575 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
576 }
577
578 pub fn verify_trunc_f32_trap_preservation(
586 &self,
587 arm_ops: &[ArmOp],
588 signed: bool,
589 ) -> Result<ValidationResult, VerificationError> {
590 use synth_synthesis::rules::VfpReg;
591 let bits = BV::new_const("input_0", 32);
592
593 let mut state = ArmState::new_symbolic();
594 state.set_vfp_reg(&VfpReg::S0, bits.clone());
595 self.arm_encoder
596 .encode_sequence_br(arm_ops, &mut state)
597 .map_err(VerificationError::UnsupportedOperation)?;
598 let arm_trap = state.may_trap.clone();
599
600 let wasm_trap = crate::trap::trap_trunc(
601 &bits,
602 crate::trap::FpFmt::F32,
603 crate::trap::IntTarget::I32,
604 signed,
605 );
606
607 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
608 }
609
610 pub fn verify_i64_div_rem_trap_preservation(
622 &self,
623 wasm_op: &WasmOp,
624 arm_ops: &[ArmOp],
625 ) -> Result<ValidationResult, VerificationError> {
626 let Some(div_op) = crate::trap::div_op(wasm_op) else {
627 return Err(VerificationError::UnsupportedOperation(format!(
628 "i64 trap-preservation gate applies to div/rem only, got {wasm_op:?}"
629 )));
630 };
631 if !matches!(
632 wasm_op,
633 WasmOp::I64DivS | WasmOp::I64DivU | WasmOp::I64RemS | WasmOp::I64RemU
634 ) {
635 return Err(VerificationError::UnsupportedOperation(format!(
636 "i64 trap-preservation gate supports i64 div/rem only, got {wasm_op:?}"
637 )));
638 }
639
640 let (elide_zero, elide_overflow) =
643 Self::i64_div_rem_guard_fields(arm_ops).ok_or_else(|| {
644 VerificationError::UnsupportedOperation(format!(
645 "i64 trap gate needs an I64Div/I64Rem pseudo-op in the sequence, \
646 got {arm_ops:?}"
647 ))
648 })?;
649
650 let dividend = BV::new_const("input_dividend_i64", 64);
655 let divisor = BV::new_const("input_divisor_i64", 64);
656 let wasm_trap = crate::trap::trap_div(div_op, ÷nd, &divisor);
657
658 let arm_trap =
662 Self::i64_arm_trap_from_fields(div_op, ÷nd, &divisor, elide_zero, elide_overflow);
663
664 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
665 }
666
667 pub fn verify_i64_rem_value_preservation(
698 &self,
699 wasm_op: &WasmOp,
700 arm_ops: &[ArmOp],
701 ) -> Result<ValidationResult, VerificationError> {
702 let Some(div_op) = crate::trap::div_op(wasm_op) else {
703 return Err(VerificationError::UnsupportedOperation(format!(
704 "i64 rem value gate applies to rem only, got {wasm_op:?}"
705 )));
706 };
707 if !matches!(wasm_op, WasmOp::I64RemU | WasmOp::I64RemS) {
708 return Err(VerificationError::UnsupportedOperation(format!(
709 "i64 rem value gate supports i64 rem_u/rem_s only, got {wasm_op:?}"
710 )));
711 }
712
713 let Some((elide_zero, _elide_overflow)) = Self::i64_div_rem_guard_fields(arm_ops) else {
721 return Err(VerificationError::UnsupportedOperation(format!(
722 "i64 rem value gate needs an I64Rem pseudo-op in the sequence, \
723 got {arm_ops:?}"
724 )));
725 };
726
727 let dividend_lo = BV::new_const("input_dividend_lo", 32);
730 let dividend_hi = BV::new_const("input_dividend_hi", 32);
731 let divisor_lo = BV::new_const("input_divisor_lo", 32);
732 let divisor_hi = BV::new_const("input_divisor_hi", 32);
733
734 let mut state = ArmState::new_symbolic();
735 state.set_reg(&Reg::R0, dividend_lo.clone());
736 state.set_reg(&Reg::R1, dividend_hi.clone());
737 state.set_reg(&Reg::R2, divisor_lo.clone());
738 state.set_reg(&Reg::R3, divisor_hi.clone());
739 self.arm_encoder
740 .encode_sequence_br(arm_ops, &mut state)
741 .map_err(VerificationError::UnsupportedOperation)?;
742
743 let arm_lo = self.arm_encoder.extract_result(&state, &Reg::R0);
746 let arm_hi = self.arm_encoder.extract_result(&state, &Reg::R1);
747 let arm_value = arm_hi.concat(&arm_lo); let dividend = dividend_hi.concat(÷nd_lo); let divisor = divisor_hi.concat(&divisor_lo); let wasm_value = match wasm_op {
754 WasmOp::I64RemU => dividend.bvurem(&divisor),
755 WasmOp::I64RemS => dividend.bvsrem(&divisor),
756 _ => unreachable!("guarded above"),
757 };
758 let wasm_trap = crate::trap::trap_div(div_op, ÷nd, &divisor);
759
760 let arm_may_trap = Self::i64_arm_trap_from_fields(
763 div_op, ÷nd, &divisor, elide_zero, false,
764 );
765
766 let orig = crate::trap::DefineOrTrap {
767 value: wasm_value,
768 may_trap: wasm_trap,
769 };
770 let opt = crate::trap::DefineOrTrap {
771 value: arm_value,
772 may_trap: arm_may_trap,
773 };
774
775 Ok(Self::trap_verdict_to_result(
776 crate::trap::prove_trap_equivalence(&orig, &opt),
777 ))
778 }
779
780 fn i64_div_rem_guard_fields(arm_ops: &[ArmOp]) -> Option<(bool, bool)> {
785 arm_ops.iter().find_map(|op| match op {
786 ArmOp::I64DivS {
787 elide_zero_guard,
788 elide_overflow_guard,
789 ..
790 } => Some((*elide_zero_guard, *elide_overflow_guard)),
791 ArmOp::I64DivU {
792 elide_zero_guard, ..
793 }
794 | ArmOp::I64RemS {
795 elide_zero_guard, ..
796 }
797 | ArmOp::I64RemU {
798 elide_zero_guard, ..
799 } => Some((*elide_zero_guard, false)),
800 _ => None,
801 })
802 }
803
804 fn i64_arm_trap_from_fields(
809 div_op: crate::trap::DivOp,
810 dividend: &BV,
811 divisor: &BV,
812 elide_zero: bool,
813 elide_overflow: bool,
814 ) -> Bool {
815 let zero = BV::from_u64(0, 64);
816 let div_by_zero = divisor.eq(&zero);
817
818 let mut clauses: Vec<Bool> = Vec::new();
820 if !elide_zero {
821 clauses.push(div_by_zero);
822 }
823
824 if matches!(div_op, crate::trap::DivOp::DivS) && !elide_overflow {
827 let int_min = BV::from_i64(i64::MIN, 64);
828 let neg_one = BV::from_i64(-1, 64);
829 let overflow = Bool::and(&[÷nd.eq(&int_min), &divisor.eq(&neg_one)]);
830 clauses.push(overflow);
831 }
832
833 if clauses.is_empty() {
834 Bool::from_bool(false)
835 } else {
836 let refs: Vec<&Bool> = clauses.iter().collect();
837 Bool::or(&refs)
838 }
839 }
840
841 pub fn verify_trunc_f64_trap_preservation(
850 &self,
851 arm_ops: &[ArmOp],
852 signed: bool,
853 ) -> Result<ValidationResult, VerificationError> {
854 use synth_synthesis::rules::VfpReg;
855 let bits = BV::new_const("input_0", 64);
856
857 let mut state = ArmState::new_symbolic();
858 state.set_vfp_reg(&VfpReg::D0, bits.clone());
859 self.arm_encoder
860 .encode_sequence_br(arm_ops, &mut state)
861 .map_err(VerificationError::UnsupportedOperation)?;
862 let arm_trap = state.may_trap.clone();
863
864 let wasm_trap = crate::trap::trap_trunc(
865 &bits,
866 crate::trap::FpFmt::F64,
867 crate::trap::IntTarget::I32,
868 signed,
869 );
870
871 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
872 }
873
874 pub fn verify_call_indirect_trap_preservation(
892 &self,
893 arm_op: &ArmOp,
894 spec: &CallIndirectSpec,
895 ) -> Result<ValidationResult, VerificationError> {
896 let ArmOp::CallIndirect {
897 table_size,
898 null_check,
899 type_check,
900 ..
901 } = arm_op
902 else {
903 return Err(VerificationError::UnsupportedOperation(format!(
904 "call_indirect trap gate needs the CallIndirect pseudo-op, got {arm_op:?}"
905 )));
906 };
907
908 let index = BV::new_const("input_0", 32);
909 let slot = BV::new_const("slot_ptr", 32);
910 let nonnull_slot = slot.bvor(BV::from_u64(1, 32));
911 let actual_ty = BV::new_const("slot_type_id", 32);
912
913 let build = |size: u32, may_null: bool, expected: Option<u32>| {
914 let expected_bv = expected.map(|e| BV::from_u64(e as u64, 32));
915 let size_bv = BV::from_u64(size as u64, 32);
916 let slot_term = if may_null { &slot } else { &nonnull_slot };
917 let type_trap = match &expected_bv {
918 Some(e) => crate::trap::TypeTrap::Runtime {
919 actual_type_id: &actual_ty,
920 expected_id: e,
921 },
922 None => crate::trap::TypeTrap::StaticallyDischarged,
923 };
924 crate::trap::trap_call_indirect(&crate::trap::CallIndirect {
925 index: &index,
926 table_size: &size_bv,
927 slot_ptr: slot_term,
928 type_trap,
929 })
930 };
931
932 let wasm_trap = build(
933 spec.table_size,
934 spec.may_have_null_slot,
935 spec.heterogeneous_expected_type,
936 );
937 let arm_trap = build(
938 *table_size,
939 *null_check,
940 type_check.as_ref().map(|(expected, _)| *expected),
941 );
942
943 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
944 }
945
946 fn derive_arm_state(
949 &self,
950 arm_ops: &[ArmOp],
951 inputs: &[BV],
952 vfp_s0: Option<&BV>,
953 ) -> Result<(ArmState, Bool), VerificationError> {
954 let mut state = ArmState::new_symbolic();
955 Self::seed_inputs(&mut state, inputs)?;
956 if let Some(bits) = vfp_s0 {
957 state.set_vfp_reg(&synth_synthesis::rules::VfpReg::S0, bits.clone());
958 }
959 self.arm_encoder
960 .encode_sequence_br(arm_ops, &mut state)
961 .map_err(VerificationError::UnsupportedOperation)?;
962 let trap = state.may_trap.clone();
963 Ok((state, trap))
964 }
965
966 fn seed_inputs(state: &mut ArmState, inputs: &[BV]) -> Result<(), VerificationError> {
967 for (i, input) in inputs.iter().enumerate() {
968 let reg = match i {
969 0 => Reg::R0,
970 1 => Reg::R1,
971 2 => Reg::R2,
972 _ => {
973 return Err(VerificationError::UnsupportedOperation(format!(
974 "Too many inputs: {}",
975 inputs.len()
976 )));
977 }
978 };
979 state.set_reg(®, input.clone());
980 }
981 Ok(())
982 }
983
984 fn condition_verdict(wasm_trap: &Bool, arm_trap: &Bool) -> ValidationResult {
986 Self::trap_verdict_to_result(crate::trap::prove_trap_condition_equivalence(
987 wasm_trap, arm_trap,
988 ))
989 }
990
991 fn trap_verdict_to_result(verdict: crate::trap::TrapVerdict) -> ValidationResult {
992 match verdict {
993 crate::trap::TrapVerdict::Preserved => ValidationResult::Verified,
994 crate::trap::TrapVerdict::Dropped(model) => ValidationResult::Invalid {
995 counterexample: model
996 .into_iter()
997 .filter_map(|(n, v)| i64::try_from(v).ok().map(|x| (n, x)))
998 .collect(),
999 },
1000 crate::trap::TrapVerdict::Unknown => ValidationResult::Unknown {
1001 reason: "trap-preservation VC returned Unknown (conservative reject)".to_string(),
1002 },
1003 }
1004 }
1005
1006 fn get_num_inputs(&self, wasm_op: &WasmOp) -> usize {
1008 use WasmOp::*;
1009 match wasm_op {
1010 I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or
1012 | I32Xor | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr | I32Eq | I32Ne | I32LtS
1013 | I32LtU | I32LeS | I32LeU | I32GtS | I32GtU | I32GeS | I32GeU => 2,
1014
1015 I32Clz | I32Ctz | I32Popcnt | I32Eqz => 1,
1017
1018 I32Const(_) => 0,
1020
1021 I32Load { .. } => 1, I32Store { .. } => 2, LocalGet(_) | GlobalGet(_) => 0,
1027 LocalSet(_) | GlobalSet(_) | LocalTee(_) => 1,
1028 Br(_) | BrIf(_) | Return => 0,
1029
1030 Drop => 1,
1032 Select => 3, Nop | Unreachable | Block | Loop | If | Else | End => 0,
1034
1035 _ => 0,
1037 }
1038 }
1039
1040 pub fn verify_rules(
1042 &self,
1043 rules: &[SynthesisRule],
1044 ) -> Vec<(String, Result<ValidationResult, VerificationError>)> {
1045 rules
1046 .iter()
1047 .map(|rule| {
1048 let result = self.verify_rule(rule);
1049 (rule.name.clone(), result)
1050 })
1051 .collect()
1052 }
1053}
1054
1055#[cfg(test)]
1056mod tests {
1057 use super::*;
1058 use crate::with_verification_context;
1059 use synth_synthesis::rules::Condition;
1060 use synth_synthesis::{Cost, Operand2, Pattern, Replacement};
1061
1062 fn create_test_rule(wasm_op: WasmOp, arm_op: ArmOp) -> SynthesisRule {
1063 SynthesisRule {
1064 name: format!("{:?}", wasm_op),
1065 priority: 0,
1066 pattern: Pattern::WasmInstr(wasm_op),
1067 replacement: Replacement::ArmInstr(arm_op),
1068 cost: Cost {
1069 cycles: 1,
1070 code_size: 4,
1071 registers: 2,
1072 },
1073 }
1074 }
1075
1076 #[test]
1079 fn div_lowering_without_guard_is_rejected_as_trap_drop() {
1080 with_verification_context(|| {
1081 let validator = TranslationValidator::new();
1082 let arm_ops = [ArmOp::Udiv {
1085 rd: Reg::R0,
1086 rn: Reg::R0,
1087 rm: Reg::R1,
1088 }];
1089 let result = validator
1090 .verify_div_rem_trap_preservation(&WasmOp::I32DivU, &arm_ops)
1091 .unwrap();
1092 match result {
1093 ValidationResult::Invalid { counterexample } => {
1094 let divisor = counterexample.iter().find(|(n, _)| n == "input_1");
1096 assert_eq!(
1097 divisor.map(|(_, v)| *v),
1098 Some(0),
1099 "trap-drop counterexample must set the divisor to 0"
1100 );
1101 }
1102 other => panic!("unguarded div must be Invalid, got {other:?}"),
1103 }
1104 });
1105 }
1106
1107 fn shipped_divu_guard() -> Vec<ArmOp> {
1111 vec![
1112 ArmOp::Cmp {
1113 rn: Reg::R1,
1114 op2: Operand2::Imm(0),
1115 },
1116 ArmOp::BCondOffset {
1117 cond: Condition::NE,
1118 offset: 0,
1119 },
1120 ArmOp::Udf { imm: 0 },
1121 ArmOp::Udiv {
1122 rd: Reg::R0,
1123 rn: Reg::R0,
1124 rm: Reg::R1,
1125 },
1126 ]
1127 }
1128
1129 fn shipped_divs_double_guard() -> Vec<ArmOp> {
1133 vec![
1134 ArmOp::Cmp {
1135 rn: Reg::R1,
1136 op2: Operand2::Imm(0),
1137 },
1138 ArmOp::BCondOffset {
1139 cond: Condition::NE,
1140 offset: 0,
1141 },
1142 ArmOp::Udf { imm: 0 },
1143 ArmOp::Movw {
1144 rd: Reg::R12,
1145 imm16: 0,
1146 },
1147 ArmOp::Movt {
1148 rd: Reg::R12,
1149 imm16: 0x8000,
1150 },
1151 ArmOp::Cmp {
1152 rn: Reg::R0,
1153 op2: Operand2::Reg(Reg::R12),
1154 },
1155 ArmOp::BCondOffset {
1156 cond: Condition::NE,
1157 offset: 3,
1158 },
1159 ArmOp::Cmn {
1160 rn: Reg::R1,
1161 op2: Operand2::Imm(1),
1162 },
1163 ArmOp::BCondOffset {
1164 cond: Condition::NE,
1165 offset: 0,
1166 },
1167 ArmOp::Udf { imm: 1 },
1168 ArmOp::Sdiv {
1169 rd: Reg::R0,
1170 rn: Reg::R0,
1171 rm: Reg::R1,
1172 },
1173 ]
1174 }
1175
1176 #[test]
1177 fn div_lowering_with_guard_preserves_the_trap() {
1178 with_verification_context(|| {
1179 let validator = TranslationValidator::new();
1180 let result = validator
1181 .verify_div_rem_trap_preservation(&WasmOp::I32DivU, &shipped_divu_guard())
1182 .unwrap();
1183 assert_eq!(result, ValidationResult::Verified);
1184 });
1185 }
1186
1187 #[test]
1193 fn div_guard_with_inverted_polarity_is_rejected() {
1194 with_verification_context(|| {
1195 let validator = TranslationValidator::new();
1196 let mut arm_ops = shipped_divu_guard();
1197 arm_ops[1] = ArmOp::BCondOffset {
1198 cond: Condition::EQ,
1199 offset: 0,
1200 };
1201 let result = validator
1202 .verify_div_rem_trap_preservation(&WasmOp::I32DivU, &arm_ops)
1203 .unwrap();
1204 assert!(
1205 matches!(result, ValidationResult::Invalid { .. }),
1206 "inverted guard polarity must be Invalid, got {result:?}"
1207 );
1208 });
1209 }
1210
1211 #[test]
1212 fn signed_div_double_guard_preserves_both_zero_and_overflow_traps() {
1213 with_verification_context(|| {
1214 let validator = TranslationValidator::new();
1215 let result = validator
1216 .verify_div_rem_trap_preservation(&WasmOp::I32DivS, &shipped_divs_double_guard())
1217 .unwrap();
1218 assert_eq!(result, ValidationResult::Verified);
1219 });
1220 }
1221
1222 #[test]
1228 fn signed_div_with_overflow_guard_stripped_is_rejected() {
1229 with_verification_context(|| {
1230 let validator = TranslationValidator::new();
1231 let arm_ops = [
1232 ArmOp::Cmp {
1233 rn: Reg::R1,
1234 op2: Operand2::Imm(0),
1235 },
1236 ArmOp::BCondOffset {
1237 cond: Condition::NE,
1238 offset: 0,
1239 },
1240 ArmOp::Udf { imm: 0 },
1241 ArmOp::Sdiv {
1242 rd: Reg::R0,
1243 rn: Reg::R0,
1244 rm: Reg::R1,
1245 },
1246 ];
1247 let result = validator
1248 .verify_div_rem_trap_preservation(&WasmOp::I32DivS, &arm_ops)
1249 .unwrap();
1250 match result {
1251 ValidationResult::Invalid { counterexample } => {
1252 let get = |n: &str| {
1253 counterexample
1254 .iter()
1255 .find(|(name, _)| name == n)
1256 .map(|(_, v)| *v)
1257 };
1258 assert_eq!(
1259 get("input_0"),
1260 Some(i32::MIN as u32 as i64),
1261 "dropped overflow trap must exhibit dividend INT_MIN: {counterexample:?}"
1262 );
1263 assert_eq!(
1264 get("input_1"),
1265 Some(u32::MAX as i64),
1266 "dropped overflow trap must exhibit divisor -1: {counterexample:?}"
1267 );
1268 }
1269 other => panic!("overflow-guard-stripped div_s must be Invalid, got {other:?}"),
1270 }
1271 });
1272 }
1273
1274 #[test]
1277 fn rems_single_zero_guard_is_exactly_right() {
1278 with_verification_context(|| {
1279 let validator = TranslationValidator::new();
1280 let arm_ops = [
1281 ArmOp::Cmp {
1282 rn: Reg::R1,
1283 op2: Operand2::Imm(0),
1284 },
1285 ArmOp::BCondOffset {
1286 cond: Condition::NE,
1287 offset: 0,
1288 },
1289 ArmOp::Udf { imm: 0 },
1290 ArmOp::Sdiv {
1291 rd: Reg::R2,
1292 rn: Reg::R0,
1293 rm: Reg::R1,
1294 },
1295 ArmOp::Mls {
1296 rd: Reg::R0,
1297 rn: Reg::R2,
1298 rm: Reg::R1,
1299 ra: Reg::R0,
1300 },
1301 ];
1302 let result = validator
1303 .verify_div_rem_trap_preservation(&WasmOp::I32RemS, &arm_ops)
1304 .unwrap();
1305 assert_eq!(result, ValidationResult::Verified);
1306 });
1307 }
1308
1309 #[test]
1312 fn unreachable_udf_lowering_preserves_the_trap() {
1313 with_verification_context(|| {
1314 let validator = TranslationValidator::new();
1315 let result = validator
1316 .verify_trap_preservation(&WasmOp::Unreachable, &[ArmOp::Udf { imm: 0 }])
1317 .unwrap();
1318 assert_eq!(result, ValidationResult::Verified);
1319 });
1320 }
1321
1322 #[test]
1323 fn unreachable_lowered_to_nop_is_rejected() {
1324 with_verification_context(|| {
1325 let validator = TranslationValidator::new();
1326 let result = validator
1328 .verify_trap_preservation(&WasmOp::Unreachable, &[ArmOp::Nop])
1329 .unwrap();
1330 assert!(
1331 matches!(result, ValidationResult::Invalid { .. }),
1332 "trap-dropping unreachable lowering must be Invalid, got {result:?}"
1333 );
1334 });
1335 }
1336
1337 fn shipped_software_bounds_ops(wasm_op: &WasmOp) -> Vec<ArmOp> {
1350 use synth_synthesis::instruction_selector::InstructionSelector;
1351 use synth_synthesis::rules::MemAddr;
1352 let (offset, size) = match wasm_op {
1353 WasmOp::I32Load { offset, .. } | WasmOp::I32Store { offset, .. } => (*offset, 4u32),
1354 WasmOp::I32Load16S { offset, .. }
1355 | WasmOp::I32Load16U { offset, .. }
1356 | WasmOp::I32Store16 { offset, .. } => (*offset, 2),
1357 WasmOp::I32Load8S { offset, .. }
1358 | WasmOp::I32Load8U { offset, .. }
1359 | WasmOp::I32Store8 { offset, .. } => (*offset, 1),
1360 other => panic!("not a guarded i32 access: {other:?}"),
1361 };
1362 let addr = MemAddr::reg_imm(Reg::R11, Reg::R0, offset as i32);
1363 let access = match wasm_op {
1364 WasmOp::I32Load { .. } => ArmOp::Ldr { rd: Reg::R0, addr },
1365 WasmOp::I32Load8S { .. } => ArmOp::Ldrsb { rd: Reg::R0, addr },
1366 WasmOp::I32Load8U { .. } => ArmOp::Ldrb { rd: Reg::R0, addr },
1367 WasmOp::I32Load16S { .. } => ArmOp::Ldrsh { rd: Reg::R0, addr },
1368 WasmOp::I32Load16U { .. } => ArmOp::Ldrh { rd: Reg::R0, addr },
1369 WasmOp::I32Store { .. } => ArmOp::Str { rd: Reg::R1, addr },
1370 WasmOp::I32Store8 { .. } => ArmOp::Strb { rd: Reg::R1, addr },
1371 WasmOp::I32Store16 { .. } => ArmOp::Strh { rd: Reg::R1, addr },
1372 other => panic!("not a guarded i32 access: {other:?}"),
1373 };
1374 let mut ops = InstructionSelector::software_bounds_guard(Reg::R0, offset as i32, size);
1375 ops.push(access);
1376 ops
1377 }
1378
1379 #[test]
1382 fn load_without_bounds_guard_is_rejected() {
1383 use synth_synthesis::rules::MemAddr;
1384 with_verification_context(|| {
1385 let validator = TranslationValidator::new();
1386 let arm_ops = [ArmOp::Ldr {
1387 rd: Reg::R0,
1388 addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 0),
1389 }];
1390 let result = validator
1391 .verify_trap_preservation(
1392 &WasmOp::I32Load {
1393 offset: 0,
1394 align: 2,
1395 },
1396 &arm_ops,
1397 )
1398 .unwrap();
1399 assert!(
1400 matches!(result, ValidationResult::Invalid { .. }),
1401 "guard-stripped load must be Invalid, got {result:?}"
1402 );
1403 });
1404 }
1405
1406 #[test]
1410 fn byte_load_software_bounds_guard_preserves_the_trap() {
1411 with_verification_context(|| {
1412 let validator = TranslationValidator::new();
1413 let result = validator
1414 .verify_trap_preservation(
1415 &WasmOp::I32Load8U {
1416 offset: 0,
1417 align: 0,
1418 },
1419 &shipped_software_bounds_ops(&WasmOp::I32Load8U {
1420 offset: 0,
1421 align: 0,
1422 }),
1423 )
1424 .unwrap();
1425 assert_eq!(result, ValidationResult::Verified);
1426 });
1427 }
1428
1429 #[test]
1440 fn word_load_software_bounds_guard_survives_the_address_top_752() {
1441 with_verification_context(|| {
1442 let validator = TranslationValidator::new();
1443 let result = validator
1444 .verify_trap_preservation(
1445 &WasmOp::I32Load {
1446 offset: 0,
1447 align: 2,
1448 },
1449 &shipped_software_bounds_ops(&WasmOp::I32Load {
1450 offset: 0,
1451 align: 2,
1452 }),
1453 )
1454 .unwrap();
1455 assert_eq!(
1456 result,
1457 ValidationResult::Verified,
1458 "the #752 wraparound divergence must be closed for every addr"
1459 );
1460 });
1461 }
1462
1463 #[test]
1467 fn all_load_widths_software_bounds_guard_verify_752() {
1468 let cases: Vec<WasmOp> = vec![
1469 WasmOp::I32Load {
1470 offset: 4,
1471 align: 2,
1472 },
1473 WasmOp::I32Load8S {
1474 offset: 3,
1475 align: 0,
1476 },
1477 WasmOp::I32Load8U {
1478 offset: 1,
1479 align: 0,
1480 },
1481 WasmOp::I32Load16S {
1482 offset: 2,
1483 align: 1,
1484 },
1485 WasmOp::I32Load16U {
1486 offset: 0,
1487 align: 1,
1488 },
1489 ];
1490 with_verification_context(|| {
1491 let validator = TranslationValidator::new();
1492 for wasm_op in &cases {
1493 let result = validator
1494 .verify_trap_preservation(wasm_op, &shipped_software_bounds_ops(wasm_op))
1495 .unwrap();
1496 assert_eq!(result, ValidationResult::Verified, "{wasm_op:?}");
1497 }
1498 });
1499 }
1500
1501 #[test]
1504 fn store_software_bounds_guard_verifies_752() {
1505 let cases: Vec<WasmOp> = vec![
1506 WasmOp::I32Store {
1507 offset: 0,
1508 align: 2,
1509 },
1510 WasmOp::I32Store8 {
1511 offset: 5,
1512 align: 0,
1513 },
1514 WasmOp::I32Store16 {
1515 offset: 3,
1516 align: 1,
1517 },
1518 ];
1519 with_verification_context(|| {
1520 let validator = TranslationValidator::new();
1521 for wasm_op in &cases {
1522 let result = validator
1523 .verify_trap_preservation(wasm_op, &shipped_software_bounds_ops(wasm_op))
1524 .unwrap();
1525 assert_eq!(result, ValidationResult::Verified, "{wasm_op:?}");
1526 }
1527 });
1528 }
1529
1530 #[test]
1534 fn large_offset_software_bounds_guard_verifies_752() {
1535 let wasm_op = WasmOp::I32Load {
1536 offset: 0x2000,
1537 align: 2,
1538 };
1539 with_verification_context(|| {
1540 let validator = TranslationValidator::new();
1541 let result = validator
1542 .verify_trap_preservation(&wasm_op, &shipped_software_bounds_ops(&wasm_op))
1543 .unwrap();
1544 assert_eq!(result, ValidationResult::Verified);
1545 });
1546 }
1547
1548 #[test]
1552 fn offset_overflow_software_bounds_guard_always_traps_752() {
1553 let wasm_op = WasmOp::I32Load {
1554 offset: u32::MAX,
1555 align: 2,
1556 };
1557 with_verification_context(|| {
1558 let validator = TranslationValidator::new();
1559 let result = validator
1560 .verify_trap_preservation(&wasm_op, &shipped_software_bounds_ops(&wasm_op))
1561 .unwrap();
1562 assert_eq!(result, ValidationResult::Verified);
1563 });
1564 }
1565
1566 #[test]
1571 fn retired_add_computed_guard_stays_invalid_at_the_address_top_752() {
1572 use synth_synthesis::rules::{Condition, MemAddr, Operand2};
1573 with_verification_context(|| {
1574 let validator = TranslationValidator::new();
1575 let arm_ops = [
1576 ArmOp::Add {
1577 rd: Reg::R12,
1578 rn: Reg::R0,
1579 op2: Operand2::Imm(3), },
1581 ArmOp::Cmp {
1582 rn: Reg::R12,
1583 op2: Operand2::Reg(Reg::R10),
1584 },
1585 ArmOp::BCondOffset {
1586 cond: Condition::LO,
1587 offset: 0,
1588 },
1589 ArmOp::Udf { imm: 0 },
1590 ArmOp::Ldr {
1591 rd: Reg::R0,
1592 addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 0),
1593 },
1594 ];
1595 let result = validator
1596 .verify_trap_preservation(
1597 &WasmOp::I32Load {
1598 offset: 0,
1599 align: 2,
1600 },
1601 &arm_ops,
1602 )
1603 .unwrap();
1604 match result {
1605 ValidationResult::Invalid { counterexample } => {
1606 let addr = counterexample
1607 .iter()
1608 .find(|(n, _)| n == "input_0")
1609 .map(|(_, v)| *v)
1610 .expect("counterexample must assign the address");
1611 assert!(
1612 addr >= 0xFFFF_FFFD,
1613 "the divergence is the 32-bit end-address wrap at the top \
1614 of the address space, got addr {addr:#x}"
1615 );
1616 }
1617 other => panic!("the retired wrapping guard must stay Invalid, got {other:?}"),
1618 }
1619 });
1620 }
1621
1622 #[test]
1627 fn wraparound_safe_bounds_guard_verifies() {
1628 use synth_synthesis::rules::MemAddr;
1629 with_verification_context(|| {
1630 let validator = TranslationValidator::new();
1631 let k = 4; let arm_ops = [
1633 ArmOp::Cmp {
1635 rn: Reg::R10,
1636 op2: Operand2::Imm(k),
1637 },
1638 ArmOp::BCondOffset {
1639 cond: Condition::HS,
1640 offset: 0,
1641 },
1642 ArmOp::Udf { imm: 0 },
1643 ArmOp::Sub {
1646 rd: Reg::R12,
1647 rn: Reg::R10,
1648 op2: Operand2::Imm(k),
1649 },
1650 ArmOp::Cmp {
1651 rn: Reg::R0,
1652 op2: Operand2::Reg(Reg::R12),
1653 },
1654 ArmOp::BCondOffset {
1655 cond: Condition::LS,
1656 offset: 0,
1657 },
1658 ArmOp::Udf { imm: 0 },
1659 ArmOp::Ldr {
1660 rd: Reg::R0,
1661 addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 0),
1662 },
1663 ];
1664 let result = validator
1665 .verify_trap_preservation(
1666 &WasmOp::I32Load {
1667 offset: 0,
1668 align: 2,
1669 },
1670 &arm_ops,
1671 )
1672 .unwrap();
1673 assert_eq!(result, ValidationResult::Verified);
1674 });
1675 }
1676
1677 fn shipped_trunc_f32_guard(signed: bool) -> Vec<ArmOp> {
1684 use synth_synthesis::rules::VfpReg;
1685 let (hi, lo) = if signed {
1686 (2147483648.0_f32, -2147483648.0_f32)
1687 } else {
1688 (4294967296.0_f32, -1.0_f32)
1689 };
1690 let mut ops = Vec::new();
1691 let guard = |ops: &mut Vec<ArmOp>, bound: f32, upper: bool| {
1692 ops.push(ArmOp::F32Const {
1693 sd: VfpReg::S1,
1694 value: bound,
1695 });
1696 let cmp = if upper {
1697 ArmOp::F32Lt {
1698 rd: Reg::R0,
1699 sn: VfpReg::S0,
1700 sm: VfpReg::S1,
1701 }
1702 } else if signed {
1703 ArmOp::F32Ge {
1704 rd: Reg::R0,
1705 sn: VfpReg::S0,
1706 sm: VfpReg::S1,
1707 }
1708 } else {
1709 ArmOp::F32Gt {
1710 rd: Reg::R0,
1711 sn: VfpReg::S0,
1712 sm: VfpReg::S1,
1713 }
1714 };
1715 ops.push(cmp);
1716 ops.push(ArmOp::Cmp {
1717 rn: Reg::R0,
1718 op2: Operand2::Imm(0),
1719 });
1720 ops.push(ArmOp::BCondOffset {
1721 cond: Condition::NE,
1722 offset: 0,
1723 });
1724 ops.push(ArmOp::Udf { imm: 0 });
1725 };
1726 guard(&mut ops, hi, true);
1727 guard(&mut ops, lo, false);
1728 if signed {
1729 ops.push(ArmOp::I32TruncF32S {
1730 rd: Reg::R0,
1731 sm: VfpReg::S0,
1732 });
1733 } else {
1734 ops.push(ArmOp::I32TruncF32U {
1735 rd: Reg::R0,
1736 sm: VfpReg::S0,
1737 });
1738 }
1739 ops
1740 }
1741
1742 #[test]
1743 fn trunc_f32_s_domain_guard_preserves_the_trap() {
1744 with_verification_context(|| {
1745 let validator = TranslationValidator::new();
1746 let result = validator
1747 .verify_trap_preservation(&WasmOp::I32TruncF32S, &shipped_trunc_f32_guard(true))
1748 .unwrap();
1749 assert_eq!(result, ValidationResult::Verified);
1750 });
1751 }
1752
1753 #[test]
1754 fn trunc_f32_u_domain_guard_preserves_the_trap() {
1755 with_verification_context(|| {
1756 let validator = TranslationValidator::new();
1757 let result = validator
1758 .verify_trap_preservation(&WasmOp::I32TruncF32U, &shipped_trunc_f32_guard(false))
1759 .unwrap();
1760 assert_eq!(result, ValidationResult::Verified);
1761 });
1762 }
1763
1764 #[test]
1767 fn trunc_f32_without_domain_guard_is_rejected() {
1768 use synth_synthesis::rules::VfpReg;
1769 with_verification_context(|| {
1770 let validator = TranslationValidator::new();
1771 let arm_ops = [ArmOp::I32TruncF32S {
1772 rd: Reg::R0,
1773 sm: VfpReg::S0,
1774 }];
1775 let result = validator
1776 .verify_trap_preservation(&WasmOp::I32TruncF32S, &arm_ops)
1777 .unwrap();
1778 assert!(
1779 matches!(result, ValidationResult::Invalid { .. }),
1780 "guard-stripped trunc must be Invalid, got {result:?}"
1781 );
1782 });
1783 }
1784
1785 #[test]
1787 fn trunc_f32_with_only_upper_guard_is_rejected() {
1788 with_verification_context(|| {
1789 let validator = TranslationValidator::new();
1790 let mut arm_ops = shipped_trunc_f32_guard(true);
1791 arm_ops.drain(5..10);
1793 let result = validator
1794 .verify_trap_preservation(&WasmOp::I32TruncF32S, &arm_ops)
1795 .unwrap();
1796 assert!(
1797 matches!(result, ValidationResult::Invalid { .. }),
1798 "upper-only trunc guard must be Invalid, got {result:?}"
1799 );
1800 });
1801 }
1802
1803 fn shipped_trunc_f64_guard(signed: bool) -> Vec<ArmOp> {
1812 use synth_synthesis::rules::VfpReg;
1813 let (hi, lo) = if signed {
1814 (2147483648.0_f64, -2147483649.0_f64) } else {
1816 (4294967296.0_f64, -1.0_f64) };
1818 let mut ops = Vec::new();
1819 let guard = |ops: &mut Vec<ArmOp>, bound: f64, upper: bool| {
1820 ops.push(ArmOp::F64Const {
1821 dd: VfpReg::D1,
1822 value: bound,
1823 });
1824 let cmp = if upper {
1825 ArmOp::F64Lt {
1826 rd: Reg::R0,
1827 dn: VfpReg::D0,
1828 dm: VfpReg::D1,
1829 }
1830 } else {
1831 ArmOp::F64Gt {
1832 rd: Reg::R0,
1833 dn: VfpReg::D0,
1834 dm: VfpReg::D1,
1835 }
1836 };
1837 ops.push(cmp);
1838 ops.push(ArmOp::Cmp {
1839 rn: Reg::R0,
1840 op2: Operand2::Imm(0),
1841 });
1842 ops.push(ArmOp::BCondOffset {
1843 cond: Condition::NE,
1844 offset: 0,
1845 });
1846 ops.push(ArmOp::Udf { imm: 0 });
1847 };
1848 guard(&mut ops, hi, true); guard(&mut ops, lo, false); if signed {
1851 ops.push(ArmOp::I32TruncF64S {
1852 rd: Reg::R0,
1853 dm: VfpReg::D0,
1854 });
1855 } else {
1856 ops.push(ArmOp::I32TruncF64U {
1857 rd: Reg::R0,
1858 dm: VfpReg::D0,
1859 });
1860 }
1861 ops
1862 }
1863
1864 #[test]
1865 fn trunc_f64_s_domain_guard_preserves_the_trap() {
1866 with_verification_context(|| {
1867 let validator = TranslationValidator::new();
1868 let result = validator
1869 .verify_trap_preservation(&WasmOp::I32TruncF64S, &shipped_trunc_f64_guard(true))
1870 .unwrap();
1871 assert_eq!(
1872 result,
1873 ValidationResult::Verified,
1874 "GREEN: correct f64→i32_s domain guard must be Verified (Unsat)"
1875 );
1876 });
1877 }
1878
1879 #[test]
1880 fn trunc_f64_u_domain_guard_preserves_the_trap() {
1881 with_verification_context(|| {
1882 let validator = TranslationValidator::new();
1883 let result = validator
1884 .verify_trap_preservation(&WasmOp::I32TruncF64U, &shipped_trunc_f64_guard(false))
1885 .unwrap();
1886 assert_eq!(
1887 result,
1888 ValidationResult::Verified,
1889 "GREEN: correct f64→i32_u domain guard must be Verified (Unsat)"
1890 );
1891 });
1892 }
1893
1894 #[test]
1897 fn trunc_f64_without_domain_guard_is_rejected() {
1898 use synth_synthesis::rules::VfpReg;
1899 with_verification_context(|| {
1900 let validator = TranslationValidator::new();
1901 let arm_ops = [ArmOp::I32TruncF64S {
1902 rd: Reg::R0,
1903 dm: VfpReg::D0,
1904 }];
1905 let result = validator
1906 .verify_trap_preservation(&WasmOp::I32TruncF64S, &arm_ops)
1907 .unwrap();
1908 assert!(
1909 matches!(result, ValidationResult::Invalid { .. }),
1910 "RED: guard-stripped f64 trunc must be Invalid (Sat), got {result:?}"
1911 );
1912 });
1913 }
1914
1915 #[test]
1918 fn trunc_f64_with_only_upper_guard_is_rejected() {
1919 with_verification_context(|| {
1920 let validator = TranslationValidator::new();
1921 let mut arm_ops = shipped_trunc_f64_guard(true);
1922 arm_ops.drain(5..10);
1924 let result = validator
1925 .verify_trap_preservation(&WasmOp::I32TruncF64S, &arm_ops)
1926 .unwrap();
1927 assert!(
1928 matches!(result, ValidationResult::Invalid { .. }),
1929 "RED: upper-only f64 trunc guard must be Invalid (Sat), got {result:?}"
1930 );
1931 });
1932 }
1933
1934 fn shipped_i64_div_rem(op: &WasmOp, elide_zero: bool, elide_overflow: bool) -> Vec<ArmOp> {
1941 let arm = match op {
1942 WasmOp::I64DivS => ArmOp::I64DivS {
1943 rdlo: Reg::R0,
1944 rdhi: Reg::R1,
1945 rnlo: Reg::R0,
1946 rnhi: Reg::R1,
1947 rmlo: Reg::R2,
1948 rmhi: Reg::R3,
1949 elide_zero_guard: elide_zero,
1950 elide_overflow_guard: elide_overflow,
1951 },
1952 WasmOp::I64DivU => ArmOp::I64DivU {
1953 rdlo: Reg::R0,
1954 rdhi: Reg::R1,
1955 rnlo: Reg::R0,
1956 rnhi: Reg::R1,
1957 rmlo: Reg::R2,
1958 rmhi: Reg::R3,
1959 elide_zero_guard: elide_zero,
1960 },
1961 WasmOp::I64RemS => ArmOp::I64RemS {
1962 rdlo: Reg::R0,
1963 rdhi: Reg::R1,
1964 rnlo: Reg::R0,
1965 rnhi: Reg::R1,
1966 rmlo: Reg::R2,
1967 rmhi: Reg::R3,
1968 elide_zero_guard: elide_zero,
1969 },
1970 WasmOp::I64RemU => ArmOp::I64RemU {
1971 rdlo: Reg::R0,
1972 rdhi: Reg::R1,
1973 rnlo: Reg::R0,
1974 rnhi: Reg::R1,
1975 rmlo: Reg::R2,
1976 rmhi: Reg::R3,
1977 elide_zero_guard: elide_zero,
1978 },
1979 _ => unreachable!("shipped_i64_div_rem: not an i64 div/rem op"),
1980 };
1981 vec![arm]
1982 }
1983
1984 #[test]
1985 fn i64_div_rem_all_four_full_guards_preserve_the_trap() {
1986 with_verification_context(|| {
1987 let validator = TranslationValidator::new();
1988 for op in [
1989 WasmOp::I64DivU,
1990 WasmOp::I64DivS,
1991 WasmOp::I64RemU,
1992 WasmOp::I64RemS,
1993 ] {
1994 let arm_ops = shipped_i64_div_rem(&op, false, false);
1995 let result = validator.verify_trap_preservation(&op, &arm_ops).unwrap();
1996 assert_eq!(
1997 result,
1998 ValidationResult::Verified,
1999 "GREEN: {op:?} with full guards must be Verified (Unsat)"
2000 );
2001 }
2002 });
2003 }
2004
2005 #[test]
2009 fn i64_div_rem_dropped_zero_guard_is_rejected() {
2010 with_verification_context(|| {
2011 let validator = TranslationValidator::new();
2012 for op in [
2013 WasmOp::I64DivU,
2014 WasmOp::I64DivS,
2015 WasmOp::I64RemU,
2016 WasmOp::I64RemS,
2017 ] {
2018 let arm_ops = shipped_i64_div_rem(&op, true, false);
2019 let result = validator.verify_trap_preservation(&op, &arm_ops).unwrap();
2020 assert!(
2021 matches!(result, ValidationResult::Invalid { .. }),
2022 "RED: {op:?} with the ÷0 guard dropped must be Invalid (Sat), got {result:?}"
2023 );
2024 }
2025 });
2026 }
2027
2028 #[test]
2031 fn i64_div_s_dropped_overflow_guard_is_rejected() {
2032 with_verification_context(|| {
2033 let validator = TranslationValidator::new();
2034 let arm_ops = shipped_i64_div_rem(&WasmOp::I64DivS, false, true);
2035 let result = validator
2036 .verify_trap_preservation(&WasmOp::I64DivS, &arm_ops)
2037 .unwrap();
2038 assert!(
2039 matches!(result, ValidationResult::Invalid { .. }),
2040 "RED: i64.div_s with the overflow guard dropped must be Invalid (Sat), got {result:?}"
2041 );
2042 });
2043 }
2044
2045 #[test]
2047 fn i64_div_s_dropped_both_guards_is_rejected() {
2048 with_verification_context(|| {
2049 let validator = TranslationValidator::new();
2050 let arm_ops = shipped_i64_div_rem(&WasmOp::I64DivS, true, true);
2051 let result = validator
2052 .verify_trap_preservation(&WasmOp::I64DivS, &arm_ops)
2053 .unwrap();
2054 assert!(
2055 matches!(result, ValidationResult::Invalid { .. }),
2056 "RED: i64.div_s with both guards dropped must be Invalid (Sat), got {result:?}"
2057 );
2058 });
2059 }
2060
2061 #[test]
2065 fn i64_div_s_overflow_field_is_load_bearing() {
2066 with_verification_context(|| {
2067 let validator = TranslationValidator::new();
2068 let full = validator
2069 .verify_trap_preservation(
2070 &WasmOp::I64DivS,
2071 &shipped_i64_div_rem(&WasmOp::I64DivS, false, false),
2072 )
2073 .unwrap();
2074 let overflow_dropped = validator
2075 .verify_trap_preservation(
2076 &WasmOp::I64DivS,
2077 &shipped_i64_div_rem(&WasmOp::I64DivS, false, true),
2078 )
2079 .unwrap();
2080 assert_eq!(full, ValidationResult::Verified);
2081 assert!(matches!(overflow_dropped, ValidationResult::Invalid { .. }));
2082 assert_ne!(
2083 full, overflow_dropped,
2084 "non-vacuity: the overflow-guard field must change the verdict"
2085 );
2086 });
2087 }
2088
2089 #[test]
2095 fn dump_756_non_vacuity_verdicts() {
2096 with_verification_context(|| {
2097 let validator = TranslationValidator::new();
2098 let raw = |r: &ValidationResult| match r {
2099 ValidationResult::Verified => "Verified/Unsat (trap PRESERVED)",
2100 ValidationResult::Invalid { .. } => "Invalid/Sat (trap DROPPED — caught)",
2101 ValidationResult::Unknown { .. } => "Unknown",
2102 };
2103 println!("\n=== #756 live trap-preservation non-vacuity ===");
2104 for (op, oflow_field) in [
2105 (WasmOp::I64DivU, false),
2106 (WasmOp::I64DivS, true),
2107 (WasmOp::I64RemU, false),
2108 (WasmOp::I64RemS, false),
2109 ] {
2110 let green = validator
2111 .verify_trap_preservation(&op, &shipped_i64_div_rem(&op, false, false))
2112 .unwrap();
2113 let red = validator
2114 .verify_trap_preservation(&op, &shipped_i64_div_rem(&op, true, false))
2115 .unwrap();
2116 println!(
2117 " {op:?}: full-guards -> {} | drop-÷0 -> {}",
2118 raw(&green),
2119 raw(&red)
2120 );
2121 assert_ne!(
2122 green, red,
2123 "{op:?}: green and red must differ (non-vacuous)"
2124 );
2125 if oflow_field {
2126 let red_o = validator
2127 .verify_trap_preservation(&op, &shipped_i64_div_rem(&op, false, true))
2128 .unwrap();
2129 println!(" {op:?}: drop-overflow -> {}", raw(&red_o));
2130 assert_ne!(green, red_o);
2131 }
2132 }
2133 for (op, sgn) in [(WasmOp::I32TruncF64S, true), (WasmOp::I32TruncF64U, false)] {
2134 let green = validator
2135 .verify_trap_preservation(&op, &shipped_trunc_f64_guard(sgn))
2136 .unwrap();
2137 let bare = if sgn {
2138 vec![ArmOp::I32TruncF64S {
2139 rd: Reg::R0,
2140 dm: synth_synthesis::rules::VfpReg::D0,
2141 }]
2142 } else {
2143 vec![ArmOp::I32TruncF64U {
2144 rd: Reg::R0,
2145 dm: synth_synthesis::rules::VfpReg::D0,
2146 }]
2147 };
2148 let red = validator.verify_trap_preservation(&op, &bare).unwrap();
2149 println!(
2150 " {op:?}: domain-guard -> {} | bare-VCVT -> {}",
2151 raw(&green),
2152 raw(&red)
2153 );
2154 assert_ne!(
2155 green, red,
2156 "{op:?}: green and red must differ (non-vacuous)"
2157 );
2158 }
2159 println!("=== all rows discriminate: gate is non-vacuous ===\n");
2160 });
2161 }
2162
2163 fn i64_rem_with_dest(op: &WasmOp, rdlo: Reg, rdhi: Reg) -> Vec<ArmOp> {
2175 let arm = match op {
2176 WasmOp::I64RemU => ArmOp::I64RemU {
2177 rdlo,
2178 rdhi,
2179 rnlo: Reg::R0,
2180 rnhi: Reg::R1,
2181 rmlo: Reg::R2,
2182 rmhi: Reg::R3,
2183 elide_zero_guard: false,
2184 },
2185 WasmOp::I64RemS => ArmOp::I64RemS {
2186 rdlo,
2187 rdhi,
2188 rnlo: Reg::R0,
2189 rnhi: Reg::R1,
2190 rmlo: Reg::R2,
2191 rmhi: Reg::R3,
2192 elide_zero_guard: false,
2193 },
2194 _ => unreachable!("i64_rem_with_dest: not an i64 rem op"),
2195 };
2196 vec![arm]
2197 }
2198
2199 #[test]
2203 fn i64_rem_shipped_value_is_verified() {
2204 with_verification_context(|| {
2205 let validator = TranslationValidator::new();
2206 for op in [WasmOp::I64RemU, WasmOp::I64RemS] {
2207 let arm_ops = shipped_i64_div_rem(&op, false, false);
2208 let result = validator.verify_trap_preservation(&op, &arm_ops).unwrap();
2209 assert_eq!(
2210 result,
2211 ValidationResult::Verified,
2212 "GREEN: {op:?} shipped lowering (correct value + ÷0 guard) must be Verified"
2213 );
2214 }
2215 });
2216 }
2217
2218 #[test]
2226 fn i64_rem_wrong_destination_register_is_rejected() {
2227 with_verification_context(|| {
2228 let validator = TranslationValidator::new();
2229 for op in [WasmOp::I64RemU, WasmOp::I64RemS] {
2230 let arm_ops = i64_rem_with_dest(&op, Reg::R2, Reg::R3);
2231 let result = validator.verify_trap_preservation(&op, &arm_ops).unwrap();
2232 assert!(
2233 matches!(result, ValidationResult::Invalid { .. }),
2234 "RED: {op:?} writing the remainder to R2:R3 (not the ABI R0:R1) \
2235 leaves R0:R1 non-remainder — must be Invalid, got {result:?}"
2236 );
2237 }
2238 });
2239 }
2240
2241 #[test]
2254 fn i64_rem_wrong_signedness_is_rejected() {
2255 with_verification_context(|| {
2256 let validator = TranslationValidator::new();
2257 for (spec, lowered) in [
2259 (WasmOp::I64RemU, WasmOp::I64RemS),
2260 (WasmOp::I64RemS, WasmOp::I64RemU),
2261 ] {
2262 let arm_ops = i64_rem_with_dest(&lowered, Reg::R0, Reg::R1);
2263 let result = validator.verify_trap_preservation(&spec, &arm_ops).unwrap();
2264 assert!(
2265 matches!(result, ValidationResult::Invalid { .. }),
2266 "RED: {spec:?} lowered as {lowered:?} computes the wrong remainder \
2267 into the right registers — must be Invalid, got {result:?}"
2268 );
2269 }
2270 });
2271 }
2272
2273 #[test]
2278 fn i64_rem_destination_field_is_load_bearing() {
2279 with_verification_context(|| {
2280 let validator = TranslationValidator::new();
2281 for op in [WasmOp::I64RemU, WasmOp::I64RemS] {
2282 let correct = validator
2283 .verify_trap_preservation(&op, &i64_rem_with_dest(&op, Reg::R0, Reg::R1))
2284 .unwrap();
2285 let wrong = validator
2286 .verify_trap_preservation(&op, &i64_rem_with_dest(&op, Reg::R2, Reg::R3))
2287 .unwrap();
2288 assert_eq!(correct, ValidationResult::Verified, "{op:?} correct dest");
2289 assert!(
2290 matches!(wrong, ValidationResult::Invalid { .. }),
2291 "{op:?} wrong dest"
2292 );
2293 assert_ne!(
2294 correct, wrong,
2295 "non-vacuity: {op:?} destination register must change the verdict"
2296 );
2297 }
2298 });
2299 }
2300
2301 #[test]
2309 fn i64_rem_value_model_closes_a_trap_only_gap() {
2310 with_verification_context(|| {
2311 let validator = TranslationValidator::new();
2312 for op in [WasmOp::I64RemU, WasmOp::I64RemS] {
2313 let wrong = i64_rem_with_dest(&op, Reg::R2, Reg::R3);
2314 let trap_only = validator
2316 .verify_i64_div_rem_trap_preservation(&op, &wrong)
2317 .unwrap();
2318 assert_eq!(
2319 trap_only,
2320 ValidationResult::Verified,
2321 "the trap-ONLY VC is blind to the wrong destination (this is the \
2322 havoc-era gap): {op:?} expected Verified, got {trap_only:?}"
2323 );
2324 let value = validator.verify_trap_preservation(&op, &wrong).unwrap();
2326 assert!(
2327 matches!(value, ValidationResult::Invalid { .. }),
2328 "the value VC catches it: {op:?} expected Invalid, got {value:?}"
2329 );
2330 assert_ne!(trap_only, value, "the two gates must disagree ({op:?})");
2331 }
2332 });
2333 }
2334
2335 fn call_indirect_pseudo(
2338 table_size: u32,
2339 null_check: bool,
2340 type_check: Option<(u32, u32)>,
2341 ) -> ArmOp {
2342 ArmOp::CallIndirect {
2343 rd: Reg::R0,
2344 type_idx: 0,
2345 table_index_reg: Reg::R0,
2346 table_size,
2347 table_byte_offset: 0,
2348 null_check,
2349 type_check,
2350 }
2351 }
2352
2353 #[test]
2354 fn call_indirect_matching_guards_preserve_the_traps() {
2355 with_verification_context(|| {
2356 let validator = TranslationValidator::new();
2357 let result = validator
2359 .verify_call_indirect_trap_preservation(
2360 &call_indirect_pseudo(8, false, None),
2361 &CallIndirectSpec {
2362 table_size: 8,
2363 may_have_null_slot: false,
2364 heterogeneous_expected_type: None,
2365 },
2366 )
2367 .unwrap();
2368 assert_eq!(result, ValidationResult::Verified);
2369 let result = validator
2371 .verify_call_indirect_trap_preservation(
2372 &call_indirect_pseudo(8, true, Some((3, 32))),
2373 &CallIndirectSpec {
2374 table_size: 8,
2375 may_have_null_slot: true,
2376 heterogeneous_expected_type: Some(3),
2377 },
2378 )
2379 .unwrap();
2380 assert_eq!(result, ValidationResult::Verified);
2381 });
2382 }
2383
2384 #[test]
2387 fn call_indirect_dropped_null_check_is_rejected() {
2388 with_verification_context(|| {
2389 let validator = TranslationValidator::new();
2390 let result = validator
2391 .verify_call_indirect_trap_preservation(
2392 &call_indirect_pseudo(8, false, None),
2393 &CallIndirectSpec {
2394 table_size: 8,
2395 may_have_null_slot: true,
2396 heterogeneous_expected_type: None,
2397 },
2398 )
2399 .unwrap();
2400 assert!(
2401 matches!(result, ValidationResult::Invalid { .. }),
2402 "dropped null check must be Invalid, got {result:?}"
2403 );
2404 });
2405 }
2406
2407 #[test]
2410 fn call_indirect_wrong_table_size_is_rejected() {
2411 with_verification_context(|| {
2412 let validator = TranslationValidator::new();
2413 let result = validator
2414 .verify_call_indirect_trap_preservation(
2415 &call_indirect_pseudo(16, false, None),
2416 &CallIndirectSpec {
2417 table_size: 8,
2418 may_have_null_slot: false,
2419 heterogeneous_expected_type: None,
2420 },
2421 )
2422 .unwrap();
2423 assert!(
2424 matches!(result, ValidationResult::Invalid { .. }),
2425 "wrong bounds size must be Invalid, got {result:?}"
2426 );
2427 });
2428 }
2429
2430 #[test]
2433 fn call_indirect_dropped_type_check_is_rejected() {
2434 with_verification_context(|| {
2435 let validator = TranslationValidator::new();
2436 let result = validator
2437 .verify_call_indirect_trap_preservation(
2438 &call_indirect_pseudo(8, true, None),
2439 &CallIndirectSpec {
2440 table_size: 8,
2441 may_have_null_slot: true,
2442 heterogeneous_expected_type: Some(3),
2443 },
2444 )
2445 .unwrap();
2446 assert!(
2447 matches!(result, ValidationResult::Invalid { .. }),
2448 "dropped type check must be Invalid, got {result:?}"
2449 );
2450 });
2451 }
2452
2453 #[test]
2456 fn verify_rule_routes_partial_ops_through_the_trap_gate() {
2457 with_verification_context(|| {
2458 let validator = TranslationValidator::new();
2459 let rule = SynthesisRule {
2462 name: "i32.div_u → bare UDIV (trap-dropping)".into(),
2463 priority: 0,
2464 pattern: Pattern::WasmInstr(WasmOp::I32DivU),
2465 replacement: Replacement::ArmInstr(ArmOp::Udiv {
2466 rd: Reg::R0,
2467 rn: Reg::R0,
2468 rm: Reg::R1,
2469 }),
2470 cost: Cost {
2471 cycles: 1,
2472 code_size: 4,
2473 registers: 2,
2474 },
2475 };
2476 let result = validator.verify_rule(&rule).unwrap();
2477 assert!(
2478 matches!(result, ValidationResult::Invalid { .. }),
2479 "verify_rule must reject the trap-dropping div rule, got {result:?}"
2480 );
2481
2482 let rule = SynthesisRule {
2484 name: "i32.div_u → guarded UDIV".into(),
2485 priority: 0,
2486 pattern: Pattern::WasmInstr(WasmOp::I32DivU),
2487 replacement: Replacement::ArmSequence(shipped_divu_guard()),
2488 cost: Cost {
2489 cycles: 4,
2490 code_size: 10,
2491 registers: 2,
2492 },
2493 };
2494 assert_eq!(
2495 validator.verify_rule(&rule).unwrap(),
2496 ValidationResult::Verified
2497 );
2498 });
2499 }
2500
2501 #[test]
2502 fn trap_preservation_gate_rejects_non_div_ops() {
2503 with_verification_context(|| {
2504 let validator = TranslationValidator::new();
2505 let err = validator
2506 .verify_div_rem_trap_preservation(&WasmOp::I32Add, &[])
2507 .unwrap_err();
2508 assert!(matches!(err, VerificationError::UnsupportedOperation(_)));
2509 let err64 = validator
2512 .verify_div_rem_trap_preservation(&WasmOp::I64DivU, &[])
2513 .unwrap_err();
2514 assert!(matches!(err64, VerificationError::UnsupportedOperation(_)));
2515 });
2516 }
2517
2518 #[test]
2519 fn test_verify_add_correct() {
2520 with_verification_context(|| {
2521 let validator = TranslationValidator::new();
2522
2523 let rule = create_test_rule(
2524 WasmOp::I32Add,
2525 ArmOp::Add {
2526 rd: Reg::R0,
2527 rn: Reg::R0,
2528 op2: Operand2::Reg(Reg::R1),
2529 },
2530 );
2531
2532 let result = validator.verify_rule(&rule).unwrap();
2533 assert_eq!(result, ValidationResult::Verified);
2534 });
2535 }
2536
2537 #[test]
2538 fn test_verify_sub_correct() {
2539 with_verification_context(|| {
2540 let validator = TranslationValidator::new();
2541
2542 let rule = create_test_rule(
2543 WasmOp::I32Sub,
2544 ArmOp::Sub {
2545 rd: Reg::R0,
2546 rn: Reg::R0,
2547 op2: Operand2::Reg(Reg::R1),
2548 },
2549 );
2550
2551 let result = validator.verify_rule(&rule).unwrap();
2552 assert_eq!(result, ValidationResult::Verified);
2553 });
2554 }
2555
2556 #[test]
2557 fn test_verify_mul_correct() {
2558 with_verification_context(|| {
2559 let validator = TranslationValidator::new();
2560
2561 let rule = create_test_rule(
2562 WasmOp::I32Mul,
2563 ArmOp::Mul {
2564 rd: Reg::R0,
2565 rn: Reg::R0,
2566 rm: Reg::R1,
2567 },
2568 );
2569
2570 let result = validator.verify_rule(&rule).unwrap();
2571 assert_eq!(result, ValidationResult::Verified);
2572 });
2573 }
2574
2575 #[test]
2576 fn test_verify_and_correct() {
2577 with_verification_context(|| {
2578 let validator = TranslationValidator::new();
2579
2580 let rule = create_test_rule(
2581 WasmOp::I32And,
2582 ArmOp::And {
2583 rd: Reg::R0,
2584 rn: Reg::R0,
2585 op2: Operand2::Reg(Reg::R1),
2586 },
2587 );
2588
2589 let result = validator.verify_rule(&rule).unwrap();
2590 assert_eq!(result, ValidationResult::Verified);
2591 });
2592 }
2593
2594 #[test]
2595 fn test_verify_incorrect_rule() {
2596 with_verification_context(|| {
2597 let validator = TranslationValidator::new();
2598
2599 let rule = create_test_rule(
2601 WasmOp::I32Add,
2602 ArmOp::Sub {
2603 rd: Reg::R0,
2604 rn: Reg::R0,
2605 op2: Operand2::Reg(Reg::R1),
2606 },
2607 );
2608
2609 let result = validator.verify_rule(&rule).unwrap();
2610
2611 match result {
2612 ValidationResult::Invalid { counterexample } => {
2613 assert!(!counterexample.is_empty());
2614 }
2615 _ => panic!("Expected counterexample but got: {:?}", result),
2616 }
2617 });
2618 }
2619
2620 #[test]
2621 fn test_verify_bitwise_ops() {
2622 with_verification_context(|| {
2623 let validator = TranslationValidator::new();
2624
2625 let or_rule = create_test_rule(
2627 WasmOp::I32Or,
2628 ArmOp::Orr {
2629 rd: Reg::R0,
2630 rn: Reg::R0,
2631 op2: Operand2::Reg(Reg::R1),
2632 },
2633 );
2634 assert_eq!(
2635 validator.verify_rule(&or_rule).unwrap(),
2636 ValidationResult::Verified
2637 );
2638
2639 let xor_rule = create_test_rule(
2641 WasmOp::I32Xor,
2642 ArmOp::Eor {
2643 rd: Reg::R0,
2644 rn: Reg::R0,
2645 op2: Operand2::Reg(Reg::R1),
2646 },
2647 );
2648 assert_eq!(
2649 validator.verify_rule(&xor_rule).unwrap(),
2650 ValidationResult::Verified
2651 );
2652 });
2653 }
2654
2655 #[test]
2656 fn test_verify_shift_ops() {
2657 }
2662}