Skip to main content

synth_verify/
arm_semantics.rs

1//! ARM Semantics Encoding to SMT
2//!
3//! Encodes ARM operation semantics as SMT bitvector formulas.
4//! Each ARM operation is translated to a mathematical formula that precisely
5//! captures its behavior, including register updates and condition flags.
6
7use crate::term::{BV, Bool};
8use std::collections::HashMap;
9use synth_synthesis::rules::{ArmOp, Operand2, Reg, VfpReg};
10
11/// ARM processor state representation in SMT
12///
13/// Z3 0.19 uses thread-local context -- no lifetime parameters needed.
14pub struct ArmState {
15    /// General purpose registers R0-R15
16    pub registers: Vec<BV>,
17    /// Condition flags (N, Z, C, V)
18    pub flags: ConditionFlags,
19    /// VFP (floating-point) registers
20    pub vfp_registers: Vec<BV>,
21    /// Memory model (simplified for bounded verification)
22    pub memory: Vec<BV>,
23    /// Local variables (for WASM verification)
24    pub locals: Vec<BV>,
25    /// Global variables (for WASM verification)
26    pub globals: Vec<BV>,
27    /// VCR-VER-002 (#166): the accumulated condition under which the executed
28    /// sequence TRAPS (reaches a `UDF`). `false` in a fresh state; `encode_op`
29    /// sets it unconditionally on a `Udf`, and the branch-taking executor
30    /// [`ArmSemantics::encode_sequence_br`] conditions it on the path guard the
31    /// `UDF` is reached under — this is the ARM-side trap term the
32    /// trap-preservation VC compares against the WASM op's trap condition.
33    pub may_trap: Bool,
34}
35
36/// ARM condition flags
37pub struct ConditionFlags {
38    pub n: Bool, // Negative
39    pub z: Bool, // Zero
40    pub c: Bool, // Carry
41    pub v: Bool, // Overflow
42}
43
44impl ArmState {
45    /// Create a new ARM state with symbolic values
46    pub fn new_symbolic() -> Self {
47        let registers = (0..16)
48            .map(|i| BV::new_const(format!("r{}", i), 32))
49            .collect();
50
51        let flags = ConditionFlags {
52            n: Bool::new_const("flag_n"),
53            z: Bool::new_const("flag_z"),
54            c: Bool::new_const("flag_c"),
55            v: Bool::new_const("flag_v"),
56        };
57
58        let memory = (0..256)
59            .map(|i| BV::new_const(format!("mem_{}", i), 32))
60            .collect();
61
62        let locals = (0..32)
63            .map(|i| BV::new_const(format!("local_{}", i), 32))
64            .collect();
65
66        let globals = (0..16)
67            .map(|i| BV::new_const(format!("global_{}", i), 32))
68            .collect();
69
70        let vfp_registers = (0..48)
71            .map(|i| BV::new_const(format!("vfp_{}", i), 32))
72            .collect();
73
74        Self {
75            registers,
76            flags,
77            vfp_registers,
78            memory,
79            locals,
80            globals,
81            may_trap: Bool::from_bool(false),
82        }
83    }
84
85    /// Get register value
86    pub fn get_reg(&self, reg: &Reg) -> &BV {
87        let index = reg_to_index(reg);
88        &self.registers[index]
89    }
90
91    /// Set register value
92    pub fn set_reg(&mut self, reg: &Reg, value: BV) {
93        let index = reg_to_index(reg);
94        self.registers[index] = value;
95    }
96
97    /// Get VFP register value
98    pub fn get_vfp_reg(&self, reg: &VfpReg) -> &BV {
99        let index = vfp_reg_to_index(reg);
100        &self.vfp_registers[index]
101    }
102
103    /// Set VFP register value
104    pub fn set_vfp_reg(&mut self, reg: &VfpReg, value: BV) {
105        let index = vfp_reg_to_index(reg);
106        self.vfp_registers[index] = value;
107    }
108}
109
110/// Convert register enum to index
111fn reg_to_index(reg: &Reg) -> usize {
112    match reg {
113        Reg::R0 => 0,
114        Reg::R1 => 1,
115        Reg::R2 => 2,
116        Reg::R3 => 3,
117        Reg::R4 => 4,
118        Reg::R5 => 5,
119        Reg::R6 => 6,
120        Reg::R7 => 7,
121        Reg::R8 => 8,
122        Reg::R9 => 9,
123        Reg::R10 => 10,
124        Reg::R11 => 11,
125        Reg::R12 => 12,
126        Reg::SP => 13,
127        Reg::LR => 14,
128        Reg::PC => 15,
129    }
130}
131
132/// Convert VFP register enum to index
133fn vfp_reg_to_index(reg: &VfpReg) -> usize {
134    match reg {
135        // Single-precision registers S0-S31 (indices 0-31)
136        VfpReg::S0 => 0,
137        VfpReg::S1 => 1,
138        VfpReg::S2 => 2,
139        VfpReg::S3 => 3,
140        VfpReg::S4 => 4,
141        VfpReg::S5 => 5,
142        VfpReg::S6 => 6,
143        VfpReg::S7 => 7,
144        VfpReg::S8 => 8,
145        VfpReg::S9 => 9,
146        VfpReg::S10 => 10,
147        VfpReg::S11 => 11,
148        VfpReg::S12 => 12,
149        VfpReg::S13 => 13,
150        VfpReg::S14 => 14,
151        VfpReg::S15 => 15,
152        VfpReg::S16 => 16,
153        VfpReg::S17 => 17,
154        VfpReg::S18 => 18,
155        VfpReg::S19 => 19,
156        VfpReg::S20 => 20,
157        VfpReg::S21 => 21,
158        VfpReg::S22 => 22,
159        VfpReg::S23 => 23,
160        VfpReg::S24 => 24,
161        VfpReg::S25 => 25,
162        VfpReg::S26 => 26,
163        VfpReg::S27 => 27,
164        VfpReg::S28 => 28,
165        VfpReg::S29 => 29,
166        VfpReg::S30 => 30,
167        VfpReg::S31 => 31,
168        // Double-precision registers D0-D15 (indices 32-47)
169        // Note: D0 = S0:S1, D1 = S2:S3, etc.
170        // We store the "low" part of each D register
171        VfpReg::D0 => 32,
172        VfpReg::D1 => 33,
173        VfpReg::D2 => 34,
174        VfpReg::D3 => 35,
175        VfpReg::D4 => 36,
176        VfpReg::D5 => 37,
177        VfpReg::D6 => 38,
178        VfpReg::D7 => 39,
179        VfpReg::D8 => 40,
180        VfpReg::D9 => 41,
181        VfpReg::D10 => 42,
182        VfpReg::D11 => 43,
183        VfpReg::D12 => 44,
184        VfpReg::D13 => 45,
185        VfpReg::D14 => 46,
186        VfpReg::D15 => 47,
187    }
188}
189
190/// ARM semantics encoder
191///
192/// Z3 0.19 uses thread-local context -- no lifetime parameters needed.
193pub struct ArmSemantics;
194
195impl Default for ArmSemantics {
196    fn default() -> Self {
197        Self::new()
198    }
199}
200
201impl ArmSemantics {
202    /// Create a new ARM semantics encoder
203    pub fn new() -> Self {
204        Self
205    }
206
207    /// Encode an ARM operation and return the resulting state
208    ///
209    /// This models the effect of executing the ARM instruction on the processor state.
210    pub fn encode_op(&self, op: &ArmOp, state: &mut ArmState) {
211        match op {
212            ArmOp::Add { rd, rn, op2 } => {
213                let rn_val = state.get_reg(rn).clone();
214                let op2_val = self.evaluate_operand2(op2, state);
215                let result = rn_val.bvadd(&op2_val);
216                state.set_reg(rd, result);
217            }
218
219            ArmOp::Sub { rd, rn, op2 } => {
220                let rn_val = state.get_reg(rn).clone();
221                let op2_val = self.evaluate_operand2(op2, state);
222                let result = rn_val.bvsub(&op2_val);
223                state.set_reg(rd, result);
224            }
225
226            ArmOp::Mul { rd, rn, rm } => {
227                let rn_val = state.get_reg(rn).clone();
228                let rm_val = state.get_reg(rm).clone();
229                let result = rn_val.bvmul(&rm_val);
230                state.set_reg(rd, result);
231            }
232
233            ArmOp::Umull { rdlo, rdhi, rn, rm } => {
234                // {rdhi:rdlo} = zext64(rn) * zext64(rm); rdhi = high 32 bits.
235                let rn64 = state.get_reg(rn).zero_ext(32);
236                let rm64 = state.get_reg(rm).zero_ext(32);
237                let prod = rn64.bvmul(&rm64);
238                state.set_reg(rdlo, prod.extract(31, 0));
239                state.set_reg(rdhi, prod.extract(63, 32));
240            }
241
242            ArmOp::Sdiv { rd, rn, rm } => {
243                let rn_val = state.get_reg(rn).clone();
244                let rm_val = state.get_reg(rm).clone();
245                let result = rn_val.bvsdiv(&rm_val);
246                state.set_reg(rd, result);
247            }
248
249            ArmOp::Udiv { rd, rn, rm } => {
250                let rn_val = state.get_reg(rn).clone();
251                let rm_val = state.get_reg(rm).clone();
252                let result = rn_val.bvudiv(&rm_val);
253                state.set_reg(rd, result);
254            }
255
256            ArmOp::Mls { rd, rn, rm, ra } => {
257                // MLS (Multiply and Subtract): Rd = Ra - Rn * Rm
258                // Used for remainder operations: a % b = a - (a/b) * b
259                let rn_val = state.get_reg(rn).clone();
260                let rm_val = state.get_reg(rm).clone();
261                let ra_val = state.get_reg(ra).clone();
262                let product = rn_val.bvmul(&rm_val);
263                let result = ra_val.bvsub(&product);
264                state.set_reg(rd, result);
265            }
266
267            ArmOp::And { rd, rn, op2 } => {
268                let rn_val = state.get_reg(rn).clone();
269                let op2_val = self.evaluate_operand2(op2, state);
270                let result = rn_val.bvand(&op2_val);
271                state.set_reg(rd, result);
272            }
273
274            ArmOp::Orr { rd, rn, op2 } => {
275                let rn_val = state.get_reg(rn).clone();
276                let op2_val = self.evaluate_operand2(op2, state);
277                let result = rn_val.bvor(&op2_val);
278                state.set_reg(rd, result);
279            }
280
281            ArmOp::Eor { rd, rn, op2 } => {
282                let rn_val = state.get_reg(rn).clone();
283                let op2_val = self.evaluate_operand2(op2, state);
284                let result = rn_val.bvxor(&op2_val);
285                state.set_reg(rd, result);
286            }
287
288            ArmOp::Lsl { rd, rn, shift } => {
289                let rn_val = state.get_reg(rn).clone();
290                let shift_val = BV::from_i64(*shift as i64, 32);
291                let result = rn_val.bvshl(&shift_val);
292                state.set_reg(rd, result);
293            }
294
295            ArmOp::Lsr { rd, rn, shift } => {
296                let rn_val = state.get_reg(rn).clone();
297                let shift_val = BV::from_i64(*shift as i64, 32);
298                let result = rn_val.bvlshr(&shift_val);
299                state.set_reg(rd, result);
300            }
301
302            ArmOp::Asr { rd, rn, shift } => {
303                let rn_val = state.get_reg(rn).clone();
304                let shift_val = BV::from_i64(*shift as i64, 32);
305                let result = rn_val.bvashr(&shift_val);
306                state.set_reg(rd, result);
307            }
308
309            ArmOp::Ror { rd, rn, shift } => {
310                // Rotate right - ARM ROR instruction
311                // ROR(x, n) rotates x right by n positions
312                let rn_val = state.get_reg(rn).clone();
313                let shift_val = BV::from_i64(*shift as i64, 32);
314                let result = rn_val.bvrotr(&shift_val);
315                state.set_reg(rd, result);
316            }
317
318            ArmOp::Mov { rd, op2 } => {
319                let op2_val = self.evaluate_operand2(op2, state);
320                state.set_reg(rd, op2_val);
321            }
322
323            ArmOp::Mvn { rd, op2 } => {
324                let op2_val = self.evaluate_operand2(op2, state);
325                let result = op2_val.bvnot();
326                state.set_reg(rd, result);
327            }
328
329            ArmOp::Cmp { rn, op2 } => {
330                // Compare sets flags but doesn't write to a register
331                // CMP performs: Rn - Op2 and updates all condition flags
332                let rn_val = state.get_reg(rn).clone();
333                let op2_val = self.evaluate_operand2(op2, state);
334
335                // Compute result of subtraction
336                let result = rn_val.bvsub(&op2_val);
337
338                // Update all condition flags
339                self.update_flags_sub(state, &rn_val, &op2_val, &result);
340            }
341
342            ArmOp::Clz { rd, rm } => {
343                // Count leading zeros - ARM CLZ instruction
344                // Uses binary search algorithm matching WASM i32.clz semantics
345                let input = state.get_reg(rm).clone();
346                let result = self.encode_clz(&input);
347                state.set_reg(rd, result);
348            }
349
350            ArmOp::Rbit { rd, rm } => {
351                // Reverse bits - ARM RBIT instruction
352                // Reverses the bit order in a 32-bit value
353                let input = state.get_reg(rm).clone();
354                let result = self.encode_rbit(&input);
355                state.set_reg(rd, result);
356            }
357
358            ArmOp::Popcnt { rd, rm } => {
359                // Population count - count number of 1 bits
360                // This is a pseudo-instruction for verification
361                let input = state.get_reg(rm).clone();
362                let result = self.encode_popcnt(&input);
363                state.set_reg(rd, result);
364            }
365
366            ArmOp::Nop => {
367                // No operation - state unchanged
368            }
369
370            ArmOp::SetCond { rd, cond } => {
371                // SetCond evaluates a condition based on NZCV flags and sets rd to 0 or 1
372                // This is a pseudo-instruction for verification purposes
373                let cond_result = self.evaluate_condition(cond, &state.flags);
374                let result = self.bool_to_bv32(&cond_result);
375                state.set_reg(rd, result);
376            }
377
378            ArmOp::Select {
379                rd,
380                rval1,
381                rval2,
382                rcond,
383            } => {
384                // Select operation: if rcond != 0, select rval1, else rval2
385                // This is a pseudo-instruction for verification purposes
386                let val1 = state.get_reg(rval1).clone();
387                let val2 = state.get_reg(rval2).clone();
388                let cond = state.get_reg(rcond).clone();
389                let zero = BV::from_i64(0, 32);
390                let cond_bool = cond.eq(&zero).not(); // cond != 0
391                let result = cond_bool.ite(&val1, &val2);
392                state.set_reg(rd, result);
393            }
394
395            // Memory operations simplified for now
396            ArmOp::Ldr { rd, addr: _ } => {
397                // Load from memory
398                // Simplified: return symbolic value
399                let result = BV::new_const(format!("load_{:?}", rd), 32);
400                state.set_reg(rd, result);
401            }
402
403            ArmOp::Str { rd: _, addr: _ } => {
404                // Store to memory
405                // Simplified: memory updates not fully modeled yet
406            }
407
408            // Control flow operations
409            ArmOp::B { label: _ } => {
410                // Branch - would update PC in full model
411                // For bounded verification, we treat this symbolically
412            }
413
414            ArmOp::Bl { label: _ } => {
415                // Branch with link - would update PC and LR
416            }
417
418            ArmOp::Bx { rm: _ } => {
419                // Branch and exchange - would update PC
420            }
421
422            // Local/Global variable access (pseudo-instructions for verification)
423            ArmOp::LocalGet { rd, index } => {
424                // Load local variable into register
425                let value = state
426                    .locals
427                    .get(*index as usize)
428                    .cloned()
429                    .unwrap_or_else(|| BV::new_const(format!("local_{}", index), 32));
430                state.set_reg(rd, value);
431            }
432
433            ArmOp::LocalSet { rs, index } => {
434                // Store register into local variable
435                let value = state.get_reg(rs).clone();
436                if let Some(local) = state.locals.get_mut(*index as usize) {
437                    *local = value;
438                }
439            }
440
441            ArmOp::LocalTee { rd, rs, index } => {
442                // Store register into local variable and also copy to destination
443                let value = state.get_reg(rs).clone();
444                if let Some(local) = state.locals.get_mut(*index as usize) {
445                    *local = value.clone();
446                }
447                state.set_reg(rd, value);
448            }
449
450            ArmOp::GlobalGet { rd, index } => {
451                // Load global variable into register
452                let value = state
453                    .globals
454                    .get(*index as usize)
455                    .cloned()
456                    .unwrap_or_else(|| BV::new_const(format!("global_{}", index), 32));
457                state.set_reg(rd, value);
458            }
459
460            ArmOp::GlobalSet { rs, index } => {
461                // Store register into global variable
462                let value = state.get_reg(rs).clone();
463                if let Some(global) = state.globals.get_mut(*index as usize) {
464                    *global = value;
465                }
466            }
467
468            ArmOp::BrTable {
469                rd,
470                index_reg,
471                targets,
472                default,
473            } => {
474                // Multi-way branch based on index
475                // For verification, we model the control flow symbolically
476                let _index = state.get_reg(index_reg).clone();
477                let result = BV::new_const(format!("br_table_{}_{}", targets.len(), default), 32);
478                state.set_reg(rd, result);
479            }
480
481            ArmOp::Call { rd, func_idx } => {
482                // Function call - model result symbolically
483                let result = BV::new_const(format!("call_{}", func_idx), 32);
484                state.set_reg(rd, result);
485            }
486
487            ArmOp::CallIndirect {
488                rd,
489                type_idx,
490                table_index_reg,
491                // #642: the bounds guard is a control-flow effect (trap), not
492                // modeled by the symbolic call result. #650: the table base
493                // offset only changes WHICH pointer is loaded, not the
494                // symbolic result shape. #664: the null check is likewise a
495                // trap (control-flow effect) on the loaded pointer.
496                table_size: _,
497                table_byte_offset: _,
498                null_check: _,
499                // #676: the runtime type check is likewise a trap
500                // (control-flow effect) on the sidecar-loaded class id.
501                type_check: _,
502            } => {
503                // Indirect function call through table
504                let _table_index = state.get_reg(table_index_reg).clone();
505                let result = BV::new_const(format!("call_indirect_{}", type_idx), 32);
506                state.set_reg(rd, result);
507            }
508
509            // ================================================================
510            // i64 Operations (Phase 2) - Simplified implementation
511            // ================================================================
512            // These use register pairs on ARM32 but simplified to single
513            // registers for initial implementation
514            ArmOp::I64Const { rdlo, rdhi, value } => {
515                // Load 64-bit constant into register pair
516                let low32 = (*value as u32) as i64;
517                let high32 = *value >> 32;
518                state.set_reg(rdlo, BV::from_i64(low32, 32));
519                state.set_reg(rdhi, BV::from_i64(high32, 32));
520            }
521
522            ArmOp::I64Add {
523                rdlo,
524                rdhi,
525                rnlo,
526                rnhi,
527                rmlo,
528                rmhi,
529            } => {
530                // 64-bit addition with register pairs and carry propagation
531                // ARM: ADDS rdlo, rnlo, rmlo  ; Add low parts, set carry
532                //      ADC  rdhi, rnhi, rmhi  ; Add high parts with carry
533
534                let n_low = state.get_reg(rnlo).clone();
535                let m_low = state.get_reg(rmlo).clone();
536                let n_high = state.get_reg(rnhi).clone();
537                let m_high = state.get_reg(rmhi).clone();
538
539                // Low part: simple addition
540                let result_low = n_low.bvadd(&m_low);
541                state.set_reg(rdlo, result_low.clone());
542
543                // Detect carry: overflow occurred if result < either operand
544                // For unsigned: carry = (result_low < n_low)
545                let carry = result_low.bvult(&n_low);
546                let carry_bv = carry.ite(BV::from_i64(1, 32), BV::from_i64(0, 32));
547
548                // High part: add with carry
549                let high_sum = n_high.bvadd(&m_high);
550                let result_high = high_sum.bvadd(&carry_bv);
551                state.set_reg(rdhi, result_high);
552            }
553
554            ArmOp::I64Eqz { rd, rnlo, rnhi } => {
555                // Check if 64-bit value is zero
556                // True if both low and high parts are zero
557                let zero = BV::from_i64(0, 32);
558                let low_zero = state.get_reg(rnlo).eq(&zero);
559                let high_zero = state.get_reg(rnhi).eq(&zero);
560                let both_zero = Bool::and(&[&low_zero, &high_zero]);
561                let result = self.bool_to_bv32(&both_zero);
562                state.set_reg(rd, result);
563            }
564
565            ArmOp::I32WrapI64 { rd, rnlo } => {
566                // Wrap 64-bit to 32-bit (take low 32 bits)
567                let low_val = state.get_reg(rnlo).clone();
568                state.set_reg(rd, low_val);
569            }
570
571            ArmOp::I64ExtendI32S { rdlo, rdhi, rn } => {
572                // Sign-extend 32-bit to 64-bit
573                let value = state.get_reg(rn).clone();
574                state.set_reg(rdlo, value.clone());
575
576                // High part is sign extension (all 0s or all 1s based on sign bit)
577                let sign_bit = value.extract(31, 31); // Extract bit 31
578                let all_ones = BV::from_i64(-1, 32);
579                let zero = BV::from_i64(0, 32);
580                // If sign bit is 1, high = 0xFFFFFFFF, else high = 0
581                let high_val = sign_bit.eq(BV::from_i64(1, 1)).ite(&all_ones, &zero);
582                state.set_reg(rdhi, high_val);
583            }
584
585            ArmOp::I64ExtendI32U { rdlo, rdhi, rn } => {
586                // Zero-extend 32-bit to 64-bit
587                let value = state.get_reg(rn).clone();
588                state.set_reg(rdlo, value);
589                // High part is always zero for unsigned extend
590                state.set_reg(rdhi, BV::from_i64(0, 32));
591            }
592
593            ArmOp::I64Sub {
594                rdlo,
595                rdhi,
596                rnlo,
597                rnhi,
598                rmlo,
599                rmhi,
600            } => {
601                // 64-bit subtraction with register pairs and borrow propagation
602                // ARM: SUBS rdlo, rnlo, rmlo  ; Subtract low parts, set borrow
603                //      SBC  rdhi, rnhi, rmhi  ; Subtract high parts with borrow
604
605                let n_low = state.get_reg(rnlo).clone();
606                let m_low = state.get_reg(rmlo).clone();
607                let n_high = state.get_reg(rnhi).clone();
608                let m_high = state.get_reg(rmhi).clone();
609
610                // Low part: simple subtraction
611                let result_low = n_low.bvsub(&m_low);
612                state.set_reg(rdlo, result_low.clone());
613
614                // Detect borrow: borrow occurred if n_low < m_low (unsigned)
615                let borrow = n_low.bvult(&m_low);
616                let borrow_bv = borrow.ite(BV::from_i64(1, 32), BV::from_i64(0, 32));
617
618                // High part: subtract with borrow
619                let high_diff = n_high.bvsub(&m_high);
620                let result_high = high_diff.bvsub(&borrow_bv);
621                state.set_reg(rdhi, result_high);
622            }
623
624            ArmOp::I64Mul {
625                rd_lo,
626                rd_hi,
627                rn_lo,
628                rn_hi,
629                rm_lo,
630                rm_hi,
631            } => {
632                // 64-bit multiplication: (a_hi:a_lo) * (b_hi:b_lo) → (result_hi:result_lo)
633                // Algorithm for 64x64→64 bit multiplication:
634                // result = (a_hi * b_lo * 2^32) + (a_lo * b_hi * 2^32) + (a_lo * b_lo)
635                // Only the low 64 bits are kept
636
637                let a_lo = state.get_reg(rn_lo).clone();
638                let a_hi = state.get_reg(rn_hi).clone();
639                let b_lo = state.get_reg(rm_lo).clone();
640                let b_hi = state.get_reg(rm_hi).clone();
641
642                // Low part: a_lo * b_lo (32x32→64, we need both parts)
643                // For SMT, we can use bvmul which gives 32-bit result (truncated)
644                let lo_lo = a_lo.bvmul(&b_lo);
645                state.set_reg(rd_lo, lo_lo.clone());
646
647                // For the high part, we need to handle overflow from a_lo * b_lo
648                // and add the cross products: a_hi * b_lo + a_lo * b_hi
649                //
650                // Simplified approach: use symbolic representation for now
651                // TODO: Implement full 64-bit multiplication with proper overflow handling
652                // This requires 64-bit bitvector intermediate computations
653
654                // Cross products (take low 32 bits of each)
655                let hi_lo = a_hi.bvmul(&b_lo); // a_hi * b_lo (low 32 bits)
656                let lo_hi = a_lo.bvmul(&b_hi); // a_lo * b_hi (low 32 bits)
657
658                // High part approximation (missing carry from a_lo * b_lo)
659                // result_hi ≈ hi_lo + lo_hi
660                let hi_sum = hi_lo.bvadd(&lo_hi);
661                state.set_reg(rd_hi, hi_sum);
662
663                // Note: This is a simplified implementation. A complete implementation
664                // would need to:
665                // 1. Extract high 32 bits of (a_lo * b_lo)
666                // 2. Add that to the cross products
667                // 3. Handle carries properly
668            }
669
670            // ========================================================================
671            // i64 Division and Remainder
672            // ========================================================================
673            // Note: Full 64-bit division on ARM32 requires library calls or
674            // very complex multi-instruction sequences. For verification, we model
675            // the results symbolically.
676            ArmOp::I64DivS { rdlo, rdhi, .. } => {
677                // Signed 64-bit division
678                // Real implementation would require __aeabi_ldivmod or equivalent
679                // For verification, return symbolic values
680                state.set_reg(rdlo, BV::new_const("i64_divs_lo", 32));
681                state.set_reg(rdhi, BV::new_const("i64_divs_hi", 32));
682            }
683
684            ArmOp::I64DivU { rdlo, rdhi, .. } => {
685                // Unsigned 64-bit division
686                // Real implementation would require __aeabi_uldivmod or equivalent
687                // For verification, return symbolic values
688                state.set_reg(rdlo, BV::new_const("i64_divu_lo", 32));
689                state.set_reg(rdhi, BV::new_const("i64_divu_hi", 32));
690            }
691
692            ArmOp::I64RemS { rdlo, rdhi, .. } => {
693                // Signed 64-bit remainder (modulo)
694                // Real implementation would require __aeabi_ldivmod or equivalent
695                // For verification, return symbolic values
696                state.set_reg(rdlo, BV::new_const("i64_rems_lo", 32));
697                state.set_reg(rdhi, BV::new_const("i64_rems_hi", 32));
698            }
699
700            ArmOp::I64RemU { rdlo, rdhi, .. } => {
701                // Unsigned 64-bit remainder (modulo)
702                // Real implementation would require __aeabi_uldivmod or equivalent
703                // For verification, return symbolic values
704                state.set_reg(rdlo, BV::new_const("i64_remu_lo", 32));
705                state.set_reg(rdhi, BV::new_const("i64_remu_hi", 32));
706            }
707
708            ArmOp::I64And {
709                rdlo,
710                rdhi,
711                rnlo,
712                rnhi,
713                rmlo,
714                rmhi,
715            } => {
716                let n_low = state.get_reg(rnlo).clone();
717                let m_low = state.get_reg(rmlo).clone();
718                state.set_reg(rdlo, n_low.bvand(&m_low));
719
720                let n_high = state.get_reg(rnhi).clone();
721                let m_high = state.get_reg(rmhi).clone();
722                state.set_reg(rdhi, n_high.bvand(&m_high));
723            }
724
725            ArmOp::I64Or {
726                rdlo,
727                rdhi,
728                rnlo,
729                rnhi,
730                rmlo,
731                rmhi,
732            } => {
733                let n_low = state.get_reg(rnlo).clone();
734                let m_low = state.get_reg(rmlo).clone();
735                state.set_reg(rdlo, n_low.bvor(&m_low));
736
737                let n_high = state.get_reg(rnhi).clone();
738                let m_high = state.get_reg(rmhi).clone();
739                state.set_reg(rdhi, n_high.bvor(&m_high));
740            }
741
742            ArmOp::I64Xor {
743                rdlo,
744                rdhi,
745                rnlo,
746                rnhi,
747                rmlo,
748                rmhi,
749            } => {
750                let n_low = state.get_reg(rnlo).clone();
751                let m_low = state.get_reg(rmlo).clone();
752                state.set_reg(rdlo, n_low.bvxor(&m_low));
753
754                let n_high = state.get_reg(rnhi).clone();
755                let m_high = state.get_reg(rmhi).clone();
756                state.set_reg(rdhi, n_high.bvxor(&m_high));
757            }
758
759            ArmOp::I64Eq {
760                rd,
761                rnlo,
762                rnhi,
763                rmlo,
764                rmhi,
765            } => {
766                let n_low = state.get_reg(rnlo).clone();
767                let m_low = state.get_reg(rmlo).clone();
768                let n_high = state.get_reg(rnhi).clone();
769                let m_high = state.get_reg(rmhi).clone();
770
771                let low_eq = n_low.eq(&m_low);
772                let high_eq = n_high.eq(&m_high);
773                let both_eq = Bool::and(&[&low_eq, &high_eq]);
774                let result = self.bool_to_bv32(&both_eq);
775                state.set_reg(rd, result);
776            }
777
778            ArmOp::I64LtS {
779                rd,
780                rnlo,
781                rnhi,
782                rmlo,
783                rmhi,
784            } => {
785                // Signed less than: n < m
786                // Compare high parts first (signed), tiebreak with low parts (unsigned)
787                let n_low = state.get_reg(rnlo).clone();
788                let m_low = state.get_reg(rmlo).clone();
789                let n_high = state.get_reg(rnhi).clone();
790                let m_high = state.get_reg(rmhi).clone();
791
792                // High parts comparison (signed)
793                let high_lt = n_high.bvslt(&m_high);
794                let high_eq = n_high.eq(&m_high);
795
796                // Low parts comparison (unsigned)
797                let low_lt = n_low.bvult(&m_low);
798
799                // Result: high_lt OR (high_eq AND low_lt)
800                let eq_and_low = Bool::and(&[&high_eq, &low_lt]);
801                let result_bool = Bool::or(&[&high_lt, &eq_and_low]);
802                let result = self.bool_to_bv32(&result_bool);
803                state.set_reg(rd, result);
804            }
805
806            ArmOp::I64LtU {
807                rd,
808                rnlo,
809                rnhi,
810                rmlo,
811                rmhi,
812            } => {
813                // Unsigned less than: n < m
814                // Compare high parts first (unsigned), tiebreak with low parts (unsigned)
815                let n_low = state.get_reg(rnlo).clone();
816                let m_low = state.get_reg(rmlo).clone();
817                let n_high = state.get_reg(rnhi).clone();
818                let m_high = state.get_reg(rmhi).clone();
819
820                // High parts comparison (unsigned)
821                let high_lt = n_high.bvult(&m_high);
822                let high_eq = n_high.eq(&m_high);
823
824                // Low parts comparison (unsigned)
825                let low_lt = n_low.bvult(&m_low);
826
827                // Result: high_lt OR (high_eq AND low_lt)
828                let eq_and_low = Bool::and(&[&high_eq, &low_lt]);
829                let result_bool = Bool::or(&[&high_lt, &eq_and_low]);
830                let result = self.bool_to_bv32(&result_bool);
831                state.set_reg(rd, result);
832            }
833
834            ArmOp::I64Ne {
835                rd,
836                rnlo,
837                rnhi,
838                rmlo,
839                rmhi,
840            } => {
841                // Not equal: !(n == m)
842                let n_low = state.get_reg(rnlo).clone();
843                let m_low = state.get_reg(rmlo).clone();
844                let n_high = state.get_reg(rnhi).clone();
845                let m_high = state.get_reg(rmhi).clone();
846
847                let low_eq = n_low.eq(&m_low);
848                let high_eq = n_high.eq(&m_high);
849                let both_eq = Bool::and(&[&low_eq, &high_eq]);
850                let not_eq = both_eq.not();
851                let result = self.bool_to_bv32(&not_eq);
852                state.set_reg(rd, result);
853            }
854
855            ArmOp::I64LeS {
856                rd,
857                rnlo,
858                rnhi,
859                rmlo,
860                rmhi,
861            } => {
862                // Signed less than or equal: n <= m
863                // Equivalent to: n < m OR n == m
864                let n_low = state.get_reg(rnlo).clone();
865                let m_low = state.get_reg(rmlo).clone();
866                let n_high = state.get_reg(rnhi).clone();
867                let m_high = state.get_reg(rmhi).clone();
868
869                let high_lt = n_high.bvslt(&m_high);
870                let high_eq = n_high.eq(&m_high);
871                let low_le = n_low.bvule(&m_low); // Low parts unsigned LE
872
873                let eq_and_le = Bool::and(&[&high_eq, &low_le]);
874                let result_bool = Bool::or(&[&high_lt, &eq_and_le]);
875                let result = self.bool_to_bv32(&result_bool);
876                state.set_reg(rd, result);
877            }
878
879            ArmOp::I64LeU {
880                rd,
881                rnlo,
882                rnhi,
883                rmlo,
884                rmhi,
885            } => {
886                // Unsigned less than or equal: n <= m
887                let n_low = state.get_reg(rnlo).clone();
888                let m_low = state.get_reg(rmlo).clone();
889                let n_high = state.get_reg(rnhi).clone();
890                let m_high = state.get_reg(rmhi).clone();
891
892                let high_lt = n_high.bvult(&m_high);
893                let high_eq = n_high.eq(&m_high);
894                let low_le = n_low.bvule(&m_low);
895
896                let eq_and_le = Bool::and(&[&high_eq, &low_le]);
897                let result_bool = Bool::or(&[&high_lt, &eq_and_le]);
898                let result = self.bool_to_bv32(&result_bool);
899                state.set_reg(rd, result);
900            }
901
902            ArmOp::I64GtS {
903                rd,
904                rnlo,
905                rnhi,
906                rmlo,
907                rmhi,
908            } => {
909                // Signed greater than: n > m
910                // Equivalent to: m < n
911                let n_low = state.get_reg(rnlo).clone();
912                let m_low = state.get_reg(rmlo).clone();
913                let n_high = state.get_reg(rnhi).clone();
914                let m_high = state.get_reg(rmhi).clone();
915
916                let high_gt = n_high.bvsgt(&m_high);
917                let high_eq = n_high.eq(&m_high);
918                let low_gt = n_low.bvugt(&m_low); // Low parts unsigned GT
919
920                let eq_and_gt = Bool::and(&[&high_eq, &low_gt]);
921                let result_bool = Bool::or(&[&high_gt, &eq_and_gt]);
922                let result = self.bool_to_bv32(&result_bool);
923                state.set_reg(rd, result);
924            }
925
926            ArmOp::I64GtU {
927                rd,
928                rnlo,
929                rnhi,
930                rmlo,
931                rmhi,
932            } => {
933                // Unsigned greater than: n > m
934                let n_low = state.get_reg(rnlo).clone();
935                let m_low = state.get_reg(rmlo).clone();
936                let n_high = state.get_reg(rnhi).clone();
937                let m_high = state.get_reg(rmhi).clone();
938
939                let high_gt = n_high.bvugt(&m_high);
940                let high_eq = n_high.eq(&m_high);
941                let low_gt = n_low.bvugt(&m_low);
942
943                let eq_and_gt = Bool::and(&[&high_eq, &low_gt]);
944                let result_bool = Bool::or(&[&high_gt, &eq_and_gt]);
945                let result = self.bool_to_bv32(&result_bool);
946                state.set_reg(rd, result);
947            }
948
949            ArmOp::I64GeS {
950                rd,
951                rnlo,
952                rnhi,
953                rmlo,
954                rmhi,
955            } => {
956                // Signed greater than or equal: n >= m
957                // Equivalent to: !(n < m)
958                let n_low = state.get_reg(rnlo).clone();
959                let m_low = state.get_reg(rmlo).clone();
960                let n_high = state.get_reg(rnhi).clone();
961                let m_high = state.get_reg(rmhi).clone();
962
963                let high_lt = n_high.bvslt(&m_high);
964                let high_eq = n_high.eq(&m_high);
965                let low_lt = n_low.bvult(&m_low);
966
967                let eq_and_lt = Bool::and(&[&high_eq, &low_lt]);
968                let lt_bool = Bool::or(&[&high_lt, &eq_and_lt]);
969                let result_bool = lt_bool.not(); // GE is !(LT)
970                let result = self.bool_to_bv32(&result_bool);
971                state.set_reg(rd, result);
972            }
973
974            ArmOp::I64GeU {
975                rd,
976                rnlo,
977                rnhi,
978                rmlo,
979                rmhi,
980            } => {
981                // Unsigned greater than or equal: n >= m
982                // Equivalent to: !(n < m)
983                let n_low = state.get_reg(rnlo).clone();
984                let m_low = state.get_reg(rmlo).clone();
985                let n_high = state.get_reg(rnhi).clone();
986                let m_high = state.get_reg(rmhi).clone();
987
988                let high_lt = n_high.bvult(&m_high);
989                let high_eq = n_high.eq(&m_high);
990                let low_lt = n_low.bvult(&m_low);
991
992                let eq_and_lt = Bool::and(&[&high_eq, &low_lt]);
993                let lt_bool = Bool::or(&[&high_lt, &eq_and_lt]);
994                let result_bool = lt_bool.not(); // GE is !(LT)
995                let result = self.bool_to_bv32(&result_bool);
996                state.set_reg(rd, result);
997            }
998
999            // ================================================================
1000            // i64 Shift Operations
1001            // ================================================================
1002            ArmOp::I64Shl {
1003                rd_lo,
1004                rd_hi,
1005                rn_lo,
1006                rn_hi,
1007                rm_lo,
1008                rm_hi: _,
1009            } => {
1010                // 64-bit left shift: (n_hi:n_lo) << shift
1011                // WASM spec: shift amount is modulo 64
1012                let n_lo = state.get_reg(rn_lo).clone();
1013                let n_hi = state.get_reg(rn_hi).clone();
1014                let shift_amt = state.get_reg(rm_lo).clone();
1015
1016                // Modulo 64: shift_amt = shift_amt & 63
1017                let shift_mod = shift_amt.bvand(BV::from_i64(63, 32));
1018
1019                // If shift < 32: normal shift with bits moving from low to high
1020                // If shift >= 32: low becomes 0, high gets shifted low part
1021                let shift_32 = BV::from_i64(32, 32);
1022                let is_large = shift_mod.bvuge(&shift_32); // shift >= 32
1023
1024                // Small shift (< 32):
1025                // result_lo = n_lo << shift
1026                // result_hi = (n_hi << shift) | (n_lo >> (32 - shift))
1027                let result_lo_small = n_lo.bvshl(&shift_mod);
1028                let shift_complement = shift_32.bvsub(&shift_mod);
1029                let bits_to_high = n_lo.bvlshr(&shift_complement);
1030                let result_hi_small = n_hi.bvshl(&shift_mod).bvor(&bits_to_high);
1031
1032                // Large shift (>= 32):
1033                // result_lo = 0
1034                // result_hi = n_lo << (shift - 32)
1035                let zero = BV::from_i64(0, 32);
1036                let shift_minus_32 = shift_mod.bvsub(&shift_32);
1037                let result_lo_large = zero.clone();
1038                let result_hi_large = n_lo.bvshl(&shift_minus_32);
1039
1040                // Select based on shift size
1041                let result_lo = is_large.ite(&result_lo_large, &result_lo_small);
1042                let result_hi = is_large.ite(&result_hi_large, &result_hi_small);
1043
1044                state.set_reg(rd_lo, result_lo);
1045                state.set_reg(rd_hi, result_hi);
1046            }
1047
1048            ArmOp::I64ShrU {
1049                rd_lo,
1050                rd_hi,
1051                rn_lo,
1052                rn_hi,
1053                rm_lo,
1054                rm_hi: _,
1055            } => {
1056                // 64-bit logical (unsigned) right shift
1057                let n_lo = state.get_reg(rn_lo).clone();
1058                let n_hi = state.get_reg(rn_hi).clone();
1059                let shift_amt = state.get_reg(rm_lo).clone();
1060
1061                let shift_mod = shift_amt.bvand(BV::from_i64(63, 32));
1062                let shift_32 = BV::from_i64(32, 32);
1063                let is_large = shift_mod.bvuge(&shift_32);
1064
1065                // Small shift (< 32):
1066                // result_hi = n_hi >> shift
1067                // result_lo = (n_lo >> shift) | (n_hi << (32 - shift))
1068                let result_hi_small = n_hi.bvlshr(&shift_mod);
1069                let shift_complement = shift_32.bvsub(&shift_mod);
1070                let bits_to_low = n_hi.bvshl(&shift_complement);
1071                let result_lo_small = n_lo.bvlshr(&shift_mod).bvor(&bits_to_low);
1072
1073                // Large shift (>= 32):
1074                // result_hi = 0
1075                // result_lo = n_hi >> (shift - 32)
1076                let zero = BV::from_i64(0, 32);
1077                let shift_minus_32 = shift_mod.bvsub(&shift_32);
1078                let result_hi_large = zero.clone();
1079                let result_lo_large = n_hi.bvlshr(&shift_minus_32);
1080
1081                let result_lo = is_large.ite(&result_lo_large, &result_lo_small);
1082                let result_hi = is_large.ite(&result_hi_large, &result_hi_small);
1083
1084                state.set_reg(rd_lo, result_lo);
1085                state.set_reg(rd_hi, result_hi);
1086            }
1087
1088            ArmOp::I64ShrS {
1089                rd_lo,
1090                rd_hi,
1091                rn_lo,
1092                rn_hi,
1093                rm_lo,
1094                rm_hi: _,
1095            } => {
1096                // 64-bit arithmetic (signed) right shift
1097                let n_lo = state.get_reg(rn_lo).clone();
1098                let n_hi = state.get_reg(rn_hi).clone();
1099                let shift_amt = state.get_reg(rm_lo).clone();
1100
1101                let shift_mod = shift_amt.bvand(BV::from_i64(63, 32));
1102                let shift_32 = BV::from_i64(32, 32);
1103                let is_large = shift_mod.bvuge(&shift_32);
1104
1105                // Small shift (< 32):
1106                // result_hi = n_hi >> shift (arithmetic)
1107                // result_lo = (n_lo >> shift) | (n_hi << (32 - shift))
1108                let result_hi_small = n_hi.bvashr(&shift_mod);
1109                let shift_complement = shift_32.bvsub(&shift_mod);
1110                let bits_to_low = n_hi.bvshl(&shift_complement);
1111                let result_lo_small = n_lo.bvlshr(&shift_mod).bvor(&bits_to_low);
1112
1113                // Large shift (>= 32):
1114                // result_hi = n_hi >> 31 (sign extension: all 0s or all 1s)
1115                // result_lo = n_hi >> (shift - 32) (arithmetic)
1116                let shift_31 = BV::from_i64(31, 32);
1117                let result_hi_large = n_hi.bvashr(&shift_31);
1118                let shift_minus_32 = shift_mod.bvsub(&shift_32);
1119                let result_lo_large = n_hi.bvashr(&shift_minus_32);
1120
1121                let result_lo = is_large.ite(&result_lo_large, &result_lo_small);
1122                let result_hi = is_large.ite(&result_hi_large, &result_hi_small);
1123
1124                state.set_reg(rd_lo, result_lo);
1125                state.set_reg(rd_hi, result_hi);
1126            }
1127
1128            // ========================================================================
1129            // i64 Rotation Operations
1130            // ========================================================================
1131            ArmOp::I64Rotl {
1132                rdlo,
1133                rdhi,
1134                rnlo,
1135                rnhi,
1136                shift,
1137            } => {
1138                // 64-bit rotate left: rotl(hi:lo, shift)
1139                // Result = (value << shift) | (value >> (64 - shift))
1140                let n_lo = state.get_reg(rnlo).clone();
1141                let n_hi = state.get_reg(rnhi).clone();
1142                let shift_amt = state.get_reg(shift).clone();
1143
1144                // Normalize shift to 0-63 range
1145                let shift_mod = shift_amt.bvand(BV::from_i64(63, 32));
1146                let shift_32 = BV::from_i64(32, 32);
1147                let is_large = shift_mod.bvuge(&shift_32); // shift >= 32
1148
1149                // For shift < 32:
1150                // result_lo = (n_lo << shift) | (n_hi >> (32 - shift))
1151                // result_hi = (n_hi << shift) | (n_lo >> (32 - shift))
1152                let shift_complement = shift_32.bvsub(&shift_mod);
1153
1154                let lo_shifted_left = n_lo.bvshl(&shift_mod);
1155                let hi_bits_to_lo = n_hi.bvlshr(&shift_complement);
1156                let result_lo_small = lo_shifted_left.bvor(&hi_bits_to_lo);
1157
1158                let hi_shifted_left = n_hi.bvshl(&shift_mod);
1159                let lo_bits_to_hi = n_lo.bvlshr(&shift_complement);
1160                let result_hi_small = hi_shifted_left.bvor(&lo_bits_to_hi);
1161
1162                // For shift >= 32:
1163                // Swap and rotate by (shift - 32)
1164                let shift_minus_32 = shift_mod.bvsub(&shift_32);
1165                let complement_large = shift_32.bvsub(&shift_minus_32);
1166
1167                let hi_shifted_left_large = n_hi.bvshl(&shift_minus_32);
1168                let lo_bits_to_hi_large = n_lo.bvlshr(&complement_large);
1169                let result_lo_large = hi_shifted_left_large.bvor(&lo_bits_to_hi_large);
1170
1171                let lo_shifted_left_large = n_lo.bvshl(&shift_minus_32);
1172                let hi_bits_to_lo_large = n_hi.bvlshr(&complement_large);
1173                let result_hi_large = lo_shifted_left_large.bvor(&hi_bits_to_lo_large);
1174
1175                // Select based on shift size
1176                let result_lo = is_large.ite(&result_lo_large, &result_lo_small);
1177                let result_hi = is_large.ite(&result_hi_large, &result_hi_small);
1178
1179                state.set_reg(rdlo, result_lo);
1180                state.set_reg(rdhi, result_hi);
1181            }
1182
1183            ArmOp::I64Rotr {
1184                rdlo,
1185                rdhi,
1186                rnlo,
1187                rnhi,
1188                shift,
1189            } => {
1190                // 64-bit rotate right: rotr(hi:lo, shift)
1191                // Result = (value >> shift) | (value << (64 - shift))
1192                let n_lo = state.get_reg(rnlo).clone();
1193                let n_hi = state.get_reg(rnhi).clone();
1194                let shift_amt = state.get_reg(shift).clone();
1195
1196                // Normalize shift to 0-63 range
1197                let shift_mod = shift_amt.bvand(BV::from_i64(63, 32));
1198                let shift_32 = BV::from_i64(32, 32);
1199                let is_large = shift_mod.bvuge(&shift_32); // shift >= 32
1200
1201                // For shift < 32:
1202                // result_lo = (n_lo >> shift) | (n_hi << (32 - shift))
1203                // result_hi = (n_hi >> shift) | (n_lo << (32 - shift))
1204                let shift_complement = shift_32.bvsub(&shift_mod);
1205
1206                let lo_shifted_right = n_lo.bvlshr(&shift_mod);
1207                let hi_bits_to_lo = n_hi.bvshl(&shift_complement);
1208                let result_lo_small = lo_shifted_right.bvor(&hi_bits_to_lo);
1209
1210                let hi_shifted_right = n_hi.bvlshr(&shift_mod);
1211                let lo_bits_to_hi = n_lo.bvshl(&shift_complement);
1212                let result_hi_small = hi_shifted_right.bvor(&lo_bits_to_hi);
1213
1214                // For shift >= 32:
1215                // Swap and rotate by (shift - 32)
1216                let shift_minus_32 = shift_mod.bvsub(&shift_32);
1217                let complement_large = shift_32.bvsub(&shift_minus_32);
1218
1219                let hi_shifted_right_large = n_hi.bvlshr(&shift_minus_32);
1220                let lo_bits_to_hi_large = n_lo.bvshl(&complement_large);
1221                let result_lo_large = hi_shifted_right_large.bvor(&lo_bits_to_hi_large);
1222
1223                let lo_shifted_right_large = n_lo.bvlshr(&shift_minus_32);
1224                let hi_bits_to_lo_large = n_hi.bvshl(&complement_large);
1225                let result_hi_large = lo_shifted_right_large.bvor(&hi_bits_to_lo_large);
1226
1227                // Select based on shift size
1228                let result_lo = is_large.ite(&result_lo_large, &result_lo_small);
1229                let result_hi = is_large.ite(&result_hi_large, &result_hi_small);
1230
1231                state.set_reg(rdlo, result_lo);
1232                state.set_reg(rdhi, result_hi);
1233            }
1234
1235            ArmOp::I64Clz { rd, rnlo, rnhi } => {
1236                // Count leading zeros for 64-bit value
1237                // If high part has zeros, result = clz(high) + clz(low)
1238                // If high part is zero, result = 32 + clz(low)
1239                let n_lo = state.get_reg(rnlo).clone();
1240                let n_hi = state.get_reg(rnhi).clone();
1241
1242                let hi_clz = self.encode_clz(&n_hi);
1243                let lo_clz = self.encode_clz(&n_lo);
1244
1245                // If high == 32 (all zeros), add low clz; else use high clz
1246                let thirty_two = BV::from_i64(32, 32);
1247                let hi_is_zero = hi_clz.eq(&thirty_two);
1248                let result = hi_is_zero.ite(
1249                    thirty_two.bvadd(&lo_clz), // High is zero: 32 + clz(low)
1250                    &hi_clz,                   // High has bits: clz(high)
1251                );
1252                state.set_reg(rd, result);
1253            }
1254
1255            ArmOp::I64Ctz { rd, rnlo, rnhi } => {
1256                // Count trailing zeros for 64-bit value
1257                // If low part is zero, result = 32 + ctz(high)
1258                // Else result = ctz(low)
1259                let n_lo = state.get_reg(rnlo).clone();
1260                let n_hi = state.get_reg(rnhi).clone();
1261
1262                let lo_ctz = self.encode_ctz(&n_lo);
1263                let hi_ctz = self.encode_ctz(&n_hi);
1264
1265                // If low == 32 (all zeros), add high ctz; else use low ctz
1266                let thirty_two = BV::from_i64(32, 32);
1267                let lo_is_zero = lo_ctz.eq(&thirty_two);
1268                let result = lo_is_zero.ite(
1269                    thirty_two.bvadd(&hi_ctz), // Low is zero: 32 + ctz(high)
1270                    &lo_ctz,                   // Low has bits: ctz(low)
1271                );
1272                state.set_reg(rd, result);
1273            }
1274
1275            ArmOp::I64Popcnt { rd, rnlo, rnhi } => {
1276                // Population count for 64-bit value
1277                // Result = popcnt(low) + popcnt(high)
1278                let n_lo = state.get_reg(rnlo).clone();
1279                let n_hi = state.get_reg(rnhi).clone();
1280
1281                let lo_popcnt = self.encode_popcnt(&n_lo);
1282                let hi_popcnt = self.encode_popcnt(&n_hi);
1283
1284                let result = lo_popcnt.bvadd(&hi_popcnt);
1285                state.set_reg(rd, result);
1286            }
1287
1288            // ========================================================================
1289            // i64 Memory Operations
1290            // ========================================================================
1291            ArmOp::I64Ldr { rdlo, rdhi, addr } => {
1292                // Load 64-bit value from memory
1293                // Simplified: return symbolic values for both registers
1294                // Real implementation would load from memory at [addr] and [addr+4]
1295                let result_lo = BV::new_const(format!("i64load_lo_{:?}", addr), 32);
1296                let result_hi = BV::new_const(format!("i64load_hi_{:?}", addr), 32);
1297                state.set_reg(rdlo, result_lo);
1298                state.set_reg(rdhi, result_hi);
1299            }
1300
1301            ArmOp::I64Str {
1302                rdlo: _,
1303                rdhi: _,
1304                addr: _,
1305            } => {
1306                // Store 64-bit value to memory
1307                // Simplified: memory updates not fully modeled yet
1308                // Real implementation would store rdlo to [addr] and rdhi to [addr+4]
1309                // No register changes - store operation has no output
1310            }
1311
1312            // ========================================================================
1313            // f32 Operations (Phase 2 - Floating Point)
1314            // ========================================================================
1315            // Note: f32 values are represented as 32-bit bitvectors (IEEE 754 format)
1316            // For verification, we use symbolic bitvector operations
1317            // A complete implementation would use Z3's FloatingPoint sort
1318
1319            // f32 Constants
1320            ArmOp::F32Const { sd, value } => {
1321                // Load f32 constant (represented as 32-bit bitvector)
1322                // Convert f32 to its IEEE 754 bit representation
1323                let bits = value.to_bits() as i64;
1324                let bv_val = BV::from_i64(bits, 32);
1325                state.set_vfp_reg(sd, bv_val);
1326            }
1327
1328            // f32 Arithmetic (symbolic for verification)
1329            ArmOp::F32Add { sd, sn, sm } => {
1330                // f32 addition: sd = sn + sm
1331                // For verification, return symbolic value
1332                // Full implementation would use Z3 FloatingPoint operations
1333                let result = BV::new_const(format!("f32_add_{:?}_{:?}", sn, sm), 32);
1334                state.set_vfp_reg(sd, result);
1335            }
1336
1337            ArmOp::F32Sub { sd, sn, sm } => {
1338                // f32 subtraction: sd = sn - sm
1339                let result = BV::new_const(format!("f32_sub_{:?}_{:?}", sn, sm), 32);
1340                state.set_vfp_reg(sd, result);
1341            }
1342
1343            ArmOp::F32Mul { sd, sn, sm } => {
1344                // f32 multiplication: sd = sn * sm
1345                let result = BV::new_const(format!("f32_mul_{:?}_{:?}", sn, sm), 32);
1346                state.set_vfp_reg(sd, result);
1347            }
1348
1349            ArmOp::F32Div { sd, sn, sm } => {
1350                // f32 division: sd = sn / sm
1351                let result = BV::new_const(format!("f32_div_{:?}_{:?}", sn, sm), 32);
1352                state.set_vfp_reg(sd, result);
1353            }
1354
1355            // f32 Simple Math
1356            ArmOp::F32Abs { sd, sm } => {
1357                // f32 absolute value: sd = |sm|
1358                // Clear the sign bit (bit 31)
1359                let val = state.get_vfp_reg(sm).clone();
1360                let mask = BV::from_u64(0x7FFFFFFF, 32); // Clear sign bit
1361                let result = val.bvand(&mask);
1362                state.set_vfp_reg(sd, result);
1363            }
1364
1365            ArmOp::F32Neg { sd, sm } => {
1366                // f32 negation: sd = -sm
1367                // Flip the sign bit (bit 31)
1368                let val = state.get_vfp_reg(sm).clone();
1369                let mask = BV::from_u64(0x80000000, 32); // Sign bit
1370                let result = val.bvxor(&mask);
1371                state.set_vfp_reg(sd, result);
1372            }
1373
1374            ArmOp::F32Sqrt { sd, sm } => {
1375                // f32 square root: sd = sqrt(sm)
1376                // Symbolic representation for verification
1377                let result = BV::new_const(format!("f32_sqrt_{:?}", sm), 32);
1378                state.set_vfp_reg(sd, result);
1379            }
1380
1381            ArmOp::F32Min { sd, sn, sm } => {
1382                // f32 minimum: sd = min(sn, sm)
1383                // IEEE 754 semantics: NaN propagation, -0.0 < +0.0
1384                // Symbolic representation for verification
1385                let result = BV::new_const(format!("f32_min_{:?}_{:?}", sn, sm), 32);
1386                state.set_vfp_reg(sd, result);
1387            }
1388
1389            ArmOp::F32Max { sd, sn, sm } => {
1390                // f32 maximum: sd = max(sn, sm)
1391                // IEEE 754 semantics: NaN propagation, +0.0 > -0.0
1392                // Symbolic representation for verification
1393                let result = BV::new_const(format!("f32_max_{:?}_{:?}", sn, sm), 32);
1394                state.set_vfp_reg(sd, result);
1395            }
1396
1397            ArmOp::F32Copysign { sd, sn, sm } => {
1398                // f32 copysign: sd = |sn| with sign of sm
1399                // Take magnitude of sn and sign bit from sm
1400                let val_n = state.get_vfp_reg(sn).clone();
1401                let val_m = state.get_vfp_reg(sm).clone();
1402
1403                // Extract magnitude from sn (clear sign bit)
1404                let mag_mask = BV::from_u64(0x7FFFFFFF, 32);
1405                let magnitude = val_n.bvand(&mag_mask);
1406
1407                // Extract sign from sm (bit 31 only)
1408                let sign_mask = BV::from_u64(0x80000000, 32);
1409                let sign = val_m.bvand(&sign_mask);
1410
1411                // Combine: magnitude | sign
1412                let result = magnitude.bvor(&sign);
1413                state.set_vfp_reg(sd, result);
1414            }
1415
1416            ArmOp::F32Load { sd, addr } => {
1417                // f32 load: sd = memory[addr]
1418                // Symbolic memory access for verification
1419                let result = BV::new_const(format!("f32_load_{:?}", addr), 32);
1420                state.set_vfp_reg(sd, result);
1421            }
1422
1423            // f32 Comparisons (result stored in integer register)
1424            ArmOp::F32Eq { rd, sn, sm } => {
1425                // f32 equal: rd = (sn == sm) ? 1 : 0
1426                // IEEE 754: NaN != NaN, so symbolic comparison needed
1427                let result = BV::new_const(format!("f32_eq_{:?}_{:?}", sn, sm), 32);
1428                state.set_reg(rd, result);
1429            }
1430
1431            ArmOp::F32Ne { rd, sn, sm } => {
1432                // f32 not equal: rd = (sn != sm) ? 1 : 0
1433                let result = BV::new_const(format!("f32_ne_{:?}_{:?}", sn, sm), 32);
1434                state.set_reg(rd, result);
1435            }
1436
1437            ArmOp::F32Lt { rd, sn, sm } => {
1438                // f32 less than: rd = (sn < sm) ? 1 : 0
1439                let result = BV::new_const(format!("f32_lt_{:?}_{:?}", sn, sm), 32);
1440                state.set_reg(rd, result);
1441            }
1442
1443            ArmOp::F32Le { rd, sn, sm } => {
1444                // f32 less than or equal: rd = (sn <= sm) ? 1 : 0
1445                let result = BV::new_const(format!("f32_le_{:?}_{:?}", sn, sm), 32);
1446                state.set_reg(rd, result);
1447            }
1448
1449            ArmOp::F32Gt { rd, sn, sm } => {
1450                // f32 greater than: rd = (sn > sm) ? 1 : 0
1451                let result = BV::new_const(format!("f32_gt_{:?}_{:?}", sn, sm), 32);
1452                state.set_reg(rd, result);
1453            }
1454
1455            ArmOp::F32Ge { rd, sn, sm } => {
1456                // f32 greater than or equal: rd = (sn >= sm) ? 1 : 0
1457                let result = BV::new_const(format!("f32_ge_{:?}_{:?}", sn, sm), 32);
1458                state.set_reg(rd, result);
1459            }
1460
1461            ArmOp::F32Store { sd, addr } => {
1462                // f32 store: memory[addr] = sd
1463                // Memory write - modeled symbolically for verification
1464                // In a full implementation, would update memory state
1465                // For now, this is a no-op as we model memory symbolically
1466                let _val = state.get_vfp_reg(sd);
1467                let _addr_str = format!("{:?}", addr);
1468                // TODO: Add memory state tracking when implementing full memory model
1469            }
1470
1471            // f32 Advanced Math Operations
1472            ArmOp::F32Ceil { sd, sm } => {
1473                // f32 ceil: sd = ceil(sm) - round toward +infinity
1474                // Symbolic representation for IEEE 754 rounding
1475                let result = BV::new_const(format!("f32_ceil_{:?}", sm), 32);
1476                state.set_vfp_reg(sd, result);
1477            }
1478
1479            ArmOp::F32Floor { sd, sm } => {
1480                // f32 floor: sd = floor(sm) - round toward -infinity
1481                // Symbolic representation for IEEE 754 rounding
1482                let result = BV::new_const(format!("f32_floor_{:?}", sm), 32);
1483                state.set_vfp_reg(sd, result);
1484            }
1485
1486            ArmOp::F32Trunc { sd, sm } => {
1487                // f32 trunc: sd = trunc(sm) - round toward zero
1488                // Symbolic representation for IEEE 754 rounding
1489                let result = BV::new_const(format!("f32_trunc_{:?}", sm), 32);
1490                state.set_vfp_reg(sd, result);
1491            }
1492
1493            ArmOp::F32Nearest { sd, sm } => {
1494                // f32 nearest: sd = nearest(sm) - round to nearest, ties to even
1495                // Symbolic representation for IEEE 754 rounding
1496                let result = BV::new_const(format!("f32_nearest_{:?}", sm), 32);
1497                state.set_vfp_reg(sd, result);
1498            }
1499
1500            // f32 Conversions from Integers
1501            ArmOp::F32ConvertI32S { sd, rm } => {
1502                // f32 convert from signed i32: sd = (f32)rm
1503                let int_val = state.get_reg(rm);
1504                let result = BV::new_const(format!("f32_convert_i32s_{:?}", int_val), 32);
1505                state.set_vfp_reg(sd, result);
1506            }
1507
1508            ArmOp::F32ConvertI32U { sd, rm } => {
1509                // f32 convert from unsigned i32: sd = (f32)(unsigned)rm
1510                let int_val = state.get_reg(rm);
1511                let result = BV::new_const(format!("f32_convert_i32u_{:?}", int_val), 32);
1512                state.set_vfp_reg(sd, result);
1513            }
1514
1515            ArmOp::F32ConvertI64S { sd, rmlo, rmhi } => {
1516                // f32 convert from signed i64: sd = (f32)r64
1517                let lo = state.get_reg(rmlo);
1518                let hi = state.get_reg(rmhi);
1519                let result = BV::new_const(format!("f32_convert_i64s_{:?}_{:?}", lo, hi), 32);
1520                state.set_vfp_reg(sd, result);
1521            }
1522
1523            ArmOp::F32ConvertI64U { sd, rmlo, rmhi } => {
1524                // f32 convert from unsigned i64: sd = (f32)(unsigned)r64
1525                let lo = state.get_reg(rmlo);
1526                let hi = state.get_reg(rmhi);
1527                let result = BV::new_const(format!("f32_convert_i64u_{:?}_{:?}", lo, hi), 32);
1528                state.set_vfp_reg(sd, result);
1529            }
1530
1531            // f32 Reinterpretations
1532            ArmOp::F32ReinterpretI32 { sd, rm } => {
1533                // f32 reinterpret i32: sd = reinterpret_cast<f32>(rm)
1534                // Bitwise copy without conversion
1535                let bits = state.get_reg(rm).clone();
1536                state.set_vfp_reg(sd, bits);
1537            }
1538
1539            ArmOp::I32ReinterpretF32 { rd, sm } => {
1540                // i32 reinterpret f32: rd = reinterpret_cast<i32>(sm)
1541                // Bitwise copy without conversion
1542                let bits = state.get_vfp_reg(sm).clone();
1543                state.set_reg(rd, bits);
1544            }
1545
1546            // ===================================================================
1547            // f64 Operations (Phase 2c - Double-Precision Floating Point)
1548            // ===================================================================
1549
1550            // f64 Arithmetic (symbolic for verification)
1551            ArmOp::F64Add { dd, dn, dm } => {
1552                // f64 addition: dd = dn + dm
1553                // For verification, return symbolic value
1554                // Full implementation would use Z3 FloatingPoint operations
1555                let result = BV::new_const(format!("f64_add_{:?}_{:?}", dn, dm), 64);
1556                state.set_vfp_reg(dd, result);
1557            }
1558
1559            ArmOp::F64Sub { dd, dn, dm } => {
1560                // f64 subtraction: dd = dn - dm
1561                let result = BV::new_const(format!("f64_sub_{:?}_{:?}", dn, dm), 64);
1562                state.set_vfp_reg(dd, result);
1563            }
1564
1565            ArmOp::F64Mul { dd, dn, dm } => {
1566                // f64 multiplication: dd = dn * dm
1567                let result = BV::new_const(format!("f64_mul_{:?}_{:?}", dn, dm), 64);
1568                state.set_vfp_reg(dd, result);
1569            }
1570
1571            ArmOp::F64Div { dd, dn, dm } => {
1572                // f64 division: dd = dn / dm
1573                let result = BV::new_const(format!("f64_div_{:?}_{:?}", dn, dm), 64);
1574                state.set_vfp_reg(dd, result);
1575            }
1576
1577            // f64 Simple Math
1578            ArmOp::F64Abs { dd, dm } => {
1579                // f64 absolute value: dd = |dm|
1580                // Clear the sign bit (bit 63)
1581                let val = state.get_vfp_reg(dm).clone();
1582                let mask = BV::from_u64(0x7FFFFFFFFFFFFFFF, 64); // Clear sign bit
1583                let result = val.bvand(&mask);
1584                state.set_vfp_reg(dd, result);
1585            }
1586
1587            ArmOp::F64Neg { dd, dm } => {
1588                // f64 negation: dd = -dm
1589                // Flip the sign bit (bit 63)
1590                let val = state.get_vfp_reg(dm).clone();
1591                let mask = BV::from_u64(0x8000000000000000, 64); // Sign bit
1592                let result = val.bvxor(&mask);
1593                state.set_vfp_reg(dd, result);
1594            }
1595
1596            ArmOp::F64Sqrt { dd, dm } => {
1597                // f64 square root: dd = sqrt(dm)
1598                // Symbolic representation for verification
1599                let result = BV::new_const(format!("f64_sqrt_{:?}", dm), 64);
1600                state.set_vfp_reg(dd, result);
1601            }
1602
1603            ArmOp::F64Min { dd, dn, dm } => {
1604                // f64 minimum: dd = min(dn, dm)
1605                // IEEE 754 semantics: NaN propagation, -0.0 < +0.0
1606                // Symbolic representation for verification
1607                let result = BV::new_const(format!("f64_min_{:?}_{:?}", dn, dm), 64);
1608                state.set_vfp_reg(dd, result);
1609            }
1610
1611            ArmOp::F64Max { dd, dn, dm } => {
1612                // f64 maximum: dd = max(dn, dm)
1613                // IEEE 754 semantics: NaN propagation, +0.0 > -0.0
1614                // Symbolic representation for verification
1615                let result = BV::new_const(format!("f64_max_{:?}_{:?}", dn, dm), 64);
1616                state.set_vfp_reg(dd, result);
1617            }
1618
1619            ArmOp::F64Copysign { dd, dn, dm } => {
1620                // f64 copysign: dd = |dn| with sign of dm
1621                // Take magnitude of dn and sign bit from dm
1622                let val_n = state.get_vfp_reg(dn).clone();
1623                let val_m = state.get_vfp_reg(dm).clone();
1624
1625                // Extract magnitude from dn (clear sign bit)
1626                let mag_mask = BV::from_u64(0x7FFFFFFFFFFFFFFF, 64);
1627                let magnitude = val_n.bvand(&mag_mask);
1628
1629                // Extract sign from dm (bit 63 only)
1630                let sign_mask = BV::from_u64(0x8000000000000000, 64);
1631                let sign = val_m.bvand(&sign_mask);
1632
1633                // Combine: magnitude | sign
1634                let result = magnitude.bvor(&sign);
1635                state.set_vfp_reg(dd, result);
1636            }
1637
1638            // f64 Rounding Operations (symbolic for verification)
1639            ArmOp::F64Ceil { dd, dm } => {
1640                // f64 ceil: dd = ceil(dm) - round toward +infinity
1641                let result = BV::new_const(format!("f64_ceil_{:?}", dm), 64);
1642                state.set_vfp_reg(dd, result);
1643            }
1644
1645            ArmOp::F64Floor { dd, dm } => {
1646                // f64 floor: dd = floor(dm) - round toward -infinity
1647                let result = BV::new_const(format!("f64_floor_{:?}", dm), 64);
1648                state.set_vfp_reg(dd, result);
1649            }
1650
1651            ArmOp::F64Trunc { dd, dm } => {
1652                // f64 trunc: dd = trunc(dm) - round toward zero
1653                let result = BV::new_const(format!("f64_trunc_{:?}", dm), 64);
1654                state.set_vfp_reg(dd, result);
1655            }
1656
1657            ArmOp::F64Nearest { dd, dm } => {
1658                // f64 nearest: dd = round(dm) - round to nearest, ties to even
1659                let result = BV::new_const(format!("f64_nearest_{:?}", dm), 64);
1660                state.set_vfp_reg(dd, result);
1661            }
1662
1663            // f64 Memory Operations
1664            ArmOp::F64Load { dd, addr } => {
1665                // f64 load: dd = memory[addr]
1666                // Symbolic memory access for verification
1667                let result = BV::new_const(format!("f64_load_{:?}", addr), 64);
1668                state.set_vfp_reg(dd, result);
1669            }
1670
1671            ArmOp::F64Store { dd: _, addr: _ } => {
1672                // f64 store: memory[addr] = dd
1673                // Store operations don't produce register values
1674                // No state change for symbolic execution
1675            }
1676
1677            ArmOp::F64Const { dd, value } => {
1678                // f64 constant: dd = value
1679                let bits = value.to_bits() as i64;
1680                let result = BV::from_i64(bits, 64);
1681                state.set_vfp_reg(dd, result);
1682            }
1683
1684            // f64 Comparisons (result stored in integer register)
1685            ArmOp::F64Eq { rd, dn, dm } => {
1686                // f64 equal: rd = (dn == dm) ? 1 : 0
1687                // IEEE 754: NaN != NaN, so symbolic comparison needed
1688                let result = BV::new_const(format!("f64_eq_{:?}_{:?}", dn, dm), 32);
1689                state.set_reg(rd, result);
1690            }
1691
1692            ArmOp::F64Ne { rd, dn, dm } => {
1693                // f64 not equal: rd = (dn != dm) ? 1 : 0
1694                let result = BV::new_const(format!("f64_ne_{:?}_{:?}", dn, dm), 32);
1695                state.set_reg(rd, result);
1696            }
1697
1698            ArmOp::F64Lt { rd, dn, dm } => {
1699                // f64 less than: rd = (dn < dm) ? 1 : 0
1700                let result = BV::new_const(format!("f64_lt_{:?}_{:?}", dn, dm), 32);
1701                state.set_reg(rd, result);
1702            }
1703
1704            ArmOp::F64Le { rd, dn, dm } => {
1705                // f64 less than or equal: rd = (dn <= dm) ? 1 : 0
1706                let result = BV::new_const(format!("f64_le_{:?}_{:?}", dn, dm), 32);
1707                state.set_reg(rd, result);
1708            }
1709
1710            ArmOp::F64Gt { rd, dn, dm } => {
1711                // f64 greater than: rd = (dn > dm) ? 1 : 0
1712                let result = BV::new_const(format!("f64_gt_{:?}_{:?}", dn, dm), 32);
1713                state.set_reg(rd, result);
1714            }
1715
1716            ArmOp::F64Ge { rd, dn, dm } => {
1717                // f64 greater than or equal: rd = (dn >= dm) ? 1 : 0
1718                let result = BV::new_const(format!("f64_ge_{:?}_{:?}", dn, dm), 32);
1719                state.set_reg(rd, result);
1720            }
1721
1722            // f64 Conversions
1723            ArmOp::F64ConvertI32S { dd, rm } => {
1724                // f64 convert i32 signed: dd = (f64)rm
1725                // Symbolic conversion
1726                let result = BV::new_const(format!("f64_convert_i32s_{:?}", rm), 64);
1727                state.set_vfp_reg(dd, result);
1728            }
1729
1730            ArmOp::F64ConvertI32U { dd, rm } => {
1731                // f64 convert i32 unsigned: dd = (f64)(unsigned)rm
1732                // Symbolic conversion
1733                let result = BV::new_const(format!("f64_convert_i32u_{:?}", rm), 64);
1734                state.set_vfp_reg(dd, result);
1735            }
1736
1737            ArmOp::F64ConvertI64S {
1738                dd,
1739                rmlo: _,
1740                rmhi: _,
1741            } => {
1742                // f64 convert i64 signed: dd = (f64)(rmhi:rmlo)
1743                // Symbolic conversion (complex operation)
1744                let result = BV::new_const("f64_convert_i64s_result", 64);
1745                state.set_vfp_reg(dd, result);
1746            }
1747
1748            ArmOp::F64ConvertI64U {
1749                dd,
1750                rmlo: _,
1751                rmhi: _,
1752            } => {
1753                // f64 convert i64 unsigned: dd = (f64)(unsigned)(rmhi:rmlo)
1754                // Symbolic conversion (complex operation)
1755                let result = BV::new_const("f64_convert_i64u_result", 64);
1756                state.set_vfp_reg(dd, result);
1757            }
1758
1759            ArmOp::F64PromoteF32 { dd, sm } => {
1760                // f64 promote f32: dd = (f64)sm
1761                // Promote from 32-bit to 64-bit (symbolic for verification)
1762                let result = BV::new_const(format!("f64_promote_f32_{:?}", sm), 64);
1763                state.set_vfp_reg(dd, result);
1764            }
1765
1766            ArmOp::F64ReinterpretI64 { dd, rmlo, rmhi } => {
1767                // f64 reinterpret i64: dd = reinterpret_cast<f64>(rmhi:rmlo)
1768                // Bitwise copy without conversion - combine two 32-bit registers
1769                let lo = state.get_reg(rmlo).clone();
1770                let hi = state.get_reg(rmhi).clone();
1771
1772                // Extend to 64 bits and combine: (hi << 32) | lo
1773                let lo_64 = lo.zero_ext(32); // Extend to 64 bits
1774                let hi_64 = hi.zero_ext(32);
1775                let shift_32 = BV::from_u64(32, 64);
1776                let hi_shifted = hi_64.bvshl(&shift_32);
1777                let result = hi_shifted.bvor(&lo_64);
1778
1779                state.set_vfp_reg(dd, result);
1780            }
1781
1782            ArmOp::I64ReinterpretF64 { rdlo, rdhi, dm } => {
1783                // i64 reinterpret f64: (rdhi:rdlo) = reinterpret_cast<i64>(dm)
1784                // Bitwise copy without conversion - split 64-bit into two 32-bit registers
1785                let bits = state.get_vfp_reg(dm).clone();
1786
1787                // Extract low 32 bits
1788                let lo = bits.extract(31, 0);
1789                state.set_reg(rdlo, lo);
1790
1791                // Extract high 32 bits
1792                let hi = bits.extract(63, 32);
1793                state.set_reg(rdhi, hi);
1794            }
1795
1796            ArmOp::I64TruncF64S {
1797                rdlo: _,
1798                rdhi: _,
1799                dm: _,
1800            } => {
1801                // i64 trunc f64 signed: (rdhi:rdlo) = (i64)dm
1802                // Symbolic conversion (complex operation)
1803                // Would require proper truncation with saturation
1804            }
1805
1806            ArmOp::I64TruncF64U {
1807                rdlo: _,
1808                rdhi: _,
1809                dm: _,
1810            } => {
1811                // i64 trunc f64 unsigned: (rdhi:rdlo) = (unsigned i64)dm
1812                // Symbolic conversion (complex operation)
1813                // Would require proper truncation with saturation
1814            }
1815
1816            ArmOp::I32TruncF64S { rd, dm } => {
1817                // i32 trunc f64 signed: rd = (i32)dm
1818                // Symbolic conversion
1819                let result = BV::new_const(format!("i32_trunc_f64s_{:?}", dm), 32);
1820                state.set_reg(rd, result);
1821            }
1822
1823            ArmOp::I32TruncF64U { rd, dm } => {
1824                // i32 trunc f64 unsigned: rd = (unsigned i32)dm
1825                // Symbolic conversion
1826                let result = BV::new_const(format!("i32_trunc_f64u_{:?}", dm), 32);
1827                state.set_reg(rd, result);
1828            }
1829
1830            // VCR-VER-002 (#166): UDF is the WASM trap sink — executing it
1831            // raises UsageFault. In the straight-line model (no path guards)
1832            // reaching a UDF means the sequence traps unconditionally; the
1833            // branch-taking executor [`Self::encode_sequence_br`] instead
1834            // conditions this on the guard the UDF is reached under.
1835            ArmOp::Udf { .. } => {
1836                state.may_trap = Bool::from_bool(true);
1837            }
1838
1839            _ => {
1840                // Unsupported operations - no state change
1841            }
1842        }
1843    }
1844
1845    /// Evaluate an Operand2 value
1846    fn evaluate_operand2(&self, op2: &Operand2, state: &ArmState) -> BV {
1847        match op2 {
1848            Operand2::Imm(value) => BV::from_i64(*value as i64, 32),
1849            Operand2::Reg(reg) => state.get_reg(reg).clone(),
1850            Operand2::RegShift { rm, shift, amount } => {
1851                let reg_val = state.get_reg(rm).clone();
1852                let shift_amount = BV::from_i64(*amount as i64, 32);
1853
1854                match shift {
1855                    synth_synthesis::ShiftType::LSL => reg_val.bvshl(&shift_amount),
1856                    synth_synthesis::ShiftType::LSR => reg_val.bvlshr(&shift_amount),
1857                    synth_synthesis::ShiftType::ASR => reg_val.bvashr(&shift_amount),
1858                    synth_synthesis::ShiftType::ROR => reg_val.bvrotr(&shift_amount),
1859                }
1860            }
1861        }
1862    }
1863
1864    /// Extract the result value from a register after execution
1865    pub fn extract_result(&self, state: &ArmState, reg: &Reg) -> BV {
1866        state.get_reg(reg).clone()
1867    }
1868
1869    /// Encode ARM CLZ (Count Leading Zeros) instruction
1870    ///
1871    /// Implements the same algorithm as WASM i32.clz for equivalence verification.
1872    /// Uses binary search through bit positions.
1873    fn encode_clz(&self, input: &BV) -> BV {
1874        let zero = BV::from_i64(0, 32);
1875
1876        // Special case: if input is 0, return 32
1877        let all_zero = input.eq(&zero);
1878        let result_if_zero = BV::from_i64(32, 32);
1879
1880        // Binary search approach
1881        let mut count = BV::from_i64(0, 32);
1882        let mut remaining = input.clone();
1883
1884        // Check top 16 bits
1885        let mask_16 = BV::from_u64(0xFFFF0000, 32);
1886        let top_16 = remaining.bvand(&mask_16);
1887        let top_16_zero = top_16.eq(&zero);
1888
1889        count = top_16_zero.ite(count.bvadd(BV::from_i64(16, 32)), &count);
1890        remaining = top_16_zero.ite(remaining.bvshl(BV::from_i64(16, 32)), &remaining);
1891
1892        // Check top 8 bits
1893        let mask_8 = BV::from_u64(0xFF000000, 32);
1894        let top_8 = remaining.bvand(&mask_8);
1895        let top_8_zero = top_8.eq(&zero);
1896
1897        count = top_8_zero.ite(count.bvadd(BV::from_i64(8, 32)), &count);
1898        remaining = top_8_zero.ite(remaining.bvshl(BV::from_i64(8, 32)), &remaining);
1899
1900        // Check top 4 bits
1901        let mask_4 = BV::from_u64(0xF0000000, 32);
1902        let top_4 = remaining.bvand(&mask_4);
1903        let top_4_zero = top_4.eq(&zero);
1904
1905        count = top_4_zero.ite(count.bvadd(BV::from_i64(4, 32)), &count);
1906        remaining = top_4_zero.ite(remaining.bvshl(BV::from_i64(4, 32)), &remaining);
1907
1908        // Check top 2 bits
1909        let mask_2 = BV::from_u64(0xC0000000, 32);
1910        let top_2 = remaining.bvand(&mask_2);
1911        let top_2_zero = top_2.eq(&zero);
1912
1913        count = top_2_zero.ite(count.bvadd(BV::from_i64(2, 32)), &count);
1914        remaining = top_2_zero.ite(remaining.bvshl(BV::from_i64(2, 32)), &remaining);
1915
1916        // Check top bit
1917        let mask_1 = BV::from_u64(0x80000000, 32);
1918        let top_1 = remaining.bvand(&mask_1);
1919        let top_1_zero = top_1.eq(&zero);
1920
1921        count = top_1_zero.ite(count.bvadd(BV::from_i64(1, 32)), &count);
1922
1923        // Return 32 if all zeros, otherwise return count
1924        all_zero.ite(&result_if_zero, &count)
1925    }
1926
1927    /// Encode CTZ (Count Trailing Zeros) instruction
1928    ///
1929    /// Counts the number of trailing (low-order) zero bits.
1930    /// Implemented as: ctz(x) = clz(rbit(x))
1931    /// Returns 32 if input is 0.
1932    fn encode_ctz(&self, input: &BV) -> BV {
1933        // CTZ can be implemented by reversing bits and then counting leading zeros
1934        let reversed = self.encode_rbit(input);
1935        self.encode_clz(&reversed)
1936    }
1937
1938    /// Encode ARM RBIT (Reverse Bits) instruction
1939    ///
1940    /// Reverses the bit order in a 32-bit value.
1941    /// Used in combination with CLZ to implement CTZ.
1942    fn encode_rbit(&self, input: &BV) -> BV {
1943        // Reverse bits by swapping progressively smaller chunks
1944        let mut result = input.clone();
1945
1946        // Swap 16-bit halves
1947        let mask_16 = BV::from_u64(0xFFFF0000, 32);
1948        let top_16 = result.bvand(&mask_16).bvlshr(BV::from_i64(16, 32));
1949        let bottom_16 = result.bvshl(BV::from_i64(16, 32));
1950        result = top_16.bvor(&bottom_16);
1951
1952        // Swap 8-bit chunks
1953        let mask_8_top = BV::from_u64(0xFF00FF00, 32);
1954        let mask_8_bottom = BV::from_u64(0x00FF00FF, 32);
1955        let top_8 = result.bvand(&mask_8_top).bvlshr(BV::from_i64(8, 32));
1956        let bottom_8 = result.bvand(&mask_8_bottom).bvshl(BV::from_i64(8, 32));
1957        result = top_8.bvor(&bottom_8);
1958
1959        // Swap 4-bit chunks
1960        let mask_4_top = BV::from_u64(0xF0F0F0F0, 32);
1961        let mask_4_bottom = BV::from_u64(0x0F0F0F0F, 32);
1962        let top_4 = result.bvand(&mask_4_top).bvlshr(BV::from_i64(4, 32));
1963        let bottom_4 = result.bvand(&mask_4_bottom).bvshl(BV::from_i64(4, 32));
1964        result = top_4.bvor(&bottom_4);
1965
1966        // Swap 2-bit chunks
1967        let mask_2_top = BV::from_u64(0xCCCCCCCC, 32);
1968        let mask_2_bottom = BV::from_u64(0x33333333, 32);
1969        let top_2 = result.bvand(&mask_2_top).bvlshr(BV::from_i64(2, 32));
1970        let bottom_2 = result.bvand(&mask_2_bottom).bvshl(BV::from_i64(2, 32));
1971        result = top_2.bvor(&bottom_2);
1972
1973        // Swap 1-bit chunks (individual bits)
1974        let mask_1_top = BV::from_u64(0xAAAAAAAA, 32);
1975        let mask_1_bottom = BV::from_u64(0x55555555, 32);
1976        let top_1 = result.bvand(&mask_1_top).bvlshr(BV::from_i64(1, 32));
1977        let bottom_1 = result.bvand(&mask_1_bottom).bvshl(BV::from_i64(1, 32));
1978        result = top_1.bvor(&bottom_1);
1979
1980        result
1981    }
1982
1983    /// Update condition flags for subtraction (used by CMP, SUB, etc.)
1984    ///
1985    /// Computes all four ARM condition flags based on a subtraction:
1986    /// - N (Negative): Result is negative (bit 31 set)
1987    /// - Z (Zero): Result is zero
1988    /// - C (Carry): No borrow occurred (unsigned: a >= b)
1989    /// - V (Overflow): Signed overflow occurred
1990    ///
1991    /// For subtraction result = a - b:
1992    /// - C = 1 if a >= b (unsigned), 0 if borrow
1993    /// - V = 1 if signs of a and b differ AND sign of result differs from a
1994    fn update_flags_sub(&self, state: &mut ArmState, a: &BV, b: &BV, result: &BV) {
1995        let zero = BV::from_i64(0, 32);
1996
1997        // N flag: bit 31 of result (negative if set)
1998        let sign_bit = result.extract(31, 31);
1999        let one_bit = BV::from_i64(1, 1);
2000        state.flags.n = sign_bit.eq(&one_bit);
2001
2002        // Z flag: result == 0
2003        state.flags.z = result.eq(&zero);
2004
2005        // C flag: carry/borrow flag for subtraction
2006        // For SUB: C = 1 if no borrow (i.e., a >= b unsigned)
2007        // This is equivalent to: a >= b in unsigned arithmetic
2008        state.flags.c = a.bvuge(b);
2009
2010        // V flag: signed overflow
2011        // Overflow occurs when:
2012        // - Subtracting a positive from a negative gives positive
2013        // - Subtracting a negative from a positive gives negative
2014        // Formula: (a[31] != b[31]) && (a[31] != result[31])
2015        let a_sign = a.extract(31, 31);
2016        let b_sign = b.extract(31, 31);
2017        let r_sign = result.extract(31, 31);
2018
2019        let signs_differ = a_sign.eq(&b_sign).not(); // a and b have different signs
2020        let result_sign_wrong = a_sign.eq(&r_sign).not(); // result sign differs from a
2021        state.flags.v = Bool::and(&[&signs_differ, &result_sign_wrong]);
2022    }
2023
2024    /// Update condition flags for addition
2025    ///
2026    /// Similar to subtraction but with different carry logic:
2027    /// - C = 1 if unsigned overflow (result < a or result < b)
2028    /// - V = 1 if signed overflow
2029    #[allow(dead_code)]
2030    fn update_flags_add(&self, state: &mut ArmState, a: &BV, b: &BV, result: &BV) {
2031        let zero = BV::from_i64(0, 32);
2032
2033        // N flag: bit 31 of result
2034        let sign_bit = result.extract(31, 31);
2035        let one_bit = BV::from_i64(1, 1);
2036        state.flags.n = sign_bit.eq(&one_bit);
2037
2038        // Z flag: result == 0
2039        state.flags.z = result.eq(&zero);
2040
2041        // C flag: unsigned overflow
2042        // For ADD: C = 1 if carry out (unsigned overflow)
2043        // This occurs if result < a (wrapping occurred)
2044        state.flags.c = result.bvult(a);
2045
2046        // V flag: signed overflow
2047        // Overflow occurs when:
2048        // - Adding two positives gives negative
2049        // - Adding two negatives gives positive
2050        // Formula: (a[31] == b[31]) && (a[31] != result[31])
2051        let a_sign = a.extract(31, 31);
2052        let b_sign = b.extract(31, 31);
2053        let r_sign = result.extract(31, 31);
2054
2055        let signs_same = a_sign.eq(&b_sign); // a and b have same sign
2056        let result_sign_wrong = a_sign.eq(&r_sign).not(); // result sign differs
2057        state.flags.v = Bool::and(&[&signs_same, &result_sign_wrong]);
2058    }
2059
2060    /// Evaluate an ARM condition code based on NZCV flags
2061    ///
2062    /// This implements the standard ARM condition code logic:
2063    /// - EQ: Z == 1
2064    /// - NE: Z == 0
2065    /// - LT: N != V (signed less than)
2066    /// - LE: Z == 1 || N != V (signed less or equal)
2067    /// - GT: Z == 0 && N == V (signed greater than)
2068    /// - GE: N == V (signed greater or equal)
2069    /// - LO: C == 0 (unsigned less than)
2070    /// - LS: C == 0 || Z == 1 (unsigned less or equal)
2071    /// - HI: C == 1 && Z == 0 (unsigned greater than)
2072    /// - HS: C == 1 (unsigned greater or equal)
2073    fn evaluate_condition(
2074        &self,
2075        cond: &synth_synthesis::rules::Condition,
2076        flags: &ConditionFlags,
2077    ) -> Bool {
2078        use synth_synthesis::rules::Condition;
2079
2080        match cond {
2081            Condition::EQ => flags.z.clone(),
2082            Condition::NE => flags.z.not(),
2083            Condition::LT => {
2084                // N != V: negative flag differs from overflow flag
2085                flags.n.eq(&flags.v).not()
2086            }
2087            Condition::LE => {
2088                // Z == 1 || N != V
2089                let n_ne_v = flags.n.eq(&flags.v).not();
2090                Bool::or(&[&flags.z, &n_ne_v])
2091            }
2092            Condition::GT => {
2093                // Z == 0 && N == V
2094                let z_zero = flags.z.not();
2095                let n_eq_v = flags.n.eq(&flags.v);
2096                Bool::and(&[&z_zero, &n_eq_v])
2097            }
2098            Condition::GE => {
2099                // N == V
2100                flags.n.eq(&flags.v)
2101            }
2102            Condition::LO => {
2103                // C == 0 (no carry = less than unsigned)
2104                flags.c.not()
2105            }
2106            Condition::LS => {
2107                // C == 0 || Z == 1
2108                let c_zero = flags.c.not();
2109                Bool::or(&[&flags.z, &c_zero])
2110            }
2111            Condition::HI => {
2112                // C == 1 && Z == 0
2113                let z_zero = flags.z.not();
2114                Bool::and(&[&flags.c, &z_zero])
2115            }
2116            Condition::HS => {
2117                // C == 1 (carry = greater or equal unsigned)
2118                flags.c.clone()
2119            }
2120        }
2121    }
2122
2123    /// Convert a boolean to a 32-bit bitvector (0 or 1)
2124    fn bool_to_bv32(&self, cond: &Bool) -> BV {
2125        let zero = BV::from_i64(0, 32);
2126        let one = BV::from_i64(1, 32);
2127        cond.ite(&one, &zero)
2128    }
2129
2130    /// Encode ARM POPCNT (population count)
2131    ///
2132    /// Uses the Hamming weight algorithm (same as WASM implementation).
2133    /// This is a pseudo-instruction that would be expanded into actual ARM code.
2134    fn encode_popcnt(&self, input: &BV) -> BV {
2135        let mut x = input.clone();
2136
2137        // Step 1: Count bits in pairs
2138        let mask1 = BV::from_u64(0x55555555, 32);
2139        let masked = x.bvand(&mask1);
2140        let shifted = x.bvlshr(BV::from_i64(1, 32));
2141        let shifted_masked = shifted.bvand(&mask1);
2142        x = masked.bvadd(&shifted_masked);
2143
2144        // Step 2: Count pairs in nibbles
2145        let mask2 = BV::from_u64(0x33333333, 32);
2146        let masked = x.bvand(&mask2);
2147        let shifted = x.bvlshr(BV::from_i64(2, 32));
2148        let shifted_masked = shifted.bvand(&mask2);
2149        x = masked.bvadd(&shifted_masked);
2150
2151        // Step 3: Count nibbles in bytes
2152        let mask3 = BV::from_u64(0x0F0F0F0F, 32);
2153        let masked = x.bvand(&mask3);
2154        let shifted = x.bvlshr(BV::from_i64(4, 32));
2155        let shifted_masked = shifted.bvand(&mask3);
2156        x = masked.bvadd(&shifted_masked);
2157
2158        // Step 4: Sum all bytes
2159        let multiplier = BV::from_u64(0x01010101, 32);
2160        x = x.bvmul(&multiplier);
2161        x = x.bvlshr(BV::from_i64(24, 32));
2162
2163        x
2164    }
2165}
2166
2167// ===========================================================================
2168// VCR-VER-002 (#166): branch-taking guarded executor — DERIVES the ARM trap
2169// condition from the emitted guard/branch/UDF structure
2170// ===========================================================================
2171
2172/// Path guard: the condition under which an instruction executes. `Always`
2173/// keeps the straight-line common case free of `ite` merging.
2174#[derive(Clone)]
2175enum Guard {
2176    Always,
2177    Cond(Bool),
2178}
2179
2180impl Guard {
2181    fn and_cond(&self, c: &Bool) -> Guard {
2182        match self {
2183            Guard::Always => Guard::Cond(c.clone()),
2184            Guard::Cond(g) => Guard::Cond(Bool::and(&[g, c])),
2185        }
2186    }
2187}
2188
2189/// Merge an incoming edge guard into the guard map at `at`.
2190fn merge_guard(incoming: &mut HashMap<usize, Guard>, at: usize, g: Guard) {
2191    match (incoming.get(&at), g) {
2192        (Some(Guard::Always), _) => {}
2193        (_, Guard::Always) => {
2194            incoming.insert(at, Guard::Always);
2195        }
2196        (Some(Guard::Cond(a)), Guard::Cond(b)) => {
2197            let merged = Bool::or(&[a, &b]);
2198            incoming.insert(at, Guard::Cond(merged));
2199        }
2200        (None, g @ Guard::Cond(_)) => {
2201            incoming.insert(at, g);
2202        }
2203    }
2204}
2205
2206/// Boolean if-then-else (the term API only has BV ite).
2207fn bool_ite(c: &Bool, t: &Bool, e: &Bool) -> Bool {
2208    Bool::or(&[&Bool::and(&[c, t]), &Bool::and(&[&c.not(), e])])
2209}
2210
2211/// IEEE 754 single-precision NaN test over the raw bit pattern:
2212/// exponent all-ones with a non-zero fraction.
2213fn f32_is_nan(x: &BV) -> Bool {
2214    let exp_ones = x.extract(30, 23).eq(BV::from_u64(0xFF, 8));
2215    let frac_nonzero = x.extract(22, 0).eq(BV::from_u64(0, 23)).not();
2216    Bool::and(&[&exp_ones, &frac_nonzero])
2217}
2218
2219/// Ordered `a < b` over IEEE 754 single-precision BIT PATTERNS, assuming
2220/// neither operand is NaN (the callers conjoin the NaN exclusion). Uses the
2221/// sign/magnitude case split; `+0.0 == -0.0` (neither is less).
2222fn f32_ordered_lt(a: &BV, b: &BV) -> Bool {
2223    let a_neg = a.extract(31, 31).eq(BV::from_u64(1, 1));
2224    let b_neg = b.extract(31, 31).eq(BV::from_u64(1, 1));
2225    let a_mag = a.extract(30, 0);
2226    let b_mag = b.extract(30, 0);
2227    let zero31 = BV::from_u64(0, 31);
2228    let both_zero = Bool::and(&[&a_mag.eq(&zero31), &b_mag.eq(&zero31)]);
2229    // (neg, neg): larger magnitude is smaller; (neg, pos): a < b unless both
2230    // are zeros; (pos, neg): never; (pos, pos): magnitude order.
2231    let neg_neg = b_mag.bvult(&a_mag);
2232    let neg_pos = both_zero.not();
2233    let pos_pos = a_mag.bvult(&b_mag);
2234    bool_ite(
2235        &a_neg,
2236        &bool_ite(&b_neg, &neg_neg, &neg_pos),
2237        &bool_ite(&b_neg, &Bool::from_bool(false), &pos_pos),
2238    )
2239}
2240
2241/// The three ordered VFP comparison results the trunc guards use, as total
2242/// functions over the operands' bit patterns (result is 0 on any NaN — the
2243/// unordered case — exactly the ARM `VCMP`+`VMRS`+`IT` materialization the
2244/// `F32Lt`/`F32Gt`/`F32Ge` pseudo-ops stand for).
2245fn f32_cmp_result(kind: F32CmpKind, a: &BV, b: &BV) -> Bool {
2246    let ordered = Bool::and(&[&f32_is_nan(a).not(), &f32_is_nan(b).not()]);
2247    let rel = match kind {
2248        F32CmpKind::Lt => f32_ordered_lt(a, b),
2249        F32CmpKind::Gt => f32_ordered_lt(b, a),
2250        F32CmpKind::Ge => f32_ordered_lt(a, b).not(),
2251    };
2252    Bool::and(&[&ordered, &rel])
2253}
2254
2255#[derive(Clone, Copy)]
2256enum F32CmpKind {
2257    Lt,
2258    Gt,
2259    Ge,
2260}
2261
2262/// IEEE 754 double-precision NaN test over the raw bit pattern:
2263/// exponent all-ones (bits 62..52) with a non-zero fraction (bits 51..0).
2264/// The 64-bit twin of [`f32_is_nan`], for the #709/#756 f64→i32 trunc guards.
2265fn f64_is_nan(x: &BV) -> Bool {
2266    let exp_ones = x.extract(62, 52).eq(BV::from_u64(0x7FF, 11));
2267    let frac_nonzero = x.extract(51, 0).eq(BV::from_u64(0, 52)).not();
2268    Bool::and(&[&exp_ones, &frac_nonzero])
2269}
2270
2271/// Ordered `a < b` over IEEE 754 double-precision BIT PATTERNS, assuming
2272/// neither operand is NaN (the callers conjoin the NaN exclusion). The 64-bit
2273/// twin of [`f32_ordered_lt`]: sign bit 63, magnitude bits 62..0; `+0.0 == -0.0`
2274/// (neither is less).
2275fn f64_ordered_lt(a: &BV, b: &BV) -> Bool {
2276    let a_neg = a.extract(63, 63).eq(BV::from_u64(1, 1));
2277    let b_neg = b.extract(63, 63).eq(BV::from_u64(1, 1));
2278    let a_mag = a.extract(62, 0);
2279    let b_mag = b.extract(62, 0);
2280    let zero63 = BV::from_u64(0, 63);
2281    let both_zero = Bool::and(&[&a_mag.eq(&zero63), &b_mag.eq(&zero63)]);
2282    // (neg, neg): larger magnitude is smaller; (neg, pos): a < b unless both
2283    // are zeros; (pos, neg): never; (pos, pos): magnitude order.
2284    let neg_neg = b_mag.bvult(&a_mag);
2285    let neg_pos = both_zero.not();
2286    let pos_pos = a_mag.bvult(&b_mag);
2287    bool_ite(
2288        &a_neg,
2289        &bool_ite(&b_neg, &neg_neg, &neg_pos),
2290        &bool_ite(&b_neg, &Bool::from_bool(false), &pos_pos),
2291    )
2292}
2293
2294/// The three ordered VFP.F64 comparison results the f64 trunc guards use, as
2295/// total functions over the operands' bit patterns (result is 0 on any NaN —
2296/// the unordered case — exactly the ARM `VCMP.F64`+`VMRS`+`IT` materialization
2297/// the `F64Lt`/`F64Gt`/`F64Ge` pseudo-ops stand for). The 64-bit twin of
2298/// [`f32_cmp_result`].
2299fn f64_cmp_result(kind: F32CmpKind, a: &BV, b: &BV) -> Bool {
2300    let ordered = Bool::and(&[&f64_is_nan(a).not(), &f64_is_nan(b).not()]);
2301    let rel = match kind {
2302        F32CmpKind::Lt => f64_ordered_lt(a, b),
2303        F32CmpKind::Gt => f64_ordered_lt(b, a),
2304        F32CmpKind::Ge => f64_ordered_lt(a, b).not(),
2305    };
2306    Bool::and(&[&ordered, &rel])
2307}
2308
2309impl ArmSemantics {
2310    /// Branch-taking guarded symbolic execution of an ARM sequence,
2311    /// deriving `state.may_trap` from the emitted guard structure
2312    /// (VCR-VER-002, #166).
2313    ///
2314    /// Forward-branch DAG execution over the op list: every instruction
2315    /// carries the disjunction of the path conditions that reach it
2316    /// (if-conversion), `BCondOffset` routes guards forward, and a `Udf`
2317    /// accumulates its path guard into [`ArmState::may_trap`] — and does NOT
2318    /// fall through (a trap halts execution, so the code after a guarded
2319    /// `UDF` is reached only via the guard's skip branch). This makes the ARM
2320    /// trap condition a DERIVED term: a lowering whose guard was dropped,
2321    /// inverted, or aimed at the wrong register derives a trap condition that
2322    /// fails the preservation VC — unlike the previous structural
2323    /// `Udf`-presence proxy, which only saw that *some* trap existed.
2324    ///
2325    /// Branch targets are resolved in bytes via the shipped byte-size
2326    /// estimator (`synth_synthesis::optimizer_bridge::estimate_arm_byte_size`,
2327    /// the #511 estimator that CI pins against the encoder), matching the
2328    /// encoder's `target = branch + 4 + 2*offset` halfword rule. A target
2329    /// that lands mid-instruction, a backward branch (loop), an op outside
2330    /// the modeled subset, or any label/call control flow is a loud `Err` —
2331    /// never a silent accept.
2332    pub fn encode_sequence_br(
2333        &self,
2334        arm_ops: &[ArmOp],
2335        state: &mut ArmState,
2336    ) -> Result<(), String> {
2337        use synth_synthesis::optimizer_bridge::estimate_arm_byte_size;
2338
2339        // Byte offset of each op (the estimator is the pinned encoder mirror).
2340        let mut offsets = Vec::with_capacity(arm_ops.len());
2341        let mut off = 0usize;
2342        for op in arm_ops {
2343            offsets.push(off);
2344            off += estimate_arm_byte_size(op);
2345        }
2346        let total_len = off;
2347        let boundaries: std::collections::HashSet<usize> = offsets.iter().copied().collect();
2348
2349        let mut incoming: HashMap<usize, Guard> = HashMap::new();
2350        incoming.insert(0, Guard::Always);
2351
2352        for (i, op) in arm_ops.iter().enumerate() {
2353            let o = offsets[i];
2354            // Unreached instruction (e.g. dead code behind an unconditional
2355            // trap): no incoming edge, skip — it can never execute.
2356            let Some(g) = incoming.get(&o).cloned() else {
2357                continue;
2358            };
2359            let next = o + estimate_arm_byte_size(op);
2360
2361            match op {
2362                ArmOp::BCondOffset { cond, offset } => {
2363                    if *offset < 0 {
2364                        return Err(
2365                            "backward branch (loop) outside the trap-derivation subset — held out"
2366                                .to_string(),
2367                        );
2368                    }
2369                    // Encoder rule: offset is the halfword displacement,
2370                    // target = branch_addr + 4 + 2*offset.
2371                    let target = o + 4 + 2 * (*offset as usize);
2372                    if target != total_len && !boundaries.contains(&target) {
2373                        return Err(format!(
2374                            "BCondOffset target {target} lands mid-instruction \
2375                             (sequence len {total_len}) — estimator/encoder drift or \
2376                             malformed guard"
2377                        ));
2378                    }
2379                    let c = self.evaluate_condition(cond, &state.flags);
2380                    merge_guard(&mut incoming, target, g.and_cond(&c));
2381                    merge_guard(&mut incoming, next, g.and_cond(&c.not()));
2382                }
2383
2384                ArmOp::Udf { .. } => {
2385                    // The trap fires exactly under this path guard; execution
2386                    // never continues past it (no fall-through edge).
2387                    state.may_trap = match &g {
2388                        Guard::Always => Bool::from_bool(true),
2389                        Guard::Cond(gb) => Bool::or(&[&state.may_trap, gb]),
2390                    };
2391                }
2392
2393                // Label/relative/indirect control flow has no derivable local
2394                // trap semantics here — loud decline, never a silent accept.
2395                ArmOp::B { .. }
2396                | ArmOp::BOffset { .. }
2397                | ArmOp::Bcc { .. }
2398                | ArmOp::Bhs { .. }
2399                | ArmOp::Blo { .. }
2400                | ArmOp::Bl { .. }
2401                | ArmOp::Blx { .. }
2402                | ArmOp::Bx { .. }
2403                | ArmOp::Label { .. }
2404                | ArmOp::Call { .. }
2405                | ArmOp::CallIndirect { .. }
2406                | ArmOp::BrTable { .. }
2407                | ArmOp::Push { .. }
2408                | ArmOp::Pop { .. } => {
2409                    return Err(format!(
2410                        "op {op:?} outside the trap-derivation subset — loud decline"
2411                    ));
2412                }
2413
2414                _ => {
2415                    match &g {
2416                        Guard::Always => self.exec_trap_subset_op(op, state)?,
2417                        Guard::Cond(gb) => {
2418                            // Guarded (if-converted) execution: snapshot the
2419                            // register/flag/VFP state, execute, ite-merge
2420                            // under the guard. Sound because these ops touch
2421                            // only registers/flags/VFP (the subset check in
2422                            // exec_trap_subset_op rejects everything else).
2423                            //
2424                            // Only components the op actually CHANGED are
2425                            // merged — `ite(g, x, x) ≡ x`, and wrapping every
2426                            // untouched register on every guarded step nests
2427                            // the SDIV/UDIV operands in ite chains, blowing
2428                            // the div/rem trap VC off a CDCL cliff (observed:
2429                            // the div_s double-guard query ran 45+ min / 5 GB
2430                            // with the unconditional merge, sub-second
2431                            // without).
2432                            let regs_before = state.registers.clone();
2433                            let vfp_before = state.vfp_registers.clone();
2434                            let flags_before = ConditionFlags {
2435                                n: state.flags.n.clone(),
2436                                z: state.flags.z.clone(),
2437                                c: state.flags.c.clone(),
2438                                v: state.flags.v.clone(),
2439                            };
2440                            self.exec_trap_subset_op(op, state)?;
2441                            for (r, before) in regs_before.iter().enumerate() {
2442                                if !state.registers[r].same_term(before) {
2443                                    state.registers[r] = gb.ite(&state.registers[r], before);
2444                                }
2445                            }
2446                            for (r, before) in vfp_before.iter().enumerate() {
2447                                if !state.vfp_registers[r].same_term(before) {
2448                                    state.vfp_registers[r] =
2449                                        gb.ite(&state.vfp_registers[r], before);
2450                                }
2451                            }
2452                            if !state.flags.n.same_term(&flags_before.n) {
2453                                state.flags.n = bool_ite(gb, &state.flags.n, &flags_before.n);
2454                            }
2455                            if !state.flags.z.same_term(&flags_before.z) {
2456                                state.flags.z = bool_ite(gb, &state.flags.z, &flags_before.z);
2457                            }
2458                            if !state.flags.c.same_term(&flags_before.c) {
2459                                state.flags.c = bool_ite(gb, &state.flags.c, &flags_before.c);
2460                            }
2461                            if !state.flags.v.same_term(&flags_before.v) {
2462                                state.flags.v = bool_ite(gb, &state.flags.v, &flags_before.v);
2463                            }
2464                        }
2465                    }
2466                    merge_guard(&mut incoming, next, g);
2467                }
2468            }
2469        }
2470
2471        Ok(())
2472    }
2473
2474    /// Whether the sequence's branch structure is VALUE-DEAD: every op inside
2475    /// a branch-skipped span writes no register/VFP state (`Udf`, `Cmp`,
2476    /// `Cmn`, nested `BCondOffset` only), and no op anywhere in the sequence
2477    /// turns flags into a register value (`SetCond`).
2478    ///
2479    /// Under this condition the final REGISTER state is path-independent —
2480    /// every register-writing op executes on every path, in program order —
2481    /// so the straight-line value pass
2482    /// [`Self::encode_sequence_value_straightline`] computes exactly the
2483    /// registers any non-trapping real path produces. The flag writes a taken
2484    /// branch skips (e.g. the div_s overflow guard's `CMN` behind `BNE +3`)
2485    /// can only influence which PATH is taken — the trap side, which
2486    /// [`Self::encode_sequence_br`] derives with full path sensitivity — and
2487    /// never a register value, because `SetCond` (the only flag→register op
2488    /// in the modeled subset) is excluded outright.
2489    ///
2490    /// This is what lets the div/rem trap VC keep its value clause
2491    /// STRUCTURALLY aligned with the WASM side (`bvsdiv`/`MLS` terms
2492    /// identical after canonicalization): an `ite(guard, …)` wrapper on an
2493    /// SDIV/MLS operand un-shares the 32×32 multiplier/divider circuits and
2494    /// sends the UNSAT proof off the CDCL cliff term.rs documents (observed:
2495    /// rem_s value clause 15+ min with the ite, sub-second without).
2496    pub fn branch_spans_are_value_dead(arm_ops: &[ArmOp]) -> bool {
2497        use synth_synthesis::optimizer_bridge::estimate_arm_byte_size;
2498
2499        let mut offsets = Vec::with_capacity(arm_ops.len());
2500        let mut off = 0usize;
2501        for op in arm_ops {
2502            offsets.push(off);
2503            off += estimate_arm_byte_size(op);
2504        }
2505
2506        // No flag→register materialization anywhere in the sequence.
2507        if arm_ops.iter().any(|op| matches!(op, ArmOp::SetCond { .. })) {
2508            return false;
2509        }
2510
2511        for (i, op) in arm_ops.iter().enumerate() {
2512            if let ArmOp::BCondOffset { offset, .. } = op {
2513                if *offset < 0 {
2514                    return false; // backward branch — not this subset at all
2515                }
2516                // Fall-through = next instruction; encoder rule for the
2517                // target: branch_addr + 4 + 2*offset (same as
2518                // `encode_sequence_br`). The skipped span is [fall-through,
2519                // target).
2520                let span_start = offsets[i] + estimate_arm_byte_size(op);
2521                let span_end = offsets[i] + 4 + 2 * (*offset as usize);
2522                for (j, skipped) in arm_ops.iter().enumerate() {
2523                    if offsets[j] >= span_start && offsets[j] < span_end {
2524                        match skipped {
2525                            ArmOp::Udf { .. }
2526                            | ArmOp::Cmp { .. }
2527                            | ArmOp::Cmn { .. }
2528                            | ArmOp::BCondOffset { .. } => {}
2529                            _ => return false, // a register/VFP write is skippable
2530                        }
2531                    }
2532                }
2533            }
2534        }
2535        true
2536    }
2537
2538    /// Straight-line VALUE execution of a trap-guarded sequence: branches and
2539    /// `UDF`s are register no-ops, every other op executes unconditionally
2540    /// via the same modeled subset as the branch-taking executor.
2541    ///
2542    /// ONLY sound when [`Self::branch_spans_are_value_dead`] holds (see its
2543    /// doc for the argument); callers must check it first. Produces ite-free
2544    /// register terms, keeping the trap VC's value clause structurally
2545    /// aligned with the WASM encoding.
2546    pub fn encode_sequence_value_straightline(
2547        &self,
2548        arm_ops: &[ArmOp],
2549        state: &mut ArmState,
2550    ) -> Result<(), String> {
2551        for op in arm_ops {
2552            match op {
2553                ArmOp::BCondOffset { .. } | ArmOp::Udf { .. } => {}
2554                _ => self.exec_trap_subset_op(op, state)?,
2555            }
2556        }
2557        Ok(())
2558    }
2559
2560    /// Execute one non-branch op of the trap-derivation subset. Ops the
2561    /// shipped trap-guarded lowerings use but `encode_op` leaves unmodeled
2562    /// (`Cmn`, `Movw`, `Movt`, the ordered VFP compares) get explicit
2563    /// semantics here; a WHITELIST of register-only value ops delegates to
2564    /// `encode_op`; anything else is a loud `Err` — `encode_op`'s silent
2565    /// `_ => {}` default must never green-wash a trap derivation.
2566    fn exec_trap_subset_op(&self, op: &ArmOp, state: &mut ArmState) -> Result<(), String> {
2567        match op {
2568            // CMN: compare negated — flags from rn + op2.
2569            ArmOp::Cmn { rn, op2 } => {
2570                let a = state.get_reg(rn).clone();
2571                let b = self.evaluate_operand2(op2, state);
2572                let result = a.bvadd(&b);
2573                self.update_flags_add(state, &a, &b, &result);
2574                Ok(())
2575            }
2576            ArmOp::Movw { rd, imm16 } => {
2577                state.set_reg(rd, BV::from_u64(*imm16 as u64, 32));
2578                Ok(())
2579            }
2580            ArmOp::Movt { rd, imm16 } => {
2581                let low = state.get_reg(rd).bvand(BV::from_u64(0xFFFF, 32));
2582                let v = low.bvor(BV::from_u64((*imm16 as u64) << 16, 32));
2583                state.set_reg(rd, v);
2584                Ok(())
2585            }
2586            // Ordered VFP compares (the #709 trunc guards): real bit-pattern
2587            // semantics — result register is 1 iff the ordered relation
2588            // holds, 0 on NaN. encode_op models these as uninterpreted
2589            // symbols, which cannot drive a trap derivation.
2590            ArmOp::F32Lt { rd, sn, sm } => {
2591                let a = state.get_vfp_reg(sn).clone();
2592                let b = state.get_vfp_reg(sm).clone();
2593                let r = self.bool_to_bv32(&f32_cmp_result(F32CmpKind::Lt, &a, &b));
2594                state.set_reg(rd, r);
2595                Ok(())
2596            }
2597            ArmOp::F32Gt { rd, sn, sm } => {
2598                let a = state.get_vfp_reg(sn).clone();
2599                let b = state.get_vfp_reg(sm).clone();
2600                let r = self.bool_to_bv32(&f32_cmp_result(F32CmpKind::Gt, &a, &b));
2601                state.set_reg(rd, r);
2602                Ok(())
2603            }
2604            ArmOp::F32Ge { rd, sn, sm } => {
2605                let a = state.get_vfp_reg(sn).clone();
2606                let b = state.get_vfp_reg(sm).clone();
2607                let r = self.bool_to_bv32(&f32_cmp_result(F32CmpKind::Ge, &a, &b));
2608                state.set_reg(rd, r);
2609                Ok(())
2610            }
2611            // Ordered VFP.F64 compares (the #756 f64→i32 trunc guards): real
2612            // bit-pattern semantics over the 64-bit D-register operands — 1 iff
2613            // the ordered relation holds, 0 on NaN. encode_op models these as
2614            // uninterpreted symbols, which cannot drive a trap derivation.
2615            ArmOp::F64Lt { rd, dn, dm } => {
2616                let a = state.get_vfp_reg(dn).clone();
2617                let b = state.get_vfp_reg(dm).clone();
2618                let r = self.bool_to_bv32(&f64_cmp_result(F32CmpKind::Lt, &a, &b));
2619                state.set_reg(rd, r);
2620                Ok(())
2621            }
2622            ArmOp::F64Gt { rd, dn, dm } => {
2623                let a = state.get_vfp_reg(dn).clone();
2624                let b = state.get_vfp_reg(dm).clone();
2625                let r = self.bool_to_bv32(&f64_cmp_result(F32CmpKind::Gt, &a, &b));
2626                state.set_reg(rd, r);
2627                Ok(())
2628            }
2629            ArmOp::F64Ge { rd, dn, dm } => {
2630                let a = state.get_vfp_reg(dn).clone();
2631                let b = state.get_vfp_reg(dm).clone();
2632                let r = self.bool_to_bv32(&f64_cmp_result(F32CmpKind::Ge, &a, &b));
2633                state.set_reg(rd, r);
2634                Ok(())
2635            }
2636            // Register/flag-only value ops the covered lowerings use:
2637            // delegate to the existing encode_op semantics.
2638            ArmOp::Cmp { .. }
2639            | ArmOp::Add { .. }
2640            | ArmOp::Sub { .. }
2641            | ArmOp::Rsb { .. }
2642            | ArmOp::Mov { .. }
2643            | ArmOp::And { .. }
2644            | ArmOp::Orr { .. }
2645            | ArmOp::Eor { .. }
2646            | ArmOp::Mul { .. }
2647            | ArmOp::Mls { .. }
2648            | ArmOp::Sdiv { .. }
2649            | ArmOp::Udiv { .. }
2650            | ArmOp::SetCond { .. }
2651            | ArmOp::Nop
2652            | ArmOp::F32Const { .. }
2653            | ArmOp::I32TruncF32S { .. }
2654            | ArmOp::I32TruncF32U { .. }
2655            // f64 trunc guards (#756): F64Const sets the D-reg to the real
2656            // 64-bit float bit pattern (load-bearing for the derived compare);
2657            // the saturating VCVT pseudo-ops write only the RESULT register,
2658            // which the trap derivation ignores.
2659            | ArmOp::F64Const { .. }
2660            | ArmOp::I32TruncF64S { .. }
2661            | ArmOp::I32TruncF64U { .. }
2662            | ArmOp::I64TruncF64S { .. }
2663            | ArmOp::I64TruncF64U { .. }
2664            // Ldr/Str: the value model treats loads as fresh symbols and
2665            // stores as no-ops (no memory-contents model) — fine for a trap
2666            // derivation, where only the guard's flags/registers matter.
2667            | ArmOp::Ldr { .. }
2668            | ArmOp::Str { .. } => {
2669                self.encode_op(op, state);
2670                Ok(())
2671            }
2672            // Subword accesses (#752 gate coverage for the guarded
2673            // i32.load8/16 + i32.store8/16 shapes): same treatment as
2674            // Ldr/Str — a load writes a fresh symbol (no memory-contents
2675            // model), a store touches no register. Neither affects flags,
2676            // so the trap derivation is untouched; modeling them here just
2677            // lets guarded subword sequences through instead of a loud
2678            // decline.
2679            ArmOp::Ldrb { rd, .. }
2680            | ArmOp::Ldrsb { rd, .. }
2681            | ArmOp::Ldrh { rd, .. }
2682            | ArmOp::Ldrsh { rd, .. } => {
2683                let result = BV::new_const(format!("load_{rd:?}"), 32);
2684                state.set_reg(rd, result);
2685                Ok(())
2686            }
2687            ArmOp::Strb { .. } | ArmOp::Strh { .. } => Ok(()),
2688            other => Err(format!(
2689                "op {other:?} outside the trap-derivation subset — loud decline"
2690            )),
2691        }
2692    }
2693}
2694
2695#[cfg(test)]
2696mod tests {
2697    use super::*;
2698    use crate::with_verification_context;
2699
2700    #[test]
2701    fn test_arm_add_semantics() {
2702        with_verification_context(|| {
2703            let encoder = ArmSemantics::new();
2704            let mut state = ArmState::new_symbolic();
2705
2706            // Set up concrete values for testing
2707            state.set_reg(&Reg::R1, BV::from_i64(10, 32));
2708            state.set_reg(&Reg::R2, BV::from_i64(20, 32));
2709
2710            // Execute: ADD R0, R1, R2
2711            let op = ArmOp::Add {
2712                rd: Reg::R0,
2713                rn: Reg::R1,
2714                op2: Operand2::Reg(Reg::R2),
2715            };
2716
2717            encoder.encode_op(&op, &mut state);
2718
2719            // Check result: R0 should be 30
2720            let result = state.get_reg(&Reg::R0).simplify();
2721            assert_eq!(result.as_i64(), Some(30));
2722        });
2723    }
2724
2725    #[test]
2726    fn test_arm_sub_semantics() {
2727        with_verification_context(|| {
2728            let encoder = ArmSemantics::new();
2729            let mut state = ArmState::new_symbolic();
2730
2731            state.set_reg(&Reg::R1, BV::from_i64(50, 32));
2732            state.set_reg(&Reg::R2, BV::from_i64(20, 32));
2733
2734            let op = ArmOp::Sub {
2735                rd: Reg::R0,
2736                rn: Reg::R1,
2737                op2: Operand2::Reg(Reg::R2),
2738            };
2739
2740            encoder.encode_op(&op, &mut state);
2741
2742            let result = state.get_reg(&Reg::R0);
2743            assert_eq!(result.simplify().as_i64(), Some(30));
2744        });
2745    }
2746
2747    #[test]
2748    fn test_arm_mov_immediate() {
2749        with_verification_context(|| {
2750            let encoder = ArmSemantics::new();
2751            let mut state = ArmState::new_symbolic();
2752
2753            let op = ArmOp::Mov {
2754                rd: Reg::R0,
2755                op2: Operand2::Imm(42),
2756            };
2757
2758            encoder.encode_op(&op, &mut state);
2759
2760            let result = state.get_reg(&Reg::R0);
2761            assert_eq!(result.simplify().as_i64(), Some(42));
2762        });
2763    }
2764
2765    #[test]
2766    fn test_arm_bitwise_ops() {
2767        with_verification_context(|| {
2768            let encoder = ArmSemantics::new();
2769            let mut state = ArmState::new_symbolic();
2770
2771            state.set_reg(&Reg::R1, BV::from_i64(0b1010, 32));
2772            state.set_reg(&Reg::R2, BV::from_i64(0b1100, 32));
2773
2774            // Test AND
2775            let and_op = ArmOp::And {
2776                rd: Reg::R0,
2777                rn: Reg::R1,
2778                op2: Operand2::Reg(Reg::R2),
2779            };
2780            encoder.encode_op(&and_op, &mut state);
2781            assert_eq!(state.get_reg(&Reg::R0).simplify().as_i64(), Some(0b1000));
2782
2783            // Test ORR
2784            let orr_op = ArmOp::Orr {
2785                rd: Reg::R0,
2786                rn: Reg::R1,
2787                op2: Operand2::Reg(Reg::R2),
2788            };
2789            encoder.encode_op(&orr_op, &mut state);
2790            assert_eq!(state.get_reg(&Reg::R0).simplify().as_i64(), Some(0b1110));
2791
2792            // Test EOR (XOR)
2793            let eor_op = ArmOp::Eor {
2794                rd: Reg::R0,
2795                rn: Reg::R1,
2796                op2: Operand2::Reg(Reg::R2),
2797            };
2798            encoder.encode_op(&eor_op, &mut state);
2799            assert_eq!(state.get_reg(&Reg::R0).simplify().as_i64(), Some(0b0110));
2800        });
2801    }
2802
2803    #[test]
2804    fn test_arm_mls() {
2805        // Test MLS (Multiply and Subtract): Rd = Ra - Rn * Rm
2806        // This is used for remainder: a % b = a - (a/b) * b
2807        with_verification_context(|| {
2808            let encoder = ArmSemantics::new();
2809            let mut state = ArmState::new_symbolic();
2810
2811            // Test: 17 % 5 = 17 - (17/5) * 5 = 17 - 3*5 = 17 - 15 = 2
2812            // Ra = 17, Rn = 3 (quotient), Rm = 5 (divisor)
2813            state.set_reg(&Reg::R0, BV::from_i64(17, 32)); // Ra (dividend)
2814            state.set_reg(&Reg::R1, BV::from_i64(3, 32)); // Rn (quotient)
2815            state.set_reg(&Reg::R2, BV::from_i64(5, 32)); // Rm (divisor)
2816
2817            let mls_op = ArmOp::Mls {
2818                rd: Reg::R3,
2819                rn: Reg::R1,
2820                rm: Reg::R2,
2821                ra: Reg::R0,
2822            };
2823            encoder.encode_op(&mls_op, &mut state);
2824            assert_eq!(
2825                state.get_reg(&Reg::R3).simplify().as_i64(),
2826                Some(2),
2827                "MLS: 17 - 3*5 = 2"
2828            );
2829
2830            // Test: 100 - 7 * 3 = 100 - 21 = 79
2831            state.set_reg(&Reg::R0, BV::from_i64(100, 32));
2832            state.set_reg(&Reg::R1, BV::from_i64(7, 32));
2833            state.set_reg(&Reg::R2, BV::from_i64(3, 32));
2834
2835            let mls_op2 = ArmOp::Mls {
2836                rd: Reg::R3,
2837                rn: Reg::R1,
2838                rm: Reg::R2,
2839                ra: Reg::R0,
2840            };
2841            encoder.encode_op(&mls_op2, &mut state);
2842            assert_eq!(
2843                state.get_reg(&Reg::R3).simplify().as_i64(),
2844                Some(79),
2845                "MLS: 100 - 7*3 = 79"
2846            );
2847
2848            // Test with negative numbers: (-17) - 3 * 5 = -17 - 15 = -32
2849            state.set_reg(&Reg::R0, BV::from_i64(-17, 32));
2850            state.set_reg(&Reg::R1, BV::from_i64(3, 32));
2851            state.set_reg(&Reg::R2, BV::from_i64(5, 32));
2852
2853            let mls_op3 = ArmOp::Mls {
2854                rd: Reg::R3,
2855                rn: Reg::R1,
2856                rm: Reg::R2,
2857                ra: Reg::R0,
2858            };
2859            encoder.encode_op(&mls_op3, &mut state);
2860            // Result is -32, but as_i64() returns unsigned, so we need to convert
2861            let result = state.get_reg(&Reg::R3).simplify().as_i64();
2862            let signed_result = result.map(|v| (v as i32) as i64);
2863            assert_eq!(signed_result, Some(-32), "MLS: -17 - 3*5 = -32");
2864        });
2865    }
2866
2867    #[test]
2868    fn test_arm_shift_ops() {
2869        with_verification_context(|| {
2870            let encoder = ArmSemantics::new();
2871            let mut state = ArmState::new_symbolic();
2872
2873            state.set_reg(&Reg::R1, BV::from_i64(8, 32));
2874
2875            // Test LSL (logical shift left) with immediate
2876            let lsl_op = ArmOp::Lsl {
2877                rd: Reg::R0,
2878                rn: Reg::R1,
2879                shift: 2,
2880            };
2881            encoder.encode_op(&lsl_op, &mut state);
2882            assert_eq!(state.get_reg(&Reg::R0).simplify().as_i64(), Some(32));
2883
2884            // Test LSR (logical shift right) with immediate
2885            let lsr_op = ArmOp::Lsr {
2886                rd: Reg::R0,
2887                rn: Reg::R1,
2888                shift: 2,
2889            };
2890            encoder.encode_op(&lsr_op, &mut state);
2891            assert_eq!(state.get_reg(&Reg::R0).simplify().as_i64(), Some(2));
2892        });
2893    }
2894
2895    #[test]
2896    fn test_arm_ror_comprehensive() {
2897        with_verification_context(|| {
2898            let encoder = ArmSemantics::new();
2899            let mut state = ArmState::new_symbolic();
2900
2901            // Test ROR with 0x12345678
2902            // ROR by 8 should rotate right by 8 bits
2903            state.set_reg(&Reg::R1, BV::from_u64(0x12345678, 32));
2904            let ror_op = ArmOp::Ror {
2905                rd: Reg::R0,
2906                rn: Reg::R1,
2907                shift: 8,
2908            };
2909            encoder.encode_op(&ror_op, &mut state);
2910            // 0x12345678 ROR 8 = 0x78123456
2911            assert_eq!(
2912                state.get_reg(&Reg::R0).simplify().as_i64(),
2913                Some(0x78123456),
2914                "ROR by 8"
2915            );
2916
2917            // Test ROR by 16 (swap halves)
2918            let ror_op_16 = ArmOp::Ror {
2919                rd: Reg::R0,
2920                rn: Reg::R1,
2921                shift: 16,
2922            };
2923            encoder.encode_op(&ror_op_16, &mut state);
2924            // 0x12345678 ROR 16 = 0x56781234
2925            assert_eq!(
2926                state.get_reg(&Reg::R0).simplify().as_i64(),
2927                Some(0x56781234),
2928                "ROR by 16"
2929            );
2930
2931            // Test ROR by 0 (no change)
2932            let ror_op_0 = ArmOp::Ror {
2933                rd: Reg::R0,
2934                rn: Reg::R1,
2935                shift: 0,
2936            };
2937            encoder.encode_op(&ror_op_0, &mut state);
2938            assert_eq!(
2939                state.get_reg(&Reg::R0).simplify().as_i64(),
2940                Some(0x12345678),
2941                "ROR by 0"
2942            );
2943
2944            // Test ROR by 32 (full rotation, back to original)
2945            let ror_op_32 = ArmOp::Ror {
2946                rd: Reg::R0,
2947                rn: Reg::R1,
2948                shift: 32,
2949            };
2950            encoder.encode_op(&ror_op_32, &mut state);
2951            assert_eq!(
2952                state.get_reg(&Reg::R0).simplify().as_i64(),
2953                Some(0x12345678),
2954                "ROR by 32"
2955            );
2956
2957            // Test ROR by 4 (nibble rotation)
2958            state.set_reg(&Reg::R1, BV::from_u64(0xABCDEF01, 32));
2959            let ror_op_4 = ArmOp::Ror {
2960                rd: Reg::R0,
2961                rn: Reg::R1,
2962                shift: 4,
2963            };
2964            encoder.encode_op(&ror_op_4, &mut state);
2965            // 0xABCDEF01 ROR 4 = 0x1ABCDEF0
2966            assert_eq!(
2967                state.get_reg(&Reg::R0).simplify().as_i64(),
2968                Some(0x1ABCDEF0),
2969                "ROR by 4"
2970            );
2971
2972            // Test ROR with 1-bit rotation
2973            state.set_reg(&Reg::R1, BV::from_u64(0x80000001, 32));
2974            let ror_op_1 = ArmOp::Ror {
2975                rd: Reg::R0,
2976                rn: Reg::R1,
2977                shift: 1,
2978            };
2979            encoder.encode_op(&ror_op_1, &mut state);
2980            // 0x80000001 ROR 1 = 0xC0000000
2981            let result = state.get_reg(&Reg::R0).simplify().as_i64();
2982            let signed_result = result.map(|v| (v as i32) as i64);
2983            assert_eq!(
2984                signed_result,
2985                Some(0xC0000000_u32 as i32 as i64),
2986                "ROR by 1"
2987            );
2988        });
2989    }
2990
2991    #[test]
2992    fn test_arm_clz_comprehensive() {
2993        with_verification_context(|| {
2994            let encoder = ArmSemantics::new();
2995            let mut state = ArmState::new_symbolic();
2996
2997            // Test CLZ(0) = 32
2998            state.set_reg(&Reg::R1, BV::from_i64(0, 32));
2999            let clz_op = ArmOp::Clz {
3000                rd: Reg::R0,
3001                rm: Reg::R1,
3002            };
3003            encoder.encode_op(&clz_op, &mut state);
3004            assert_eq!(
3005                state.get_reg(&Reg::R0).simplify().as_i64(),
3006                Some(32),
3007                "CLZ(0) should be 32"
3008            );
3009
3010            // Test CLZ(1) = 31
3011            state.set_reg(&Reg::R1, BV::from_i64(1, 32));
3012            encoder.encode_op(&clz_op, &mut state);
3013            assert_eq!(
3014                state.get_reg(&Reg::R0).simplify().as_i64(),
3015                Some(31),
3016                "CLZ(1) should be 31"
3017            );
3018
3019            // Test CLZ(0x80000000) = 0
3020            state.set_reg(&Reg::R1, BV::from_u64(0x80000000, 32));
3021            encoder.encode_op(&clz_op, &mut state);
3022            assert_eq!(
3023                state.get_reg(&Reg::R0).simplify().as_i64(),
3024                Some(0),
3025                "CLZ(0x80000000) should be 0"
3026            );
3027
3028            // Test CLZ(0x00FF0000) = 8
3029            state.set_reg(&Reg::R1, BV::from_u64(0x00FF0000, 32));
3030            encoder.encode_op(&clz_op, &mut state);
3031            assert_eq!(
3032                state.get_reg(&Reg::R0).simplify().as_i64(),
3033                Some(8),
3034                "CLZ(0x00FF0000) should be 8"
3035            );
3036
3037            // Test CLZ(0x00001000) = 19
3038            state.set_reg(&Reg::R1, BV::from_u64(0x00001000, 32));
3039            encoder.encode_op(&clz_op, &mut state);
3040            assert_eq!(
3041                state.get_reg(&Reg::R0).simplify().as_i64(),
3042                Some(19),
3043                "CLZ(0x00001000) should be 19"
3044            );
3045
3046            // Test CLZ(0xFFFFFFFF) = 0
3047            state.set_reg(&Reg::R1, BV::from_u64(0xFFFFFFFF, 32));
3048            encoder.encode_op(&clz_op, &mut state);
3049            assert_eq!(
3050                state.get_reg(&Reg::R0).simplify().as_i64(),
3051                Some(0),
3052                "CLZ(0xFFFFFFFF) should be 0"
3053            );
3054        });
3055    }
3056
3057    #[test]
3058    fn test_arm_rbit_comprehensive() {
3059        with_verification_context(|| {
3060            let encoder = ArmSemantics::new();
3061            let mut state = ArmState::new_symbolic();
3062
3063            let rbit_op = ArmOp::Rbit {
3064                rd: Reg::R0,
3065                rm: Reg::R1,
3066            };
3067
3068            // Test RBIT(0) = 0
3069            state.set_reg(&Reg::R1, BV::from_i64(0, 32));
3070            encoder.encode_op(&rbit_op, &mut state);
3071            assert_eq!(
3072                state.get_reg(&Reg::R0).simplify().as_i64(),
3073                Some(0),
3074                "RBIT(0) should be 0"
3075            );
3076
3077            // Test RBIT(1) = 0x80000000 (bit 0 → bit 31)
3078            state.set_reg(&Reg::R1, BV::from_i64(1, 32));
3079            encoder.encode_op(&rbit_op, &mut state);
3080            assert_eq!(
3081                state.get_reg(&Reg::R0).simplify().as_u64(),
3082                Some(0x80000000),
3083                "RBIT(1) should be 0x80000000"
3084            );
3085
3086            // Test RBIT(0x80000000) = 1 (bit 31 → bit 0)
3087            state.set_reg(&Reg::R1, BV::from_u64(0x80000000, 32));
3088            encoder.encode_op(&rbit_op, &mut state);
3089            assert_eq!(
3090                state.get_reg(&Reg::R0).simplify().as_i64(),
3091                Some(1),
3092                "RBIT(0x80000000) should be 1"
3093            );
3094
3095            // Test RBIT(0xFF000000) = 0x000000FF (top byte → bottom byte)
3096            state.set_reg(&Reg::R1, BV::from_u64(0xFF000000, 32));
3097            encoder.encode_op(&rbit_op, &mut state);
3098            assert_eq!(
3099                state.get_reg(&Reg::R0).simplify().as_u64(),
3100                Some(0x000000FF),
3101                "RBIT(0xFF000000) should be 0x000000FF"
3102            );
3103
3104            // Test RBIT(0x12345678) - specific pattern
3105            state.set_reg(&Reg::R1, BV::from_u64(0x12345678, 32));
3106            encoder.encode_op(&rbit_op, &mut state);
3107            // 0x12345678 reversed = 0x1E6A2C48
3108            assert_eq!(
3109                state.get_reg(&Reg::R0).simplify().as_u64(),
3110                Some(0x1E6A2C48),
3111                "RBIT(0x12345678) should be 0x1E6A2C48"
3112            );
3113
3114            // Test RBIT(0xFFFFFFFF) = 0xFFFFFFFF (all bits stay)
3115            state.set_reg(&Reg::R1, BV::from_u64(0xFFFFFFFF, 32));
3116            encoder.encode_op(&rbit_op, &mut state);
3117            assert_eq!(
3118                state.get_reg(&Reg::R0).simplify().as_u64(),
3119                Some(0xFFFFFFFF),
3120                "RBIT(0xFFFFFFFF) should be 0xFFFFFFFF"
3121            );
3122        });
3123    }
3124
3125    #[test]
3126    fn test_arm_cmp_flags() {
3127        // Test CMP instruction and condition flag updates
3128
3129        with_verification_context(|| {
3130            let encoder = ArmSemantics::new();
3131            let mut state = ArmState::new_symbolic();
3132
3133            // Test 1: CMP with equal values (10 - 10 = 0)
3134            // Should set: Z=1, N=0, C=1 (no borrow), V=0
3135            state.set_reg(&Reg::R0, BV::from_i64(10, 32));
3136            state.set_reg(&Reg::R1, BV::from_i64(10, 32));
3137
3138            let cmp_op = ArmOp::Cmp {
3139                rn: Reg::R0,
3140                op2: Operand2::Reg(Reg::R1),
3141            };
3142            encoder.encode_op(&cmp_op, &mut state);
3143
3144            assert_eq!(
3145                state.flags.z.simplify().as_bool(),
3146                Some(true),
3147                "Z flag should be set (equal)"
3148            );
3149            assert_eq!(
3150                state.flags.n.simplify().as_bool(),
3151                Some(false),
3152                "N flag should be clear (non-negative)"
3153            );
3154            assert_eq!(
3155                state.flags.c.simplify().as_bool(),
3156                Some(true),
3157                "C flag should be set (no borrow)"
3158            );
3159            assert_eq!(
3160                state.flags.v.simplify().as_bool(),
3161                Some(false),
3162                "V flag should be clear (no overflow)"
3163            );
3164
3165            // Test 2: CMP with first > second (20 - 10 = 10)
3166            // Should set: Z=0, N=0, C=1 (no borrow), V=0
3167            state.set_reg(&Reg::R0, BV::from_i64(20, 32));
3168            state.set_reg(&Reg::R1, BV::from_i64(10, 32));
3169            encoder.encode_op(&cmp_op, &mut state);
3170
3171            assert_eq!(
3172                state.flags.z.simplify().as_bool(),
3173                Some(false),
3174                "Z flag should be clear (not equal)"
3175            );
3176            assert_eq!(
3177                state.flags.n.simplify().as_bool(),
3178                Some(false),
3179                "N flag should be clear (positive result)"
3180            );
3181            assert_eq!(
3182                state.flags.c.simplify().as_bool(),
3183                Some(true),
3184                "C flag should be set (no borrow)"
3185            );
3186            assert_eq!(
3187                state.flags.v.simplify().as_bool(),
3188                Some(false),
3189                "V flag should be clear (no overflow)"
3190            );
3191
3192            // Test 3: CMP with first < second (unsigned: will wrap)
3193            // 10 - 20 = -10 (0xFFFFFFF6 in two's complement)
3194            // Should set: Z=0, N=1 (negative), C=0 (borrow), V=0
3195            state.set_reg(&Reg::R0, BV::from_i64(10, 32));
3196            state.set_reg(&Reg::R1, BV::from_i64(20, 32));
3197            encoder.encode_op(&cmp_op, &mut state);
3198
3199            assert_eq!(
3200                state.flags.z.simplify().as_bool(),
3201                Some(false),
3202                "Z flag should be clear"
3203            );
3204            assert_eq!(
3205                state.flags.n.simplify().as_bool(),
3206                Some(true),
3207                "N flag should be set (negative result)"
3208            );
3209            assert_eq!(
3210                state.flags.c.simplify().as_bool(),
3211                Some(false),
3212                "C flag should be clear (borrow occurred)"
3213            );
3214            assert_eq!(
3215                state.flags.v.simplify().as_bool(),
3216                Some(false),
3217                "V flag should be clear"
3218            );
3219
3220            // Test 4: Signed overflow case
3221            // Subtracting large negative from positive should overflow
3222            // 0x7FFFFFFF (max positive) - 0x80000000 (min negative)
3223            // Result wraps to negative, but mathematically should be huge positive
3224            state.set_reg(&Reg::R0, BV::from_i64(0x7FFFFFFF, 32));
3225            state.set_reg(&Reg::R1, BV::from_i64(-2147483648i64, 32)); // 0x80000000
3226            encoder.encode_op(&cmp_op, &mut state);
3227
3228            assert_eq!(
3229                state.flags.z.simplify().as_bool(),
3230                Some(false),
3231                "Z flag should be clear"
3232            );
3233            assert_eq!(
3234                state.flags.n.simplify().as_bool(),
3235                Some(true),
3236                "N flag should be set (wrapped result)"
3237            );
3238            assert_eq!(
3239                state.flags.c.simplify().as_bool(),
3240                Some(false),
3241                "C flag should be clear"
3242            );
3243            assert_eq!(
3244                state.flags.v.simplify().as_bool(),
3245                Some(true),
3246                "V flag should be set (overflow)"
3247            );
3248
3249            // Test 5: Zero comparison
3250            state.set_reg(&Reg::R0, BV::from_i64(0, 32));
3251            state.set_reg(&Reg::R1, BV::from_i64(0, 32));
3252            encoder.encode_op(&cmp_op, &mut state);
3253
3254            assert_eq!(
3255                state.flags.z.simplify().as_bool(),
3256                Some(true),
3257                "Z flag should be set (0 - 0 = 0)"
3258            );
3259            assert_eq!(
3260                state.flags.n.simplify().as_bool(),
3261                Some(false),
3262                "N flag should be clear"
3263            );
3264            assert_eq!(
3265                state.flags.c.simplify().as_bool(),
3266                Some(true),
3267                "C flag should be set"
3268            );
3269            assert_eq!(
3270                state.flags.v.simplify().as_bool(),
3271                Some(false),
3272                "V flag should be clear"
3273            );
3274        });
3275    }
3276
3277    #[test]
3278    fn test_arm_flags_all_combinations() {
3279        // Test that flags correctly distinguish all comparison outcomes
3280
3281        with_verification_context(|| {
3282            let encoder = ArmSemantics::new();
3283            let mut state = ArmState::new_symbolic();
3284
3285            let cmp_op = ArmOp::Cmp {
3286                rn: Reg::R0,
3287                op2: Operand2::Reg(Reg::R1),
3288            };
3289
3290            // Test signed comparisons using flags
3291            // For signed comparison A vs B (after CMP A, B):
3292            // - EQ (equal): Z=1
3293            // - NE (not equal): Z=0
3294            // - LT (less than): N != V
3295            // - LE (less or equal): Z=1 OR (N != V)
3296            // - GT (greater than): Z=0 AND (N == V)
3297            // - GE (greater or equal): N == V
3298
3299            // Case: 5 compared to 10 (5 < 10)
3300            state.set_reg(&Reg::R0, BV::from_i64(5, 32));
3301            state.set_reg(&Reg::R1, BV::from_i64(10, 32));
3302            encoder.encode_op(&cmp_op, &mut state);
3303
3304            let n = state.flags.n.simplify().as_bool().unwrap();
3305            let z = state.flags.z.simplify().as_bool().unwrap();
3306            let v = state.flags.v.simplify().as_bool().unwrap();
3307
3308            assert!(!z, "Not equal");
3309            assert!(n != v, "5 < 10 signed (N != V)");
3310
3311            // Case: -5 compared to 10 (-5 < 10)
3312            state.set_reg(&Reg::R0, BV::from_i64(-5, 32));
3313            state.set_reg(&Reg::R1, BV::from_i64(10, 32));
3314            encoder.encode_op(&cmp_op, &mut state);
3315
3316            let n = state.flags.n.simplify().as_bool().unwrap();
3317            let v = state.flags.v.simplify().as_bool().unwrap();
3318            assert!(n != v, "-5 < 10 signed (N != V)");
3319        });
3320    }
3321
3322    #[test]
3323    fn test_arm_setcond_eq() {
3324        with_verification_context(|| {
3325            let encoder = ArmSemantics::new();
3326            let mut state = ArmState::new_symbolic();
3327
3328            // Test EQ condition: 10 == 10
3329            state.set_reg(&Reg::R0, BV::from_i64(10, 32));
3330            state.set_reg(&Reg::R1, BV::from_i64(10, 32));
3331
3332            // CMP R0, R1 (sets Z=1 since equal)
3333            let cmp_op = ArmOp::Cmp {
3334                rn: Reg::R0,
3335                op2: Operand2::Reg(Reg::R1),
3336            };
3337            encoder.encode_op(&cmp_op, &mut state);
3338
3339            // SetCond R0, EQ (should set R0 = 1)
3340            let setcond_op = ArmOp::SetCond {
3341                rd: Reg::R0,
3342                cond: synth_synthesis::Condition::EQ,
3343            };
3344            encoder.encode_op(&setcond_op, &mut state);
3345
3346            assert_eq!(
3347                state.get_reg(&Reg::R0).simplify().as_i64(),
3348                Some(1),
3349                "EQ condition (10 == 10) should return 1"
3350            );
3351
3352            // Test NE condition: 10 != 5
3353            state.set_reg(&Reg::R0, BV::from_i64(10, 32));
3354            state.set_reg(&Reg::R1, BV::from_i64(5, 32));
3355
3356            encoder.encode_op(&cmp_op, &mut state);
3357
3358            let setcond_ne = ArmOp::SetCond {
3359                rd: Reg::R0,
3360                cond: synth_synthesis::Condition::NE,
3361            };
3362            encoder.encode_op(&setcond_ne, &mut state);
3363
3364            assert_eq!(
3365                state.get_reg(&Reg::R0).simplify().as_i64(),
3366                Some(1),
3367                "NE condition (10 != 5) should return 1"
3368            );
3369        });
3370    }
3371
3372    #[test]
3373    fn test_arm_setcond_signed() {
3374        with_verification_context(|| {
3375            let encoder = ArmSemantics::new();
3376            let mut state = ArmState::new_symbolic();
3377
3378            // Test LT signed: 5 < 10
3379            state.set_reg(&Reg::R0, BV::from_i64(5, 32));
3380            state.set_reg(&Reg::R1, BV::from_i64(10, 32));
3381
3382            let cmp_op = ArmOp::Cmp {
3383                rn: Reg::R0,
3384                op2: Operand2::Reg(Reg::R1),
3385            };
3386            encoder.encode_op(&cmp_op, &mut state);
3387
3388            let setcond_lt = ArmOp::SetCond {
3389                rd: Reg::R0,
3390                cond: synth_synthesis::Condition::LT,
3391            };
3392            encoder.encode_op(&setcond_lt, &mut state);
3393
3394            assert_eq!(
3395                state.get_reg(&Reg::R0).simplify().as_i64(),
3396                Some(1),
3397                "LT signed (5 < 10) should return 1"
3398            );
3399
3400            // Test GE signed: 10 >= 5
3401            state.set_reg(&Reg::R0, BV::from_i64(10, 32));
3402            state.set_reg(&Reg::R1, BV::from_i64(5, 32));
3403
3404            encoder.encode_op(&cmp_op, &mut state);
3405
3406            let setcond_ge = ArmOp::SetCond {
3407                rd: Reg::R0,
3408                cond: synth_synthesis::Condition::GE,
3409            };
3410            encoder.encode_op(&setcond_ge, &mut state);
3411
3412            assert_eq!(
3413                state.get_reg(&Reg::R0).simplify().as_i64(),
3414                Some(1),
3415                "GE signed (10 >= 5) should return 1"
3416            );
3417
3418            // Test GT signed: 10 > 5
3419            state.set_reg(&Reg::R0, BV::from_i64(10, 32));
3420            state.set_reg(&Reg::R1, BV::from_i64(5, 32));
3421
3422            encoder.encode_op(&cmp_op, &mut state);
3423
3424            let setcond_gt = ArmOp::SetCond {
3425                rd: Reg::R0,
3426                cond: synth_synthesis::Condition::GT,
3427            };
3428            encoder.encode_op(&setcond_gt, &mut state);
3429
3430            assert_eq!(
3431                state.get_reg(&Reg::R0).simplify().as_i64(),
3432                Some(1),
3433                "GT signed (10 > 5) should return 1"
3434            );
3435
3436            // Test LE signed: 5 <= 10
3437            state.set_reg(&Reg::R0, BV::from_i64(5, 32));
3438            state.set_reg(&Reg::R1, BV::from_i64(10, 32));
3439
3440            encoder.encode_op(&cmp_op, &mut state);
3441
3442            let setcond_le = ArmOp::SetCond {
3443                rd: Reg::R0,
3444                cond: synth_synthesis::Condition::LE,
3445            };
3446            encoder.encode_op(&setcond_le, &mut state);
3447
3448            assert_eq!(
3449                state.get_reg(&Reg::R0).simplify().as_i64(),
3450                Some(1),
3451                "LE signed (5 <= 10) should return 1"
3452            );
3453        });
3454    }
3455
3456    #[test]
3457    fn test_arm_setcond_unsigned() {
3458        with_verification_context(|| {
3459            let encoder = ArmSemantics::new();
3460            let mut state = ArmState::new_symbolic();
3461
3462            // Test LO unsigned: 5 < 10
3463            state.set_reg(&Reg::R0, BV::from_i64(5, 32));
3464            state.set_reg(&Reg::R1, BV::from_i64(10, 32));
3465
3466            let cmp_op = ArmOp::Cmp {
3467                rn: Reg::R0,
3468                op2: Operand2::Reg(Reg::R1),
3469            };
3470            encoder.encode_op(&cmp_op, &mut state);
3471
3472            let setcond_lo = ArmOp::SetCond {
3473                rd: Reg::R0,
3474                cond: synth_synthesis::Condition::LO,
3475            };
3476            encoder.encode_op(&setcond_lo, &mut state);
3477
3478            assert_eq!(
3479                state.get_reg(&Reg::R0).simplify().as_i64(),
3480                Some(1),
3481                "LO unsigned (5 < 10) should return 1"
3482            );
3483
3484            // Test HS unsigned: 10 >= 5
3485            state.set_reg(&Reg::R0, BV::from_i64(10, 32));
3486            state.set_reg(&Reg::R1, BV::from_i64(5, 32));
3487
3488            encoder.encode_op(&cmp_op, &mut state);
3489
3490            let setcond_hs = ArmOp::SetCond {
3491                rd: Reg::R0,
3492                cond: synth_synthesis::Condition::HS,
3493            };
3494            encoder.encode_op(&setcond_hs, &mut state);
3495
3496            assert_eq!(
3497                state.get_reg(&Reg::R0).simplify().as_i64(),
3498                Some(1),
3499                "HS unsigned (10 >= 5) should return 1"
3500            );
3501
3502            // Test HI unsigned: 10 > 5
3503            state.set_reg(&Reg::R0, BV::from_i64(10, 32));
3504            state.set_reg(&Reg::R1, BV::from_i64(5, 32));
3505
3506            encoder.encode_op(&cmp_op, &mut state);
3507
3508            let setcond_hi = ArmOp::SetCond {
3509                rd: Reg::R0,
3510                cond: synth_synthesis::Condition::HI,
3511            };
3512            encoder.encode_op(&setcond_hi, &mut state);
3513
3514            assert_eq!(
3515                state.get_reg(&Reg::R0).simplify().as_i64(),
3516                Some(1),
3517                "HI unsigned (10 > 5) should return 1"
3518            );
3519
3520            // Test LS unsigned: 5 <= 10
3521            state.set_reg(&Reg::R0, BV::from_i64(5, 32));
3522            state.set_reg(&Reg::R1, BV::from_i64(10, 32));
3523
3524            encoder.encode_op(&cmp_op, &mut state);
3525
3526            let setcond_ls = ArmOp::SetCond {
3527                rd: Reg::R0,
3528                cond: synth_synthesis::Condition::LS,
3529            };
3530            encoder.encode_op(&setcond_ls, &mut state);
3531
3532            assert_eq!(
3533                state.get_reg(&Reg::R0).simplify().as_i64(),
3534                Some(1),
3535                "LS unsigned (5 <= 10) should return 1"
3536            );
3537        });
3538    }
3539}