1#![cfg(feature = "arm")]
72
73use crate::solver::{CheckOutcome, new_solver};
74use crate::term::{BV, Bool};
75use crate::validator_pattern::{
76 Flags, I64Pair, SolverResultKind, bool_to_i32, i64_lt_s, i64_lt_u, i64_rotl, i64_rotr, i64_shl,
77 i64_shr_s, i64_shr_u, k32, shift_amount64, sym32,
78};
79use std::collections::HashMap;
80use synth_core::WasmOp;
81use synth_synthesis::{ArmOp, Reg};
82use thiserror::Error;
83
84#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct ExpansionWitness {
88 pub wasm_op_label: String,
90 pub instr_count: usize,
92 pub byte_len: usize,
94 pub solver_result: SolverResultKind,
96}
97
98#[derive(Debug, Error)]
101pub enum ExpansionError {
102 #[error("expansion decode failed at byte offset {offset}: {reason}")]
106 Decode { offset: usize, reason: String },
107
108 #[error("expansion validator does not support: {0}")]
110 Unsupported(String),
111
112 #[error("counterexample for {wasm_op_label}: {description}")]
115 Counterexample {
116 wasm_op_label: String,
117 description: String,
118 },
119
120 #[error("solver returned unknown for {0}")]
122 SolverUnknown(String),
123
124 #[error("internal expansion-validator error: {0}")]
126 Internal(String),
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135enum ShiftKind {
136 Lsl,
137 Lsr,
138 Asr,
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144enum DpKind {
145 And,
146 Orr,
147 Add,
148 Sub,
149 Sbcs,
151}
152
153#[derive(Debug, Clone)]
155enum T2Op {
156 Nop,
157 It {
160 firstcond: u8,
161 mask: u8,
162 },
163 CmpReg {
165 rn: u8,
166 rm: u8,
167 },
168 CmpImm {
170 rn: u8,
171 imm: u32,
172 },
173 MovsImm {
175 rd: u8,
176 imm: u32,
177 },
178 MovImm {
180 rd: u8,
181 imm: u32,
182 },
183 MovReg {
185 rd: u8,
186 rm: u8,
187 },
188 AddReg16 {
190 rdn: u8,
191 rm: u8,
192 },
193 AddsReg16 {
197 rd: u8,
198 rn: u8,
199 rm: u8,
200 },
201 BCond {
203 cc: u8,
204 target: i64,
205 },
206 B {
208 target: i64,
209 },
210 Push {
212 list: u16,
213 },
214 Pop {
216 list: u16,
217 },
218 StrPreDec {
220 rt: u8,
221 },
222 AddSpImm {
224 imm: u32,
225 },
226 AndImm {
228 rd: u8,
229 rn: u8,
230 imm: u32,
231 },
232 AddImm {
233 rd: u8,
234 rn: u8,
235 imm: u32,
236 },
237 SubsImm {
239 rd: u8,
240 rn: u8,
241 imm: u32,
242 },
243 RsbImm {
244 rd: u8,
245 rn: u8,
246 imm: u32,
247 },
248 MovwImm {
250 rd: u8,
251 imm16: u32,
252 },
253 MovtImm {
255 rd: u8,
256 imm16: u32,
257 },
258 DpReg {
260 kind: DpKind,
261 rd: u8,
262 rn: u8,
263 rm: u8,
264 },
265 ShiftImm {
267 kind: ShiftKind,
268 rd: u8,
269 rm: u8,
270 imm: u32,
271 },
272 ShiftReg {
274 kind: ShiftKind,
275 rd: u8,
276 rn: u8,
277 rm: u8,
278 },
279 Mul {
280 rd: u8,
281 rn: u8,
282 rm: u8,
283 },
284 Mla {
285 rd: u8,
286 rn: u8,
287 rm: u8,
288 ra: u8,
289 },
290 Umull {
291 rdlo: u8,
292 rdhi: u8,
293 rn: u8,
294 rm: u8,
295 },
296 Clz {
297 rd: u8,
298 rm: u8,
299 },
300 Rbit {
301 rd: u8,
302 rm: u8,
303 },
304}
305
306#[derive(Debug, Clone)]
308struct Decoded {
309 offset: usize,
310 len: usize,
311 op: T2Op,
312}
313
314fn derr<T>(offset: usize, reason: impl Into<String>) -> Result<T, ExpansionError> {
315 Err(ExpansionError::Decode {
316 offset,
317 reason: reason.into(),
318 })
319}
320
321fn thumb_expand_imm(offset: usize, i: u32, imm3: u32, imm8: u32) -> Result<u32, ExpansionError> {
325 let imm12 = (i << 11) | (imm3 << 8) | imm8;
326 if imm12 & 0xF00 == 0 {
327 Ok(imm8)
328 } else {
329 derr(
330 offset,
331 format!("modified immediate 0x{imm12:03x} outside the plain-imm8 subset"),
332 )
333 }
334}
335
336fn decode_thumb2(code: &[u8]) -> Result<Vec<Decoded>, ExpansionError> {
339 if !code.len().is_multiple_of(2) {
340 return derr(0, "odd byte length");
341 }
342 let mut out = Vec::new();
343 let mut o = 0usize;
344 while o < code.len() {
345 let hw1 = u16::from_le_bytes([code[o], code[o + 1]]);
346 let is32 = (hw1 & 0xE000) == 0xE000 && (hw1 & 0x1800) != 0;
348 if !is32 {
349 let op = decode16(o, hw1)?;
350 out.push(Decoded {
351 offset: o,
352 len: 2,
353 op,
354 });
355 o += 2;
356 } else {
357 if o + 4 > code.len() {
358 return derr(o, "truncated 32-bit instruction");
359 }
360 let hw2 = u16::from_le_bytes([code[o + 2], code[o + 3]]);
361 let op = decode32(o, hw1, hw2)?;
362 out.push(Decoded {
363 offset: o,
364 len: 4,
365 op,
366 });
367 o += 4;
368 }
369 }
370 Ok(out)
371}
372
373fn decode16(o: usize, hw: u16) -> Result<T2Op, ExpansionError> {
374 if hw == 0xBF00 {
375 return Ok(T2Op::Nop);
376 }
377 if hw & 0xFF00 == 0xBF00 {
378 return Ok(T2Op::It {
379 firstcond: ((hw >> 4) & 0xF) as u8,
380 mask: (hw & 0xF) as u8,
381 });
382 }
383 if hw & 0xFFC0 == 0x4280 {
384 return Ok(T2Op::CmpReg {
385 rn: (hw & 7) as u8,
386 rm: ((hw >> 3) & 7) as u8,
387 });
388 }
389 if hw & 0xFF00 == 0x4500 {
390 return Ok(T2Op::CmpReg {
391 rn: ((((hw >> 7) & 1) << 3) | (hw & 7)) as u8,
392 rm: ((hw >> 3) & 0xF) as u8,
393 });
394 }
395 if hw & 0xF800 == 0x2800 {
396 return Ok(T2Op::CmpImm {
397 rn: ((hw >> 8) & 7) as u8,
398 imm: (hw & 0xFF) as u32,
399 });
400 }
401 if hw & 0xF800 == 0x2000 {
402 return Ok(T2Op::MovsImm {
403 rd: ((hw >> 8) & 7) as u8,
404 imm: (hw & 0xFF) as u32,
405 });
406 }
407 if hw & 0xFF00 == 0x4600 {
408 return Ok(T2Op::MovReg {
409 rd: ((((hw >> 7) & 1) << 3) | (hw & 7)) as u8,
410 rm: ((hw >> 3) & 0xF) as u8,
411 });
412 }
413 if hw & 0xFF00 == 0x4400 {
414 return Ok(T2Op::AddReg16 {
415 rdn: ((((hw >> 7) & 1) << 3) | (hw & 7)) as u8,
416 rm: ((hw >> 3) & 0xF) as u8,
417 });
418 }
419 if hw & 0xFE00 == 0x1800 {
420 return Ok(T2Op::AddsReg16 {
421 rd: (hw & 7) as u8,
422 rn: ((hw >> 3) & 7) as u8,
423 rm: ((hw >> 6) & 7) as u8,
424 });
425 }
426 if hw & 0xF000 == 0xD000 {
427 let cc = ((hw >> 8) & 0xF) as u8;
428 if cc >= 0xE {
429 return derr(o, format!("conditional branch with cc={cc:#x}"));
430 }
431 let imm8 = (hw & 0xFF) as i64;
432 let simm = if imm8 >= 0x80 { imm8 - 0x100 } else { imm8 };
433 return Ok(T2Op::BCond {
434 cc,
435 target: o as i64 + 4 + 2 * simm,
436 });
437 }
438 if hw & 0xF800 == 0xE000 {
439 let imm11 = (hw & 0x7FF) as i64;
440 let simm = if imm11 >= 0x400 { imm11 - 0x800 } else { imm11 };
441 return Ok(T2Op::B {
442 target: o as i64 + 4 + 2 * simm,
443 });
444 }
445 if hw & 0xFE00 == 0xB400 {
446 if hw & 0x0100 != 0 {
447 return derr(o, "PUSH with LR outside the modeled subset");
448 }
449 return Ok(T2Op::Push { list: hw & 0xFF });
450 }
451 if hw & 0xFE00 == 0xBC00 {
452 if hw & 0x0100 != 0 {
453 return derr(o, "POP with PC outside the modeled subset");
454 }
455 return Ok(T2Op::Pop { list: hw & 0xFF });
456 }
457 if hw & 0xFF80 == 0xB000 {
458 return Ok(T2Op::AddSpImm {
459 imm: ((hw & 0x7F) as u32) * 4,
460 });
461 }
462 derr(
463 o,
464 format!("16-bit instruction {hw:#06x} outside the modeled subset"),
465 )
466}
467
468fn decode32(o: usize, hw1: u16, hw2: u16) -> Result<T2Op, ExpansionError> {
469 let hw1 = hw1 as u32;
470 let hw2 = hw2 as u32;
471
472 if hw1 == 0xF84D && hw2 & 0x0FFF == 0x0D04 {
474 return Ok(T2Op::StrPreDec {
475 rt: ((hw2 >> 12) & 0xF) as u8,
476 });
477 }
478
479 if hw1 & 0xFFF0 == 0xFAB0 && hw2 & 0xF0F0 == 0xF080 {
481 return Ok(T2Op::Clz {
482 rd: ((hw2 >> 8) & 0xF) as u8,
483 rm: (hw2 & 0xF) as u8,
484 });
485 }
486 if hw1 & 0xFFF0 == 0xFA90 && hw2 & 0xF0F0 == 0xF0A0 {
487 return Ok(T2Op::Rbit {
488 rd: ((hw2 >> 8) & 0xF) as u8,
489 rm: (hw2 & 0xF) as u8,
490 });
491 }
492
493 if hw1 & 0xFF80 == 0xFA00 && hw2 & 0xF0F0 == 0xF000 {
495 let kind = match (hw1 >> 5) & 3 {
496 0 => ShiftKind::Lsl,
497 1 => ShiftKind::Lsr,
498 2 => ShiftKind::Asr,
499 _ => return derr(o, "ROR.W (register) outside the modeled subset"),
500 };
501 return Ok(T2Op::ShiftReg {
502 kind,
503 rd: ((hw2 >> 8) & 0xF) as u8,
504 rn: (hw1 & 0xF) as u8,
505 rm: (hw2 & 0xF) as u8,
506 });
507 }
508
509 if hw1 & 0xFFF0 == 0xFB00 && hw2 & 0x00F0 == 0x0000 {
511 let ra = ((hw2 >> 12) & 0xF) as u8;
512 let rd = ((hw2 >> 8) & 0xF) as u8;
513 let rn = (hw1 & 0xF) as u8;
514 let rm = (hw2 & 0xF) as u8;
515 return Ok(if ra == 0xF {
516 T2Op::Mul { rd, rn, rm }
517 } else {
518 T2Op::Mla { rd, rn, rm, ra }
519 });
520 }
521
522 if hw1 & 0xFFF0 == 0xFBA0 && hw2 & 0x00F0 == 0x0000 {
524 return Ok(T2Op::Umull {
525 rdlo: ((hw2 >> 12) & 0xF) as u8,
526 rdhi: ((hw2 >> 8) & 0xF) as u8,
527 rn: (hw1 & 0xF) as u8,
528 rm: (hw2 & 0xF) as u8,
529 });
530 }
531
532 if hw1 & 0xFBF0 == 0xF240 || hw1 & 0xFBF0 == 0xF2C0 {
535 let i = (hw1 >> 10) & 1;
536 let imm4 = hw1 & 0xF;
537 let imm3 = (hw2 >> 12) & 7;
538 let rd = ((hw2 >> 8) & 0xF) as u8;
539 let imm8 = hw2 & 0xFF;
540 let imm16 = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8;
541 return Ok(if hw1 & 0xFBF0 == 0xF240 {
542 T2Op::MovwImm { rd, imm16 }
543 } else {
544 T2Op::MovtImm { rd, imm16 }
545 });
546 }
547
548 if hw1 & 0xFA00 == 0xF000 && hw2 & 0x8000 == 0 {
550 let i = (hw1 >> 10) & 1;
551 let op = (hw1 >> 5) & 0xF;
552 let s = (hw1 >> 4) & 1;
553 let rn = (hw1 & 0xF) as u8;
554 let imm3 = (hw2 >> 12) & 7;
555 let rd = ((hw2 >> 8) & 0xF) as u8;
556 let imm8 = hw2 & 0xFF;
557 let imm = thumb_expand_imm(o, i, imm3, imm8)?;
558 return match (op, s) {
559 (0b0000, 0) => Ok(T2Op::AndImm { rd, rn, imm }),
560 (0b0010, 0) if rn == 0xF => Ok(T2Op::MovImm { rd, imm }),
561 (0b1000, 0) => Ok(T2Op::AddImm { rd, rn, imm }),
562 (0b1101, 1) if rd == 0xF => Ok(T2Op::CmpImm { rn, imm }),
563 (0b1101, 1) => Ok(T2Op::SubsImm { rd, rn, imm }),
564 (0b1110, 0) => Ok(T2Op::RsbImm { rd, rn, imm }),
565 _ => derr(
566 o,
567 format!("modified-immediate DP op={op:#06b} S={s} outside the modeled subset"),
568 ),
569 };
570 }
571
572 if hw1 & 0xFE00 == 0xEA00 {
575 let op = (hw1 >> 5) & 0xF;
576 let s = (hw1 >> 4) & 1;
577 let rn = (hw1 & 0xF) as u8;
578 let imm3 = (hw2 >> 12) & 7;
579 let rd = ((hw2 >> 8) & 0xF) as u8;
580 let imm2 = (hw2 >> 6) & 3;
581 let ty = (hw2 >> 4) & 3;
582 let rm = (hw2 & 0xF) as u8;
583 let imm5 = (imm3 << 2) | imm2;
584
585 if rn == 0xF && op == 0b0010 && s == 0 {
586 let kind = match ty {
588 0 if imm5 == 0 => return Ok(T2Op::MovReg { rd, rm }),
589 0 => ShiftKind::Lsl,
590 1 => ShiftKind::Lsr,
591 2 => ShiftKind::Asr,
592 _ => return derr(o, "ROR-immediate outside the modeled subset"),
593 };
594 if imm5 == 0 {
595 return derr(
596 o,
597 "shift-by-32 encoding (imm5=0) outside the modeled subset",
598 );
599 }
600 return Ok(T2Op::ShiftImm {
601 kind,
602 rd,
603 rm,
604 imm: imm5,
605 });
606 }
607
608 if imm5 != 0 || ty != 0 {
609 return derr(
610 o,
611 format!(
612 "shifted-register operand (type={ty}, imm5={imm5}) outside the modeled subset"
613 ),
614 );
615 }
616 let kind = match (op, s) {
617 (0b0000, 0) => DpKind::And,
618 (0b0010, 0) => DpKind::Orr,
619 (0b1000, 0) => DpKind::Add,
620 (0b1101, 0) => DpKind::Sub,
621 (0b1011, 1) => DpKind::Sbcs,
622 _ => {
623 return derr(
624 o,
625 format!("register DP op={op:#06b} S={s} outside the modeled subset"),
626 );
627 }
628 };
629 return Ok(T2Op::DpReg { kind, rd, rn, rm });
630 }
631
632 derr(
633 o,
634 format!("32-bit instruction {hw1:#06x} {hw2:#06x} outside the modeled subset"),
635 )
636}
637
638fn bool_ite(c: &Bool, t: &Bool, e: &Bool) -> Bool {
644 Bool::or(&[&Bool::and(&[c, t]), &Bool::and(&[&c.not(), e])])
645}
646
647fn cc_holds(cc: u8, f: &Flags) -> Result<Bool, ExpansionError> {
649 Ok(match cc {
650 0x0 => f.z.clone(), 0x1 => f.z.not(), 0x2 => f.c.clone(), 0x3 => f.c.not(), 0x4 => f.n.clone(), 0x5 => f.n.not(), 0x6 => f.v.clone(), 0x7 => f.v.not(), 0x8 => Bool::and(&[&f.c, &f.z.not()]), 0x9 => Bool::or(&[&f.c.not(), &f.z]), 0xA => f.n.eq(&f.v), 0xB => f.n.eq(&f.v).not(), 0xC => Bool::and(&[&f.z.not(), &f.n.eq(&f.v)]), 0xD => Bool::or(&[&f.z, &f.n.eq(&f.v).not()]), _ => {
665 return Err(ExpansionError::Internal(format!(
666 "condition code {cc:#x} is not evaluatable"
667 )));
668 }
669 })
670}
671
672fn clz32(x: &BV) -> BV {
674 let mut r = k32(32);
676 for i in 0..32u32 {
677 let bit = x.extract(i, i).eq(BV::from_i64(1, 1));
679 r = bit.ite(k32((31 - i) as i64), &r);
680 }
681 r
682}
683
684fn ctz32(x: &BV) -> BV {
688 let mut r = k32(32);
689 for i in (0..32u32).rev() {
690 let bit = x.extract(i, i).eq(BV::from_i64(1, 1));
692 r = bit.ite(k32(i as i64), &r);
693 }
694 r
695}
696
697#[cfg(test)]
701fn popcnt32(x: &BV) -> BV {
702 let mut acc = k32(0);
703 for i in 0..32u32 {
704 acc = acc.bvadd(x.extract(i, i).zero_ext(31));
705 }
706 acc
707}
708
709fn popcnt32_hakmem(x: &BV) -> BV {
728 let m1 = k32(0x5555_5555);
729 let m2 = k32(0x3333_3333);
730 let m4 = k32(0x0F0F_0F0F);
731 let x1 = x.bvsub(x.bvlshr(k32(1)).bvand(&m1));
732 let x2 = x1.bvand(&m2).bvadd(x1.bvlshr(k32(2)).bvand(&m2));
733 let x3 = x2.bvadd(x2.bvlshr(k32(4))).bvand(&m4);
734 x3.bvmul(k32(0x0101_0101)).bvlshr(k32(24))
735}
736
737fn i64_mul_schoolbook(a: &I64Pair, b: &I64Pair) -> I64Pair {
758 let full = a.lo.zero_ext(32).bvmul(b.lo.zero_ext(32));
759 let lo = full.extract(31, 0);
760 let hi = full
761 .extract(63, 32)
762 .bvadd(a.lo.bvmul(&b.hi))
763 .bvadd(a.hi.bvmul(&b.lo));
764 I64Pair { lo, hi }
765}
766
767fn rbit32(x: &BV) -> BV {
769 let mut r = x.extract(0, 0);
770 for i in 1..32u32 {
771 r = r.concat(x.extract(i, i));
772 }
773 r
774}
775
776struct MachineState {
778 regs: Vec<BV>,
779 flags: Flags,
780 mem: HashMap<i64, BV>,
782 sp_off: i64,
783 fresh: usize,
784}
785
786impl MachineState {
787 fn new() -> Self {
788 Self {
789 regs: (0..16).map(|i| sym32(&format!("init_r{i}"))).collect(),
790 flags: Flags::unconstrained(),
791 mem: HashMap::new(),
792 sp_off: 0,
793 fresh: 0,
794 }
795 }
796
797 fn fresh32(&mut self, label: &str) -> BV {
798 self.fresh += 1;
799 sym32(&format!("fresh_{label}_{}", self.fresh))
800 }
801
802 fn set_reg(&mut self, g: &Bool, rd: u8, v: BV) -> Result<(), ExpansionError> {
804 let rd = rd as usize;
805 if rd == 13 || rd == 15 {
806 return Err(ExpansionError::Internal(
807 "direct SP/PC register write outside the modeled subset".into(),
808 ));
809 }
810 self.regs[rd] = g.ite(&v, &self.regs[rd]);
811 Ok(())
812 }
813
814 fn get_reg(&self, r: u8) -> Result<BV, ExpansionError> {
815 let r = r as usize;
816 if r == 13 || r == 15 {
817 return Err(ExpansionError::Internal(
818 "direct SP/PC register read outside the modeled subset".into(),
819 ));
820 }
821 Ok(self.regs[r].clone())
822 }
823
824 fn set_flags(&mut self, g: &Bool, new: Flags) {
826 self.flags = Flags {
827 n: bool_ite(g, &new.n, &self.flags.n),
828 z: bool_ite(g, &new.z, &self.flags.z),
829 c: bool_ite(g, &new.c, &self.flags.c),
830 v: bool_ite(g, &new.v, &self.flags.v),
831 };
832 }
833
834 fn mem_read(&mut self, addr: i64) -> BV {
835 if let Some(v) = self.mem.get(&addr) {
836 return v.clone();
837 }
838 let v = self.fresh32("stack");
841 self.mem.insert(addr, v.clone());
842 v
843 }
844
845 fn mem_write(&mut self, g: &Bool, addr: i64, v: BV) {
846 let old = self.mem_read(addr);
847 self.mem.insert(addr, g.ite(&v, &old));
848 }
849}
850
851fn flags_from_adds(rn: &BV, rm: &BV, result: &BV) -> Flags {
853 let n = result.bvslt(k32(0));
854 let z = result.eq(k32(0));
855 let c = result.bvult(rn); let rn_neg = rn.bvslt(k32(0));
857 let rm_neg = rm.bvslt(k32(0));
858 let r_neg = result.bvslt(k32(0));
859 let v = Bool::and(&[&rn_neg.eq(&rm_neg), &r_neg.eq(&rn_neg).not()]);
860 Flags { n, z, c, v }
861}
862
863fn exec_sbcs(rn: &BV, rm: &BV, c_in: &Bool) -> (BV, Flags) {
865 let one33 = BV::from_i64(1, 33);
866 let zero33 = BV::from_i64(0, 33);
867 let borrow = c_in.ite(&zero33, &one33);
868 let u = rn.zero_ext(1).bvsub(rm.zero_ext(1)).bvsub(&borrow);
869 let result = u.extract(31, 0);
870 let c = u.extract(32, 32).eq(BV::from_i64(0, 1)); let s = rn.sign_ext(1).bvsub(rm.sign_ext(1)).bvsub(&borrow);
872 let v = s.extract(32, 32).eq(s.extract(31, 31)).not();
873 let n = result.bvslt(k32(0));
874 let z = result.eq(k32(0));
875 (result, Flags { n, z, c, v })
876}
877
878fn execute(instrs: &[Decoded], state: &mut MachineState) -> Result<(), ExpansionError> {
888 let total_len = instrs.last().map(|d| d.offset + d.len).unwrap_or(0);
889
890 let mut incoming: HashMap<usize, Bool> = HashMap::new();
891 incoming.insert(0, Bool::from_bool(true));
892 let mut pending_joins: Vec<usize> = Vec::new();
895 let mut it_queue: Vec<u8> = Vec::new();
897
898 let merge = |incoming: &mut HashMap<usize, Bool>, at: usize, g: Bool| {
899 let cur = incoming
900 .get(&at)
901 .cloned()
902 .unwrap_or_else(|| Bool::from_bool(false));
903 incoming.insert(at, Bool::or(&[&cur, &g]));
904 };
905
906 for d in instrs {
907 let o = d.offset;
908 pending_joins.retain(|&j| j > o);
909 let g = incoming
910 .get(&o)
911 .cloned()
912 .unwrap_or_else(|| Bool::from_bool(false));
913 let in_it = !it_queue.is_empty();
914 let eff = if in_it {
916 let cc = it_queue.remove(0);
917 let c = cc_holds(cc, &state.flags)?;
918 Bool::and(&[&g, &c])
919 } else {
920 g.clone()
921 };
922 let next = o + d.len;
923 let in_cond_region = !pending_joins.is_empty();
924
925 let mut fallthrough = true;
927
928 match &d.op {
929 T2Op::Nop => {}
930 T2Op::It { firstcond, mask } => {
931 if in_it {
932 return Err(ExpansionError::Internal("nested IT block".into()));
933 }
934 if *mask == 0 {
935 return Err(ExpansionError::Internal("IT with empty mask".into()));
936 }
937 let count = 4 - mask.trailing_zeros() as usize;
940 let base = firstcond & 0xE;
941 it_queue.push(*firstcond);
942 for j in 1..count {
943 let bit = (mask >> (3 - (j - 1))) & 1;
944 it_queue.push(base | bit);
945 }
946 }
947 T2Op::CmpReg { rn, rm } => {
948 let a = state.get_reg(*rn)?;
949 let b = state.get_reg(*rm)?;
950 let f = Flags::from_cmp(&a, &b);
951 state.set_flags(&eff, f);
952 }
953 T2Op::CmpImm { rn, imm } => {
954 let a = state.get_reg(*rn)?;
955 let f = Flags::from_cmp(&a, &k32(*imm as i64));
956 state.set_flags(&eff, f);
957 }
958 T2Op::MovsImm { rd, imm } => {
959 let v = k32(*imm as i64);
960 if !in_it {
961 let f = Flags {
963 n: v.bvslt(k32(0)),
964 z: v.eq(k32(0)),
965 c: state.flags.c.clone(),
966 v: state.flags.v.clone(),
967 };
968 state.set_flags(&eff, f);
969 }
970 state.set_reg(&eff, *rd, v)?;
971 }
972 T2Op::MovImm { rd, imm } => {
973 state.set_reg(&eff, *rd, k32(*imm as i64))?;
974 }
975 T2Op::MovReg { rd, rm } => {
976 let v = state.get_reg(*rm)?;
977 state.set_reg(&eff, *rd, v)?;
978 }
979 T2Op::AddReg16 { rdn, rm } => {
980 let v = state.get_reg(*rdn)?.bvadd(&state.get_reg(*rm)?);
981 state.set_reg(&eff, *rdn, v)?;
982 }
983 T2Op::AddsReg16 { rd, rn, rm } => {
984 let a = state.get_reg(*rn)?;
985 let b = state.get_reg(*rm)?;
986 let v = a.bvadd(&b);
987 if !in_it {
988 let f = flags_from_adds(&a, &b, &v);
989 state.set_flags(&eff, f);
990 }
991 state.set_reg(&eff, *rd, v)?;
992 }
993 T2Op::BCond { cc, target } => {
994 if in_it {
995 return Err(ExpansionError::Internal("branch inside IT block".into()));
996 }
997 if *target <= o as i64 {
998 return Err(ExpansionError::Decode {
999 offset: o,
1000 reason: "backward branch (loop) — held out, needs loop unrolling"
1001 .to_string(),
1002 });
1003 }
1004 let t = *target as usize;
1005 if t > total_len {
1006 return derr(o, "branch target beyond sequence end");
1007 }
1008 let c = cc_holds(*cc, &state.flags)?;
1009 merge(&mut incoming, t, Bool::and(&[&eff, &c]));
1010 merge(&mut incoming, next, Bool::and(&[&eff, &c.not()]));
1011 pending_joins.push(t);
1012 fallthrough = false; }
1014 T2Op::B { target } => {
1015 if in_it {
1016 return Err(ExpansionError::Internal("branch inside IT block".into()));
1017 }
1018 if *target <= o as i64 {
1019 return Err(ExpansionError::Decode {
1020 offset: o,
1021 reason: "backward branch (loop) — held out, needs loop unrolling"
1022 .to_string(),
1023 });
1024 }
1025 let t = *target as usize;
1026 if t > total_len {
1027 return derr(o, "branch target beyond sequence end");
1028 }
1029 merge(&mut incoming, t, eff.clone());
1030 pending_joins.push(t);
1031 fallthrough = false;
1032 }
1033 T2Op::Push { list } => {
1034 if in_cond_region {
1035 return Err(ExpansionError::Internal(
1036 "PUSH inside a conditional region — SP discipline unmodeled".into(),
1037 ));
1038 }
1039 let n = list.count_ones() as i64;
1040 state.sp_off -= 4 * n;
1041 let mut slot = 0i64;
1042 for r in 0..8u8 {
1043 if list & (1 << r) != 0 {
1044 let v = state.get_reg(r)?;
1045 let addr = state.sp_off + 4 * slot;
1046 state.mem_write(&eff, addr, v);
1047 slot += 1;
1048 }
1049 }
1050 }
1051 T2Op::Pop { list } => {
1052 if in_cond_region {
1053 return Err(ExpansionError::Internal(
1054 "POP inside a conditional region — SP discipline unmodeled".into(),
1055 ));
1056 }
1057 let mut slot = 0i64;
1058 for r in 0..8u8 {
1059 if list & (1 << r) != 0 {
1060 let addr = state.sp_off + 4 * slot;
1061 let v = state.mem_read(addr);
1062 state.set_reg(&eff, r, v)?;
1063 slot += 1;
1064 }
1065 }
1066 state.sp_off += 4 * slot;
1067 }
1068 T2Op::StrPreDec { rt } => {
1069 if in_cond_region {
1070 return Err(ExpansionError::Internal(
1071 "STR [SP,#-4]! inside a conditional region — SP discipline unmodeled"
1072 .into(),
1073 ));
1074 }
1075 state.sp_off -= 4;
1076 let v = state.get_reg(*rt)?;
1077 let addr = state.sp_off;
1078 state.mem_write(&eff, addr, v);
1079 }
1080 T2Op::AddSpImm { imm } => {
1081 if in_cond_region {
1082 return Err(ExpansionError::Internal(
1083 "ADD SP inside a conditional region — SP discipline unmodeled".into(),
1084 ));
1085 }
1086 state.sp_off += *imm as i64;
1087 }
1088 T2Op::AndImm { rd, rn, imm } => {
1089 let v = state.get_reg(*rn)?.bvand(k32(*imm as i64));
1090 state.set_reg(&eff, *rd, v)?;
1091 }
1092 T2Op::AddImm { rd, rn, imm } => {
1093 let v = state.get_reg(*rn)?.bvadd(k32(*imm as i64));
1094 state.set_reg(&eff, *rd, v)?;
1095 }
1096 T2Op::SubsImm { rd, rn, imm } => {
1097 let a = state.get_reg(*rn)?;
1098 let b = k32(*imm as i64);
1099 let f = Flags::from_cmp(&a, &b);
1100 state.set_flags(&eff, f);
1101 state.set_reg(&eff, *rd, a.bvsub(&b))?;
1102 }
1103 T2Op::RsbImm { rd, rn, imm } => {
1104 let v = k32(*imm as i64).bvsub(&state.get_reg(*rn)?);
1105 state.set_reg(&eff, *rd, v)?;
1106 }
1107 T2Op::MovwImm { rd, imm16 } => {
1108 state.set_reg(&eff, *rd, k32(*imm16 as i64))?;
1109 }
1110 T2Op::MovtImm { rd, imm16 } => {
1111 let low = state.get_reg(*rd)?.bvand(k32(0xFFFF));
1112 let v = low.bvor(k32(((*imm16 as i64) << 16) & 0xFFFF_FFFF));
1113 state.set_reg(&eff, *rd, v)?;
1114 }
1115 T2Op::DpReg { kind, rd, rn, rm } => {
1116 let a = state.get_reg(*rn)?;
1117 let b = state.get_reg(*rm)?;
1118 match kind {
1119 DpKind::And => state.set_reg(&eff, *rd, a.bvand(&b))?,
1120 DpKind::Orr => state.set_reg(&eff, *rd, a.bvor(&b))?,
1121 DpKind::Add => state.set_reg(&eff, *rd, a.bvadd(&b))?,
1122 DpKind::Sub => state.set_reg(&eff, *rd, a.bvsub(&b))?,
1123 DpKind::Sbcs => {
1124 let (v, f) = exec_sbcs(&a, &b, &state.flags.c);
1125 state.set_flags(&eff, f);
1126 state.set_reg(&eff, *rd, v)?;
1127 }
1128 }
1129 }
1130 T2Op::ShiftImm { kind, rd, rm, imm } => {
1131 let a = state.get_reg(*rm)?;
1132 let s = k32(*imm as i64);
1133 let v = match kind {
1134 ShiftKind::Lsl => a.bvshl(&s),
1135 ShiftKind::Lsr => a.bvlshr(&s),
1136 ShiftKind::Asr => a.bvashr(&s),
1137 };
1138 state.set_reg(&eff, *rd, v)?;
1139 }
1140 T2Op::ShiftReg { kind, rd, rn, rm } => {
1141 let a = state.get_reg(*rn)?;
1142 let s = state.get_reg(*rm)?.bvand(k32(0xFF));
1146 let v = match kind {
1147 ShiftKind::Lsl => a.bvshl(&s),
1148 ShiftKind::Lsr => a.bvlshr(&s),
1149 ShiftKind::Asr => a.bvashr(&s),
1150 };
1151 state.set_reg(&eff, *rd, v)?;
1152 }
1153 T2Op::Mul { rd, rn, rm } => {
1154 let v = state.get_reg(*rn)?.bvmul(&state.get_reg(*rm)?);
1155 state.set_reg(&eff, *rd, v)?;
1156 }
1157 T2Op::Mla { rd, rn, rm, ra } => {
1158 let v = state
1159 .get_reg(*ra)?
1160 .bvadd(state.get_reg(*rn)?.bvmul(&state.get_reg(*rm)?));
1161 state.set_reg(&eff, *rd, v)?;
1162 }
1163 T2Op::Umull { rdlo, rdhi, rn, rm } => {
1164 let full = state
1165 .get_reg(*rn)?
1166 .zero_ext(32)
1167 .bvmul(state.get_reg(*rm)?.zero_ext(32));
1168 let lo = full.extract(31, 0);
1169 let hi = full.extract(63, 32);
1170 state.set_reg(&eff, *rdlo, lo)?;
1171 state.set_reg(&eff, *rdhi, hi)?;
1172 }
1173 T2Op::Clz { rd, rm } => {
1174 let v = clz32(&state.get_reg(*rm)?);
1175 state.set_reg(&eff, *rd, v)?;
1176 }
1177 T2Op::Rbit { rd, rm } => {
1178 let v = rbit32(&state.get_reg(*rm)?);
1179 state.set_reg(&eff, *rd, v)?;
1180 }
1181 }
1182
1183 if fallthrough {
1184 merge(&mut incoming, next, g);
1185 }
1186 }
1187
1188 if !it_queue.is_empty() {
1189 return Err(ExpansionError::Internal(
1190 "IT block extends past the end of the sequence".into(),
1191 ));
1192 }
1193 Ok(())
1194}
1195
1196enum RefResult {
1202 Word(BV),
1203 Pair(I64Pair),
1204}
1205
1206fn wasm_reference(op: &WasmOp, a: &I64Pair, b: &I64Pair) -> Option<RefResult> {
1208 use WasmOp::*;
1209 let word = |bv: BV| Some(RefResult::Word(bv));
1210 let pair = |p: I64Pair| Some(RefResult::Pair(p));
1211 match op {
1212 I64Mul => pair(i64_mul_schoolbook(a, b)),
1213 I64Shl => pair(i64_shl(a, &shift_amount64(b))),
1214 I64ShrU => pair(i64_shr_u(a, &shift_amount64(b))),
1215 I64ShrS => pair(i64_shr_s(a, &shift_amount64(b))),
1216 I64Rotl => pair(i64_rotl(a, &shift_amount64(b))),
1217 I64Rotr => pair(i64_rotr(a, &shift_amount64(b))),
1218 I64Eq => word(bool_to_i32(&a.eq_pair(b))),
1219 I64Ne => word(bool_to_i32(&a.eq_pair(b).not())),
1220 I64LtU => word(bool_to_i32(&i64_lt_u(a, b))),
1221 I64LtS => word(bool_to_i32(&i64_lt_s(a, b))),
1222 I64GtU => word(bool_to_i32(&i64_lt_u(b, a))),
1223 I64GtS => word(bool_to_i32(&i64_lt_s(b, a))),
1224 I64LeU => word(bool_to_i32(&i64_lt_u(b, a).not())),
1225 I64LeS => word(bool_to_i32(&i64_lt_s(b, a).not())),
1226 I64GeU => word(bool_to_i32(&i64_lt_u(a, b).not())),
1227 I64GeS => word(bool_to_i32(&i64_lt_s(a, b).not())),
1228 I64Eqz => word(bool_to_i32(&Bool::and(&[
1229 &a.lo.eq(k32(0)),
1230 &a.hi.eq(k32(0)),
1231 ]))),
1232 I64Clz => word(
1240 a.hi.eq(k32(0))
1241 .ite(clz32(&a.lo).bvadd(k32(32)), clz32(&a.hi)),
1242 ),
1243 I64Ctz => word(
1244 a.lo.eq(k32(0))
1245 .ite(ctz32(&a.hi).bvadd(k32(32)), ctz32(&a.lo)),
1246 ),
1247 I64Popcnt => word(popcnt32_hakmem(&a.lo).bvadd(popcnt32_hakmem(&a.hi))),
1251 _ => None,
1252 }
1253}
1254
1255struct Contract {
1258 a: (u8, u8),
1260 b: Option<(u8, Option<u8>)>,
1263 result: ContractResult,
1265}
1266
1267enum ContractResult {
1268 Word(u8),
1269 Pair(u8, u8),
1270}
1271
1272fn ri(r: &Reg) -> u8 {
1273 crate::validator_pattern::reg_index(r) as u8
1274}
1275
1276fn contract_of(pseudo: &ArmOp) -> Option<Contract> {
1279 match pseudo {
1280 ArmOp::I64Mul {
1281 rd_lo,
1282 rd_hi,
1283 rn_lo,
1284 rn_hi,
1285 rm_lo,
1286 rm_hi,
1287 } => Some(Contract {
1288 a: (ri(rn_lo), ri(rn_hi)),
1289 b: Some((ri(rm_lo), Some(ri(rm_hi)))),
1290 result: ContractResult::Pair(ri(rd_lo), ri(rd_hi)),
1291 }),
1292 ArmOp::I64Shl {
1293 rd_lo,
1294 rd_hi,
1295 rn_lo,
1296 rn_hi,
1297 rm_lo,
1298 rm_hi,
1299 }
1300 | ArmOp::I64ShrU {
1301 rd_lo,
1302 rd_hi,
1303 rn_lo,
1304 rn_hi,
1305 rm_lo,
1306 rm_hi,
1307 }
1308 | ArmOp::I64ShrS {
1309 rd_lo,
1310 rd_hi,
1311 rn_lo,
1312 rn_hi,
1313 rm_lo,
1314 rm_hi,
1315 } => Some(Contract {
1316 a: (ri(rn_lo), ri(rn_hi)),
1317 b: Some((ri(rm_lo), Some(ri(rm_hi)))),
1323 result: ContractResult::Pair(ri(rd_lo), ri(rd_hi)),
1324 }),
1325 ArmOp::I64Rotl {
1326 rdlo,
1327 rdhi,
1328 rnlo,
1329 rnhi,
1330 shift,
1331 }
1332 | ArmOp::I64Rotr {
1333 rdlo,
1334 rdhi,
1335 rnlo,
1336 rnhi,
1337 shift,
1338 } => Some(Contract {
1339 a: (ri(rnlo), ri(rnhi)),
1340 b: Some((ri(shift), None)),
1341 result: ContractResult::Pair(ri(rdlo), ri(rdhi)),
1342 }),
1343 ArmOp::I64SetCond {
1344 rd,
1345 rn_lo,
1346 rn_hi,
1347 rm_lo,
1348 rm_hi,
1349 ..
1350 } => Some(Contract {
1351 a: (ri(rn_lo), ri(rn_hi)),
1352 b: Some((ri(rm_lo), Some(ri(rm_hi)))),
1353 result: ContractResult::Word(ri(rd)),
1354 }),
1355 ArmOp::I64SetCondZ { rd, rn_lo, rn_hi } => Some(Contract {
1356 a: (ri(rn_lo), ri(rn_hi)),
1357 b: None,
1358 result: ContractResult::Word(ri(rd)),
1359 }),
1360 ArmOp::I64Clz { rd, rnlo, rnhi }
1366 | ArmOp::I64Ctz { rd, rnlo, rnhi }
1367 | ArmOp::I64Popcnt { rd, rnlo, rnhi } => Some(Contract {
1368 a: (ri(rnlo), ri(rnhi)),
1369 b: None,
1370 result: ContractResult::Word(ri(rd)),
1371 }),
1372 _ => None,
1373 }
1374}
1375
1376pub fn validate_expansion(
1388 wasm: &WasmOp,
1389 pseudo: &ArmOp,
1390 code: &[u8],
1391) -> Result<ExpansionWitness, ExpansionError> {
1392 let label = format!("{wasm:?}");
1393
1394 let contract = contract_of(pseudo)
1395 .ok_or_else(|| ExpansionError::Unsupported(format!("pseudo-op {pseudo:?}")))?;
1396
1397 let instrs = decode_thumb2(code)?;
1398
1399 let a = I64Pair {
1401 lo: sym32("a_lo"),
1402 hi: sym32("a_hi"),
1403 };
1404 let b = I64Pair {
1405 lo: sym32("b_lo"),
1406 hi: sym32("b_hi"),
1407 };
1408
1409 let reference = wasm_reference(wasm, &a, &b)
1410 .ok_or_else(|| ExpansionError::Unsupported(format!("wasm op {wasm:?}")))?;
1411
1412 let mut state = MachineState::new();
1414 let g_true = Bool::from_bool(true);
1415 state.set_reg(&g_true, contract.a.0, a.lo.clone())?;
1416 state.set_reg(&g_true, contract.a.1, a.hi.clone())?;
1417 if let Some((blo, bhi)) = &contract.b {
1418 state.set_reg(&g_true, *blo, b.lo.clone())?;
1419 if let Some(bhi) = bhi {
1420 state.set_reg(&g_true, *bhi, b.hi.clone())?;
1421 }
1422 }
1423 execute(&instrs, &mut state)?;
1424
1425 let result_differ = match (&reference, &contract.result) {
1427 (RefResult::Word(w), ContractResult::Word(rd)) => w.eq(&state.get_reg(*rd)?).not(),
1428 (RefResult::Pair(w), ContractResult::Pair(rd_lo, rd_hi)) => {
1429 let got = I64Pair {
1430 lo: state.get_reg(*rd_lo)?,
1431 hi: state.get_reg(*rd_hi)?,
1432 };
1433 w.eq_pair(&got).not()
1434 }
1435 _ => {
1436 return Err(ExpansionError::Internal(format!(
1437 "result-shape mismatch for {label}"
1438 )));
1439 }
1440 };
1441
1442 let result_regs: Vec<u8> = match &contract.result {
1451 ContractResult::Word(rd) => vec![*rd],
1452 ContractResult::Pair(rd_lo, rd_hi) => vec![*rd_lo, *rd_hi],
1453 };
1454 let mut seeded: Vec<(u8, BV)> =
1455 vec![(contract.a.0, a.lo.clone()), (contract.a.1, a.hi.clone())];
1456 if let Some((blo, bhi)) = &contract.b {
1457 seeded.push((*blo, b.lo.clone()));
1458 if let Some(bhi) = bhi {
1459 seeded.push((*bhi, b.hi.clone()));
1460 }
1461 }
1462 let mut clobbered: Vec<Bool> = Vec::new();
1463 for (reg, initial) in &seeded {
1464 if result_regs.contains(reg) {
1465 continue;
1466 }
1467 clobbered.push(state.get_reg(*reg)?.eq(initial).not());
1468 }
1469 let clobber_refs: Vec<&Bool> = clobbered.iter().collect();
1470 let differ = if clobber_refs.is_empty() {
1471 result_differ
1472 } else {
1473 Bool::or(&[&[&result_differ], clobber_refs.as_slice()].concat())
1474 };
1475
1476 let mut solver = new_solver();
1477 solver.assert(&differ);
1478 match solver.check() {
1479 CheckOutcome::Unsat => Ok(ExpansionWitness {
1480 wasm_op_label: label,
1481 instr_count: instrs.len(),
1482 byte_len: code.len(),
1483 solver_result: SolverResultKind::Unsat,
1484 }),
1485 CheckOutcome::Sat => {
1486 let mut parts = Vec::new();
1487 for (name, bv) in [
1488 ("a_lo", &a.lo),
1489 ("a_hi", &a.hi),
1490 ("b_lo", &b.lo),
1491 ("b_hi", &b.hi),
1492 ] {
1493 if let Some(v) = solver.value(bv) {
1494 parts.push(format!("{name}={v:#x}"));
1495 }
1496 }
1497 let description = if parts.is_empty() {
1498 "no model available".to_string()
1499 } else {
1500 parts.join(", ")
1501 };
1502 Err(ExpansionError::Counterexample {
1503 wasm_op_label: label,
1504 description,
1505 })
1506 }
1507 CheckOutcome::Unknown(reason) => {
1508 Err(ExpansionError::SolverUnknown(format!("{label}: {reason}")))
1509 }
1510 }
1511}
1512
1513pub fn covered_i64_pseudo_selections() -> Vec<(WasmOp, ArmOp)> {
1519 use synth_synthesis::rules::Condition;
1520 let mut v: Vec<(WasmOp, ArmOp)> = Vec::new();
1521
1522 v.push((
1523 WasmOp::I64Mul,
1524 ArmOp::I64Mul {
1525 rd_lo: Reg::R0,
1526 rd_hi: Reg::R1,
1527 rn_lo: Reg::R0,
1528 rn_hi: Reg::R1,
1529 rm_lo: Reg::R2,
1530 rm_hi: Reg::R3,
1531 },
1532 ));
1533
1534 let setcond = |cond: Condition| ArmOp::I64SetCond {
1535 rd: Reg::R0,
1536 rn_lo: Reg::R0,
1537 rn_hi: Reg::R1,
1538 rm_lo: Reg::R2,
1539 rm_hi: Reg::R3,
1540 cond,
1541 };
1542 v.push((WasmOp::I64Eq, setcond(Condition::EQ)));
1543 v.push((WasmOp::I64Ne, setcond(Condition::NE)));
1544 v.push((WasmOp::I64LtS, setcond(Condition::LT)));
1545 v.push((WasmOp::I64LeS, setcond(Condition::LE)));
1546 v.push((WasmOp::I64GtS, setcond(Condition::GT)));
1547 v.push((WasmOp::I64GeS, setcond(Condition::GE)));
1548 v.push((WasmOp::I64LtU, setcond(Condition::LO)));
1549 v.push((WasmOp::I64LeU, setcond(Condition::LS)));
1550 v.push((WasmOp::I64GtU, setcond(Condition::HI)));
1551 v.push((WasmOp::I64GeU, setcond(Condition::HS)));
1552
1553 v.push((
1554 WasmOp::I64Eqz,
1555 ArmOp::I64SetCondZ {
1556 rd: Reg::R0,
1557 rn_lo: Reg::R0,
1558 rn_hi: Reg::R1,
1559 },
1560 ));
1561
1562 for (op, mk) in [
1563 (
1564 WasmOp::I64Shl,
1565 (|| ArmOp::I64Shl {
1566 rd_lo: Reg::R0,
1567 rd_hi: Reg::R1,
1568 rn_lo: Reg::R0,
1569 rn_hi: Reg::R1,
1570 rm_lo: Reg::R2,
1571 rm_hi: Reg::R3,
1572 }) as fn() -> ArmOp,
1573 ),
1574 (WasmOp::I64ShrU, || ArmOp::I64ShrU {
1575 rd_lo: Reg::R0,
1576 rd_hi: Reg::R1,
1577 rn_lo: Reg::R0,
1578 rn_hi: Reg::R1,
1579 rm_lo: Reg::R2,
1580 rm_hi: Reg::R3,
1581 }),
1582 (WasmOp::I64ShrS, || ArmOp::I64ShrS {
1583 rd_lo: Reg::R0,
1584 rd_hi: Reg::R1,
1585 rn_lo: Reg::R0,
1586 rn_hi: Reg::R1,
1587 rm_lo: Reg::R2,
1588 rm_hi: Reg::R3,
1589 }),
1590 ] {
1591 v.push((op, mk()));
1592 }
1593
1594 v.push((
1595 WasmOp::I64Rotl,
1596 ArmOp::I64Rotl {
1597 rdlo: Reg::R0,
1598 rdhi: Reg::R1,
1599 rnlo: Reg::R0,
1600 rnhi: Reg::R1,
1601 shift: Reg::R2,
1602 },
1603 ));
1604 v.push((
1605 WasmOp::I64Rotr,
1606 ArmOp::I64Rotr {
1607 rdlo: Reg::R0,
1608 rdhi: Reg::R1,
1609 rnlo: Reg::R0,
1610 rnhi: Reg::R1,
1611 shift: Reg::R2,
1612 },
1613 ));
1614
1615 for (op, mk) in [
1616 (
1617 WasmOp::I64Clz,
1618 (|| ArmOp::I64Clz {
1619 rd: Reg::R0,
1620 rnlo: Reg::R0,
1621 rnhi: Reg::R1,
1622 }) as fn() -> ArmOp,
1623 ),
1624 (WasmOp::I64Ctz, || ArmOp::I64Ctz {
1625 rd: Reg::R0,
1626 rnlo: Reg::R0,
1627 rnhi: Reg::R1,
1628 }),
1629 (WasmOp::I64Popcnt, || ArmOp::I64Popcnt {
1630 rd: Reg::R0,
1631 rnlo: Reg::R0,
1632 rnhi: Reg::R1,
1633 }),
1634 ] {
1635 v.push((op, mk()));
1636 }
1637
1638 v
1639}
1640
1641#[cfg(test)]
1649mod tests {
1650 use super::*;
1651 use crate::with_verification_context;
1652
1653 fn halfwords(hws: &[u16]) -> Vec<u8> {
1654 let mut b = Vec::new();
1655 for hw in hws {
1656 b.extend_from_slice(&hw.to_le_bytes());
1657 }
1658 b
1659 }
1660
1661 #[test]
1664 fn bitcount_references_sanity() {
1665 with_verification_context(|| {
1666 for (v, clz, ctz, pop) in [
1668 (0u32, 32i64, 32i64, 0i64),
1669 (1, 31, 0, 1),
1670 (8, 28, 3, 1),
1671 (0x8000_0000, 0, 31, 1),
1672 (0xF0F, 20, 0, 8),
1673 (0xFFFF_FFFF, 0, 0, 32),
1674 ] {
1675 let x = k32(v as i64);
1676 let mut s = new_solver();
1677 let bad = Bool::or(&[
1678 &clz32(&x).eq(k32(clz)).not(),
1679 &ctz32(&x).eq(k32(ctz)).not(),
1680 &popcnt32(&x).eq(k32(pop)).not(),
1681 ]);
1682 s.assert(&bad);
1683 assert_eq!(s.check(), CheckOutcome::Unsat, "bitcount refs for {v:#x}");
1684 }
1685 });
1686 }
1687
1688 #[test]
1695 fn schoolbook_mul_matches_u64_mul() {
1696 with_verification_context(|| {
1697 let grid: &[u64] = &[
1698 0,
1699 1,
1700 2,
1701 3,
1702 0x7FFF_FFFF,
1703 0x8000_0000,
1704 0xFFFF_FFFF,
1705 0x1_0000_0000,
1706 0xDEAD_BEEF_CAFE_F00D,
1707 0x8000_0000_0000_0000,
1708 0xFFFF_FFFF_FFFF_FFFF,
1709 0x0000_0001_0000_0001,
1710 ];
1711 for &x in grid {
1712 for &y in grid {
1713 let truth = x.wrapping_mul(y);
1714 let a = I64Pair {
1715 lo: k32((x & 0xFFFF_FFFF) as i64),
1716 hi: k32((x >> 32) as i64),
1717 };
1718 let b = I64Pair {
1719 lo: k32((y & 0xFFFF_FFFF) as i64),
1720 hi: k32((y >> 32) as i64),
1721 };
1722 let got = i64_mul_schoolbook(&a, &b);
1723 let want = I64Pair {
1724 lo: k32((truth & 0xFFFF_FFFF) as i64),
1725 hi: k32((truth >> 32) as i64),
1726 };
1727 let mut s = new_solver();
1728 s.assert(&got.eq_pair(&want).not());
1729 assert_eq!(
1730 s.check(),
1731 CheckOutcome::Unsat,
1732 "schoolbook mul wrong for {x:#x} * {y:#x}"
1733 );
1734 }
1735 }
1736 });
1737 }
1738
1739 #[test]
1742 fn rbit_clz_is_ctz() {
1743 with_verification_context(|| {
1744 let x = sym32("x");
1745 let mut s = new_solver();
1746 s.assert(&clz32(&rbit32(&x)).eq(ctz32(&x)).not());
1747 assert_eq!(s.check(), CheckOutcome::Unsat);
1748 });
1749 }
1750
1751 #[test]
1757 fn popcnt_hakmem_formula_is_popcnt() {
1758 with_verification_context(|| {
1759 let w = sym32("w");
1760 let mut s = new_solver();
1761 s.assert(&popcnt32_hakmem(&w).eq(popcnt32(&w)).not());
1762 assert_eq!(
1763 s.check(),
1764 CheckOutcome::Unsat,
1765 "HAKMEM fold must equal bit-sum popcount for all inputs"
1766 );
1767 });
1768 }
1769
1770 #[test]
1772 fn decoder_rejects_unknown_loudly() {
1773 let r = decode_thumb2(&halfwords(&[0xDE00]));
1775 assert!(matches!(r, Err(ExpansionError::Decode { .. })), "{r:?}");
1776 let r = decode_thumb2(&halfwords(&[0xF7F0, 0xA000]));
1778 assert!(matches!(r, Err(ExpansionError::Decode { .. })), "{r:?}");
1779 }
1780
1781 #[test]
1784 fn backward_branch_is_held_out_loudly() {
1785 with_verification_context(|| {
1786 let code = halfwords(&[0x2800, 0xD1FC]);
1788 let pseudo = ArmOp::I64SetCondZ {
1789 rd: Reg::R0,
1790 rn_lo: Reg::R0,
1791 rn_hi: Reg::R1,
1792 };
1793 let r = validate_expansion(&WasmOp::I64Eqz, &pseudo, &code);
1794 match r {
1795 Err(ExpansionError::Decode { reason, .. }) => {
1796 assert!(reason.contains("backward branch"), "{reason}");
1797 }
1798 other => panic!("expected backward-branch decode error, got {other:?}"),
1799 }
1800 });
1801 }
1802
1803 #[test]
1805 fn div_rem_pseudo_ops_are_unsupported() {
1806 let pseudo = ArmOp::I64DivU {
1807 rdlo: Reg::R0,
1808 rdhi: Reg::R1,
1809 rnlo: Reg::R0,
1810 rnhi: Reg::R1,
1811 rmlo: Reg::R2,
1812 rmhi: Reg::R3,
1813 elide_zero_guard: false,
1814 };
1815 let r = validate_expansion(&WasmOp::I64DivU, &pseudo, &[]);
1816 assert!(matches!(r, Err(ExpansionError::Unsupported(_))), "{r:?}");
1817 }
1818
1819 #[test]
1823 fn hand_built_eqz_sequence_certifies() {
1824 with_verification_context(|| {
1825 let code = halfwords(&[
1826 0xEA40, 0x0001, 0x2800, 0xBF0C, 0x2001, 0x2000, ]);
1832 let pseudo = ArmOp::I64SetCondZ {
1833 rd: Reg::R0,
1834 rn_lo: Reg::R0,
1835 rn_hi: Reg::R1,
1836 };
1837 let w = validate_expansion(&WasmOp::I64Eqz, &pseudo, &code)
1838 .expect("eqz sequence must certify");
1839 assert_eq!(w.solver_result, SolverResultKind::Unsat);
1840 assert_eq!(w.instr_count, 5);
1841 });
1842 }
1843
1844 #[test]
1847 fn hand_built_eqz_wrong_polarity_rejected() {
1848 with_verification_context(|| {
1849 let code = halfwords(&[
1850 0xEA40, 0x0001, 0x2800, 0xBF14, 0x2001, 0x2000, ]);
1856 let pseudo = ArmOp::I64SetCondZ {
1857 rd: Reg::R0,
1858 rn_lo: Reg::R0,
1859 rn_hi: Reg::R1,
1860 };
1861 match validate_expansion(&WasmOp::I64Eqz, &pseudo, &code) {
1862 Err(ExpansionError::Counterexample { .. }) => {}
1863 other => panic!("expected Counterexample, got {other:?}"),
1864 }
1865 });
1866 }
1867
1868 #[test]
1876 fn pre_1048_shl_amount_clobber_rejected() {
1877 with_verification_context(|| {
1878 let code = halfwords(&[
1879 0xF002, 0x023F, 0xF1B2, 0x0320, 0xD50A, 0xF1C2, 0x0320, 0xFA20, 0xF303, 0xFA01, 0xF102, 0xEA41, 0x0103, 0xFA00, 0xF002, 0xE002, 0xFA00, 0xF103, 0x2000, ]);
1891 let pseudo = ArmOp::I64Shl {
1892 rd_lo: Reg::R0,
1893 rd_hi: Reg::R1,
1894 rn_lo: Reg::R0,
1895 rn_hi: Reg::R1,
1896 rm_lo: Reg::R2,
1897 rm_hi: Reg::R3,
1898 };
1899 match validate_expansion(&WasmOp::I64Shl, &pseudo, &code) {
1900 Err(ExpansionError::Counterexample { .. }) => {}
1901 other => {
1902 panic!("pre-#1048 amount-clobbering shl bytes must be rejected, got {other:?}")
1903 }
1904 }
1905 });
1906 }
1907
1908 #[test]
1912 fn pre_1048_clz_operand_hi_clear_rejected() {
1913 with_verification_context(|| {
1914 let code = halfwords(&[
1915 0xF1B1, 0x0F00, 0xD003, 0xFAB1, 0xF081, 0xE004, 0xBF00, 0xFAB0, 0xF080, 0xF100, 0x0020, 0x2100, ]);
1924 let pseudo = ArmOp::I64Clz {
1925 rd: Reg::R0,
1926 rnlo: Reg::R0,
1927 rnhi: Reg::R1,
1928 };
1929 match validate_expansion(&WasmOp::I64Clz, &pseudo, &code) {
1930 Err(ExpansionError::Counterexample { .. }) => {}
1931 other => panic!(
1932 "pre-#1048 operand-hi-clearing clz bytes must be rejected, got {other:?}"
1933 ),
1934 }
1935 });
1936 }
1937}