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 => pair(I64Pair {
1234 lo: a
1235 .hi
1236 .eq(k32(0))
1237 .ite(clz32(&a.lo).bvadd(k32(32)), clz32(&a.hi)),
1238 hi: k32(0),
1239 }),
1240 I64Ctz => pair(I64Pair {
1241 lo: a
1242 .lo
1243 .eq(k32(0))
1244 .ite(ctz32(&a.hi).bvadd(k32(32)), ctz32(&a.lo)),
1245 hi: k32(0),
1246 }),
1247 I64Popcnt => pair(I64Pair {
1251 lo: popcnt32_hakmem(&a.lo).bvadd(popcnt32_hakmem(&a.hi)),
1252 hi: k32(0),
1253 }),
1254 _ => None,
1255 }
1256}
1257
1258struct Contract {
1261 a: (u8, u8),
1263 b: Option<(u8, Option<u8>)>,
1266 result: ContractResult,
1268}
1269
1270enum ContractResult {
1271 Word(u8),
1272 Pair(u8, u8),
1273}
1274
1275fn ri(r: &Reg) -> u8 {
1276 crate::validator_pattern::reg_index(r) as u8
1277}
1278
1279fn contract_of(pseudo: &ArmOp) -> Option<Contract> {
1282 match pseudo {
1283 ArmOp::I64Mul {
1284 rd_lo,
1285 rd_hi,
1286 rn_lo,
1287 rn_hi,
1288 rm_lo,
1289 rm_hi,
1290 } => Some(Contract {
1291 a: (ri(rn_lo), ri(rn_hi)),
1292 b: Some((ri(rm_lo), Some(ri(rm_hi)))),
1293 result: ContractResult::Pair(ri(rd_lo), ri(rd_hi)),
1294 }),
1295 ArmOp::I64Shl {
1296 rd_lo,
1297 rd_hi,
1298 rn_lo,
1299 rn_hi,
1300 rm_lo,
1301 rm_hi,
1302 }
1303 | ArmOp::I64ShrU {
1304 rd_lo,
1305 rd_hi,
1306 rn_lo,
1307 rn_hi,
1308 rm_lo,
1309 rm_hi,
1310 }
1311 | ArmOp::I64ShrS {
1312 rd_lo,
1313 rd_hi,
1314 rn_lo,
1315 rn_hi,
1316 rm_lo,
1317 rm_hi,
1318 } => Some(Contract {
1319 a: (ri(rn_lo), ri(rn_hi)),
1320 b: Some((ri(rm_lo), Some(ri(rm_hi)))),
1324 result: ContractResult::Pair(ri(rd_lo), ri(rd_hi)),
1325 }),
1326 ArmOp::I64Rotl {
1327 rdlo,
1328 rdhi,
1329 rnlo,
1330 rnhi,
1331 shift,
1332 }
1333 | ArmOp::I64Rotr {
1334 rdlo,
1335 rdhi,
1336 rnlo,
1337 rnhi,
1338 shift,
1339 } => Some(Contract {
1340 a: (ri(rnlo), ri(rnhi)),
1341 b: Some((ri(shift), None)),
1342 result: ContractResult::Pair(ri(rdlo), ri(rdhi)),
1343 }),
1344 ArmOp::I64SetCond {
1345 rd,
1346 rn_lo,
1347 rn_hi,
1348 rm_lo,
1349 rm_hi,
1350 ..
1351 } => Some(Contract {
1352 a: (ri(rn_lo), ri(rn_hi)),
1353 b: Some((ri(rm_lo), Some(ri(rm_hi)))),
1354 result: ContractResult::Word(ri(rd)),
1355 }),
1356 ArmOp::I64SetCondZ { rd, rn_lo, rn_hi } => Some(Contract {
1357 a: (ri(rn_lo), ri(rn_hi)),
1358 b: None,
1359 result: ContractResult::Word(ri(rd)),
1360 }),
1361 ArmOp::I64Clz { rd, rnlo, rnhi }
1364 | ArmOp::I64Ctz { rd, rnlo, rnhi }
1365 | ArmOp::I64Popcnt { rd, rnlo, rnhi } => Some(Contract {
1366 a: (ri(rnlo), ri(rnhi)),
1367 b: None,
1368 result: ContractResult::Pair(ri(rd), ri(rnhi)),
1369 }),
1370 _ => None,
1371 }
1372}
1373
1374pub fn validate_expansion(
1386 wasm: &WasmOp,
1387 pseudo: &ArmOp,
1388 code: &[u8],
1389) -> Result<ExpansionWitness, ExpansionError> {
1390 let label = format!("{wasm:?}");
1391
1392 let contract = contract_of(pseudo)
1393 .ok_or_else(|| ExpansionError::Unsupported(format!("pseudo-op {pseudo:?}")))?;
1394
1395 let instrs = decode_thumb2(code)?;
1396
1397 let a = I64Pair {
1399 lo: sym32("a_lo"),
1400 hi: sym32("a_hi"),
1401 };
1402 let b = I64Pair {
1403 lo: sym32("b_lo"),
1404 hi: sym32("b_hi"),
1405 };
1406
1407 let reference = wasm_reference(wasm, &a, &b)
1408 .ok_or_else(|| ExpansionError::Unsupported(format!("wasm op {wasm:?}")))?;
1409
1410 let mut state = MachineState::new();
1412 let g_true = Bool::from_bool(true);
1413 state.set_reg(&g_true, contract.a.0, a.lo.clone())?;
1414 state.set_reg(&g_true, contract.a.1, a.hi.clone())?;
1415 if let Some((blo, bhi)) = &contract.b {
1416 state.set_reg(&g_true, *blo, b.lo.clone())?;
1417 if let Some(bhi) = bhi {
1418 state.set_reg(&g_true, *bhi, b.hi.clone())?;
1419 }
1420 }
1421 execute(&instrs, &mut state)?;
1422
1423 let differ = match (&reference, &contract.result) {
1425 (RefResult::Word(w), ContractResult::Word(rd)) => w.eq(&state.get_reg(*rd)?).not(),
1426 (RefResult::Pair(w), ContractResult::Pair(rd_lo, rd_hi)) => {
1427 let got = I64Pair {
1428 lo: state.get_reg(*rd_lo)?,
1429 hi: state.get_reg(*rd_hi)?,
1430 };
1431 w.eq_pair(&got).not()
1432 }
1433 _ => {
1434 return Err(ExpansionError::Internal(format!(
1435 "result-shape mismatch for {label}"
1436 )));
1437 }
1438 };
1439
1440 let mut solver = new_solver();
1441 solver.assert(&differ);
1442 match solver.check() {
1443 CheckOutcome::Unsat => Ok(ExpansionWitness {
1444 wasm_op_label: label,
1445 instr_count: instrs.len(),
1446 byte_len: code.len(),
1447 solver_result: SolverResultKind::Unsat,
1448 }),
1449 CheckOutcome::Sat => {
1450 let mut parts = Vec::new();
1451 for (name, bv) in [
1452 ("a_lo", &a.lo),
1453 ("a_hi", &a.hi),
1454 ("b_lo", &b.lo),
1455 ("b_hi", &b.hi),
1456 ] {
1457 if let Some(v) = solver.value(bv) {
1458 parts.push(format!("{name}={v:#x}"));
1459 }
1460 }
1461 let description = if parts.is_empty() {
1462 "no model available".to_string()
1463 } else {
1464 parts.join(", ")
1465 };
1466 Err(ExpansionError::Counterexample {
1467 wasm_op_label: label,
1468 description,
1469 })
1470 }
1471 CheckOutcome::Unknown(reason) => {
1472 Err(ExpansionError::SolverUnknown(format!("{label}: {reason}")))
1473 }
1474 }
1475}
1476
1477pub fn covered_i64_pseudo_selections() -> Vec<(WasmOp, ArmOp)> {
1483 use synth_synthesis::rules::Condition;
1484 let mut v: Vec<(WasmOp, ArmOp)> = Vec::new();
1485
1486 v.push((
1487 WasmOp::I64Mul,
1488 ArmOp::I64Mul {
1489 rd_lo: Reg::R0,
1490 rd_hi: Reg::R1,
1491 rn_lo: Reg::R0,
1492 rn_hi: Reg::R1,
1493 rm_lo: Reg::R2,
1494 rm_hi: Reg::R3,
1495 },
1496 ));
1497
1498 let setcond = |cond: Condition| ArmOp::I64SetCond {
1499 rd: Reg::R0,
1500 rn_lo: Reg::R0,
1501 rn_hi: Reg::R1,
1502 rm_lo: Reg::R2,
1503 rm_hi: Reg::R3,
1504 cond,
1505 };
1506 v.push((WasmOp::I64Eq, setcond(Condition::EQ)));
1507 v.push((WasmOp::I64Ne, setcond(Condition::NE)));
1508 v.push((WasmOp::I64LtS, setcond(Condition::LT)));
1509 v.push((WasmOp::I64LeS, setcond(Condition::LE)));
1510 v.push((WasmOp::I64GtS, setcond(Condition::GT)));
1511 v.push((WasmOp::I64GeS, setcond(Condition::GE)));
1512 v.push((WasmOp::I64LtU, setcond(Condition::LO)));
1513 v.push((WasmOp::I64LeU, setcond(Condition::LS)));
1514 v.push((WasmOp::I64GtU, setcond(Condition::HI)));
1515 v.push((WasmOp::I64GeU, setcond(Condition::HS)));
1516
1517 v.push((
1518 WasmOp::I64Eqz,
1519 ArmOp::I64SetCondZ {
1520 rd: Reg::R0,
1521 rn_lo: Reg::R0,
1522 rn_hi: Reg::R1,
1523 },
1524 ));
1525
1526 for (op, mk) in [
1527 (
1528 WasmOp::I64Shl,
1529 (|| ArmOp::I64Shl {
1530 rd_lo: Reg::R0,
1531 rd_hi: Reg::R1,
1532 rn_lo: Reg::R0,
1533 rn_hi: Reg::R1,
1534 rm_lo: Reg::R2,
1535 rm_hi: Reg::R3,
1536 }) as fn() -> ArmOp,
1537 ),
1538 (WasmOp::I64ShrU, || ArmOp::I64ShrU {
1539 rd_lo: Reg::R0,
1540 rd_hi: Reg::R1,
1541 rn_lo: Reg::R0,
1542 rn_hi: Reg::R1,
1543 rm_lo: Reg::R2,
1544 rm_hi: Reg::R3,
1545 }),
1546 (WasmOp::I64ShrS, || ArmOp::I64ShrS {
1547 rd_lo: Reg::R0,
1548 rd_hi: Reg::R1,
1549 rn_lo: Reg::R0,
1550 rn_hi: Reg::R1,
1551 rm_lo: Reg::R2,
1552 rm_hi: Reg::R3,
1553 }),
1554 ] {
1555 v.push((op, mk()));
1556 }
1557
1558 v.push((
1559 WasmOp::I64Rotl,
1560 ArmOp::I64Rotl {
1561 rdlo: Reg::R0,
1562 rdhi: Reg::R1,
1563 rnlo: Reg::R0,
1564 rnhi: Reg::R1,
1565 shift: Reg::R2,
1566 },
1567 ));
1568 v.push((
1569 WasmOp::I64Rotr,
1570 ArmOp::I64Rotr {
1571 rdlo: Reg::R0,
1572 rdhi: Reg::R1,
1573 rnlo: Reg::R0,
1574 rnhi: Reg::R1,
1575 shift: Reg::R2,
1576 },
1577 ));
1578
1579 for (op, mk) in [
1580 (
1581 WasmOp::I64Clz,
1582 (|| ArmOp::I64Clz {
1583 rd: Reg::R0,
1584 rnlo: Reg::R0,
1585 rnhi: Reg::R1,
1586 }) as fn() -> ArmOp,
1587 ),
1588 (WasmOp::I64Ctz, || ArmOp::I64Ctz {
1589 rd: Reg::R0,
1590 rnlo: Reg::R0,
1591 rnhi: Reg::R1,
1592 }),
1593 (WasmOp::I64Popcnt, || ArmOp::I64Popcnt {
1594 rd: Reg::R0,
1595 rnlo: Reg::R0,
1596 rnhi: Reg::R1,
1597 }),
1598 ] {
1599 v.push((op, mk()));
1600 }
1601
1602 v
1603}
1604
1605#[cfg(test)]
1613mod tests {
1614 use super::*;
1615 use crate::with_verification_context;
1616
1617 fn halfwords(hws: &[u16]) -> Vec<u8> {
1618 let mut b = Vec::new();
1619 for hw in hws {
1620 b.extend_from_slice(&hw.to_le_bytes());
1621 }
1622 b
1623 }
1624
1625 #[test]
1628 fn bitcount_references_sanity() {
1629 with_verification_context(|| {
1630 for (v, clz, ctz, pop) in [
1632 (0u32, 32i64, 32i64, 0i64),
1633 (1, 31, 0, 1),
1634 (8, 28, 3, 1),
1635 (0x8000_0000, 0, 31, 1),
1636 (0xF0F, 20, 0, 8),
1637 (0xFFFF_FFFF, 0, 0, 32),
1638 ] {
1639 let x = k32(v as i64);
1640 let mut s = new_solver();
1641 let bad = Bool::or(&[
1642 &clz32(&x).eq(k32(clz)).not(),
1643 &ctz32(&x).eq(k32(ctz)).not(),
1644 &popcnt32(&x).eq(k32(pop)).not(),
1645 ]);
1646 s.assert(&bad);
1647 assert_eq!(s.check(), CheckOutcome::Unsat, "bitcount refs for {v:#x}");
1648 }
1649 });
1650 }
1651
1652 #[test]
1659 fn schoolbook_mul_matches_u64_mul() {
1660 with_verification_context(|| {
1661 let grid: &[u64] = &[
1662 0,
1663 1,
1664 2,
1665 3,
1666 0x7FFF_FFFF,
1667 0x8000_0000,
1668 0xFFFF_FFFF,
1669 0x1_0000_0000,
1670 0xDEAD_BEEF_CAFE_F00D,
1671 0x8000_0000_0000_0000,
1672 0xFFFF_FFFF_FFFF_FFFF,
1673 0x0000_0001_0000_0001,
1674 ];
1675 for &x in grid {
1676 for &y in grid {
1677 let truth = x.wrapping_mul(y);
1678 let a = I64Pair {
1679 lo: k32((x & 0xFFFF_FFFF) as i64),
1680 hi: k32((x >> 32) as i64),
1681 };
1682 let b = I64Pair {
1683 lo: k32((y & 0xFFFF_FFFF) as i64),
1684 hi: k32((y >> 32) as i64),
1685 };
1686 let got = i64_mul_schoolbook(&a, &b);
1687 let want = I64Pair {
1688 lo: k32((truth & 0xFFFF_FFFF) as i64),
1689 hi: k32((truth >> 32) as i64),
1690 };
1691 let mut s = new_solver();
1692 s.assert(&got.eq_pair(&want).not());
1693 assert_eq!(
1694 s.check(),
1695 CheckOutcome::Unsat,
1696 "schoolbook mul wrong for {x:#x} * {y:#x}"
1697 );
1698 }
1699 }
1700 });
1701 }
1702
1703 #[test]
1706 fn rbit_clz_is_ctz() {
1707 with_verification_context(|| {
1708 let x = sym32("x");
1709 let mut s = new_solver();
1710 s.assert(&clz32(&rbit32(&x)).eq(ctz32(&x)).not());
1711 assert_eq!(s.check(), CheckOutcome::Unsat);
1712 });
1713 }
1714
1715 #[test]
1721 fn popcnt_hakmem_formula_is_popcnt() {
1722 with_verification_context(|| {
1723 let w = sym32("w");
1724 let mut s = new_solver();
1725 s.assert(&popcnt32_hakmem(&w).eq(popcnt32(&w)).not());
1726 assert_eq!(
1727 s.check(),
1728 CheckOutcome::Unsat,
1729 "HAKMEM fold must equal bit-sum popcount for all inputs"
1730 );
1731 });
1732 }
1733
1734 #[test]
1736 fn decoder_rejects_unknown_loudly() {
1737 let r = decode_thumb2(&halfwords(&[0xDE00]));
1739 assert!(matches!(r, Err(ExpansionError::Decode { .. })), "{r:?}");
1740 let r = decode_thumb2(&halfwords(&[0xF7F0, 0xA000]));
1742 assert!(matches!(r, Err(ExpansionError::Decode { .. })), "{r:?}");
1743 }
1744
1745 #[test]
1748 fn backward_branch_is_held_out_loudly() {
1749 with_verification_context(|| {
1750 let code = halfwords(&[0x2800, 0xD1FC]);
1752 let pseudo = ArmOp::I64SetCondZ {
1753 rd: Reg::R0,
1754 rn_lo: Reg::R0,
1755 rn_hi: Reg::R1,
1756 };
1757 let r = validate_expansion(&WasmOp::I64Eqz, &pseudo, &code);
1758 match r {
1759 Err(ExpansionError::Decode { reason, .. }) => {
1760 assert!(reason.contains("backward branch"), "{reason}");
1761 }
1762 other => panic!("expected backward-branch decode error, got {other:?}"),
1763 }
1764 });
1765 }
1766
1767 #[test]
1769 fn div_rem_pseudo_ops_are_unsupported() {
1770 let pseudo = ArmOp::I64DivU {
1771 rdlo: Reg::R0,
1772 rdhi: Reg::R1,
1773 rnlo: Reg::R0,
1774 rnhi: Reg::R1,
1775 rmlo: Reg::R2,
1776 rmhi: Reg::R3,
1777 elide_zero_guard: false,
1778 };
1779 let r = validate_expansion(&WasmOp::I64DivU, &pseudo, &[]);
1780 assert!(matches!(r, Err(ExpansionError::Unsupported(_))), "{r:?}");
1781 }
1782
1783 #[test]
1787 fn hand_built_eqz_sequence_certifies() {
1788 with_verification_context(|| {
1789 let code = halfwords(&[
1790 0xEA40, 0x0001, 0x2800, 0xBF0C, 0x2001, 0x2000, ]);
1796 let pseudo = ArmOp::I64SetCondZ {
1797 rd: Reg::R0,
1798 rn_lo: Reg::R0,
1799 rn_hi: Reg::R1,
1800 };
1801 let w = validate_expansion(&WasmOp::I64Eqz, &pseudo, &code)
1802 .expect("eqz sequence must certify");
1803 assert_eq!(w.solver_result, SolverResultKind::Unsat);
1804 assert_eq!(w.instr_count, 5);
1805 });
1806 }
1807
1808 #[test]
1811 fn hand_built_eqz_wrong_polarity_rejected() {
1812 with_verification_context(|| {
1813 let code = halfwords(&[
1814 0xEA40, 0x0001, 0x2800, 0xBF14, 0x2001, 0x2000, ]);
1820 let pseudo = ArmOp::I64SetCondZ {
1821 rd: Reg::R0,
1822 rn_lo: Reg::R0,
1823 rn_hi: Reg::R1,
1824 };
1825 match validate_expansion(&WasmOp::I64Eqz, &pseudo, &code) {
1826 Err(ExpansionError::Counterexample { .. }) => {}
1827 other => panic!("expected Counterexample, got {other:?}"),
1828 }
1829 });
1830 }
1831}