1use crate::arm_semantics::{ArmSemantics, ArmState};
22use crate::solver::{CheckOutcome, new_solver};
23use crate::term::BV;
24use crate::wasm_semantics::WasmSemantics;
25use synth_core::WasmOp;
26use synth_synthesis::{ArmOp, Reg, SynthesisRule};
27use thiserror::Error;
28
29fn arm_sequence_has_trap_guard(arm_ops: &[ArmOp]) -> bool {
36 arm_ops.iter().any(|op| matches!(op, ArmOp::Udf { .. }))
37}
38
39#[derive(Debug, Error)]
41pub enum VerificationError {
42 #[error("Translation is incorrect: counterexample found")]
43 CounterexampleFound {
44 wasm_result: String,
45 arm_result: String,
46 inputs: Vec<String>,
47 },
48
49 #[error("Verification timeout after {0}ms")]
50 Timeout(u64),
51
52 #[error("Unsupported operation: {0}")]
53 UnsupportedOperation(String),
54
55 #[error("SMT solver error: {0}")]
56 SolverError(String),
57
58 #[error("Invalid synthesis rule: {0}")]
59 InvalidRule(String),
60}
61
62#[derive(Debug, Clone, PartialEq)]
64pub enum ValidationResult {
65 Verified,
67
68 Invalid { counterexample: Vec<(String, i64)> },
70
71 Unknown { reason: String },
73}
74
75pub struct TranslationValidator {
79 wasm_encoder: WasmSemantics,
80 arm_encoder: ArmSemantics,
81 timeout_ms: u64,
82}
83
84impl Default for TranslationValidator {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90impl TranslationValidator {
91 pub fn new() -> Self {
93 Self {
94 wasm_encoder: WasmSemantics::new(),
95 arm_encoder: ArmSemantics::new(),
96 timeout_ms: 30000, }
98 }
99
100 pub fn set_timeout(&mut self, timeout_ms: u64) {
102 self.timeout_ms = timeout_ms;
103 }
104
105 pub fn verify_rule(&self, rule: &SynthesisRule) -> Result<ValidationResult, VerificationError> {
110 let wasm_op = match &rule.pattern {
112 synth_synthesis::Pattern::WasmInstr(op) => op,
113 _ => {
114 return Err(VerificationError::UnsupportedOperation(
115 "Only single WASM instruction patterns are supported".to_string(),
116 ));
117 }
118 };
119
120 let arm_ops = match &rule.replacement {
122 synth_synthesis::Replacement::ArmInstr(op) => vec![op.clone()],
123 synth_synthesis::Replacement::ArmSequence(ops) => ops.clone(),
124 _ => {
125 return Err(VerificationError::UnsupportedOperation(
126 "Only ARM instruction replacements are supported".to_string(),
127 ));
128 }
129 };
130
131 self.verify_equivalence(wasm_op, &arm_ops)
132 }
133
134 pub fn verify_equivalence(
136 &self,
137 wasm_op: &WasmOp,
138 arm_ops: &[ArmOp],
139 ) -> Result<ValidationResult, VerificationError> {
140 self.verify_equivalence_parameterized(wasm_op, arm_ops, &[])
141 }
142
143 pub fn verify_equivalence_parameterized(
145 &self,
146 wasm_op: &WasmOp,
147 arm_ops: &[ArmOp],
148 concrete_params: &[(usize, i64)],
149 ) -> Result<ValidationResult, VerificationError> {
150 let mut solver = new_solver();
151
152 let num_inputs = self.get_num_inputs(wasm_op);
154 let mut inputs: Vec<BV> = Vec::new();
155
156 for i in 0..num_inputs {
157 let input = if let Some((_, value)) = concrete_params.iter().find(|(idx, _)| *idx == i)
158 {
159 BV::from_i64(*value, 32)
161 } else {
162 BV::new_const(format!("input_{}", i), 32)
164 };
165 inputs.push(input);
166 }
167
168 let wasm_result = self.wasm_encoder.encode_op(wasm_op, &inputs);
170
171 let arm_result = self.encode_arm_sequence(arm_ops, &inputs)?;
173
174 solver.assert(&wasm_result.eq(&arm_result).not());
178
179 match solver.check() {
180 CheckOutcome::Unsat => {
181 Ok(ValidationResult::Verified)
183 }
184
185 CheckOutcome::Sat => {
186 let mut counterexample = Vec::new();
190 for (i, input) in inputs.iter().enumerate() {
191 if let Some(value) = solver.value(input)
192 && let Ok(int_val) = i64::try_from(value)
193 {
194 counterexample.push((format!("input_{}", i), int_val));
195 }
196 }
197
198 Ok(ValidationResult::Invalid { counterexample })
199 }
200
201 CheckOutcome::Unknown(reason) => {
202 Ok(ValidationResult::Unknown {
204 reason: format!("SMT solver returned unknown: {reason}"),
205 })
206 }
207 }
208 }
209
210 fn encode_arm_sequence(
212 &self,
213 arm_ops: &[ArmOp],
214 inputs: &[BV],
215 ) -> Result<BV, VerificationError> {
216 let mut state = ArmState::new_symbolic();
217
218 for (i, input) in inputs.iter().enumerate() {
220 let reg = match i {
221 0 => Reg::R0,
222 1 => Reg::R1,
223 2 => Reg::R2,
224 _ => {
225 return Err(VerificationError::UnsupportedOperation(format!(
226 "Too many inputs: {}",
227 inputs.len()
228 )));
229 }
230 };
231 state.set_reg(®, input.clone());
232 }
233
234 for arm_op in arm_ops {
236 self.arm_encoder.encode_op(arm_op, &mut state);
237 }
238
239 Ok(self.arm_encoder.extract_result(&state, &Reg::R0))
241 }
242
243 pub fn verify_parameterized_range<F>(
245 &self,
246 wasm_op: &WasmOp,
247 create_arm_ops: F,
248 param_index: usize,
249 range: std::ops::Range<i64>,
250 ) -> Result<ValidationResult, VerificationError>
251 where
252 F: Fn(i64) -> Vec<ArmOp>,
253 {
254 for value in range {
255 let arm_ops = create_arm_ops(value);
256 let result =
257 self.verify_equivalence_parameterized(wasm_op, &arm_ops, &[(param_index, value)])?;
258
259 match result {
260 ValidationResult::Verified => continue,
261 ValidationResult::Invalid { counterexample } => {
262 return Ok(ValidationResult::Invalid {
263 counterexample: counterexample
264 .into_iter()
265 .map(|(k, v)| (format!("{} (param={})", k, value), v))
266 .collect(),
267 });
268 }
269 ValidationResult::Unknown { reason } => {
270 return Ok(ValidationResult::Unknown {
271 reason: format!("Failed at param={}: {}", value, reason),
272 });
273 }
274 }
275 }
276
277 Ok(ValidationResult::Verified)
278 }
279
280 pub fn verify_div_rem_trap_preservation(
309 &self,
310 wasm_op: &WasmOp,
311 arm_ops: &[ArmOp],
312 ) -> Result<ValidationResult, VerificationError> {
313 let Some(div_op) = crate::trap::div_op(wasm_op) else {
314 return Err(VerificationError::UnsupportedOperation(format!(
315 "trap-preservation gate applies to div/rem only, got {wasm_op:?}"
316 )));
317 };
318 if !matches!(
322 wasm_op,
323 WasmOp::I32DivS | WasmOp::I32DivU | WasmOp::I32RemS | WasmOp::I32RemU
324 ) {
325 return Err(VerificationError::UnsupportedOperation(format!(
326 "trap-preservation gate currently supports i32 div/rem only, got {wasm_op:?}"
327 )));
328 }
329
330 let dividend = BV::new_const("input_0", 32);
333 let divisor = BV::new_const("input_1", 32);
334 let inputs = vec![dividend.clone(), divisor.clone()];
335
336 let wasm_value = self.wasm_encoder.encode_op(wasm_op, &inputs);
337 let arm_value = self.encode_arm_sequence(arm_ops, &inputs)?;
338
339 let orig = crate::trap::DefineOrTrap {
340 value: wasm_value,
341 may_trap: crate::trap::trap_div(div_op, ÷nd, &divisor),
342 };
343 let arm_may_trap = if arm_sequence_has_trap_guard(arm_ops) {
344 crate::trap::trap_div(div_op, ÷nd, &divisor)
345 } else {
346 crate::term::Bool::from_bool(false)
347 };
348 let opt = crate::trap::DefineOrTrap {
349 value: arm_value,
350 may_trap: arm_may_trap,
351 };
352
353 Ok(match crate::trap::prove_trap_equivalence(&orig, &opt) {
354 crate::trap::TrapVerdict::Preserved => ValidationResult::Verified,
355 crate::trap::TrapVerdict::Dropped(model) => ValidationResult::Invalid {
356 counterexample: model
357 .into_iter()
358 .filter_map(|(n, v)| i64::try_from(v).ok().map(|x| (n, x)))
359 .collect(),
360 },
361 crate::trap::TrapVerdict::Unknown => ValidationResult::Unknown {
362 reason: "trap-preservation VC returned Unknown (conservative reject)".to_string(),
363 },
364 })
365 }
366
367 fn get_num_inputs(&self, wasm_op: &WasmOp) -> usize {
369 use WasmOp::*;
370 match wasm_op {
371 I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or
373 | I32Xor | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr | I32Eq | I32Ne | I32LtS
374 | I32LtU | I32LeS | I32LeU | I32GtS | I32GtU | I32GeS | I32GeU => 2,
375
376 I32Clz | I32Ctz | I32Popcnt | I32Eqz => 1,
378
379 I32Const(_) => 0,
381
382 I32Load { .. } => 1, I32Store { .. } => 2, LocalGet(_) | GlobalGet(_) => 0,
388 LocalSet(_) | GlobalSet(_) | LocalTee(_) => 1,
389 Br(_) | BrIf(_) | Return => 0,
390
391 Drop => 1,
393 Select => 3, Nop | Unreachable | Block | Loop | If | Else | End => 0,
395
396 _ => 0,
398 }
399 }
400
401 pub fn verify_rules(
403 &self,
404 rules: &[SynthesisRule],
405 ) -> Vec<(String, Result<ValidationResult, VerificationError>)> {
406 rules
407 .iter()
408 .map(|rule| {
409 let result = self.verify_rule(rule);
410 (rule.name.clone(), result)
411 })
412 .collect()
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419 use crate::with_verification_context;
420 use synth_synthesis::{Cost, Operand2, Pattern, Replacement};
421
422 fn create_test_rule(wasm_op: WasmOp, arm_op: ArmOp) -> SynthesisRule {
423 SynthesisRule {
424 name: format!("{:?}", wasm_op),
425 priority: 0,
426 pattern: Pattern::WasmInstr(wasm_op),
427 replacement: Replacement::ArmInstr(arm_op),
428 cost: Cost {
429 cycles: 1,
430 code_size: 4,
431 registers: 2,
432 },
433 }
434 }
435
436 #[test]
439 fn div_lowering_without_guard_is_rejected_as_trap_drop() {
440 with_verification_context(|| {
441 let validator = TranslationValidator::new();
442 let arm_ops = [ArmOp::Udiv {
445 rd: Reg::R0,
446 rn: Reg::R0,
447 rm: Reg::R1,
448 }];
449 let result = validator
450 .verify_div_rem_trap_preservation(&WasmOp::I32DivU, &arm_ops)
451 .unwrap();
452 match result {
453 ValidationResult::Invalid { counterexample } => {
454 let divisor = counterexample.iter().find(|(n, _)| n == "input_1");
456 assert_eq!(
457 divisor.map(|(_, v)| *v),
458 Some(0),
459 "trap-drop counterexample must set the divisor to 0"
460 );
461 }
462 other => panic!("unguarded div must be Invalid, got {other:?}"),
463 }
464 });
465 }
466
467 #[test]
468 fn div_lowering_with_guard_preserves_the_trap() {
469 with_verification_context(|| {
470 let validator = TranslationValidator::new();
471 let arm_ops = [
474 ArmOp::Cmp {
475 rn: Reg::R1,
476 op2: Operand2::Imm(0),
477 },
478 ArmOp::Udf { imm: 0 },
479 ArmOp::Udiv {
480 rd: Reg::R0,
481 rn: Reg::R0,
482 rm: Reg::R1,
483 },
484 ];
485 let result = validator
486 .verify_div_rem_trap_preservation(&WasmOp::I32DivU, &arm_ops)
487 .unwrap();
488 assert_eq!(result, ValidationResult::Verified);
489 });
490 }
491
492 #[test]
493 fn signed_div_guard_preserves_both_zero_and_overflow_traps() {
494 with_verification_context(|| {
495 let validator = TranslationValidator::new();
496 let arm_ops = [
497 ArmOp::Udf { imm: 0 }, ArmOp::Sdiv {
499 rd: Reg::R0,
500 rn: Reg::R0,
501 rm: Reg::R1,
502 },
503 ];
504 let result = validator
505 .verify_div_rem_trap_preservation(&WasmOp::I32DivS, &arm_ops)
506 .unwrap();
507 assert_eq!(result, ValidationResult::Verified);
508 });
509 }
510
511 #[test]
512 fn trap_preservation_gate_rejects_non_div_ops() {
513 with_verification_context(|| {
514 let validator = TranslationValidator::new();
515 let err = validator
516 .verify_div_rem_trap_preservation(&WasmOp::I32Add, &[])
517 .unwrap_err();
518 assert!(matches!(err, VerificationError::UnsupportedOperation(_)));
519 let err64 = validator
522 .verify_div_rem_trap_preservation(&WasmOp::I64DivU, &[])
523 .unwrap_err();
524 assert!(matches!(err64, VerificationError::UnsupportedOperation(_)));
525 });
526 }
527
528 #[test]
529 fn test_verify_add_correct() {
530 with_verification_context(|| {
531 let validator = TranslationValidator::new();
532
533 let rule = create_test_rule(
534 WasmOp::I32Add,
535 ArmOp::Add {
536 rd: Reg::R0,
537 rn: Reg::R0,
538 op2: Operand2::Reg(Reg::R1),
539 },
540 );
541
542 let result = validator.verify_rule(&rule).unwrap();
543 assert_eq!(result, ValidationResult::Verified);
544 });
545 }
546
547 #[test]
548 fn test_verify_sub_correct() {
549 with_verification_context(|| {
550 let validator = TranslationValidator::new();
551
552 let rule = create_test_rule(
553 WasmOp::I32Sub,
554 ArmOp::Sub {
555 rd: Reg::R0,
556 rn: Reg::R0,
557 op2: Operand2::Reg(Reg::R1),
558 },
559 );
560
561 let result = validator.verify_rule(&rule).unwrap();
562 assert_eq!(result, ValidationResult::Verified);
563 });
564 }
565
566 #[test]
567 fn test_verify_mul_correct() {
568 with_verification_context(|| {
569 let validator = TranslationValidator::new();
570
571 let rule = create_test_rule(
572 WasmOp::I32Mul,
573 ArmOp::Mul {
574 rd: Reg::R0,
575 rn: Reg::R0,
576 rm: Reg::R1,
577 },
578 );
579
580 let result = validator.verify_rule(&rule).unwrap();
581 assert_eq!(result, ValidationResult::Verified);
582 });
583 }
584
585 #[test]
586 fn test_verify_and_correct() {
587 with_verification_context(|| {
588 let validator = TranslationValidator::new();
589
590 let rule = create_test_rule(
591 WasmOp::I32And,
592 ArmOp::And {
593 rd: Reg::R0,
594 rn: Reg::R0,
595 op2: Operand2::Reg(Reg::R1),
596 },
597 );
598
599 let result = validator.verify_rule(&rule).unwrap();
600 assert_eq!(result, ValidationResult::Verified);
601 });
602 }
603
604 #[test]
605 fn test_verify_incorrect_rule() {
606 with_verification_context(|| {
607 let validator = TranslationValidator::new();
608
609 let rule = create_test_rule(
611 WasmOp::I32Add,
612 ArmOp::Sub {
613 rd: Reg::R0,
614 rn: Reg::R0,
615 op2: Operand2::Reg(Reg::R1),
616 },
617 );
618
619 let result = validator.verify_rule(&rule).unwrap();
620
621 match result {
622 ValidationResult::Invalid { counterexample } => {
623 assert!(!counterexample.is_empty());
624 }
625 _ => panic!("Expected counterexample but got: {:?}", result),
626 }
627 });
628 }
629
630 #[test]
631 fn test_verify_bitwise_ops() {
632 with_verification_context(|| {
633 let validator = TranslationValidator::new();
634
635 let or_rule = create_test_rule(
637 WasmOp::I32Or,
638 ArmOp::Orr {
639 rd: Reg::R0,
640 rn: Reg::R0,
641 op2: Operand2::Reg(Reg::R1),
642 },
643 );
644 assert_eq!(
645 validator.verify_rule(&or_rule).unwrap(),
646 ValidationResult::Verified
647 );
648
649 let xor_rule = create_test_rule(
651 WasmOp::I32Xor,
652 ArmOp::Eor {
653 rd: Reg::R0,
654 rn: Reg::R0,
655 op2: Operand2::Reg(Reg::R1),
656 },
657 );
658 assert_eq!(
659 validator.verify_rule(&xor_rule).unwrap(),
660 ValidationResult::Verified
661 );
662 });
663 }
664
665 #[test]
666 fn test_verify_shift_ops() {
667 }
672}