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 {
296 self.arm_encoder.encode_op(arm_op, &mut state);
297 }
298
299 Ok(self.arm_encoder.extract_result(&state, &Reg::R0))
301 }
302
303 pub fn verify_parameterized_range<F>(
305 &self,
306 wasm_op: &WasmOp,
307 create_arm_ops: F,
308 param_index: usize,
309 range: std::ops::Range<i64>,
310 ) -> Result<ValidationResult, VerificationError>
311 where
312 F: Fn(i64) -> Vec<ArmOp>,
313 {
314 for value in range {
315 let arm_ops = create_arm_ops(value);
316 let result =
317 self.verify_equivalence_parameterized(wasm_op, &arm_ops, &[(param_index, value)])?;
318
319 match result {
320 ValidationResult::Verified => continue,
321 ValidationResult::Invalid { counterexample } => {
322 return Ok(ValidationResult::Invalid {
323 counterexample: counterexample
324 .into_iter()
325 .map(|(k, v)| (format!("{} (param={})", k, value), v))
326 .collect(),
327 });
328 }
329 ValidationResult::Unknown { reason } => {
330 return Ok(ValidationResult::Unknown {
331 reason: format!("Failed at param={}: {}", value, reason),
332 });
333 }
334 }
335 }
336
337 Ok(ValidationResult::Verified)
338 }
339
340 pub fn verify_trap_preservation(
384 &self,
385 wasm_op: &WasmOp,
386 arm_ops: &[ArmOp],
387 ) -> Result<ValidationResult, VerificationError> {
388 match wasm_op {
389 WasmOp::I32DivS | WasmOp::I32DivU | WasmOp::I32RemS | WasmOp::I32RemU => {
390 self.verify_div_rem_trap_preservation(wasm_op, arm_ops)
391 }
392 WasmOp::I64RemU | WasmOp::I64RemS => {
400 self.verify_i64_rem_value_preservation(wasm_op, arm_ops)
401 }
402 WasmOp::I64DivS | WasmOp::I64DivU => {
403 self.verify_i64_div_rem_trap_preservation(wasm_op, arm_ops)
404 }
405 WasmOp::Unreachable => {
406 let (_state, arm_trap) = self.derive_arm_state(arm_ops, &[], None)?;
407 Ok(Self::condition_verdict(
408 &crate::trap::trap_always(),
409 &arm_trap,
410 ))
411 }
412 WasmOp::I32Load { offset, .. }
413 | WasmOp::I32Load8S { offset, .. }
414 | WasmOp::I32Load8U { offset, .. }
415 | WasmOp::I32Load16S { offset, .. }
416 | WasmOp::I32Load16U { offset, .. }
417 | WasmOp::I32Store { offset, .. }
418 | WasmOp::I32Store8 { offset, .. }
419 | WasmOp::I32Store16 { offset, .. } => {
420 let size: u64 = match wasm_op {
421 WasmOp::I32Load8S { .. }
422 | WasmOp::I32Load8U { .. }
423 | WasmOp::I32Store8 { .. } => 1,
424 WasmOp::I32Load16S { .. }
425 | WasmOp::I32Load16U { .. }
426 | WasmOp::I32Store16 { .. } => 2,
427 _ => 4,
428 };
429 self.verify_mem_trap_preservation(arm_ops, *offset, size)
430 }
431 WasmOp::I32TruncF32S | WasmOp::I32TruncF32U => {
432 let signed = matches!(wasm_op, WasmOp::I32TruncF32S);
433 self.verify_trunc_f32_trap_preservation(arm_ops, signed)
434 }
435 WasmOp::I32TruncF64S | WasmOp::I32TruncF64U => {
436 let signed = matches!(wasm_op, WasmOp::I32TruncF64S);
437 self.verify_trunc_f64_trap_preservation(arm_ops, signed)
438 }
439 other => Err(VerificationError::UnsupportedOperation(format!(
440 "trap-preservation gate does not cover {other:?} \
441 (i64.trunc_f64 has no shipped lowering — the selector declines \
442 it; its classifier is unit-gated — see method docs)"
443 ))),
444 }
445 }
446
447 pub fn verify_div_rem_trap_preservation(
465 &self,
466 wasm_op: &WasmOp,
467 arm_ops: &[ArmOp],
468 ) -> Result<ValidationResult, VerificationError> {
469 let Some(div_op) = crate::trap::div_op(wasm_op) else {
470 return Err(VerificationError::UnsupportedOperation(format!(
471 "trap-preservation gate applies to div/rem only, got {wasm_op:?}"
472 )));
473 };
474 if !matches!(
478 wasm_op,
479 WasmOp::I32DivS | WasmOp::I32DivU | WasmOp::I32RemS | WasmOp::I32RemU
480 ) {
481 return Err(VerificationError::UnsupportedOperation(format!(
482 "trap-preservation gate currently supports i32 div/rem only, got {wasm_op:?}"
483 )));
484 }
485
486 let dividend = BV::new_const("input_0", 32);
489 let divisor = BV::new_const("input_1", 32);
490 let inputs = vec![dividend.clone(), divisor.clone()];
491
492 let wasm_value = self.wasm_encoder.encode_op(wasm_op, &inputs);
493 let (state, arm_may_trap) = self.derive_arm_state(arm_ops, &inputs, None)?;
494 let arm_value = if ArmSemantics::branch_spans_are_value_dead(arm_ops) {
495 let mut vstate = ArmState::new_symbolic();
498 Self::seed_inputs(&mut vstate, &inputs)?;
499 self.arm_encoder
500 .encode_sequence_value_straightline(arm_ops, &mut vstate)
501 .map_err(VerificationError::UnsupportedOperation)?;
502 self.arm_encoder.extract_result(&vstate, &Reg::R0)
503 } else {
504 self.arm_encoder.extract_result(&state, &Reg::R0)
505 };
506
507 let orig = crate::trap::DefineOrTrap {
508 value: wasm_value,
509 may_trap: crate::trap::trap_div(div_op, ÷nd, &divisor),
510 };
511 let opt = crate::trap::DefineOrTrap {
512 value: arm_value,
513 may_trap: arm_may_trap,
514 };
515
516 Ok(Self::trap_verdict_to_result(
517 crate::trap::prove_trap_equivalence(&orig, &opt),
518 ))
519 }
520
521 pub fn verify_mem_trap_preservation(
529 &self,
530 arm_ops: &[ArmOp],
531 offset: u32,
532 access_size: u64,
533 ) -> Result<ValidationResult, VerificationError> {
534 let addr = BV::new_const("input_0", 32);
535 let value = BV::new_const("input_1", 32);
536 let inputs = vec![addr.clone(), value];
537
538 let mut state = ArmState::new_symbolic();
539 let mem_bound = state.get_reg(&Reg::R10).clone();
542 Self::seed_inputs(&mut state, &inputs)?;
543 self.arm_encoder
544 .encode_sequence_br(arm_ops, &mut state)
545 .map_err(VerificationError::UnsupportedOperation)?;
546 let arm_trap = state.may_trap.clone();
547
548 let static_bytes = offset as u64 + access_size;
552 let wasm_trap = if static_bytes > u32::MAX as u64 {
553 crate::trap::trap_always()
555 } else {
556 crate::trap::trap_mem_oob(&addr, &BV::from_u64(static_bytes, 32), &mem_bound)
557 };
558
559 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
560 }
561
562 pub fn verify_trunc_f32_trap_preservation(
570 &self,
571 arm_ops: &[ArmOp],
572 signed: bool,
573 ) -> Result<ValidationResult, VerificationError> {
574 use synth_synthesis::rules::VfpReg;
575 let bits = BV::new_const("input_0", 32);
576
577 let mut state = ArmState::new_symbolic();
578 state.set_vfp_reg(&VfpReg::S0, bits.clone());
579 self.arm_encoder
580 .encode_sequence_br(arm_ops, &mut state)
581 .map_err(VerificationError::UnsupportedOperation)?;
582 let arm_trap = state.may_trap.clone();
583
584 let wasm_trap = crate::trap::trap_trunc(
585 &bits,
586 crate::trap::FpFmt::F32,
587 crate::trap::IntTarget::I32,
588 signed,
589 );
590
591 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
592 }
593
594 pub fn verify_i64_div_rem_trap_preservation(
606 &self,
607 wasm_op: &WasmOp,
608 arm_ops: &[ArmOp],
609 ) -> Result<ValidationResult, VerificationError> {
610 let Some(div_op) = crate::trap::div_op(wasm_op) else {
611 return Err(VerificationError::UnsupportedOperation(format!(
612 "i64 trap-preservation gate applies to div/rem only, got {wasm_op:?}"
613 )));
614 };
615 if !matches!(
616 wasm_op,
617 WasmOp::I64DivS | WasmOp::I64DivU | WasmOp::I64RemS | WasmOp::I64RemU
618 ) {
619 return Err(VerificationError::UnsupportedOperation(format!(
620 "i64 trap-preservation gate supports i64 div/rem only, got {wasm_op:?}"
621 )));
622 }
623
624 let (elide_zero, elide_overflow) =
627 Self::i64_div_rem_guard_fields(arm_ops).ok_or_else(|| {
628 VerificationError::UnsupportedOperation(format!(
629 "i64 trap gate needs an I64Div/I64Rem pseudo-op in the sequence, \
630 got {arm_ops:?}"
631 ))
632 })?;
633
634 let dividend = BV::new_const("input_dividend_i64", 64);
639 let divisor = BV::new_const("input_divisor_i64", 64);
640 let wasm_trap = crate::trap::trap_div(div_op, ÷nd, &divisor);
641
642 let arm_trap =
646 Self::i64_arm_trap_from_fields(div_op, ÷nd, &divisor, elide_zero, elide_overflow);
647
648 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
649 }
650
651 pub fn verify_i64_rem_value_preservation(
682 &self,
683 wasm_op: &WasmOp,
684 arm_ops: &[ArmOp],
685 ) -> Result<ValidationResult, VerificationError> {
686 let Some(div_op) = crate::trap::div_op(wasm_op) else {
687 return Err(VerificationError::UnsupportedOperation(format!(
688 "i64 rem value gate applies to rem only, got {wasm_op:?}"
689 )));
690 };
691 if !matches!(wasm_op, WasmOp::I64RemU | WasmOp::I64RemS) {
692 return Err(VerificationError::UnsupportedOperation(format!(
693 "i64 rem value gate supports i64 rem_u/rem_s only, got {wasm_op:?}"
694 )));
695 }
696
697 let Some((elide_zero, _elide_overflow)) = Self::i64_div_rem_guard_fields(arm_ops) else {
705 return Err(VerificationError::UnsupportedOperation(format!(
706 "i64 rem value gate needs an I64Rem pseudo-op in the sequence, \
707 got {arm_ops:?}"
708 )));
709 };
710
711 let dividend_lo = BV::new_const("input_dividend_lo", 32);
714 let dividend_hi = BV::new_const("input_dividend_hi", 32);
715 let divisor_lo = BV::new_const("input_divisor_lo", 32);
716 let divisor_hi = BV::new_const("input_divisor_hi", 32);
717
718 let mut state = ArmState::new_symbolic();
719 state.set_reg(&Reg::R0, dividend_lo.clone());
720 state.set_reg(&Reg::R1, dividend_hi.clone());
721 state.set_reg(&Reg::R2, divisor_lo.clone());
722 state.set_reg(&Reg::R3, divisor_hi.clone());
723 self.arm_encoder
724 .encode_sequence_br(arm_ops, &mut state)
725 .map_err(VerificationError::UnsupportedOperation)?;
726
727 let arm_lo = self.arm_encoder.extract_result(&state, &Reg::R0);
730 let arm_hi = self.arm_encoder.extract_result(&state, &Reg::R1);
731 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 {
738 WasmOp::I64RemU => dividend.bvurem(&divisor),
739 WasmOp::I64RemS => dividend.bvsrem(&divisor),
740 _ => unreachable!("guarded above"),
741 };
742 let wasm_trap = crate::trap::trap_div(div_op, ÷nd, &divisor);
743
744 let arm_may_trap = Self::i64_arm_trap_from_fields(
747 div_op, ÷nd, &divisor, elide_zero, false,
748 );
749
750 let orig = crate::trap::DefineOrTrap {
751 value: wasm_value,
752 may_trap: wasm_trap,
753 };
754 let opt = crate::trap::DefineOrTrap {
755 value: arm_value,
756 may_trap: arm_may_trap,
757 };
758
759 Ok(Self::trap_verdict_to_result(
760 crate::trap::prove_trap_equivalence(&orig, &opt),
761 ))
762 }
763
764 fn i64_div_rem_guard_fields(arm_ops: &[ArmOp]) -> Option<(bool, bool)> {
769 arm_ops.iter().find_map(|op| match op {
770 ArmOp::I64DivS {
771 elide_zero_guard,
772 elide_overflow_guard,
773 ..
774 } => Some((*elide_zero_guard, *elide_overflow_guard)),
775 ArmOp::I64DivU {
776 elide_zero_guard, ..
777 }
778 | ArmOp::I64RemS {
779 elide_zero_guard, ..
780 }
781 | ArmOp::I64RemU {
782 elide_zero_guard, ..
783 } => Some((*elide_zero_guard, false)),
784 _ => None,
785 })
786 }
787
788 fn i64_arm_trap_from_fields(
793 div_op: crate::trap::DivOp,
794 dividend: &BV,
795 divisor: &BV,
796 elide_zero: bool,
797 elide_overflow: bool,
798 ) -> Bool {
799 let zero = BV::from_u64(0, 64);
800 let div_by_zero = divisor.eq(&zero);
801
802 let mut clauses: Vec<Bool> = Vec::new();
804 if !elide_zero {
805 clauses.push(div_by_zero);
806 }
807
808 if matches!(div_op, crate::trap::DivOp::DivS) && !elide_overflow {
811 let int_min = BV::from_i64(i64::MIN, 64);
812 let neg_one = BV::from_i64(-1, 64);
813 let overflow = Bool::and(&[÷nd.eq(&int_min), &divisor.eq(&neg_one)]);
814 clauses.push(overflow);
815 }
816
817 if clauses.is_empty() {
818 Bool::from_bool(false)
819 } else {
820 let refs: Vec<&Bool> = clauses.iter().collect();
821 Bool::or(&refs)
822 }
823 }
824
825 pub fn verify_trunc_f64_trap_preservation(
834 &self,
835 arm_ops: &[ArmOp],
836 signed: bool,
837 ) -> Result<ValidationResult, VerificationError> {
838 use synth_synthesis::rules::VfpReg;
839 let bits = BV::new_const("input_0", 64);
840
841 let mut state = ArmState::new_symbolic();
842 state.set_vfp_reg(&VfpReg::D0, bits.clone());
843 self.arm_encoder
844 .encode_sequence_br(arm_ops, &mut state)
845 .map_err(VerificationError::UnsupportedOperation)?;
846 let arm_trap = state.may_trap.clone();
847
848 let wasm_trap = crate::trap::trap_trunc(
849 &bits,
850 crate::trap::FpFmt::F64,
851 crate::trap::IntTarget::I32,
852 signed,
853 );
854
855 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
856 }
857
858 pub fn verify_call_indirect_trap_preservation(
876 &self,
877 arm_op: &ArmOp,
878 spec: &CallIndirectSpec,
879 ) -> Result<ValidationResult, VerificationError> {
880 let ArmOp::CallIndirect {
881 table_size,
882 null_check,
883 type_check,
884 ..
885 } = arm_op
886 else {
887 return Err(VerificationError::UnsupportedOperation(format!(
888 "call_indirect trap gate needs the CallIndirect pseudo-op, got {arm_op:?}"
889 )));
890 };
891
892 let index = BV::new_const("input_0", 32);
893 let slot = BV::new_const("slot_ptr", 32);
894 let nonnull_slot = slot.bvor(BV::from_u64(1, 32));
895 let actual_ty = BV::new_const("slot_type_id", 32);
896
897 let build = |size: u32, may_null: bool, expected: Option<u32>| {
898 let expected_bv = expected.map(|e| BV::from_u64(e as u64, 32));
899 let size_bv = BV::from_u64(size as u64, 32);
900 let slot_term = if may_null { &slot } else { &nonnull_slot };
901 let type_trap = match &expected_bv {
902 Some(e) => crate::trap::TypeTrap::Runtime {
903 actual_type_id: &actual_ty,
904 expected_id: e,
905 },
906 None => crate::trap::TypeTrap::StaticallyDischarged,
907 };
908 crate::trap::trap_call_indirect(&crate::trap::CallIndirect {
909 index: &index,
910 table_size: &size_bv,
911 slot_ptr: slot_term,
912 type_trap,
913 })
914 };
915
916 let wasm_trap = build(
917 spec.table_size,
918 spec.may_have_null_slot,
919 spec.heterogeneous_expected_type,
920 );
921 let arm_trap = build(
922 *table_size,
923 *null_check,
924 type_check.as_ref().map(|(expected, _)| *expected),
925 );
926
927 Ok(Self::condition_verdict(&wasm_trap, &arm_trap))
928 }
929
930 fn derive_arm_state(
933 &self,
934 arm_ops: &[ArmOp],
935 inputs: &[BV],
936 vfp_s0: Option<&BV>,
937 ) -> Result<(ArmState, Bool), VerificationError> {
938 let mut state = ArmState::new_symbolic();
939 Self::seed_inputs(&mut state, inputs)?;
940 if let Some(bits) = vfp_s0 {
941 state.set_vfp_reg(&synth_synthesis::rules::VfpReg::S0, bits.clone());
942 }
943 self.arm_encoder
944 .encode_sequence_br(arm_ops, &mut state)
945 .map_err(VerificationError::UnsupportedOperation)?;
946 let trap = state.may_trap.clone();
947 Ok((state, trap))
948 }
949
950 fn seed_inputs(state: &mut ArmState, inputs: &[BV]) -> Result<(), VerificationError> {
951 for (i, input) in inputs.iter().enumerate() {
952 let reg = match i {
953 0 => Reg::R0,
954 1 => Reg::R1,
955 2 => Reg::R2,
956 _ => {
957 return Err(VerificationError::UnsupportedOperation(format!(
958 "Too many inputs: {}",
959 inputs.len()
960 )));
961 }
962 };
963 state.set_reg(®, input.clone());
964 }
965 Ok(())
966 }
967
968 fn condition_verdict(wasm_trap: &Bool, arm_trap: &Bool) -> ValidationResult {
970 Self::trap_verdict_to_result(crate::trap::prove_trap_condition_equivalence(
971 wasm_trap, arm_trap,
972 ))
973 }
974
975 fn trap_verdict_to_result(verdict: crate::trap::TrapVerdict) -> ValidationResult {
976 match verdict {
977 crate::trap::TrapVerdict::Preserved => ValidationResult::Verified,
978 crate::trap::TrapVerdict::Dropped(model) => ValidationResult::Invalid {
979 counterexample: model
980 .into_iter()
981 .filter_map(|(n, v)| i64::try_from(v).ok().map(|x| (n, x)))
982 .collect(),
983 },
984 crate::trap::TrapVerdict::Unknown => ValidationResult::Unknown {
985 reason: "trap-preservation VC returned Unknown (conservative reject)".to_string(),
986 },
987 }
988 }
989
990 fn get_num_inputs(&self, wasm_op: &WasmOp) -> usize {
992 use WasmOp::*;
993 match wasm_op {
994 I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or
996 | I32Xor | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr | I32Eq | I32Ne | I32LtS
997 | I32LtU | I32LeS | I32LeU | I32GtS | I32GtU | I32GeS | I32GeU => 2,
998
999 I32Clz | I32Ctz | I32Popcnt | I32Eqz => 1,
1001
1002 I32Const(_) => 0,
1004
1005 I32Load { .. } => 1, I32Store { .. } => 2, LocalGet(_) | GlobalGet(_) => 0,
1011 LocalSet(_) | GlobalSet(_) | LocalTee(_) => 1,
1012 Br(_) | BrIf(_) | Return => 0,
1013
1014 Drop => 1,
1016 Select => 3, Nop | Unreachable | Block | Loop | If | Else | End => 0,
1018
1019 _ => 0,
1021 }
1022 }
1023
1024 pub fn verify_rules(
1026 &self,
1027 rules: &[SynthesisRule],
1028 ) -> Vec<(String, Result<ValidationResult, VerificationError>)> {
1029 rules
1030 .iter()
1031 .map(|rule| {
1032 let result = self.verify_rule(rule);
1033 (rule.name.clone(), result)
1034 })
1035 .collect()
1036 }
1037}
1038
1039#[cfg(test)]
1040mod tests {
1041 use super::*;
1042 use crate::with_verification_context;
1043 use synth_synthesis::rules::Condition;
1044 use synth_synthesis::{Cost, Operand2, Pattern, Replacement};
1045
1046 fn create_test_rule(wasm_op: WasmOp, arm_op: ArmOp) -> SynthesisRule {
1047 SynthesisRule {
1048 name: format!("{:?}", wasm_op),
1049 priority: 0,
1050 pattern: Pattern::WasmInstr(wasm_op),
1051 replacement: Replacement::ArmInstr(arm_op),
1052 cost: Cost {
1053 cycles: 1,
1054 code_size: 4,
1055 registers: 2,
1056 },
1057 }
1058 }
1059
1060 #[test]
1063 fn div_lowering_without_guard_is_rejected_as_trap_drop() {
1064 with_verification_context(|| {
1065 let validator = TranslationValidator::new();
1066 let arm_ops = [ArmOp::Udiv {
1069 rd: Reg::R0,
1070 rn: Reg::R0,
1071 rm: Reg::R1,
1072 }];
1073 let result = validator
1074 .verify_div_rem_trap_preservation(&WasmOp::I32DivU, &arm_ops)
1075 .unwrap();
1076 match result {
1077 ValidationResult::Invalid { counterexample } => {
1078 let divisor = counterexample.iter().find(|(n, _)| n == "input_1");
1080 assert_eq!(
1081 divisor.map(|(_, v)| *v),
1082 Some(0),
1083 "trap-drop counterexample must set the divisor to 0"
1084 );
1085 }
1086 other => panic!("unguarded div must be Invalid, got {other:?}"),
1087 }
1088 });
1089 }
1090
1091 fn shipped_divu_guard() -> Vec<ArmOp> {
1095 vec![
1096 ArmOp::Cmp {
1097 rn: Reg::R1,
1098 op2: Operand2::Imm(0),
1099 },
1100 ArmOp::BCondOffset {
1101 cond: Condition::NE,
1102 offset: 0,
1103 },
1104 ArmOp::Udf { imm: 0 },
1105 ArmOp::Udiv {
1106 rd: Reg::R0,
1107 rn: Reg::R0,
1108 rm: Reg::R1,
1109 },
1110 ]
1111 }
1112
1113 fn shipped_divs_double_guard() -> Vec<ArmOp> {
1117 vec![
1118 ArmOp::Cmp {
1119 rn: Reg::R1,
1120 op2: Operand2::Imm(0),
1121 },
1122 ArmOp::BCondOffset {
1123 cond: Condition::NE,
1124 offset: 0,
1125 },
1126 ArmOp::Udf { imm: 0 },
1127 ArmOp::Movw {
1128 rd: Reg::R12,
1129 imm16: 0,
1130 },
1131 ArmOp::Movt {
1132 rd: Reg::R12,
1133 imm16: 0x8000,
1134 },
1135 ArmOp::Cmp {
1136 rn: Reg::R0,
1137 op2: Operand2::Reg(Reg::R12),
1138 },
1139 ArmOp::BCondOffset {
1140 cond: Condition::NE,
1141 offset: 3,
1142 },
1143 ArmOp::Cmn {
1144 rn: Reg::R1,
1145 op2: Operand2::Imm(1),
1146 },
1147 ArmOp::BCondOffset {
1148 cond: Condition::NE,
1149 offset: 0,
1150 },
1151 ArmOp::Udf { imm: 1 },
1152 ArmOp::Sdiv {
1153 rd: Reg::R0,
1154 rn: Reg::R0,
1155 rm: Reg::R1,
1156 },
1157 ]
1158 }
1159
1160 #[test]
1161 fn div_lowering_with_guard_preserves_the_trap() {
1162 with_verification_context(|| {
1163 let validator = TranslationValidator::new();
1164 let result = validator
1165 .verify_div_rem_trap_preservation(&WasmOp::I32DivU, &shipped_divu_guard())
1166 .unwrap();
1167 assert_eq!(result, ValidationResult::Verified);
1168 });
1169 }
1170
1171 #[test]
1177 fn div_guard_with_inverted_polarity_is_rejected() {
1178 with_verification_context(|| {
1179 let validator = TranslationValidator::new();
1180 let mut arm_ops = shipped_divu_guard();
1181 arm_ops[1] = ArmOp::BCondOffset {
1182 cond: Condition::EQ,
1183 offset: 0,
1184 };
1185 let result = validator
1186 .verify_div_rem_trap_preservation(&WasmOp::I32DivU, &arm_ops)
1187 .unwrap();
1188 assert!(
1189 matches!(result, ValidationResult::Invalid { .. }),
1190 "inverted guard polarity must be Invalid, got {result:?}"
1191 );
1192 });
1193 }
1194
1195 #[test]
1196 fn signed_div_double_guard_preserves_both_zero_and_overflow_traps() {
1197 with_verification_context(|| {
1198 let validator = TranslationValidator::new();
1199 let result = validator
1200 .verify_div_rem_trap_preservation(&WasmOp::I32DivS, &shipped_divs_double_guard())
1201 .unwrap();
1202 assert_eq!(result, ValidationResult::Verified);
1203 });
1204 }
1205
1206 #[test]
1212 fn signed_div_with_overflow_guard_stripped_is_rejected() {
1213 with_verification_context(|| {
1214 let validator = TranslationValidator::new();
1215 let arm_ops = [
1216 ArmOp::Cmp {
1217 rn: Reg::R1,
1218 op2: Operand2::Imm(0),
1219 },
1220 ArmOp::BCondOffset {
1221 cond: Condition::NE,
1222 offset: 0,
1223 },
1224 ArmOp::Udf { imm: 0 },
1225 ArmOp::Sdiv {
1226 rd: Reg::R0,
1227 rn: Reg::R0,
1228 rm: Reg::R1,
1229 },
1230 ];
1231 let result = validator
1232 .verify_div_rem_trap_preservation(&WasmOp::I32DivS, &arm_ops)
1233 .unwrap();
1234 match result {
1235 ValidationResult::Invalid { counterexample } => {
1236 let get = |n: &str| {
1237 counterexample
1238 .iter()
1239 .find(|(name, _)| name == n)
1240 .map(|(_, v)| *v)
1241 };
1242 assert_eq!(
1243 get("input_0"),
1244 Some(i32::MIN as u32 as i64),
1245 "dropped overflow trap must exhibit dividend INT_MIN: {counterexample:?}"
1246 );
1247 assert_eq!(
1248 get("input_1"),
1249 Some(u32::MAX as i64),
1250 "dropped overflow trap must exhibit divisor -1: {counterexample:?}"
1251 );
1252 }
1253 other => panic!("overflow-guard-stripped div_s must be Invalid, got {other:?}"),
1254 }
1255 });
1256 }
1257
1258 #[test]
1261 fn rems_single_zero_guard_is_exactly_right() {
1262 with_verification_context(|| {
1263 let validator = TranslationValidator::new();
1264 let arm_ops = [
1265 ArmOp::Cmp {
1266 rn: Reg::R1,
1267 op2: Operand2::Imm(0),
1268 },
1269 ArmOp::BCondOffset {
1270 cond: Condition::NE,
1271 offset: 0,
1272 },
1273 ArmOp::Udf { imm: 0 },
1274 ArmOp::Sdiv {
1275 rd: Reg::R2,
1276 rn: Reg::R0,
1277 rm: Reg::R1,
1278 },
1279 ArmOp::Mls {
1280 rd: Reg::R0,
1281 rn: Reg::R2,
1282 rm: Reg::R1,
1283 ra: Reg::R0,
1284 },
1285 ];
1286 let result = validator
1287 .verify_div_rem_trap_preservation(&WasmOp::I32RemS, &arm_ops)
1288 .unwrap();
1289 assert_eq!(result, ValidationResult::Verified);
1290 });
1291 }
1292
1293 #[test]
1296 fn unreachable_udf_lowering_preserves_the_trap() {
1297 with_verification_context(|| {
1298 let validator = TranslationValidator::new();
1299 let result = validator
1300 .verify_trap_preservation(&WasmOp::Unreachable, &[ArmOp::Udf { imm: 0 }])
1301 .unwrap();
1302 assert_eq!(result, ValidationResult::Verified);
1303 });
1304 }
1305
1306 #[test]
1307 fn unreachable_lowered_to_nop_is_rejected() {
1308 with_verification_context(|| {
1309 let validator = TranslationValidator::new();
1310 let result = validator
1312 .verify_trap_preservation(&WasmOp::Unreachable, &[ArmOp::Nop])
1313 .unwrap();
1314 assert!(
1315 matches!(result, ValidationResult::Invalid { .. }),
1316 "trap-dropping unreachable lowering must be Invalid, got {result:?}"
1317 );
1318 });
1319 }
1320
1321 fn shipped_software_bounds_ops(wasm_op: &WasmOp) -> Vec<ArmOp> {
1334 use synth_synthesis::instruction_selector::InstructionSelector;
1335 use synth_synthesis::rules::MemAddr;
1336 let (offset, size) = match wasm_op {
1337 WasmOp::I32Load { offset, .. } | WasmOp::I32Store { offset, .. } => (*offset, 4u32),
1338 WasmOp::I32Load16S { offset, .. }
1339 | WasmOp::I32Load16U { offset, .. }
1340 | WasmOp::I32Store16 { offset, .. } => (*offset, 2),
1341 WasmOp::I32Load8S { offset, .. }
1342 | WasmOp::I32Load8U { offset, .. }
1343 | WasmOp::I32Store8 { offset, .. } => (*offset, 1),
1344 other => panic!("not a guarded i32 access: {other:?}"),
1345 };
1346 let addr = MemAddr::reg_imm(Reg::R11, Reg::R0, offset as i32);
1347 let access = match wasm_op {
1348 WasmOp::I32Load { .. } => ArmOp::Ldr { rd: Reg::R0, addr },
1349 WasmOp::I32Load8S { .. } => ArmOp::Ldrsb { rd: Reg::R0, addr },
1350 WasmOp::I32Load8U { .. } => ArmOp::Ldrb { rd: Reg::R0, addr },
1351 WasmOp::I32Load16S { .. } => ArmOp::Ldrsh { rd: Reg::R0, addr },
1352 WasmOp::I32Load16U { .. } => ArmOp::Ldrh { rd: Reg::R0, addr },
1353 WasmOp::I32Store { .. } => ArmOp::Str { rd: Reg::R1, addr },
1354 WasmOp::I32Store8 { .. } => ArmOp::Strb { rd: Reg::R1, addr },
1355 WasmOp::I32Store16 { .. } => ArmOp::Strh { rd: Reg::R1, addr },
1356 other => panic!("not a guarded i32 access: {other:?}"),
1357 };
1358 let mut ops = InstructionSelector::software_bounds_guard(Reg::R0, offset as i32, size);
1359 ops.push(access);
1360 ops
1361 }
1362
1363 #[test]
1366 fn load_without_bounds_guard_is_rejected() {
1367 use synth_synthesis::rules::MemAddr;
1368 with_verification_context(|| {
1369 let validator = TranslationValidator::new();
1370 let arm_ops = [ArmOp::Ldr {
1371 rd: Reg::R0,
1372 addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 0),
1373 }];
1374 let result = validator
1375 .verify_trap_preservation(
1376 &WasmOp::I32Load {
1377 offset: 0,
1378 align: 2,
1379 },
1380 &arm_ops,
1381 )
1382 .unwrap();
1383 assert!(
1384 matches!(result, ValidationResult::Invalid { .. }),
1385 "guard-stripped load must be Invalid, got {result:?}"
1386 );
1387 });
1388 }
1389
1390 #[test]
1394 fn byte_load_software_bounds_guard_preserves_the_trap() {
1395 with_verification_context(|| {
1396 let validator = TranslationValidator::new();
1397 let result = validator
1398 .verify_trap_preservation(
1399 &WasmOp::I32Load8U {
1400 offset: 0,
1401 align: 0,
1402 },
1403 &shipped_software_bounds_ops(&WasmOp::I32Load8U {
1404 offset: 0,
1405 align: 0,
1406 }),
1407 )
1408 .unwrap();
1409 assert_eq!(result, ValidationResult::Verified);
1410 });
1411 }
1412
1413 #[test]
1424 fn word_load_software_bounds_guard_survives_the_address_top_752() {
1425 with_verification_context(|| {
1426 let validator = TranslationValidator::new();
1427 let result = validator
1428 .verify_trap_preservation(
1429 &WasmOp::I32Load {
1430 offset: 0,
1431 align: 2,
1432 },
1433 &shipped_software_bounds_ops(&WasmOp::I32Load {
1434 offset: 0,
1435 align: 2,
1436 }),
1437 )
1438 .unwrap();
1439 assert_eq!(
1440 result,
1441 ValidationResult::Verified,
1442 "the #752 wraparound divergence must be closed for every addr"
1443 );
1444 });
1445 }
1446
1447 #[test]
1451 fn all_load_widths_software_bounds_guard_verify_752() {
1452 let cases: Vec<WasmOp> = vec![
1453 WasmOp::I32Load {
1454 offset: 4,
1455 align: 2,
1456 },
1457 WasmOp::I32Load8S {
1458 offset: 3,
1459 align: 0,
1460 },
1461 WasmOp::I32Load8U {
1462 offset: 1,
1463 align: 0,
1464 },
1465 WasmOp::I32Load16S {
1466 offset: 2,
1467 align: 1,
1468 },
1469 WasmOp::I32Load16U {
1470 offset: 0,
1471 align: 1,
1472 },
1473 ];
1474 with_verification_context(|| {
1475 let validator = TranslationValidator::new();
1476 for wasm_op in &cases {
1477 let result = validator
1478 .verify_trap_preservation(wasm_op, &shipped_software_bounds_ops(wasm_op))
1479 .unwrap();
1480 assert_eq!(result, ValidationResult::Verified, "{wasm_op:?}");
1481 }
1482 });
1483 }
1484
1485 #[test]
1488 fn store_software_bounds_guard_verifies_752() {
1489 let cases: Vec<WasmOp> = vec![
1490 WasmOp::I32Store {
1491 offset: 0,
1492 align: 2,
1493 },
1494 WasmOp::I32Store8 {
1495 offset: 5,
1496 align: 0,
1497 },
1498 WasmOp::I32Store16 {
1499 offset: 3,
1500 align: 1,
1501 },
1502 ];
1503 with_verification_context(|| {
1504 let validator = TranslationValidator::new();
1505 for wasm_op in &cases {
1506 let result = validator
1507 .verify_trap_preservation(wasm_op, &shipped_software_bounds_ops(wasm_op))
1508 .unwrap();
1509 assert_eq!(result, ValidationResult::Verified, "{wasm_op:?}");
1510 }
1511 });
1512 }
1513
1514 #[test]
1518 fn large_offset_software_bounds_guard_verifies_752() {
1519 let wasm_op = WasmOp::I32Load {
1520 offset: 0x2000,
1521 align: 2,
1522 };
1523 with_verification_context(|| {
1524 let validator = TranslationValidator::new();
1525 let result = validator
1526 .verify_trap_preservation(&wasm_op, &shipped_software_bounds_ops(&wasm_op))
1527 .unwrap();
1528 assert_eq!(result, ValidationResult::Verified);
1529 });
1530 }
1531
1532 #[test]
1536 fn offset_overflow_software_bounds_guard_always_traps_752() {
1537 let wasm_op = WasmOp::I32Load {
1538 offset: u32::MAX,
1539 align: 2,
1540 };
1541 with_verification_context(|| {
1542 let validator = TranslationValidator::new();
1543 let result = validator
1544 .verify_trap_preservation(&wasm_op, &shipped_software_bounds_ops(&wasm_op))
1545 .unwrap();
1546 assert_eq!(result, ValidationResult::Verified);
1547 });
1548 }
1549
1550 #[test]
1555 fn retired_add_computed_guard_stays_invalid_at_the_address_top_752() {
1556 use synth_synthesis::rules::{Condition, MemAddr, Operand2};
1557 with_verification_context(|| {
1558 let validator = TranslationValidator::new();
1559 let arm_ops = [
1560 ArmOp::Add {
1561 rd: Reg::R12,
1562 rn: Reg::R0,
1563 op2: Operand2::Imm(3), },
1565 ArmOp::Cmp {
1566 rn: Reg::R12,
1567 op2: Operand2::Reg(Reg::R10),
1568 },
1569 ArmOp::BCondOffset {
1570 cond: Condition::LO,
1571 offset: 0,
1572 },
1573 ArmOp::Udf { imm: 0 },
1574 ArmOp::Ldr {
1575 rd: Reg::R0,
1576 addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 0),
1577 },
1578 ];
1579 let result = validator
1580 .verify_trap_preservation(
1581 &WasmOp::I32Load {
1582 offset: 0,
1583 align: 2,
1584 },
1585 &arm_ops,
1586 )
1587 .unwrap();
1588 match result {
1589 ValidationResult::Invalid { counterexample } => {
1590 let addr = counterexample
1591 .iter()
1592 .find(|(n, _)| n == "input_0")
1593 .map(|(_, v)| *v)
1594 .expect("counterexample must assign the address");
1595 assert!(
1596 addr >= 0xFFFF_FFFD,
1597 "the divergence is the 32-bit end-address wrap at the top \
1598 of the address space, got addr {addr:#x}"
1599 );
1600 }
1601 other => panic!("the retired wrapping guard must stay Invalid, got {other:?}"),
1602 }
1603 });
1604 }
1605
1606 #[test]
1611 fn wraparound_safe_bounds_guard_verifies() {
1612 use synth_synthesis::rules::MemAddr;
1613 with_verification_context(|| {
1614 let validator = TranslationValidator::new();
1615 let k = 4; let arm_ops = [
1617 ArmOp::Cmp {
1619 rn: Reg::R10,
1620 op2: Operand2::Imm(k),
1621 },
1622 ArmOp::BCondOffset {
1623 cond: Condition::HS,
1624 offset: 0,
1625 },
1626 ArmOp::Udf { imm: 0 },
1627 ArmOp::Sub {
1630 rd: Reg::R12,
1631 rn: Reg::R10,
1632 op2: Operand2::Imm(k),
1633 },
1634 ArmOp::Cmp {
1635 rn: Reg::R0,
1636 op2: Operand2::Reg(Reg::R12),
1637 },
1638 ArmOp::BCondOffset {
1639 cond: Condition::LS,
1640 offset: 0,
1641 },
1642 ArmOp::Udf { imm: 0 },
1643 ArmOp::Ldr {
1644 rd: Reg::R0,
1645 addr: MemAddr::reg_imm(Reg::R11, Reg::R0, 0),
1646 },
1647 ];
1648 let result = validator
1649 .verify_trap_preservation(
1650 &WasmOp::I32Load {
1651 offset: 0,
1652 align: 2,
1653 },
1654 &arm_ops,
1655 )
1656 .unwrap();
1657 assert_eq!(result, ValidationResult::Verified);
1658 });
1659 }
1660
1661 fn shipped_trunc_f32_guard(signed: bool) -> Vec<ArmOp> {
1668 use synth_synthesis::rules::VfpReg;
1669 let (hi, lo) = if signed {
1670 (2147483648.0_f32, -2147483648.0_f32)
1671 } else {
1672 (4294967296.0_f32, -1.0_f32)
1673 };
1674 let mut ops = Vec::new();
1675 let guard = |ops: &mut Vec<ArmOp>, bound: f32, upper: bool| {
1676 ops.push(ArmOp::F32Const {
1677 sd: VfpReg::S1,
1678 value: bound,
1679 });
1680 let cmp = if upper {
1681 ArmOp::F32Lt {
1682 rd: Reg::R0,
1683 sn: VfpReg::S0,
1684 sm: VfpReg::S1,
1685 }
1686 } else if signed {
1687 ArmOp::F32Ge {
1688 rd: Reg::R0,
1689 sn: VfpReg::S0,
1690 sm: VfpReg::S1,
1691 }
1692 } else {
1693 ArmOp::F32Gt {
1694 rd: Reg::R0,
1695 sn: VfpReg::S0,
1696 sm: VfpReg::S1,
1697 }
1698 };
1699 ops.push(cmp);
1700 ops.push(ArmOp::Cmp {
1701 rn: Reg::R0,
1702 op2: Operand2::Imm(0),
1703 });
1704 ops.push(ArmOp::BCondOffset {
1705 cond: Condition::NE,
1706 offset: 0,
1707 });
1708 ops.push(ArmOp::Udf { imm: 0 });
1709 };
1710 guard(&mut ops, hi, true);
1711 guard(&mut ops, lo, false);
1712 if signed {
1713 ops.push(ArmOp::I32TruncF32S {
1714 rd: Reg::R0,
1715 sm: VfpReg::S0,
1716 });
1717 } else {
1718 ops.push(ArmOp::I32TruncF32U {
1719 rd: Reg::R0,
1720 sm: VfpReg::S0,
1721 });
1722 }
1723 ops
1724 }
1725
1726 #[test]
1727 fn trunc_f32_s_domain_guard_preserves_the_trap() {
1728 with_verification_context(|| {
1729 let validator = TranslationValidator::new();
1730 let result = validator
1731 .verify_trap_preservation(&WasmOp::I32TruncF32S, &shipped_trunc_f32_guard(true))
1732 .unwrap();
1733 assert_eq!(result, ValidationResult::Verified);
1734 });
1735 }
1736
1737 #[test]
1738 fn trunc_f32_u_domain_guard_preserves_the_trap() {
1739 with_verification_context(|| {
1740 let validator = TranslationValidator::new();
1741 let result = validator
1742 .verify_trap_preservation(&WasmOp::I32TruncF32U, &shipped_trunc_f32_guard(false))
1743 .unwrap();
1744 assert_eq!(result, ValidationResult::Verified);
1745 });
1746 }
1747
1748 #[test]
1751 fn trunc_f32_without_domain_guard_is_rejected() {
1752 use synth_synthesis::rules::VfpReg;
1753 with_verification_context(|| {
1754 let validator = TranslationValidator::new();
1755 let arm_ops = [ArmOp::I32TruncF32S {
1756 rd: Reg::R0,
1757 sm: VfpReg::S0,
1758 }];
1759 let result = validator
1760 .verify_trap_preservation(&WasmOp::I32TruncF32S, &arm_ops)
1761 .unwrap();
1762 assert!(
1763 matches!(result, ValidationResult::Invalid { .. }),
1764 "guard-stripped trunc must be Invalid, got {result:?}"
1765 );
1766 });
1767 }
1768
1769 #[test]
1771 fn trunc_f32_with_only_upper_guard_is_rejected() {
1772 with_verification_context(|| {
1773 let validator = TranslationValidator::new();
1774 let mut arm_ops = shipped_trunc_f32_guard(true);
1775 arm_ops.drain(5..10);
1777 let result = validator
1778 .verify_trap_preservation(&WasmOp::I32TruncF32S, &arm_ops)
1779 .unwrap();
1780 assert!(
1781 matches!(result, ValidationResult::Invalid { .. }),
1782 "upper-only trunc guard must be Invalid, got {result:?}"
1783 );
1784 });
1785 }
1786
1787 fn shipped_trunc_f64_guard(signed: bool) -> Vec<ArmOp> {
1796 use synth_synthesis::rules::VfpReg;
1797 let (hi, lo) = if signed {
1798 (2147483648.0_f64, -2147483649.0_f64) } else {
1800 (4294967296.0_f64, -1.0_f64) };
1802 let mut ops = Vec::new();
1803 let guard = |ops: &mut Vec<ArmOp>, bound: f64, upper: bool| {
1804 ops.push(ArmOp::F64Const {
1805 dd: VfpReg::D1,
1806 value: bound,
1807 });
1808 let cmp = if upper {
1809 ArmOp::F64Lt {
1810 rd: Reg::R0,
1811 dn: VfpReg::D0,
1812 dm: VfpReg::D1,
1813 }
1814 } else {
1815 ArmOp::F64Gt {
1816 rd: Reg::R0,
1817 dn: VfpReg::D0,
1818 dm: VfpReg::D1,
1819 }
1820 };
1821 ops.push(cmp);
1822 ops.push(ArmOp::Cmp {
1823 rn: Reg::R0,
1824 op2: Operand2::Imm(0),
1825 });
1826 ops.push(ArmOp::BCondOffset {
1827 cond: Condition::NE,
1828 offset: 0,
1829 });
1830 ops.push(ArmOp::Udf { imm: 0 });
1831 };
1832 guard(&mut ops, hi, true); guard(&mut ops, lo, false); if signed {
1835 ops.push(ArmOp::I32TruncF64S {
1836 rd: Reg::R0,
1837 dm: VfpReg::D0,
1838 });
1839 } else {
1840 ops.push(ArmOp::I32TruncF64U {
1841 rd: Reg::R0,
1842 dm: VfpReg::D0,
1843 });
1844 }
1845 ops
1846 }
1847
1848 #[test]
1849 fn trunc_f64_s_domain_guard_preserves_the_trap() {
1850 with_verification_context(|| {
1851 let validator = TranslationValidator::new();
1852 let result = validator
1853 .verify_trap_preservation(&WasmOp::I32TruncF64S, &shipped_trunc_f64_guard(true))
1854 .unwrap();
1855 assert_eq!(
1856 result,
1857 ValidationResult::Verified,
1858 "GREEN: correct f64→i32_s domain guard must be Verified (Unsat)"
1859 );
1860 });
1861 }
1862
1863 #[test]
1864 fn trunc_f64_u_domain_guard_preserves_the_trap() {
1865 with_verification_context(|| {
1866 let validator = TranslationValidator::new();
1867 let result = validator
1868 .verify_trap_preservation(&WasmOp::I32TruncF64U, &shipped_trunc_f64_guard(false))
1869 .unwrap();
1870 assert_eq!(
1871 result,
1872 ValidationResult::Verified,
1873 "GREEN: correct f64→i32_u domain guard must be Verified (Unsat)"
1874 );
1875 });
1876 }
1877
1878 #[test]
1881 fn trunc_f64_without_domain_guard_is_rejected() {
1882 use synth_synthesis::rules::VfpReg;
1883 with_verification_context(|| {
1884 let validator = TranslationValidator::new();
1885 let arm_ops = [ArmOp::I32TruncF64S {
1886 rd: Reg::R0,
1887 dm: VfpReg::D0,
1888 }];
1889 let result = validator
1890 .verify_trap_preservation(&WasmOp::I32TruncF64S, &arm_ops)
1891 .unwrap();
1892 assert!(
1893 matches!(result, ValidationResult::Invalid { .. }),
1894 "RED: guard-stripped f64 trunc must be Invalid (Sat), got {result:?}"
1895 );
1896 });
1897 }
1898
1899 #[test]
1902 fn trunc_f64_with_only_upper_guard_is_rejected() {
1903 with_verification_context(|| {
1904 let validator = TranslationValidator::new();
1905 let mut arm_ops = shipped_trunc_f64_guard(true);
1906 arm_ops.drain(5..10);
1908 let result = validator
1909 .verify_trap_preservation(&WasmOp::I32TruncF64S, &arm_ops)
1910 .unwrap();
1911 assert!(
1912 matches!(result, ValidationResult::Invalid { .. }),
1913 "RED: upper-only f64 trunc guard must be Invalid (Sat), got {result:?}"
1914 );
1915 });
1916 }
1917
1918 fn shipped_i64_div_rem(op: &WasmOp, elide_zero: bool, elide_overflow: bool) -> Vec<ArmOp> {
1925 let arm = match op {
1926 WasmOp::I64DivS => ArmOp::I64DivS {
1927 rdlo: Reg::R0,
1928 rdhi: Reg::R1,
1929 rnlo: Reg::R0,
1930 rnhi: Reg::R1,
1931 rmlo: Reg::R2,
1932 rmhi: Reg::R3,
1933 elide_zero_guard: elide_zero,
1934 elide_overflow_guard: elide_overflow,
1935 },
1936 WasmOp::I64DivU => ArmOp::I64DivU {
1937 rdlo: Reg::R0,
1938 rdhi: Reg::R1,
1939 rnlo: Reg::R0,
1940 rnhi: Reg::R1,
1941 rmlo: Reg::R2,
1942 rmhi: Reg::R3,
1943 elide_zero_guard: elide_zero,
1944 },
1945 WasmOp::I64RemS => ArmOp::I64RemS {
1946 rdlo: Reg::R0,
1947 rdhi: Reg::R1,
1948 rnlo: Reg::R0,
1949 rnhi: Reg::R1,
1950 rmlo: Reg::R2,
1951 rmhi: Reg::R3,
1952 elide_zero_guard: elide_zero,
1953 },
1954 WasmOp::I64RemU => ArmOp::I64RemU {
1955 rdlo: Reg::R0,
1956 rdhi: Reg::R1,
1957 rnlo: Reg::R0,
1958 rnhi: Reg::R1,
1959 rmlo: Reg::R2,
1960 rmhi: Reg::R3,
1961 elide_zero_guard: elide_zero,
1962 },
1963 _ => unreachable!("shipped_i64_div_rem: not an i64 div/rem op"),
1964 };
1965 vec![arm]
1966 }
1967
1968 #[test]
1969 fn i64_div_rem_all_four_full_guards_preserve_the_trap() {
1970 with_verification_context(|| {
1971 let validator = TranslationValidator::new();
1972 for op in [
1973 WasmOp::I64DivU,
1974 WasmOp::I64DivS,
1975 WasmOp::I64RemU,
1976 WasmOp::I64RemS,
1977 ] {
1978 let arm_ops = shipped_i64_div_rem(&op, false, false);
1979 let result = validator.verify_trap_preservation(&op, &arm_ops).unwrap();
1980 assert_eq!(
1981 result,
1982 ValidationResult::Verified,
1983 "GREEN: {op:?} with full guards must be Verified (Unsat)"
1984 );
1985 }
1986 });
1987 }
1988
1989 #[test]
1993 fn i64_div_rem_dropped_zero_guard_is_rejected() {
1994 with_verification_context(|| {
1995 let validator = TranslationValidator::new();
1996 for op in [
1997 WasmOp::I64DivU,
1998 WasmOp::I64DivS,
1999 WasmOp::I64RemU,
2000 WasmOp::I64RemS,
2001 ] {
2002 let arm_ops = shipped_i64_div_rem(&op, true, false);
2003 let result = validator.verify_trap_preservation(&op, &arm_ops).unwrap();
2004 assert!(
2005 matches!(result, ValidationResult::Invalid { .. }),
2006 "RED: {op:?} with the ÷0 guard dropped must be Invalid (Sat), got {result:?}"
2007 );
2008 }
2009 });
2010 }
2011
2012 #[test]
2015 fn i64_div_s_dropped_overflow_guard_is_rejected() {
2016 with_verification_context(|| {
2017 let validator = TranslationValidator::new();
2018 let arm_ops = shipped_i64_div_rem(&WasmOp::I64DivS, false, true);
2019 let result = validator
2020 .verify_trap_preservation(&WasmOp::I64DivS, &arm_ops)
2021 .unwrap();
2022 assert!(
2023 matches!(result, ValidationResult::Invalid { .. }),
2024 "RED: i64.div_s with the overflow guard dropped must be Invalid (Sat), got {result:?}"
2025 );
2026 });
2027 }
2028
2029 #[test]
2031 fn i64_div_s_dropped_both_guards_is_rejected() {
2032 with_verification_context(|| {
2033 let validator = TranslationValidator::new();
2034 let arm_ops = shipped_i64_div_rem(&WasmOp::I64DivS, true, 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 both guards dropped must be Invalid (Sat), got {result:?}"
2041 );
2042 });
2043 }
2044
2045 #[test]
2049 fn i64_div_s_overflow_field_is_load_bearing() {
2050 with_verification_context(|| {
2051 let validator = TranslationValidator::new();
2052 let full = validator
2053 .verify_trap_preservation(
2054 &WasmOp::I64DivS,
2055 &shipped_i64_div_rem(&WasmOp::I64DivS, false, false),
2056 )
2057 .unwrap();
2058 let overflow_dropped = validator
2059 .verify_trap_preservation(
2060 &WasmOp::I64DivS,
2061 &shipped_i64_div_rem(&WasmOp::I64DivS, false, true),
2062 )
2063 .unwrap();
2064 assert_eq!(full, ValidationResult::Verified);
2065 assert!(matches!(overflow_dropped, ValidationResult::Invalid { .. }));
2066 assert_ne!(
2067 full, overflow_dropped,
2068 "non-vacuity: the overflow-guard field must change the verdict"
2069 );
2070 });
2071 }
2072
2073 #[test]
2079 fn dump_756_non_vacuity_verdicts() {
2080 with_verification_context(|| {
2081 let validator = TranslationValidator::new();
2082 let raw = |r: &ValidationResult| match r {
2083 ValidationResult::Verified => "Verified/Unsat (trap PRESERVED)",
2084 ValidationResult::Invalid { .. } => "Invalid/Sat (trap DROPPED — caught)",
2085 ValidationResult::Unknown { .. } => "Unknown",
2086 };
2087 println!("\n=== #756 live trap-preservation non-vacuity ===");
2088 for (op, oflow_field) in [
2089 (WasmOp::I64DivU, false),
2090 (WasmOp::I64DivS, true),
2091 (WasmOp::I64RemU, false),
2092 (WasmOp::I64RemS, false),
2093 ] {
2094 let green = validator
2095 .verify_trap_preservation(&op, &shipped_i64_div_rem(&op, false, false))
2096 .unwrap();
2097 let red = validator
2098 .verify_trap_preservation(&op, &shipped_i64_div_rem(&op, true, false))
2099 .unwrap();
2100 println!(
2101 " {op:?}: full-guards -> {} | drop-÷0 -> {}",
2102 raw(&green),
2103 raw(&red)
2104 );
2105 assert_ne!(
2106 green, red,
2107 "{op:?}: green and red must differ (non-vacuous)"
2108 );
2109 if oflow_field {
2110 let red_o = validator
2111 .verify_trap_preservation(&op, &shipped_i64_div_rem(&op, false, true))
2112 .unwrap();
2113 println!(" {op:?}: drop-overflow -> {}", raw(&red_o));
2114 assert_ne!(green, red_o);
2115 }
2116 }
2117 for (op, sgn) in [(WasmOp::I32TruncF64S, true), (WasmOp::I32TruncF64U, false)] {
2118 let green = validator
2119 .verify_trap_preservation(&op, &shipped_trunc_f64_guard(sgn))
2120 .unwrap();
2121 let bare = if sgn {
2122 vec![ArmOp::I32TruncF64S {
2123 rd: Reg::R0,
2124 dm: synth_synthesis::rules::VfpReg::D0,
2125 }]
2126 } else {
2127 vec![ArmOp::I32TruncF64U {
2128 rd: Reg::R0,
2129 dm: synth_synthesis::rules::VfpReg::D0,
2130 }]
2131 };
2132 let red = validator.verify_trap_preservation(&op, &bare).unwrap();
2133 println!(
2134 " {op:?}: domain-guard -> {} | bare-VCVT -> {}",
2135 raw(&green),
2136 raw(&red)
2137 );
2138 assert_ne!(
2139 green, red,
2140 "{op:?}: green and red must differ (non-vacuous)"
2141 );
2142 }
2143 println!("=== all rows discriminate: gate is non-vacuous ===\n");
2144 });
2145 }
2146
2147 fn i64_rem_with_dest(op: &WasmOp, rdlo: Reg, rdhi: Reg) -> Vec<ArmOp> {
2159 let arm = match op {
2160 WasmOp::I64RemU => ArmOp::I64RemU {
2161 rdlo,
2162 rdhi,
2163 rnlo: Reg::R0,
2164 rnhi: Reg::R1,
2165 rmlo: Reg::R2,
2166 rmhi: Reg::R3,
2167 elide_zero_guard: false,
2168 },
2169 WasmOp::I64RemS => ArmOp::I64RemS {
2170 rdlo,
2171 rdhi,
2172 rnlo: Reg::R0,
2173 rnhi: Reg::R1,
2174 rmlo: Reg::R2,
2175 rmhi: Reg::R3,
2176 elide_zero_guard: false,
2177 },
2178 _ => unreachable!("i64_rem_with_dest: not an i64 rem op"),
2179 };
2180 vec![arm]
2181 }
2182
2183 #[test]
2187 fn i64_rem_shipped_value_is_verified() {
2188 with_verification_context(|| {
2189 let validator = TranslationValidator::new();
2190 for op in [WasmOp::I64RemU, WasmOp::I64RemS] {
2191 let arm_ops = shipped_i64_div_rem(&op, false, false);
2192 let result = validator.verify_trap_preservation(&op, &arm_ops).unwrap();
2193 assert_eq!(
2194 result,
2195 ValidationResult::Verified,
2196 "GREEN: {op:?} shipped lowering (correct value + ÷0 guard) must be Verified"
2197 );
2198 }
2199 });
2200 }
2201
2202 #[test]
2210 fn i64_rem_wrong_destination_register_is_rejected() {
2211 with_verification_context(|| {
2212 let validator = TranslationValidator::new();
2213 for op in [WasmOp::I64RemU, WasmOp::I64RemS] {
2214 let arm_ops = i64_rem_with_dest(&op, Reg::R2, Reg::R3);
2215 let result = validator.verify_trap_preservation(&op, &arm_ops).unwrap();
2216 assert!(
2217 matches!(result, ValidationResult::Invalid { .. }),
2218 "RED: {op:?} writing the remainder to R2:R3 (not the ABI R0:R1) \
2219 leaves R0:R1 non-remainder — must be Invalid, got {result:?}"
2220 );
2221 }
2222 });
2223 }
2224
2225 #[test]
2238 fn i64_rem_wrong_signedness_is_rejected() {
2239 with_verification_context(|| {
2240 let validator = TranslationValidator::new();
2241 for (spec, lowered) in [
2243 (WasmOp::I64RemU, WasmOp::I64RemS),
2244 (WasmOp::I64RemS, WasmOp::I64RemU),
2245 ] {
2246 let arm_ops = i64_rem_with_dest(&lowered, Reg::R0, Reg::R1);
2247 let result = validator.verify_trap_preservation(&spec, &arm_ops).unwrap();
2248 assert!(
2249 matches!(result, ValidationResult::Invalid { .. }),
2250 "RED: {spec:?} lowered as {lowered:?} computes the wrong remainder \
2251 into the right registers — must be Invalid, got {result:?}"
2252 );
2253 }
2254 });
2255 }
2256
2257 #[test]
2262 fn i64_rem_destination_field_is_load_bearing() {
2263 with_verification_context(|| {
2264 let validator = TranslationValidator::new();
2265 for op in [WasmOp::I64RemU, WasmOp::I64RemS] {
2266 let correct = validator
2267 .verify_trap_preservation(&op, &i64_rem_with_dest(&op, Reg::R0, Reg::R1))
2268 .unwrap();
2269 let wrong = validator
2270 .verify_trap_preservation(&op, &i64_rem_with_dest(&op, Reg::R2, Reg::R3))
2271 .unwrap();
2272 assert_eq!(correct, ValidationResult::Verified, "{op:?} correct dest");
2273 assert!(
2274 matches!(wrong, ValidationResult::Invalid { .. }),
2275 "{op:?} wrong dest"
2276 );
2277 assert_ne!(
2278 correct, wrong,
2279 "non-vacuity: {op:?} destination register must change the verdict"
2280 );
2281 }
2282 });
2283 }
2284
2285 #[test]
2293 fn i64_rem_value_model_closes_a_trap_only_gap() {
2294 with_verification_context(|| {
2295 let validator = TranslationValidator::new();
2296 for op in [WasmOp::I64RemU, WasmOp::I64RemS] {
2297 let wrong = i64_rem_with_dest(&op, Reg::R2, Reg::R3);
2298 let trap_only = validator
2300 .verify_i64_div_rem_trap_preservation(&op, &wrong)
2301 .unwrap();
2302 assert_eq!(
2303 trap_only,
2304 ValidationResult::Verified,
2305 "the trap-ONLY VC is blind to the wrong destination (this is the \
2306 havoc-era gap): {op:?} expected Verified, got {trap_only:?}"
2307 );
2308 let value = validator.verify_trap_preservation(&op, &wrong).unwrap();
2310 assert!(
2311 matches!(value, ValidationResult::Invalid { .. }),
2312 "the value VC catches it: {op:?} expected Invalid, got {value:?}"
2313 );
2314 assert_ne!(trap_only, value, "the two gates must disagree ({op:?})");
2315 }
2316 });
2317 }
2318
2319 fn call_indirect_pseudo(
2322 table_size: u32,
2323 null_check: bool,
2324 type_check: Option<(u32, u32)>,
2325 ) -> ArmOp {
2326 ArmOp::CallIndirect {
2327 rd: Reg::R0,
2328 type_idx: 0,
2329 table_index_reg: Reg::R0,
2330 table_size,
2331 table_byte_offset: 0,
2332 null_check,
2333 type_check,
2334 }
2335 }
2336
2337 #[test]
2338 fn call_indirect_matching_guards_preserve_the_traps() {
2339 with_verification_context(|| {
2340 let validator = TranslationValidator::new();
2341 let result = validator
2343 .verify_call_indirect_trap_preservation(
2344 &call_indirect_pseudo(8, false, None),
2345 &CallIndirectSpec {
2346 table_size: 8,
2347 may_have_null_slot: false,
2348 heterogeneous_expected_type: None,
2349 },
2350 )
2351 .unwrap();
2352 assert_eq!(result, ValidationResult::Verified);
2353 let result = validator
2355 .verify_call_indirect_trap_preservation(
2356 &call_indirect_pseudo(8, true, Some((3, 32))),
2357 &CallIndirectSpec {
2358 table_size: 8,
2359 may_have_null_slot: true,
2360 heterogeneous_expected_type: Some(3),
2361 },
2362 )
2363 .unwrap();
2364 assert_eq!(result, ValidationResult::Verified);
2365 });
2366 }
2367
2368 #[test]
2371 fn call_indirect_dropped_null_check_is_rejected() {
2372 with_verification_context(|| {
2373 let validator = TranslationValidator::new();
2374 let result = validator
2375 .verify_call_indirect_trap_preservation(
2376 &call_indirect_pseudo(8, false, None),
2377 &CallIndirectSpec {
2378 table_size: 8,
2379 may_have_null_slot: true,
2380 heterogeneous_expected_type: None,
2381 },
2382 )
2383 .unwrap();
2384 assert!(
2385 matches!(result, ValidationResult::Invalid { .. }),
2386 "dropped null check must be Invalid, got {result:?}"
2387 );
2388 });
2389 }
2390
2391 #[test]
2394 fn call_indirect_wrong_table_size_is_rejected() {
2395 with_verification_context(|| {
2396 let validator = TranslationValidator::new();
2397 let result = validator
2398 .verify_call_indirect_trap_preservation(
2399 &call_indirect_pseudo(16, false, None),
2400 &CallIndirectSpec {
2401 table_size: 8,
2402 may_have_null_slot: false,
2403 heterogeneous_expected_type: None,
2404 },
2405 )
2406 .unwrap();
2407 assert!(
2408 matches!(result, ValidationResult::Invalid { .. }),
2409 "wrong bounds size must be Invalid, got {result:?}"
2410 );
2411 });
2412 }
2413
2414 #[test]
2417 fn call_indirect_dropped_type_check_is_rejected() {
2418 with_verification_context(|| {
2419 let validator = TranslationValidator::new();
2420 let result = validator
2421 .verify_call_indirect_trap_preservation(
2422 &call_indirect_pseudo(8, true, None),
2423 &CallIndirectSpec {
2424 table_size: 8,
2425 may_have_null_slot: true,
2426 heterogeneous_expected_type: Some(3),
2427 },
2428 )
2429 .unwrap();
2430 assert!(
2431 matches!(result, ValidationResult::Invalid { .. }),
2432 "dropped type check must be Invalid, got {result:?}"
2433 );
2434 });
2435 }
2436
2437 #[test]
2440 fn verify_rule_routes_partial_ops_through_the_trap_gate() {
2441 with_verification_context(|| {
2442 let validator = TranslationValidator::new();
2443 let rule = SynthesisRule {
2446 name: "i32.div_u → bare UDIV (trap-dropping)".into(),
2447 priority: 0,
2448 pattern: Pattern::WasmInstr(WasmOp::I32DivU),
2449 replacement: Replacement::ArmInstr(ArmOp::Udiv {
2450 rd: Reg::R0,
2451 rn: Reg::R0,
2452 rm: Reg::R1,
2453 }),
2454 cost: Cost {
2455 cycles: 1,
2456 code_size: 4,
2457 registers: 2,
2458 },
2459 };
2460 let result = validator.verify_rule(&rule).unwrap();
2461 assert!(
2462 matches!(result, ValidationResult::Invalid { .. }),
2463 "verify_rule must reject the trap-dropping div rule, got {result:?}"
2464 );
2465
2466 let rule = SynthesisRule {
2468 name: "i32.div_u → guarded UDIV".into(),
2469 priority: 0,
2470 pattern: Pattern::WasmInstr(WasmOp::I32DivU),
2471 replacement: Replacement::ArmSequence(shipped_divu_guard()),
2472 cost: Cost {
2473 cycles: 4,
2474 code_size: 10,
2475 registers: 2,
2476 },
2477 };
2478 assert_eq!(
2479 validator.verify_rule(&rule).unwrap(),
2480 ValidationResult::Verified
2481 );
2482 });
2483 }
2484
2485 #[test]
2486 fn trap_preservation_gate_rejects_non_div_ops() {
2487 with_verification_context(|| {
2488 let validator = TranslationValidator::new();
2489 let err = validator
2490 .verify_div_rem_trap_preservation(&WasmOp::I32Add, &[])
2491 .unwrap_err();
2492 assert!(matches!(err, VerificationError::UnsupportedOperation(_)));
2493 let err64 = validator
2496 .verify_div_rem_trap_preservation(&WasmOp::I64DivU, &[])
2497 .unwrap_err();
2498 assert!(matches!(err64, VerificationError::UnsupportedOperation(_)));
2499 });
2500 }
2501
2502 #[test]
2503 fn test_verify_add_correct() {
2504 with_verification_context(|| {
2505 let validator = TranslationValidator::new();
2506
2507 let rule = create_test_rule(
2508 WasmOp::I32Add,
2509 ArmOp::Add {
2510 rd: Reg::R0,
2511 rn: Reg::R0,
2512 op2: Operand2::Reg(Reg::R1),
2513 },
2514 );
2515
2516 let result = validator.verify_rule(&rule).unwrap();
2517 assert_eq!(result, ValidationResult::Verified);
2518 });
2519 }
2520
2521 #[test]
2522 fn test_verify_sub_correct() {
2523 with_verification_context(|| {
2524 let validator = TranslationValidator::new();
2525
2526 let rule = create_test_rule(
2527 WasmOp::I32Sub,
2528 ArmOp::Sub {
2529 rd: Reg::R0,
2530 rn: Reg::R0,
2531 op2: Operand2::Reg(Reg::R1),
2532 },
2533 );
2534
2535 let result = validator.verify_rule(&rule).unwrap();
2536 assert_eq!(result, ValidationResult::Verified);
2537 });
2538 }
2539
2540 #[test]
2541 fn test_verify_mul_correct() {
2542 with_verification_context(|| {
2543 let validator = TranslationValidator::new();
2544
2545 let rule = create_test_rule(
2546 WasmOp::I32Mul,
2547 ArmOp::Mul {
2548 rd: Reg::R0,
2549 rn: Reg::R0,
2550 rm: Reg::R1,
2551 },
2552 );
2553
2554 let result = validator.verify_rule(&rule).unwrap();
2555 assert_eq!(result, ValidationResult::Verified);
2556 });
2557 }
2558
2559 #[test]
2560 fn test_verify_and_correct() {
2561 with_verification_context(|| {
2562 let validator = TranslationValidator::new();
2563
2564 let rule = create_test_rule(
2565 WasmOp::I32And,
2566 ArmOp::And {
2567 rd: Reg::R0,
2568 rn: Reg::R0,
2569 op2: Operand2::Reg(Reg::R1),
2570 },
2571 );
2572
2573 let result = validator.verify_rule(&rule).unwrap();
2574 assert_eq!(result, ValidationResult::Verified);
2575 });
2576 }
2577
2578 #[test]
2579 fn test_verify_incorrect_rule() {
2580 with_verification_context(|| {
2581 let validator = TranslationValidator::new();
2582
2583 let rule = create_test_rule(
2585 WasmOp::I32Add,
2586 ArmOp::Sub {
2587 rd: Reg::R0,
2588 rn: Reg::R0,
2589 op2: Operand2::Reg(Reg::R1),
2590 },
2591 );
2592
2593 let result = validator.verify_rule(&rule).unwrap();
2594
2595 match result {
2596 ValidationResult::Invalid { counterexample } => {
2597 assert!(!counterexample.is_empty());
2598 }
2599 _ => panic!("Expected counterexample but got: {:?}", result),
2600 }
2601 });
2602 }
2603
2604 #[test]
2605 fn test_verify_bitwise_ops() {
2606 with_verification_context(|| {
2607 let validator = TranslationValidator::new();
2608
2609 let or_rule = create_test_rule(
2611 WasmOp::I32Or,
2612 ArmOp::Orr {
2613 rd: Reg::R0,
2614 rn: Reg::R0,
2615 op2: Operand2::Reg(Reg::R1),
2616 },
2617 );
2618 assert_eq!(
2619 validator.verify_rule(&or_rule).unwrap(),
2620 ValidationResult::Verified
2621 );
2622
2623 let xor_rule = create_test_rule(
2625 WasmOp::I32Xor,
2626 ArmOp::Eor {
2627 rd: Reg::R0,
2628 rn: Reg::R0,
2629 op2: Operand2::Reg(Reg::R1),
2630 },
2631 );
2632 assert_eq!(
2633 validator.verify_rule(&xor_rule).unwrap(),
2634 ValidationResult::Verified
2635 );
2636 });
2637 }
2638
2639 #[test]
2640 fn test_verify_shift_ops() {
2641 }
2646}