Skip to main content

synth_verify/
translation_validator.rs

1//! Translation Validator - Proves equivalence between WASM and ARM code
2//!
3//! This module implements SMT-based translation validation inspired by Alive2.
4//! For each synthesis rule WASM → ARM, we prove that the ARM code has
5//! semantically equivalent behavior to the WASM code.
6//!
7//! # Verification Approach
8//!
9//! 1. Create symbolic inputs for both WASM and ARM
10//! 2. Encode WASM semantics as SMT formula phi_wasm
11//! 3. Encode ARM semantics as SMT formula phi_arm
12//! 4. Assert: phi_wasm(inputs) == phi_arm(inputs)
13//! 5. Check satisfiability - if UNSAT, then equivalence is proven
14//!
15//! # Example
16//!
17//! For the rule: WASM `i32.add` -> ARM `ADD Rd, Rn, Rm`
18//!
19//! We prove: forall a,b. i32.add(a, b) == ADD(a, b)
20
21use 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
29/// Verification error types
30#[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/// Result of translation validation
53#[derive(Debug, Clone, PartialEq)]
54pub enum ValidationResult {
55    /// Translation is provably correct
56    Verified,
57
58    /// Counterexample found - translation is incorrect
59    Invalid { counterexample: Vec<(String, i64)> },
60
61    /// Verification inconclusive (timeout or unsupported operations)
62    Unknown { reason: String },
63}
64
65/// Translation validator over the configured SMT engine (see
66/// [`crate::solver::new_solver`]: ordeal by default, optionally
67/// cross-checked against Z3 when `SYNTH_SOLVER_DIFF=1`).
68pub struct TranslationValidator {
69    wasm_encoder: WasmSemantics,
70    arm_encoder: ArmSemantics,
71    timeout_ms: u64,
72}
73
74impl Default for TranslationValidator {
75    fn default() -> Self {
76        Self::new()
77    }
78}
79
80impl TranslationValidator {
81    /// Create a new translation validator
82    pub fn new() -> Self {
83        Self {
84            wasm_encoder: WasmSemantics::new(),
85            arm_encoder: ArmSemantics::new(),
86            timeout_ms: 30000, // 30 seconds default
87        }
88    }
89
90    /// Set verification timeout in milliseconds
91    pub fn set_timeout(&mut self, timeout_ms: u64) {
92        self.timeout_ms = timeout_ms;
93    }
94
95    /// Verify a synthesis rule
96    ///
97    /// Proves that the ARM code generated by the rule has equivalent semantics
98    /// to the WASM code matched by the pattern.
99    pub fn verify_rule(&self, rule: &SynthesisRule) -> Result<ValidationResult, VerificationError> {
100        // Extract WASM operation from pattern
101        let wasm_op = match &rule.pattern {
102            synth_synthesis::Pattern::WasmInstr(op) => op,
103            _ => {
104                return Err(VerificationError::UnsupportedOperation(
105                    "Only single WASM instruction patterns are supported".to_string(),
106                ));
107            }
108        };
109
110        // Extract ARM operations from replacement
111        let arm_ops = match &rule.replacement {
112            synth_synthesis::Replacement::ArmInstr(op) => vec![op.clone()],
113            synth_synthesis::Replacement::ArmSequence(ops) => ops.clone(),
114            _ => {
115                return Err(VerificationError::UnsupportedOperation(
116                    "Only ARM instruction replacements are supported".to_string(),
117                ));
118            }
119        };
120
121        self.verify_equivalence(wasm_op, &arm_ops)
122    }
123
124    /// Verify equivalence between a WASM operation and ARM operations
125    pub fn verify_equivalence(
126        &self,
127        wasm_op: &WasmOp,
128        arm_ops: &[ArmOp],
129    ) -> Result<ValidationResult, VerificationError> {
130        self.verify_equivalence_parameterized(wasm_op, arm_ops, &[])
131    }
132
133    /// Verify equivalence with concrete parameter values
134    pub fn verify_equivalence_parameterized(
135        &self,
136        wasm_op: &WasmOp,
137        arm_ops: &[ArmOp],
138        concrete_params: &[(usize, i64)],
139    ) -> Result<ValidationResult, VerificationError> {
140        let mut solver = new_solver();
141
142        // Create inputs - some symbolic, some concrete
143        let num_inputs = self.get_num_inputs(wasm_op);
144        let mut inputs: Vec<BV> = Vec::new();
145
146        for i in 0..num_inputs {
147            let input = if let Some((_, value)) = concrete_params.iter().find(|(idx, _)| *idx == i)
148            {
149                // Concrete value
150                BV::from_i64(*value, 32)
151            } else {
152                // Symbolic value
153                BV::new_const(format!("input_{}", i), 32)
154            };
155            inputs.push(input);
156        }
157
158        // Encode WASM semantics
159        let wasm_result = self.wasm_encoder.encode_op(wasm_op, &inputs);
160
161        // Encode ARM semantics
162        let arm_result = self.encode_arm_sequence(arm_ops, &inputs)?;
163
164        // Assert that results are NOT equal
165        // If this is UNSAT, then the results are always equal (proven correct)
166        // If this is SAT, we found a counterexample
167        solver.assert(&wasm_result.eq(&arm_result).not());
168
169        match solver.check() {
170            CheckOutcome::Unsat => {
171                // Proven correct - no inputs exist where results differ
172                Ok(ValidationResult::Verified)
173            }
174
175            CheckOutcome::Sat => {
176                // Found counterexample: read the differing inputs back from
177                // the model (symbolic inputs only — concrete params have no
178                // model entry). Values are reported unsigned, as before.
179                let mut counterexample = Vec::new();
180                for (i, input) in inputs.iter().enumerate() {
181                    if let Some(value) = solver.value(input)
182                        && let Ok(int_val) = i64::try_from(value)
183                    {
184                        counterexample.push((format!("input_{}", i), int_val));
185                    }
186                }
187
188                Ok(ValidationResult::Invalid { counterexample })
189            }
190
191            CheckOutcome::Unknown(reason) => {
192                // Verification inconclusive
193                Ok(ValidationResult::Unknown {
194                    reason: format!("SMT solver returned unknown: {reason}"),
195                })
196            }
197        }
198    }
199
200    /// Encode a sequence of ARM operations
201    fn encode_arm_sequence(
202        &self,
203        arm_ops: &[ArmOp],
204        inputs: &[BV],
205    ) -> Result<BV, VerificationError> {
206        let mut state = ArmState::new_symbolic();
207
208        // Initialize input registers
209        for (i, input) in inputs.iter().enumerate() {
210            let reg = match i {
211                0 => Reg::R0,
212                1 => Reg::R1,
213                2 => Reg::R2,
214                _ => {
215                    return Err(VerificationError::UnsupportedOperation(format!(
216                        "Too many inputs: {}",
217                        inputs.len()
218                    )));
219                }
220            };
221            state.set_reg(&reg, input.clone());
222        }
223
224        // Execute ARM operations
225        for arm_op in arm_ops {
226            self.arm_encoder.encode_op(arm_op, &mut state);
227        }
228
229        // Extract result from R0 (ARM calling convention)
230        Ok(self.arm_encoder.extract_result(&state, &Reg::R0))
231    }
232
233    /// Verify operation for all parameter values in a range
234    pub fn verify_parameterized_range<F>(
235        &self,
236        wasm_op: &WasmOp,
237        create_arm_ops: F,
238        param_index: usize,
239        range: std::ops::Range<i64>,
240    ) -> Result<ValidationResult, VerificationError>
241    where
242        F: Fn(i64) -> Vec<ArmOp>,
243    {
244        for value in range {
245            let arm_ops = create_arm_ops(value);
246            let result =
247                self.verify_equivalence_parameterized(wasm_op, &arm_ops, &[(param_index, value)])?;
248
249            match result {
250                ValidationResult::Verified => continue,
251                ValidationResult::Invalid { counterexample } => {
252                    return Ok(ValidationResult::Invalid {
253                        counterexample: counterexample
254                            .into_iter()
255                            .map(|(k, v)| (format!("{} (param={})", k, value), v))
256                            .collect(),
257                    });
258                }
259                ValidationResult::Unknown { reason } => {
260                    return Ok(ValidationResult::Unknown {
261                        reason: format!("Failed at param={}: {}", value, reason),
262                    });
263                }
264            }
265        }
266
267        Ok(ValidationResult::Verified)
268    }
269
270    /// Get number of inputs required for a WASM operation
271    fn get_num_inputs(&self, wasm_op: &WasmOp) -> usize {
272        use WasmOp::*;
273        match wasm_op {
274            // Binary operations
275            I32Add | I32Sub | I32Mul | I32DivS | I32DivU | I32RemS | I32RemU | I32And | I32Or
276            | I32Xor | I32Shl | I32ShrS | I32ShrU | I32Rotl | I32Rotr | I32Eq | I32Ne | I32LtS
277            | I32LtU | I32LeS | I32LeU | I32GtS | I32GtU | I32GeS | I32GeU => 2,
278
279            // Unary operations
280            I32Clz | I32Ctz | I32Popcnt | I32Eqz => 1,
281
282            // Constants
283            I32Const(_) => 0,
284
285            // Memory operations
286            I32Load { .. } => 1,  // address
287            I32Store { .. } => 2, // address + value
288
289            // Control flow
290            LocalGet(_) | GlobalGet(_) => 0,
291            LocalSet(_) | GlobalSet(_) | LocalTee(_) => 1,
292            Br(_) | BrIf(_) | Return => 0,
293
294            // Other operations
295            Drop => 1,
296            Select => 3, // condition + two values
297            Nop | Unreachable | Block | Loop | If | Else | End => 0,
298
299            // Default for unknown
300            _ => 0,
301        }
302    }
303
304    /// Batch verify multiple synthesis rules
305    pub fn verify_rules(
306        &self,
307        rules: &[SynthesisRule],
308    ) -> Vec<(String, Result<ValidationResult, VerificationError>)> {
309        rules
310            .iter()
311            .map(|rule| {
312                let result = self.verify_rule(rule);
313                (rule.name.clone(), result)
314            })
315            .collect()
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322    use crate::with_verification_context;
323    use synth_synthesis::{Cost, Operand2, Pattern, Replacement};
324
325    fn create_test_rule(wasm_op: WasmOp, arm_op: ArmOp) -> SynthesisRule {
326        SynthesisRule {
327            name: format!("{:?}", wasm_op),
328            priority: 0,
329            pattern: Pattern::WasmInstr(wasm_op),
330            replacement: Replacement::ArmInstr(arm_op),
331            cost: Cost {
332                cycles: 1,
333                code_size: 4,
334                registers: 2,
335            },
336        }
337    }
338
339    #[test]
340    fn test_verify_add_correct() {
341        with_verification_context(|| {
342            let validator = TranslationValidator::new();
343
344            let rule = create_test_rule(
345                WasmOp::I32Add,
346                ArmOp::Add {
347                    rd: Reg::R0,
348                    rn: Reg::R0,
349                    op2: Operand2::Reg(Reg::R1),
350                },
351            );
352
353            let result = validator.verify_rule(&rule).unwrap();
354            assert_eq!(result, ValidationResult::Verified);
355        });
356    }
357
358    #[test]
359    fn test_verify_sub_correct() {
360        with_verification_context(|| {
361            let validator = TranslationValidator::new();
362
363            let rule = create_test_rule(
364                WasmOp::I32Sub,
365                ArmOp::Sub {
366                    rd: Reg::R0,
367                    rn: Reg::R0,
368                    op2: Operand2::Reg(Reg::R1),
369                },
370            );
371
372            let result = validator.verify_rule(&rule).unwrap();
373            assert_eq!(result, ValidationResult::Verified);
374        });
375    }
376
377    #[test]
378    fn test_verify_mul_correct() {
379        with_verification_context(|| {
380            let validator = TranslationValidator::new();
381
382            let rule = create_test_rule(
383                WasmOp::I32Mul,
384                ArmOp::Mul {
385                    rd: Reg::R0,
386                    rn: Reg::R0,
387                    rm: Reg::R1,
388                },
389            );
390
391            let result = validator.verify_rule(&rule).unwrap();
392            assert_eq!(result, ValidationResult::Verified);
393        });
394    }
395
396    #[test]
397    fn test_verify_and_correct() {
398        with_verification_context(|| {
399            let validator = TranslationValidator::new();
400
401            let rule = create_test_rule(
402                WasmOp::I32And,
403                ArmOp::And {
404                    rd: Reg::R0,
405                    rn: Reg::R0,
406                    op2: Operand2::Reg(Reg::R1),
407                },
408            );
409
410            let result = validator.verify_rule(&rule).unwrap();
411            assert_eq!(result, ValidationResult::Verified);
412        });
413    }
414
415    #[test]
416    fn test_verify_incorrect_rule() {
417        with_verification_context(|| {
418            let validator = TranslationValidator::new();
419
420            // INCORRECT rule: WASM i32.add -> ARM SUB (should find counterexample)
421            let rule = create_test_rule(
422                WasmOp::I32Add,
423                ArmOp::Sub {
424                    rd: Reg::R0,
425                    rn: Reg::R0,
426                    op2: Operand2::Reg(Reg::R1),
427                },
428            );
429
430            let result = validator.verify_rule(&rule).unwrap();
431
432            match result {
433                ValidationResult::Invalid { counterexample } => {
434                    assert!(!counterexample.is_empty());
435                }
436                _ => panic!("Expected counterexample but got: {:?}", result),
437            }
438        });
439    }
440
441    #[test]
442    fn test_verify_bitwise_ops() {
443        with_verification_context(|| {
444            let validator = TranslationValidator::new();
445
446            // Test OR
447            let or_rule = create_test_rule(
448                WasmOp::I32Or,
449                ArmOp::Orr {
450                    rd: Reg::R0,
451                    rn: Reg::R0,
452                    op2: Operand2::Reg(Reg::R1),
453                },
454            );
455            assert_eq!(
456                validator.verify_rule(&or_rule).unwrap(),
457                ValidationResult::Verified
458            );
459
460            // Test XOR
461            let xor_rule = create_test_rule(
462                WasmOp::I32Xor,
463                ArmOp::Eor {
464                    rd: Reg::R0,
465                    rn: Reg::R0,
466                    op2: Operand2::Reg(Reg::R1),
467                },
468            );
469            assert_eq!(
470                validator.verify_rule(&xor_rule).unwrap(),
471                ValidationResult::Verified
472            );
473        });
474    }
475
476    #[test]
477    fn test_verify_shift_ops() {
478        // Note: Shift operations require concrete immediate values in ARM
479        // but use register operands in WASM. Verification requires
480        // modeling the shift amount modulo operation.
481        // TODO: Implement shift verification with proper modulo handling
482    }
483}