Path: blob/main/cranelift/isle/veri/veri_engine/src/solver.rs
1693 views
/// Convert our internal Verification IR to an external SMT AST and pass1/// queries to that solver.2///3/// This uses the easy-smt crate to interact with any solver.4///5use cranelift_isle as isle;6use isle::sema::{Pattern, Rule, TermEnv, TypeEnv};78use crate::solver::encoded_ops::popcnt::popcnt;9use crate::type_inference::RuleSemantics;10use crate::Config;11use easy_smt::{Response, SExpr};12use std::cmp::Ordering;13use std::collections::HashMap;14use veri_ir::{15BinaryOp, ConcreteTest, Counterexample, Expr, TermSignature, Terminal, Type, TypeContext,16UnaryOp, VerificationResult,17};1819mod encoded_ops;2021use encoded_ops::cls;22use encoded_ops::clz;23use encoded_ops::rev;24use encoded_ops::subs;2526use crate::MAX_WIDTH;2728pub struct SolverCtx {29smt: easy_smt::Context,30pub find_widths: bool,31tyctx: TypeContext,32pub bitwidth: usize,33var_map: HashMap<String, SExpr>,34width_vars: HashMap<u32, String>,35width_assumptions: Vec<SExpr>,36pub additional_decls: Vec<(String, SExpr)>,37pub additional_assumptions: Vec<SExpr>,38pub additional_assertions: Vec<SExpr>,39fresh_bits_idx: usize,40lhs_load_args: Option<Vec<SExpr>>,41rhs_load_args: Option<Vec<SExpr>>,42lhs_store_args: Option<Vec<SExpr>>,43rhs_store_args: Option<Vec<SExpr>>,44load_return: Option<SExpr>,45lhs_flag: bool,46}4748pub struct RuleCtx<'a> {49rule_sem: &'a RuleSemantics,50rule: &'a Rule,51termenv: &'a TermEnv,52typeenv: &'a TypeEnv,53config: &'a Config,54}5556impl SolverCtx {57pub fn new_fresh_bits(&mut self, width: usize) -> SExpr {58let name = format!("fresh{}", self.fresh_bits_idx);59self.fresh_bits_idx += 1;60self.additional_decls61.push((name.clone(), self.smt.bit_vec_sort(self.smt.numeral(width))));62self.smt.atom(name)63}6465fn new_fresh_int(&mut self) -> SExpr {66let name = format!("fresh{}", self.fresh_bits_idx);67self.fresh_bits_idx += 1;68self.additional_decls69.push((name.clone(), self.smt.int_sort()));70self.smt.atom(name)71}7273fn new_fresh_bool(&mut self) -> SExpr {74let name = format!("fresh{}", self.fresh_bits_idx);75self.fresh_bits_idx += 1;76self.additional_decls77.push((name.clone(), self.smt.bool_sort()));78self.smt.atom(name)79}8081fn declare(&mut self, name: String, typ: SExpr) -> SExpr {82let atom = self.smt.atom(&name);83self.additional_decls.push((name, typ));84atom85}8687fn assume(&mut self, expr: SExpr) {88self.additional_assumptions.push(expr);89}9091fn assert(&mut self, expr: SExpr) {92self.additional_assertions.push(expr);93}9495/// Construct a constant bit-vector value of the given width. (This is used so pervasively that96/// perhaps we should submit it for inclusion in the easy_smt library...)97fn bv(&self, value: i128, width: usize) -> SExpr {98if value < 0 {99return self100.smt101.list(vec![self.smt.atom("bvneg"), self.bv(-value, width)]);102}103self.smt.list(vec![104self.smt.atoms().und,105self.smt.atom(format!("bv{value}")),106self.smt.numeral(width),107])108}109110/// Convert an SMT integer to a bit vector of a given width.111fn int2bv(&self, width: usize, value: SExpr) -> SExpr {112self.smt.list(vec![113self.smt.list(vec![114self.smt.atoms().und,115self.smt.atom("int2bv"),116self.smt.numeral(width),117]),118value,119])120}121122/// Convert an SMT bit vector to a nat.123fn bv2nat(&self, value: SExpr) -> SExpr {124self.smt.list(vec![self.smt.atom("bv2nat"), value])125}126127/// Zero-extend an SMT bit vector to a wider bit vector by adding `padding` zeroes to the128/// front.129fn zero_extend(&self, padding: usize, value: SExpr) -> SExpr {130if padding == 0 {131return value;132}133self.smt.list(vec![134self.smt.list(vec![135self.smt.atoms().und,136self.smt.atom("zero_extend"),137self.smt.numeral(padding),138]),139value,140])141}142143/// Sign-extend an SMT bit vector to a wider bit vector by adding `padding` zeroes to the144/// front.145fn sign_extend(&self, padding: usize, value: SExpr) -> SExpr {146self.smt.list(vec![147self.smt.list(vec![148self.smt.atoms().und,149self.smt.atom("sign_extend"),150self.smt.numeral(padding),151]),152value,153])154}155156// Extend with concrete source and destination sizes. Includes extracting relevant bits.157fn extend_concrete(158&mut self,159dest_width: usize,160source: SExpr,161source_width: usize,162op: &str,163) -> SExpr {164if dest_width < source_width {165log::warn!(166"Unexpected extend widths for {}: dest {dest_width} source {source_width} ",167self.smt.display(source),168);169self.assert(self.smt.false_());170return self.bv(1710,172if self.find_widths {173self.bitwidth174} else {175dest_width176},177);178}179180let delta = dest_width - source_width;181if !self.find_widths {182return self.smt.list(vec![183self.smt.list(vec![184self.smt.atoms().und,185self.smt.atom(op),186self.smt.numeral(delta),187]),188source,189]);190}191192// Extract the relevant bits of the source (which is modeled with a wider,193// register-width bitvector).194let extract = self195.smt196.extract(source_width.wrapping_sub(1).try_into().unwrap(), 0, source);197198// Do the extend itself.199let extend = self.smt.list(vec![200self.smt.list(vec![201self.smt.atoms().und,202self.smt.atom(op),203self.smt.numeral(delta),204]),205extract,206]);207208// Pad the extended result back to the full register bitwidth. Use the bits209// that were already in the source register. That is, given:210// reg - source width source width211// | |212// SOURCE: [ don't care bits | care bits ]213//214// dest width215// |216// OUT: [ same don't care bits | defined extend | care bits ]217let mut unconstrained_bits = 0;218if dest_width < self.bitwidth {219unconstrained_bits = self220.bitwidth221.checked_sub(delta)222.unwrap()223.checked_sub(source_width)224.unwrap();225}226227// If we are extending to the full register width, no padding needed228if unconstrained_bits == 0 {229extend230} else {231let padding = self.smt.extract(232self.bitwidth.checked_sub(1).unwrap().try_into().unwrap(),233self.bitwidth234.checked_sub(unconstrained_bits)235.unwrap()236.try_into()237.unwrap(),238source,239);240self.smt.concat(padding, extend)241}242}243244// SMT-LIB only supports extends (zero or sign) by concrete amounts, but we245// need symbolic ones. This method essentially does if-conversion over possible246// concrete forms, outputting nested ITE blocks. We consider both the starting247// width and the destination width to be potentially symbolic.248// For safety, we add an assertion that some arm of this ITE must match.249fn extend_symbolic(250&mut self,251dest_width: SExpr,252source: SExpr,253source_width: SExpr,254op: &str,255) -> SExpr {256if self.find_widths {257return source;258}259// Symbolic expression for amount to shift260let shift = self.smt.sub(dest_width, source_width);261262let mut some_match = vec![];263let mut ite_str = source;264265// Special case: if we are asked to extend by 0, just return the source266let matching = self.smt.eq(self.smt.numeral(0), shift);267some_match.push(matching);268ite_str = self.smt.ite(matching, source, ite_str);269270// Possible amounts to extend by271for possible_delta in 1..self.bitwidth + 1 {272// Possible starting widths273for possible_source in 1..self.bitwidth + 1 {274// For now, ignore extends beyond the bitwidth. This is safe because275// we will fail the rule feasibility check if this is violated.276if possible_source + possible_delta > self.bitwidth {277continue;278}279280// Statement meaning the symbolic case matches this concrete case281let matching = self.smt.and(282self.smt.eq(self.smt.numeral(possible_delta), shift),283self.smt.eq(self.smt.numeral(possible_source), source_width),284);285some_match.push(matching);286let extend = self.extend_concrete(287possible_source + possible_delta,288source,289possible_source,290op,291);292ite_str = self.smt.ite(matching, extend, ite_str);293}294}295let some_shift_matches = self.smt.or_many(some_match);296self.width_assumptions.push(some_shift_matches);297ite_str298}299300fn encode_rotate(&self, op: &str, source: SExpr, amount: SExpr, width: usize) -> SExpr {301// SMT bitvector rotate_left requires that the rotate amount be302// statically specified. Instead, to use a dynamic amount, desugar303// to shifts and bit arithmetic.304let width_as_bv = self.bv(width.try_into().unwrap(), width);305let wrapped_amount = self.smt.bvurem(amount, width_as_bv);306let wrapped_delta = self.smt.bvsub(width_as_bv, wrapped_amount);307match op {308"rotate_left" => self.smt.bvor(309self.smt.bvshl(source, wrapped_amount),310self.smt.bvlshr(source, wrapped_delta),311),312"rotate_right" => self.smt.bvor(313self.smt.bvshl(source, wrapped_delta),314self.smt.bvlshr(source, wrapped_amount),315),316_ => unreachable!(),317}318}319320// SMT bitvector rotate requires that the rotate amount be321// statically specified. Instead, to use a dynamic amount, desugar322// to shifts and bit arithmetic.323fn rotate_symbolic(324&mut self,325source: SExpr,326source_width: usize,327amount: SExpr,328op: &str,329) -> SExpr {330if self.find_widths {331return source;332}333let (s, a) = if self.find_widths {334// Extract the relevant bits of the source (which is modeled with a wider,335// register-width bitvector).336let extract_source = self.smt.extract(337source_width.checked_sub(1).unwrap().try_into().unwrap(),3380,339source,340);341342let extract_amount = self.smt.extract(343source_width.checked_sub(1).unwrap().try_into().unwrap(),3440,345amount,346);347(extract_source, extract_amount)348} else {349(source, amount)350};351352// Do the rotate itself.353let rotate = self.encode_rotate(op, s, a, source_width);354355// Pad the extended result back to the full register bitwidth. Use the bits356// that were already in the source register. That is, given:357// reg - source width source width358// | |359// SOURCE: [ don't care bits | care bits ]360//361// dest width362// |363// OUT: [ same don't care bits | care bits ]364let unconstrained_bits = self.bitwidth.checked_sub(source_width).unwrap();365366// If we are extending to the full register width, no padding needed367if unconstrained_bits == 0 || !self.find_widths {368rotate369} else {370let padding = self.smt.extract(371self.bitwidth.checked_sub(1).unwrap().try_into().unwrap(),372self.bitwidth373.checked_sub(unconstrained_bits)374.unwrap()375.try_into()376.unwrap(),377source,378);379self.smt.concat(padding, rotate)380}381}382383// SMT-LIB only supports rotates by concrete amounts, but we384// need symbolic ones. This method essentially does if-conversion over possible385// concrete forms, outputting nested ITE blocks. We consider both the starting386// width and the rotate amount to be potentially symbolic.387// For safety, we add an assertion that some arm of this ITE must match.388fn rotate_symbolic_dyn_source_width(389&mut self,390source: SExpr,391source_width: SExpr,392amount: SExpr,393op: &str,394) -> SExpr {395if self.find_widths {396return source;397}398let mut some_match = vec![];399let mut ite_str = source;400401// Special case: if we are asked to rotate by 0, just return the source402let matching = self.smt.eq(self.bv(0, self.bitwidth), amount);403some_match.push(matching);404ite_str = self.smt.ite(matching, source, ite_str);405406// Possible starting widths407for possible_source in [8usize, 16, 32, 64] {408// Statement meaning the symbolic case matches this concrete case409let matching = self.smt.eq(self.smt.numeral(possible_source), source_width);410some_match.push(matching);411412// Extract the relevant bits of the source (which is modeled with a wider,413// register-width bitvector).414let extract_source = self.smt.extract(415possible_source.checked_sub(1).unwrap().try_into().unwrap(),4160,417source,418);419let extract_amount = self.smt.extract(420possible_source.checked_sub(1).unwrap().try_into().unwrap(),4210,422amount,423);424425// SMT bitvector rotate_left requires that the rotate amount be426// statically specified. Instead, to use a dynamic amount, desugar427// to shifts and bit arithmetic.428let rotate = self.encode_rotate(op, extract_source, extract_amount, possible_source);429430// Pad the extended result back to the full register bitwidth. Use the bits431// that were already in the source register. That is, given:432// reg - source width source width433// | |434// SOURCE: [ don't care bits | care bits ]435//436// dest width437// |438// OUT: [ same don't care bits | care bits ]439let unconstrained_bits = self.bitwidth.checked_sub(possible_source).unwrap();440441// If we are extending to the full register width, no padding needed442let rotate = if unconstrained_bits == 0 {443rotate444} else {445let padding = self.smt.extract(446self.bitwidth.checked_sub(1).unwrap().try_into().unwrap(),447self.bitwidth448.checked_sub(unconstrained_bits)449.unwrap()450.try_into()451.unwrap(),452source,453);454self.smt.concat(padding, rotate)455};456457ite_str = self.smt.ite(matching, rotate, ite_str);458}459let some_shift_matches = self.smt.or_many(some_match);460self.width_assumptions.push(some_shift_matches);461ite_str462}463464pub fn widen_to_register_width(465&mut self,466tyvar: u32,467narrow_width: usize,468narrow_decl: SExpr,469name: Option<String>,470) -> SExpr {471let width = self.bitwidth.checked_sub(narrow_width).unwrap();472if width > 0 {473let mut narrow_name = format!("narrow__{tyvar}");474let mut wide_name = format!("wide__{tyvar}");475if let Some(s) = name {476narrow_name = format!("{s}_{narrow_name}");477wide_name = format!("{s}_{wide_name}");478}479self.assume(self.smt.eq(self.smt.atom(&narrow_name), narrow_decl));480self.additional_decls.push((481narrow_name.clone(),482self.smt.bit_vec_sort(self.smt.numeral(narrow_width)),483));484self.additional_decls.push((485wide_name.clone(),486self.smt.bit_vec_sort(self.smt.numeral(self.bitwidth)),487));488let padding = self.new_fresh_bits(width);489self.assume(self.smt.eq(490self.smt.atom(&wide_name),491self.smt.concat(padding, self.smt.atom(narrow_name)),492));493self.smt.atom(wide_name)494} else if let Some(s) = name {495self.assume(self.smt.eq(self.smt.atom(&s), narrow_decl));496self.smt.atom(&s)497} else {498narrow_decl499}500}501502pub fn get_expr_width_var(&self, e: &Expr) -> Option<SExpr> {503if let Some(tyvar) = self.tyctx.tyvars.get(e) {504self.width_vars.get(tyvar).map(|s| self.smt.atom(s))505} else {506None507}508}509510pub fn vir_to_smt_ty(&self, ty: &Type) -> SExpr {511match ty {512Type::BitVector(w) => {513let width = w.unwrap_or(self.bitwidth);514self.smt.bit_vec_sort(self.smt.numeral(width))515}516Type::Int => self.smt.int_sort(),517Type::Bool | Type::Unit => self.smt.bool_sort(),518}519}520521pub fn get_type(&self, x: &Expr) -> Option<&Type> {522self.tyctx.tymap.get(self.tyctx.tyvars.get(x)?)523}524525pub fn get_expr_value(&self, e: &Expr) -> Option<i128> {526if let Some(tyvar) = self.tyctx.tyvars.get(e) {527self.tyctx.tyvals.get(tyvar).copied()528} else {529None530}531}532533pub fn static_width(&self, x: &Expr) -> Option<usize> {534match self.get_type(x) {535Some(Type::BitVector(w)) => *w,536_ => None,537}538}539540pub fn assume_same_width(&mut self, x: &Expr, y: &Expr) {541let xw = self.get_expr_width_var(x).unwrap();542let yw = self.get_expr_width_var(y).unwrap();543self.width_assumptions.push(self.smt.eq(xw, yw));544}545546pub fn assume_same_width_from_sexpr(&mut self, x: SExpr, y: &Expr) {547let yw = self.get_expr_width_var(y).unwrap();548self.width_assumptions.push(self.smt.eq(x, yw));549}550551pub fn assume_comparable_types(&mut self, x: &Expr, y: &Expr) {552match (self.get_type(x), self.get_type(y)) {553(None, _) | (_, None) => panic!("Missing type(s) {x:?} {y:?}"),554(Some(Type::Bool), Some(Type::Bool))555| (Some(Type::Int), Some(Type::Int))556| (Some(Type::Unit), Some(Type::Unit)) => (),557(Some(Type::BitVector(Some(xw))), Some(Type::BitVector(Some(yw)))) => {558assert_eq!(xw, yw, "incompatible {x:?} {y:?}")559}560(_, _) => self.assume_same_width(x, y),561}562}563564pub fn vir_expr_to_sexp(&mut self, e: Expr) -> SExpr {565let tyvar = self.tyctx.tyvars.get(&e);566let ty = self.get_type(&e);567let width = self.get_expr_width_var(&e);568let static_expr_width = self.static_width(&e);569match e {570Expr::Terminal(t) => match t {571Terminal::Literal(v, tyvar) => {572let lit = self.smt.atom(v);573if self.find_widths && matches!(ty.unwrap(), Type::BitVector(_)) {574self.widen_to_register_width(tyvar, static_expr_width.unwrap(), lit, None)575} else {576lit577}578}579Terminal::Var(v) => match self.var_map.get(&v) {580Some(o) => *o,581None => self.smt.atom(v),582},583Terminal::Const(i, _) => match ty.unwrap() {584Type::BitVector(w) => {585let width = w.unwrap_or(self.bitwidth);586let narrow_decl = self.bv(i, width);587if self.find_widths {588self.zero_extend(self.bitwidth - width, narrow_decl)589} else {590narrow_decl591}592}593Type::Int => self.smt.numeral(i),594Type::Bool => {595if i == 0 {596self.smt.false_()597} else {598self.smt.true_()599}600}601Type::Unit => self.smt.true_(),602},603Terminal::True => self.smt.true_(),604Terminal::False => self.smt.false_(),605Terminal::Wildcard(_) => match ty.unwrap() {606Type::BitVector(Some(w)) if !self.find_widths => self.new_fresh_bits(*w),607Type::BitVector(_) => self.new_fresh_bits(self.bitwidth),608Type::Int => self.new_fresh_int(),609Type::Bool => self.new_fresh_bool(),610Type::Unit => self.smt.true_(),611},612},613Expr::Unary(op, arg) => {614let op = match op {615UnaryOp::Not => "not",616UnaryOp::BVNeg => {617if self.find_widths {618self.assume_same_width_from_sexpr(width.unwrap(), &arg);619}620"bvneg"621}622UnaryOp::BVNot => {623if self.find_widths {624self.assume_same_width_from_sexpr(width.unwrap(), &arg);625}626"bvnot"627}628};629let subexp = self.vir_expr_to_sexp(*arg);630self.smt.list(vec![self.smt.atom(op), subexp])631}632Expr::Binary(op, x, y) => {633if self.find_widths {634match op {635BinaryOp::BVMul636| BinaryOp::BVUDiv637| BinaryOp::BVSDiv638| BinaryOp::BVUrem639| BinaryOp::BVSrem640| BinaryOp::BVAdd641| BinaryOp::BVSub642| BinaryOp::BVAnd643| BinaryOp::BVOr644| BinaryOp::BVShl645| BinaryOp::BVShr646| BinaryOp::BVAShr647| BinaryOp::BVRotl648| BinaryOp::BVRotr => self.assume_same_width_from_sexpr(width.unwrap(), &x),649BinaryOp::Eq => {650if let Some(Type::BitVector(_)) = self.get_type(&x) {651self.assume_comparable_types(&x, &y)652}653}654_ => (),655};656self.assume_comparable_types(&x, &y);657}658match op {659BinaryOp::BVRotl => {660let source_width = self.static_width(&x);661match source_width {662Some(w) => {663let xs = self.vir_expr_to_sexp(*x);664let ys = self.vir_expr_to_sexp(*y);665return self.rotate_symbolic(xs, w, ys, "rotate_left");666}667None => {668let arg_width = self.get_expr_width_var(&x).unwrap();669let xs = self.vir_expr_to_sexp(*x);670let ys = self.vir_expr_to_sexp(*y);671return self.rotate_symbolic_dyn_source_width(672xs,673arg_width,674ys,675"rotate_left",676);677}678}679}680BinaryOp::BVRotr => {681let source_width = self.static_width(&x);682match source_width {683Some(w) => {684let xs = self.vir_expr_to_sexp(*x);685let ys = self.vir_expr_to_sexp(*y);686return self.rotate_symbolic(xs, w, ys, "rotate_right");687}688None => {689let arg_width = self.get_expr_width_var(&x).unwrap();690let xs = self.vir_expr_to_sexp(*x);691let ys = self.vir_expr_to_sexp(*y);692return self.rotate_symbolic_dyn_source_width(693xs,694arg_width,695ys,696"rotate_right",697);698}699}700}701// To shift right, we need to make sure the bits to the right get zeroed. Shift left first.702BinaryOp::BVShr => {703let arg_width = if self.find_widths {704self.get_expr_width_var(&x).unwrap()705} else {706self.smt.numeral(self.static_width(&x).unwrap())707};708let xs = self.vir_expr_to_sexp(*x);709710// Strategy: shift left by (bitwidth - arg width) to zero bits to the right711// of the bits in the argument size. Then shift right by (amt + (bitwidth - arg width))712713// Width math714if self.find_widths {715// The shift arg needs to be extracted to the right width, default to 8 if unknown716let y_static_width = self.static_width(&y).unwrap_or(8);717let y_rec = self.vir_expr_to_sexp(*y);718if self.find_widths {719return xs;720}721let extract = self.smt.extract(722y_static_width.checked_sub(1).unwrap().try_into().unwrap(),7230,724y_rec,725);726let ys = self.zero_extend(self.bitwidth - y_static_width, extract);727let arg_width_as_bv = self.int2bv(self.bitwidth, arg_width);728let bitwidth_as_bv =729self.bv(self.bitwidth.try_into().unwrap(), self.bitwidth);730let extra_shift = self.smt.bvsub(bitwidth_as_bv, arg_width_as_bv);731let shl_to_zero = self.smt.bvshl(xs, extra_shift);732733let amt_plus_extra = self.smt.bvadd(ys, extra_shift);734return self.smt.bvlshr(shl_to_zero, amt_plus_extra);735} else {736let ys = self.vir_expr_to_sexp(*y);737return self.smt.bvlshr(xs, ys);738}739}740BinaryOp::BVAShr => {741let arg_width = if self.find_widths {742self.get_expr_width_var(&x).unwrap()743} else {744self.smt.numeral(self.static_width(&x).unwrap())745};746let xs = self.vir_expr_to_sexp(*x);747748// Strategy: shift left by (bitwidth - arg width) to eliminate bits to the left749// of the bits in the argument size. Then shift right by (amt + (bitwidth - arg width))750751// Width math752if self.find_widths {753// The shift arg needs to be extracted to the right width, default to 8 if unknown754let y_static_width = self.static_width(&y).unwrap_or(8);755let ys = self.vir_expr_to_sexp(*y);756let extract = self.smt.extract(757y_static_width.checked_sub(1).unwrap().try_into().unwrap(),7580,759ys,760);761let ysext = self.zero_extend(self.bitwidth - y_static_width, extract);762763let arg_width_as_bv = self.int2bv(self.bitwidth, arg_width);764let bitwidth_as_bv =765self.bv(self.bitwidth.try_into().unwrap(), self.bitwidth);766let extra_shift = self.smt.bvsub(bitwidth_as_bv, arg_width_as_bv);767let shl_to_zero = self.smt.bvshl(xs, extra_shift);768769let amt_plus_extra = self.smt.bvadd(ysext, extra_shift);770return self.smt.bvashr(shl_to_zero, amt_plus_extra);771} else {772let ys = self.vir_expr_to_sexp(*y);773return self.smt.bvashr(xs, ys);774}775}776_ => (),777};778let op_str = match op {779BinaryOp::And => "and",780BinaryOp::Or => "or",781BinaryOp::Imp => "=>",782BinaryOp::Eq => "=",783BinaryOp::Lte => match (self.get_type(&x), self.get_type(&y)) {784(Some(Type::Int), Some(Type::Int)) => "<=",785(Some(Type::BitVector(_)), Some(Type::BitVector(_))) => "bvule",786_ => unreachable!(),787},788BinaryOp::Lt => match (self.get_type(&x), self.get_type(&y)) {789(Some(Type::Int), Some(Type::Int)) => "<",790(Some(Type::BitVector(_)), Some(Type::BitVector(_))) => "bvult",791_ => unreachable!(),792},793BinaryOp::BVSgt => "bvsgt",794BinaryOp::BVSgte => "bvsge",795BinaryOp::BVSlt => "bvslt",796BinaryOp::BVSlte => "bvsle",797BinaryOp::BVUgt => "bvugt",798BinaryOp::BVUgte => "bvuge",799BinaryOp::BVUlt => "bvult",800BinaryOp::BVUlte => "bvule",801BinaryOp::BVMul => "bvmul",802BinaryOp::BVUDiv => "bvudiv",803BinaryOp::BVSDiv => "bvsdiv",804BinaryOp::BVAdd => "bvadd",805BinaryOp::BVSub => "bvsub",806BinaryOp::BVUrem => "bvurem",807BinaryOp::BVSrem => "bvsrem",808BinaryOp::BVAnd => "bvand",809BinaryOp::BVOr => "bvor",810BinaryOp::BVXor => "bvxor",811BinaryOp::BVShl => "bvshl",812BinaryOp::BVSaddo => "bvsaddo",813_ => unreachable!("{:?}", op),814};815// If we have some static width that isn't the bitwidth, extract based on it816// before performing the operation for the dynamic case.817match static_expr_width {818Some(w) if w < self.bitwidth && self.find_widths => {819let h: i32 = (w - 1).try_into().unwrap();820let x_sexp = self.vir_expr_to_sexp(*x);821let y_sexp = self.vir_expr_to_sexp(*y);822self.zero_extend(823self.bitwidth.checked_sub(w).unwrap(),824self.smt.list(vec![825self.smt.atom(op_str),826self.smt.extract(h, 0, x_sexp),827self.smt.extract(h, 0, y_sexp),828]),829)830}831_ => {832let x_sexp = self.vir_expr_to_sexp(*x);833let y_sexp = self.vir_expr_to_sexp(*y);834self.smt.list(vec![self.smt.atom(op_str), x_sexp, y_sexp])835}836}837}838Expr::BVIntToBV(w, x) => {839let x_sexp = self.vir_expr_to_sexp(*x);840if self.find_widths {841let padded_width = self.bitwidth - w;842self.zero_extend(padded_width, self.int2bv(w, x_sexp))843} else {844self.int2bv(w, x_sexp)845}846}847Expr::BVToInt(x) => {848let x_sexp = self.vir_expr_to_sexp(*x);849self.bv2nat(x_sexp)850}851Expr::BVZeroExtTo(i, x) => {852let arg_width = if self.find_widths {853let expr_width = width.unwrap();854self.width_assumptions855.push(self.smt.eq(expr_width, self.smt.numeral(i)));856self.get_expr_width_var(&x).unwrap()857} else {858self.smt.numeral(self.static_width(&x).unwrap())859};860let static_width = self.static_width(&x);861let xs = self.vir_expr_to_sexp(*x);862if let Some(size) = static_width {863self.extend_concrete(i, xs, size, "zero_extend")864} else {865self.extend_symbolic(self.smt.numeral(i), xs, arg_width, "zero_extend")866}867}868Expr::BVZeroExtToVarWidth(i, x) => {869let static_arg_width = self.static_width(&x);870let arg_width = self.get_expr_width_var(&x);871let is = self.vir_expr_to_sexp(*i);872let xs = self.vir_expr_to_sexp(*x);873if self.find_widths {874let expr_width = width.unwrap();875self.width_assumptions.push(self.smt.eq(expr_width, is));876}877if let (Some(arg_size), Some(e_size)) = (static_arg_width, static_expr_width) {878self.extend_concrete(e_size, xs, arg_size, "zero_extend")879} else {880self.extend_symbolic(is, xs, arg_width.unwrap(), "zero_extend")881}882}883Expr::BVSignExtTo(i, x) => {884let arg_width = if self.find_widths {885let expr_width = width.unwrap();886self.width_assumptions887.push(self.smt.eq(expr_width, self.smt.numeral(i)));888self.get_expr_width_var(&x).unwrap()889} else {890self.smt.numeral(self.static_width(&x).unwrap())891};892let static_width = self.static_width(&x);893let xs = self.vir_expr_to_sexp(*x);894if let Some(size) = static_width {895self.extend_concrete(i, xs, size, "sign_extend")896} else {897self.extend_symbolic(self.smt.numeral(i), xs, arg_width, "sign_extend")898}899}900Expr::BVSignExtToVarWidth(i, x) => {901let static_arg_width = self.static_width(&x);902let arg_width = self.get_expr_width_var(&x);903let is = self.vir_expr_to_sexp(*i);904let xs = self.vir_expr_to_sexp(*x);905if self.find_widths {906let expr_width = width.unwrap();907self.width_assumptions.push(self.smt.eq(expr_width, is));908}909if let (Some(arg_size), Some(e_size)) = (static_arg_width, static_expr_width) {910self.extend_concrete(e_size, xs, arg_size, "sign_extend")911} else {912self.extend_symbolic(is, xs, arg_width.unwrap(), "sign_extend")913}914}915Expr::BVConvTo(x, y) => {916if self.find_widths {917let expr_width = width.unwrap();918let dyn_width = self.vir_expr_to_sexp(*x);919let eq = self.smt.eq(expr_width, dyn_width);920self.width_assumptions.push(eq);921self.vir_expr_to_sexp(*y)922} else {923let arg_width = self.static_width(&y).unwrap();924match ty {925Some(Type::BitVector(Some(w))) => match arg_width.cmp(w) {926Ordering::Less => {927let padding =928self.new_fresh_bits(w.checked_sub(arg_width).unwrap());929let ys = self.vir_expr_to_sexp(*y);930self.smt.concat(padding, ys)931}932Ordering::Greater => {933let new = (w - 1).try_into().unwrap();934let ys = self.vir_expr_to_sexp(*y);935self.smt.extract(new, 0, ys)936}937Ordering::Equal => self.vir_expr_to_sexp(*y),938},939_ => unreachable!("{:?}, {:?}", x, y),940}941}942}943Expr::WidthOf(x) => {944if self.find_widths {945self.get_expr_width_var(&x).unwrap()946} else {947self.smt.numeral(self.static_width(&x).unwrap())948}949}950Expr::BVExtract(i, j, x) => {951assert!(i >= j);952if self.get_type(&x).is_some() {953let xs = self.vir_expr_to_sexp(*x);954// No-op if we are extracting exactly the full bitwidth955if j == 0 && i == self.bitwidth - 1 && self.find_widths {956return xs;957}958let extract =959self.smt960.extract(i.try_into().unwrap(), j.try_into().unwrap(), xs);961let new_width = i - j + 1;962if new_width < self.bitwidth && self.find_widths {963let padding =964self.new_fresh_bits(self.bitwidth.checked_sub(new_width).unwrap());965self.smt.concat(padding, extract)966} else {967extract968}969} else {970unreachable!("Must perform extraction on bv with known width")971}972}973Expr::Conditional(c, t, e) => {974if self.find_widths && matches!(ty, Some(Type::BitVector(_))) {975self.assume_same_width_from_sexpr(width.unwrap(), &t);976self.assume_same_width_from_sexpr(width.unwrap(), &e);977}978let cs = self.vir_expr_to_sexp(*c);979let ts = self.vir_expr_to_sexp(*t);980let es = self.vir_expr_to_sexp(*e);981self.smt.ite(cs, ts, es)982}983Expr::Switch(c, cases) => {984if self.find_widths {985if matches!(ty, Some(Type::BitVector(_))) {986for (_, b) in &cases {987self.assume_same_width_from_sexpr(width.unwrap(), b);988}989}990let cty = self.get_type(&c);991if matches!(cty, Some(Type::BitVector(_))) {992let cwidth = self.get_expr_width_var(&c);993for (m, _) in &cases {994self.assume_same_width_from_sexpr(cwidth.unwrap(), m);995}996}997}998let cs = self.vir_expr_to_sexp(*c);999let mut case_sexprs: Vec<(SExpr, SExpr)> = cases1000.iter()1001.map(|(m, b)| {1002(1003self.vir_expr_to_sexp(m.clone()),1004self.vir_expr_to_sexp(b.clone()),1005)1006})1007.collect();10081009// Assert that some case must match1010let some_case_matches: Vec<SExpr> = case_sexprs1011.iter()1012.map(|(m, _)| self.smt.eq(cs, *m))1013.collect();1014self.assert(self.smt.or_many(some_case_matches.clone()));10151016let (_, last_body) = case_sexprs.remove(case_sexprs.len() - 1);10171018// Reverse to keep the order of the switch1019case_sexprs.iter().rev().fold(last_body, |acc, (m, b)| {1020self.smt.ite(self.smt.eq(cs, *m), *b, acc)1021})1022}1023Expr::CLZ(e) => {1024let tyvar = *tyvar.unwrap();1025if self.find_widths {1026self.assume_same_width_from_sexpr(width.unwrap(), &e);1027}1028let es = self.vir_expr_to_sexp(*e);1029match static_expr_width {1030Some(1) => clz::clz1(self, es, tyvar),1031Some(8) => clz::clz8(self, es, tyvar),1032Some(16) => clz::clz16(self, es, tyvar),1033Some(32) => clz::clz32(self, es, tyvar),1034Some(64) => clz::clz64(self, es, tyvar),1035Some(w) => unreachable!("Unexpected CLZ width {}", w),1036None => unreachable!("Need static CLZ width"),1037}1038}1039Expr::CLS(e) => {1040let tyvar = *tyvar.unwrap();1041if self.find_widths {1042self.assume_same_width_from_sexpr(width.unwrap(), &e);1043}1044let es = self.vir_expr_to_sexp(*e);1045match static_expr_width {1046Some(1) => cls::cls1(self, tyvar),1047Some(8) => cls::cls8(self, es, tyvar),1048Some(16) => cls::cls16(self, es, tyvar),1049Some(32) => cls::cls32(self, es, tyvar),1050Some(64) => cls::cls64(self, es, tyvar),1051Some(w) => unreachable!("Unexpected CLS width {}", w),1052None => unreachable!("Need static CLS width"),1053}1054}1055Expr::Rev(e) => {1056let tyvar = *tyvar.unwrap();1057if self.find_widths {1058self.assume_same_width_from_sexpr(width.unwrap(), &e);1059}1060let es = self.vir_expr_to_sexp(*e);1061match static_expr_width {1062Some(1) => rev::rev1(self, es, tyvar),1063Some(8) => rev::rev8(self, es, tyvar),1064Some(16) => rev::rev16(self, es, tyvar),1065Some(32) => rev::rev32(self, es, tyvar),1066Some(64) => rev::rev64(self, es, tyvar),1067Some(w) => unreachable!("Unexpected CLS width {}", w),1068None => unreachable!("Need static CLS width"),1069}1070}1071Expr::BVSubs(ty, x, y) => {1072let tyvar = *tyvar.unwrap();1073if self.find_widths {1074self.assume_comparable_types(&x, &y);1075}1076let ety = self.vir_expr_to_sexp(*ty);1077let ex = self.vir_expr_to_sexp(*x);1078let ey = self.vir_expr_to_sexp(*y);10791080let encoded_32 = subs::subs(self, 32, ex, ey, tyvar);1081let encoded_64 = subs::subs(self, 64, ex, ey, tyvar);10821083self.smt.ite(1084self.smt.eq(ety, self.smt.numeral(32)),1085encoded_32,1086encoded_64,1087)1088}1089Expr::BVPopcnt(x) => {1090let tyvar = *tyvar.unwrap();1091if self.find_widths {1092self.assume_same_width_from_sexpr(width.unwrap(), &x);1093}1094let ex = self.vir_expr_to_sexp(*x);10951096match static_expr_width {1097Some(8) => {1098let p = popcnt(self, 8, ex, tyvar);1099if self.find_widths {1100self.zero_extend(self.bitwidth - 8, p)1101} else {1102p1103}1104}1105Some(16) => {1106let p = popcnt(self, 16, ex, tyvar);1107if self.find_widths {1108self.zero_extend(self.bitwidth - 8, p)1109} else {1110self.zero_extend(8, p)1111}1112}1113Some(32) => {1114let p = popcnt(self, 32, ex, tyvar);1115if self.find_widths {1116self.zero_extend(self.bitwidth - 8, p)1117} else {1118self.zero_extend(24, p)1119}1120}1121Some(64) => {1122let p = popcnt(self, 64, ex, tyvar);1123if self.find_widths {1124self.zero_extend(self.bitwidth - 8, p)1125} else {1126self.zero_extend(56, p)1127}1128}1129Some(w) => unreachable!("Unexpected popcnt width {}", w),1130None => unreachable!("Need static popcnt width"),1131}1132}1133Expr::BVConcat(xs) => {1134if self.find_widths {1135let widths: Vec<SExpr> = xs1136.iter()1137.map(|x| self.get_expr_width_var(x).unwrap())1138.collect();1139let sum = self.smt.plus_many(widths);1140self.width_assumptions1141.push(self.smt.eq(width.unwrap(), sum));1142}1143let mut sexprs: Vec<SExpr> = xs1144.iter()1145.map(|x| self.vir_expr_to_sexp(x.clone()))1146.collect();1147let last = sexprs.remove(sexprs.len() - 1);11481149// Width hack for now1150if self.find_widths {1151return sexprs[0];1152}1153// Reverse to keep the order of the cases1154sexprs1155.iter()1156.rev()1157.fold(last, |acc, x| self.smt.concat(*x, acc))1158}1159Expr::LoadEffect(x, y, z) => {1160let ex = self.vir_expr_to_sexp(*x);1161let ey = self.vir_expr_to_sexp(*y);1162let ez = self.vir_expr_to_sexp(*z);11631164if self.find_widths {1165self.width_assumptions.push(self.smt.eq(width.unwrap(), ey));1166}11671168if self.lhs_flag {1169if self.lhs_load_args.is_some() {1170panic!("Only one load on the LHS currently supported, found multiple.")1171}1172self.lhs_load_args = Some(vec![ex, ey, ez]);1173let load_ret = if self.find_widths {1174self.new_fresh_bits(self.bitwidth)1175} else {1176self.new_fresh_bits(static_expr_width.unwrap())1177};1178self.load_return = Some(load_ret);1179load_ret1180} else {1181if self.rhs_load_args.is_some() {1182panic!("Only one load on the RHS currently supported, found miltiple.")1183}1184self.rhs_load_args = Some(vec![ex, ey, ez]);1185self.load_return.unwrap()1186}1187}1188Expr::StoreEffect(w, x, y, z) => {1189let ew = self.vir_expr_to_sexp(*w);1190let ex = self.vir_expr_to_sexp(*x);1191let ez = self.vir_expr_to_sexp(*z);11921193if self.find_widths {1194let y_width = self.get_expr_width_var(&y).unwrap();1195self.width_assumptions.push(self.smt.eq(y_width, ex));1196}1197let ey = self.vir_expr_to_sexp(*y);11981199if self.lhs_flag {1200self.lhs_store_args = Some(vec![ew, ex, ey, ez]);1201} else {1202self.rhs_store_args = Some(vec![ew, ex, ey, ez]);1203}1204self.smt.atom("true")1205}1206}1207}12081209// Checks whether the assumption list is always false1210fn check_assumptions_feasibility(1211&mut self,1212assumptions: &[SExpr],1213term_input_bs: &[String],1214config: &Config,1215) -> VerificationResult {1216log::debug!("Checking assumption feasibility");1217self.smt.push().unwrap();1218for (i, a) in assumptions.iter().enumerate() {1219self.smt1220.assert(self.smt.named(format!("assum{i}"), *a))1221.unwrap();1222}12231224let res = match self.smt.check() {1225Ok(Response::Sat) => {1226if !config.distinct_check || term_input_bs.is_empty() {1227log::debug!("Assertion list is feasible for at least one input!");1228self.smt.pop().unwrap();1229return VerificationResult::Success;1230}1231// Check that there is a model with distinct bitvector inputs1232let mut not_all_same = vec![];1233let atoms: Vec<SExpr> = term_input_bs.iter().map(|n| self.smt.atom(n)).collect();1234let solution = self.smt.get_value(atoms).unwrap();1235assert_eq!(term_input_bs.len(), solution.len());1236for (variable, value) in solution {1237not_all_same.push(self.smt.not(self.smt.eq(variable, value)));1238}1239match not_all_same.len().cmp(&1) {1240Ordering::Equal => self.smt.assert(not_all_same[0]).unwrap(),1241Ordering::Greater => self.smt.assert(self.smt.and_many(not_all_same)).unwrap(),1242Ordering::Less => unreachable!("must have some BV inputs"),1243}1244match self.smt.check() {1245Ok(Response::Sat) => {1246log::debug!("Assertion list is feasible for two distinct inputs");1247VerificationResult::Success1248}1249Ok(Response::Unsat) => {1250log::debug!(1251"Assertion list is only feasible for one input with distinct BV values!"1252);1253VerificationResult::NoDistinctModels1254}1255Ok(Response::Unknown) => {1256panic!("Solver said 'unk'");1257}1258Err(err) => {1259unreachable!("Error! {:?}", err);1260}1261}1262}1263Ok(Response::Unsat) => {1264log::debug!("Assertion list is infeasible!");1265let unsat = self.smt.get_unsat_core().unwrap();1266log::debug!("Unsat core:\n{}", self.smt.display(unsat));1267VerificationResult::InapplicableRule1268}1269Ok(Response::Unknown) => {1270panic!("Solver said 'unk'");1271}1272Err(err) => {1273unreachable!("Error! {:?}", err);1274}1275};1276self.smt.pop().unwrap();1277res1278}12791280fn display_hex_to_bin(&self, value: SExpr) -> String {1281let sexpr_hex_prefix = "#x";1282let val_str = self.smt.display(value).to_string();1283if val_str.starts_with(sexpr_hex_prefix) {1284let without_prefix = val_str.trim_start_matches("#x");1285let as_unsigned = u128::from_str_radix(without_prefix, 16).unwrap();1286// SMT-LIB: bvhexX where X is a hexadecimal numeral of length m defines the bitvector1287// constant with value X and size 4*m.1288match without_prefix.len() {12892 => format!("{}|{:#010b}", self.smt.display(value), as_unsigned),12903 => format!("{}|{:#014b}", self.smt.display(value), as_unsigned),12914 => format!("{}|{:#018b}", self.smt.display(value), as_unsigned),12928 => format!("{}|{:#034b}", self.smt.display(value), as_unsigned),129316 => format!("{}|{:#068b}", self.smt.display(value), as_unsigned),129417 => format!("{}|{:#070b}", self.smt.display(value), as_unsigned),129532 => format!("{}|{:#0130b}", self.smt.display(value), as_unsigned),1296_ => {1297format!("{}|{:#b}", self.smt.display(value), as_unsigned)1298}1299}1300} else {1301val_str1302}1303}13041305fn display_value(&self, variable: SExpr, value: SExpr) -> (String, String) {1306let var_str = self.smt.display(variable).to_string();1307(var_str, self.display_hex_to_bin(value))1308}13091310fn display_isle_pattern(1311&mut self,1312termenv: &TermEnv,1313typeenv: &TypeEnv,1314vars: &Vec<(String, String)>,1315rule: &Rule,1316pat: &Pattern,1317) -> SExpr {1318let mut to_sexpr = |p| self.display_isle_pattern(termenv, typeenv, vars, rule, p);13191320match pat {1321isle::sema::Pattern::Term(_, term_id, args) => {1322let sym = termenv.terms[term_id.index()].name;1323let name = typeenv.syms[sym.index()].clone();13241325let mut sexprs = args.iter().map(&mut to_sexpr).collect::<Vec<SExpr>>();13261327sexprs.insert(0, self.smt.atom(name));1328self.smt.list(sexprs)1329}1330isle::sema::Pattern::Var(_, var_id) => {1331let sym = rule.vars[var_id.index()].name;1332let ident = typeenv.syms[sym.index()].clone();1333let smt_ident_prefix = format!("{}__clif{}__", ident, var_id.index());13341335let var = self.display_var_from_smt_prefix(vars, &ident, &smt_ident_prefix);1336self.smt.atom(var)1337}1338isle::sema::Pattern::BindPattern(_, var_id, subpat) => {1339let sym = rule.vars[var_id.index()].name;1340let ident = &typeenv.syms[sym.index()];1341let smt_ident_prefix = format!("{}__clif{}__", ident, var_id.index(),);1342let subpat_node = to_sexpr(subpat);13431344let var = self.display_var_from_smt_prefix(vars, ident, &smt_ident_prefix);13451346// Special case: elide bind patterns to wildcars1347if matches!(**subpat, isle::sema::Pattern::Wildcard(_)) {1348self.smt.atom(var)1349} else {1350self.smt1351.list(vec![self.smt.atom(var), self.smt.atom("@"), subpat_node])1352}1353}1354isle::sema::Pattern::Wildcard(_) => self.smt.list(vec![self.smt.atom("_")]),13551356isle::sema::Pattern::ConstPrim(_, sym) => {1357let name = typeenv.syms[sym.index()].clone();1358self.smt.list(vec![self.smt.atom(name)])1359}1360isle::sema::Pattern::ConstBool(_, val) => {1361self.smt.list(vec![self.smt.atom(format!("{val}"))])1362}1363isle::sema::Pattern::ConstInt(_, num) => {1364let _smt_name_prefix = format!("{num}__");1365self.smt.list(vec![self.smt.atom(num.to_string())])1366}1367isle::sema::Pattern::And(_, subpats) => {1368let mut sexprs = subpats.iter().map(to_sexpr).collect::<Vec<SExpr>>();13691370sexprs.insert(0, self.smt.atom("and"));1371self.smt.list(sexprs)1372}1373}1374}13751376fn display_var_from_smt_prefix(1377&self,1378vars: &Vec<(String, String)>,1379ident: &str,1380prefix: &str,1381) -> String {1382let matches: Vec<&(String, String)> =1383vars.iter().filter(|(v, _)| v.starts_with(prefix)).collect();1384if matches.is_empty() {1385panic!("Can't find match for: {prefix}\n{vars:?}");1386} else if matches.len() == 3 {1387assert!(1388self.find_widths,1389"Only expect multiple matches with dynamic widths"1390);1391for (name, model) in matches {1392if name.contains("narrow") {1393return format!("[{}|{}]", self.smt.display(self.smt.atom(ident)), model);1394}1395}1396panic!("narrow not found");1397} else if matches.len() == 1 {1398let model = &matches.first().unwrap().1;1399format!("[{}|{}]", self.smt.display(self.smt.atom(ident)), model)1400} else {1401panic!("Unexpected number of matches!")1402}1403}14041405fn display_isle_expr(1406&self,1407termenv: &TermEnv,1408typeenv: &TypeEnv,1409vars: &Vec<(String, String)>,1410rule: &Rule,1411expr: &isle::sema::Expr,1412) -> SExpr {1413let to_sexpr = |e| self.display_isle_expr(termenv, typeenv, vars, rule, e);14141415match expr {1416isle::sema::Expr::Term(_, term_id, args) => {1417let sym = termenv.terms[term_id.index()].name;1418let name = typeenv.syms[sym.index()].clone();14191420let mut sexprs = args.iter().map(to_sexpr).collect::<Vec<SExpr>>();14211422sexprs.insert(0, self.smt.atom(name));1423self.smt.list(sexprs)1424}1425isle::sema::Expr::Var(_, var_id) => {1426let sym = rule.vars[var_id.index()].name;1427let ident = typeenv.syms[sym.index()].clone();1428let smt_ident_prefix = format!("{}__clif{}__", ident, var_id.index());14291430let var = self.display_var_from_smt_prefix(vars, &ident, &smt_ident_prefix);1431self.smt.atom(var)1432}1433isle::sema::Expr::ConstPrim(_, sym) => {1434let name = typeenv.syms[sym.index()].clone();1435self.smt.list(vec![self.smt.atom(name)])1436}1437isle::sema::Expr::ConstBool(_, val) => {1438self.smt.list(vec![self.smt.atom(format!("{val}"))])1439}1440isle::sema::Expr::ConstInt(_, num) => {1441let _smt_name_prefix = format!("{num}__");1442self.smt.list(vec![self.smt.atom(num.to_string())])1443}1444isle::sema::Expr::Let { bindings, body, .. } => {1445let mut sexprs = vec![];1446for (varid, _, expr) in bindings {1447let sym = rule.vars[varid.index()].name;1448let ident = typeenv.syms[sym.index()].clone();1449let smt_prefix = format!("{}__clif{}__", ident, varid.index());1450let var = self.display_var_from_smt_prefix(vars, &ident, &smt_prefix);14511452sexprs.push(self.smt.list(vec![self.smt.atom(var), to_sexpr(expr)]));1453}1454self.smt.list(vec![1455self.smt.atom("let"),1456self.smt.list(sexprs),1457to_sexpr(body),1458])1459}1460}1461}14621463fn display_model(1464&mut self,1465termenv: &TermEnv,1466typeenv: &TypeEnv,1467rule: &Rule,1468lhs_sexpr: SExpr,1469rhs_sexpr: SExpr,1470) {1471let mut vars = vec![];1472let mut lhs_value = None;1473let mut rhs_value = None;1474for (name, atom) in &self.var_map {1475let solution = self1476.smt1477.get_value(vec![self.smt.atom(name), *atom])1478.unwrap();1479for (variable, value) in solution {1480let display = self.display_value(variable, value);1481vars.push(display.clone());1482if variable == lhs_sexpr {1483lhs_value = Some(display.1);1484} else if variable == rhs_sexpr {1485rhs_value = Some(display.1);1486}1487}1488}1489for (name, _) in &self.additional_decls {1490let solution = self.smt.get_value(vec![self.smt.atom(name)]).unwrap();1491for (variable, value) in solution {1492vars.push(self.display_value(variable, value));1493}1494}1495vars.sort_by_key(|x| x.0.clone());1496vars.dedup();14971498// TODO VERBOSE1499println!("Counterexample summary");1500let lhs = self.display_isle_pattern(1501termenv,1502typeenv,1503&vars,1504rule,1505&Pattern::Term(1506cranelift_isle::sema::TypeId(0),1507rule.root_term,1508rule.args.clone(),1509),1510);1511println!("{}", self.smt.display(lhs));15121513// if-let statement processing1514if !&rule.iflets.is_empty() {1515print!("(if-let ");1516}1517for if_let_struct in &rule.iflets {1518let if_lhs = &if_let_struct.lhs;1519let if_rhs: &cranelift_isle::sema::Expr = &if_let_struct.rhs;15201521let if_lhs_expr = self.display_isle_pattern(termenv, typeenv, &vars, rule, if_lhs);15221523let if_rhs_expr = self.display_isle_expr(termenv, typeenv, &vars, rule, if_rhs);15241525println!(1526"({} {})",1527self.smt.display(if_lhs_expr),1528self.smt.display(if_rhs_expr)1529);1530}1531println!(")");15321533println!("=>");1534let rhs = self.display_isle_expr(termenv, typeenv, &vars, rule, &rule.rhs);1535println!("{}", self.smt.display(rhs));15361537println!("\n{} =>\n{}\n", lhs_value.unwrap(), rhs_value.unwrap(),);1538}15391540fn declare_variables(1541&mut self,1542rule_sem: &RuleSemantics,1543config: &Config,1544) -> (Vec<SExpr>, Vec<SExpr>) {1545let mut assumptions: Vec<SExpr> = vec![];1546log::trace!("Declaring quantified variables");1547for v in &rule_sem.quantified_vars {1548let name = &v.name;1549let ty = self.tyctx.tymap[&v.tyvar];1550let var_ty = self.vir_to_smt_ty(&ty);1551log::trace!("\t{} : {}", name, self.smt.display(var_ty));1552if let Type::BitVector(w) = ty {1553if self.find_widths {1554let wide = self.widen_to_register_width(1555v.tyvar,1556w.unwrap_or(self.bitwidth),1557self.smt.atom(name),1558Some(name.to_string()),1559);1560self.var_map.insert(name.clone(), wide);1561} else {1562self.var_map.insert(name.clone(), self.smt.atom(name));1563}1564} else {1565self.var_map.insert(name.clone(), self.smt.atom(name));1566}1567self.smt.declare_const(name, var_ty).unwrap();1568}1569self.lhs_flag = true;1570for a in &rule_sem.lhs_assumptions {1571let p = self.vir_expr_to_sexp(a.clone());1572assumptions.push(p)1573}1574self.lhs_flag = false;1575for a in &rule_sem.rhs_assumptions {1576let p = self.vir_expr_to_sexp(a.clone());1577assumptions.push(p)1578}1579if self.find_widths {1580for a in &self.width_assumptions {1581assumptions.push(*a);1582}1583}1584self.additional_assumptions.is_empty();1585for a in &self.additional_assumptions {1586assumptions.push(*a);1587}1588// Look at RHS assertions, which are checked, not trusted1589let assertions: Vec<SExpr> = rule_sem1590.rhs_assertions1591.iter()1592.map(|a| self.vir_expr_to_sexp(a.clone()))1593.collect();15941595for (name, ty) in &self.additional_decls {1596self.smt.declare_const(name, *ty).unwrap();1597}15981599if let Some(a) = &config.custom_assumptions {1600let term_args = rule_sem1601.term_args1602.iter()1603.map(|s| self.smt.atom(s))1604.collect();1605let custom_assumptions = a(&self.smt, term_args);1606log::debug!(1607"Custom assumptions:\n\t{}\n",1608self.smt.display(custom_assumptions)1609);1610assumptions.push(custom_assumptions);1611}1612(assumptions, assertions)1613}1614}16151616/// Overall query for single rule:1617/// <declare vars>1618/// (not (=> <assumptions> (= <LHS> <RHS>))))))1619pub fn run_solver(1620rule_sem: &RuleSemantics,1621rule: &Rule,1622termenv: &TermEnv,1623typeenv: &TypeEnv,1624concrete: &Option<ConcreteTest>,1625config: &Config,1626_types: &TermSignature,1627) -> VerificationResult {1628if std::env::var("SKIP_SOLVER").is_ok() {1629log::debug!("Environment variable SKIP_SOLVER set, returning Unknown");1630return VerificationResult::Unknown;1631}16321633let mut solver = easy_smt::ContextBuilder::new()1634.replay_file(Some(std::fs::File::create("dynamic_widths.smt2").unwrap()))1635.solver("z3", ["-smt2", "-in"])1636.build()1637.unwrap();16381639solver1640.set_option(":produce-unsat-cores", solver.true_())1641.unwrap();16421643// We start with logic to determine the width of all bitvectors1644let mut ctx = SolverCtx {1645smt: solver,1646// Always find widths at first1647find_widths: true,1648tyctx: rule_sem.tyctx.clone(),1649bitwidth: MAX_WIDTH,1650var_map: HashMap::new(),1651width_vars: HashMap::new(),1652width_assumptions: vec![],1653additional_decls: vec![],1654additional_assumptions: vec![],1655additional_assertions: vec![],1656fresh_bits_idx: 0,1657lhs_load_args: None,1658rhs_load_args: None,1659lhs_store_args: None,1660rhs_store_args: None,1661load_return: None,1662lhs_flag: true,1663};16641665let mut unresolved_widths = vec![];16661667// Check whether the non-solver type inference was able to resolve all bitvector widths,1668// and add assumptions for known widths1669for (_e, t) in &ctx.tyctx.tyvars {1670let ty = &ctx.tyctx.tymap[t];1671if let Type::BitVector(w) = ty {1672let width_name = format!("width__{t}");1673ctx.additional_decls1674.push((width_name.clone(), ctx.smt.int_sort()));1675match *w {1676Some(bitwidth) => {1677let eq = ctx1678.smt1679.eq(ctx.smt.atom(&width_name), ctx.smt.numeral(bitwidth));1680ctx.width_assumptions.push(eq);1681}1682None => {1683log::debug!("Unresolved width: {:?} ({})", &_e, *t);1684ctx.width_assumptions1685.push(ctx.smt.gt(ctx.smt.atom(&width_name), ctx.smt.numeral(0)));1686unresolved_widths.push(width_name.clone());1687}1688};1689ctx.width_vars.insert(*t, width_name.clone());1690}1691}16921693if unresolved_widths.is_empty() {1694log::debug!("All widths resolved after basic type inference");1695return run_solver_with_static_widths(1696&RuleCtx {1697rule_sem,1698rule,1699termenv,1700typeenv,1701config,1702},1703&ctx.tyctx,1704concrete,1705);1706}17071708log::debug!("Some unresolved widths after basic type inference");1709log::debug!("Finding widths from the solver");1710ctx.find_widths = true;1711let (assumptions, _) = ctx.declare_variables(rule_sem, config);1712ctx.smt.push().unwrap();1713for (i, a) in assumptions.iter().enumerate() {1714ctx.smt1715.assert(ctx.smt.named(format!("dyn{i}"), *a))1716.unwrap();1717}17181719resolve_dynamic_widths(1720RuleCtx {1721rule_sem,1722rule,1723termenv,1724typeenv,1725config,1726},1727concrete,1728&mut ctx,1729unresolved_widths,17300,1731)1732}17331734fn resolve_dynamic_widths(1735rulectx: RuleCtx,1736concrete: &Option<ConcreteTest>,1737ctx: &mut SolverCtx,1738unresolved_widths: Vec<String>,1739attempt: usize,1740) -> VerificationResult {1741if attempt > 10 {1742panic!("Unexpected number of attempts to resolve dynamic widths!")1743}1744match ctx.smt.check() {1745Ok(Response::Sat) => {1746let mut cur_tyctx = ctx.tyctx.clone();1747let mut width_resolutions = HashMap::new();1748for (e, t) in &ctx.tyctx.tyvars {1749let ty = &ctx.tyctx.tymap[t];1750if let Type::BitVector(w) = ty {1751let width_name = format!("width__{t}");1752let atom = ctx.smt.atom(&width_name);1753let width = ctx.smt.get_value(vec![atom]).unwrap().first().unwrap().1;1754let width_int = u8::try_from(ctx.smt.get(width)).unwrap();17551756// Check that we haven't contradicted previous widths1757if let Some(before_width) = w {1758assert_eq!(*before_width, width_int as usize)1759};17601761// Check that the width is nonzero1762if width_int == 0 {1763panic!("Unexpected, zero width! {t} {e:?}");1764}17651766if unresolved_widths.contains(&width_name) {1767log::debug!("\tResolved width: {width_name}, {width_int}");1768width_resolutions.insert(width_name, width_int);1769cur_tyctx1770.tymap1771.insert(*t, Type::BitVector(Some(width_int as usize)));1772}1773}1774}1775let static_result = run_solver_with_static_widths(&rulectx, &cur_tyctx, concrete);17761777// If we have a failure or unknown, return right away1778if !matches!(static_result, VerificationResult::Success) {1779return static_result;1780}17811782// Otherwise, try again, but adding the assertion that some width is1783// different than our current assigment1784let not_equals = width_resolutions.iter().map(|(s, w)| {1785ctx.smt.not(1786ctx.smt1787.eq(ctx.smt.atom(s.clone()), ctx.smt.atom((*w).to_string())),1788)1789});1790ctx.smt.assert(ctx.smt.or_many(not_equals)).unwrap();17911792resolve_dynamic_widths(rulectx, concrete, ctx, unresolved_widths, attempt + 1)1793}1794Ok(Response::Unsat) => {1795if attempt == 0 {1796log::warn!(1797"Rule not applicable as written for rule assumptions, skipping full query"1798);1799let unsat = ctx.smt.get_unsat_core().unwrap();1800log::warn!("Unsat core:\n{}", ctx.smt.display(unsat));1801VerificationResult::InapplicableRule1802} else {1803// If this is not the first attempt, some previous width assignment must1804// have succeeded.1805VerificationResult::Success1806}1807}1808Ok(Response::Unknown) => {1809panic!("Solver said 'unk'");1810}1811Err(err) => {1812unreachable!("Error! {:?}", err);1813}1814}1815}18161817pub fn run_solver_with_static_widths(1818rulectx: &RuleCtx,1819tyctx: &TypeContext,1820concrete: &Option<ConcreteTest>,1821) -> VerificationResult {1822// Declare variables again, this time with all static widths1823let mut solver = easy_smt::ContextBuilder::new()1824.replay_file(Some(std::fs::File::create("static_widths.smt2").unwrap()))1825.solver("z3", ["-smt2", "-in"])1826.build()1827.unwrap();1828solver1829.set_option(":produce-unsat-cores", solver.true_())1830.unwrap();1831let mut ctx = SolverCtx {1832smt: solver,1833find_widths: false,1834tyctx: tyctx.clone(),1835bitwidth: MAX_WIDTH,1836var_map: HashMap::new(),1837width_vars: HashMap::new(),1838width_assumptions: vec![],1839additional_decls: vec![],1840additional_assumptions: vec![],1841additional_assertions: vec![],1842fresh_bits_idx: 0,1843lhs_load_args: None,1844rhs_load_args: None,1845lhs_store_args: None,1846rhs_store_args: None,1847load_return: None,1848lhs_flag: true,1849};1850let (assumptions, mut assertions) = ctx.declare_variables(rulectx.rule_sem, rulectx.config);18511852let lhs = ctx.vir_expr_to_sexp(rulectx.rule_sem.lhs.clone());1853ctx.lhs_flag = false;1854let rhs = ctx.vir_expr_to_sexp(rulectx.rule_sem.rhs.clone());18551856// For debugging1857let unnamed_rule = String::from("<unnamed rule>");1858let rulename = rulectx1859.rule1860.name1861.map(|name| &rulectx.typeenv.syms[name.index()])1862.unwrap_or(&unnamed_rule);1863let unit = "()".to_string();1864let widthname = ctx1865.static_width(&rulectx.rule_sem.lhs)1866.map_or(unit, |s| format!("width {s}"));18671868// Check whether the assumptions are possible1869let feasibility = ctx.check_assumptions_feasibility(1870&assumptions,1871&rulectx.rule_sem.term_input_bvs,1872rulectx.config,1873);1874if feasibility != VerificationResult::Success {1875log::warn!("Rule not applicable as written for rule assumptions, skipping full query");1876return feasibility;1877}18781879// Correctness query1880// Verification condition: first rule's LHS and RHS are equal1881if let Some(concrete) = concrete {1882return test_concrete_with_static_widths(1883rulectx,1884concrete,1885lhs,1886rhs,1887&mut ctx,1888assumptions,1889);1890}18911892let condition = if let Some(condition) = &rulectx.config.custom_verification_condition {1893let term_args = rulectx1894.rule_sem1895.term_args1896.iter()1897.map(|s| ctx.smt.atom(s))1898.collect();1899let custom_condition = condition(&ctx.smt, term_args, lhs, rhs);1900log::debug!(1901"Custom verification condition:\n\t{}\n",1902ctx.smt.display(custom_condition)1903);1904custom_condition1905} else {1906// Note: this is where we ask if the LHS and the RHS are equal1907let side_equality = ctx.smt.eq(lhs, rhs);1908log::debug!(1909"LHS and RHS equality condition:{}",1910ctx.smt.display(side_equality)1911);1912side_equality1913};19141915for a in &ctx.additional_assertions {1916assertions.push(*a);1917}19181919let assumption_conjunction = ctx.smt.and_many(assumptions);1920let mut full_condition = if !assertions.is_empty() {1921let assertion_conjunction = ctx.smt.and_many(assertions.clone());1922ctx.smt.and(condition, assertion_conjunction)1923} else {1924condition1925};19261927let mut load_conditions = vec![];1928match (&ctx.lhs_load_args, &ctx.rhs_load_args) {1929(Some(_), Some(_)) => {1930let lhs_args_vec = ctx.lhs_load_args.clone().unwrap();1931let rhs_args_vec = ctx.rhs_load_args.clone().unwrap();1932log::debug!("Load argument conditions:");1933for i in 0..lhs_args_vec.len() {1934let arg_equal = ctx.smt.eq(lhs_args_vec[i], rhs_args_vec[i]);1935load_conditions.push(arg_equal);1936log::debug!("\t{}", ctx.smt.display(arg_equal));1937full_condition = ctx.smt.and(full_condition, arg_equal);1938}1939}1940(None, None) => (),1941(Some(_), None) => {1942log::error!("Verification failed for {rulename}, {widthname}");1943log::error!("Left hand side has load statement but right hand side does not.");1944return VerificationResult::Failure(Counterexample {});1945}1946(None, Some(_)) => {1947log::error!("Verification failed for {rulename}, {widthname}");1948log::error!("Right hand side has load statement but left hand side does not.");1949return VerificationResult::Failure(Counterexample {});1950}1951}19521953let mut store_conditions = vec![];1954match (&ctx.lhs_store_args, &ctx.rhs_store_args) {1955(Some(_), Some(_)) => {1956let lhs_args_vec = ctx.lhs_store_args.clone().unwrap();1957let rhs_args_vec = ctx.rhs_store_args.clone().unwrap();1958log::debug!("Store argument conditions:");19591960for i in 0..lhs_args_vec.len() {1961let arg_equal = ctx.smt.eq(lhs_args_vec[i], rhs_args_vec[i]);1962store_conditions.push(arg_equal);1963log::debug!("\t{}", ctx.smt.display(arg_equal));1964full_condition = ctx.smt.and(full_condition, arg_equal)1965}1966}1967(None, None) => (),1968(Some(_), None) => {1969log::error!("Verification failed for {rulename}, {widthname}");1970log::error!("Left hand side has store statement but right hand side does not.");1971return VerificationResult::Failure(Counterexample {});1972}1973(None, Some(_)) => {1974log::error!("Verification failed for {rulename}, {widthname}");1975log::error!("Right hand side has store statement but left hand side does not.");1976return VerificationResult::Failure(Counterexample {});1977}1978}19791980log::trace!(1981"Full verification condition:{}",1982ctx.smt.display(full_condition)1983);1984let query = ctx1985.smt1986.not(ctx.smt.imp(assumption_conjunction, full_condition));1987log::trace!("Running query");1988ctx.smt.assert(query).unwrap();19891990match ctx.smt.check() {1991Ok(Response::Sat) => {1992println!("Verification failed for {rulename}, {widthname}");1993ctx.display_model(rulectx.termenv, rulectx.typeenv, rulectx.rule, lhs, rhs);1994let vals = ctx.smt.get_value(vec![condition]).unwrap();1995for (variable, value) in vals {1996if value == ctx.smt.false_() {1997println!("Failed condition:\n{}", ctx.smt.display(variable));1998} else if value == ctx.smt.true_() {1999println!("Condition met, but failed some assertion(s).")2000}2001}20022003if !assertions.is_empty() {2004let vals = ctx.smt.get_value(assertions).unwrap();2005for (variable, value) in vals {2006if value == ctx.smt.false_() {2007println!("Failed assertion:\n{}", ctx.smt.display(variable));2008}2009}2010}20112012if !load_conditions.is_empty() {2013let vals = ctx.smt.get_value(load_conditions).unwrap();2014for (variable, value) in vals {2015if value == ctx.smt.false_() {2016log::error!("Failed load condition:\n{}", ctx.smt.display(variable));2017}2018}2019}2020VerificationResult::Failure(Counterexample {})2021}2022Ok(Response::Unsat) => {2023println!("Verification succeeded for {rulename}, {widthname}");2024VerificationResult::Success2025}2026Ok(Response::Unknown) => {2027panic!("Solver said 'unk'");2028}2029Err(err) => {2030unreachable!("Error! {:?}", err);2031}2032}2033}20342035pub fn test_concrete_with_static_widths(2036rulectx: &RuleCtx,2037concrete: &ConcreteTest,2038lhs: SExpr,2039rhs: SExpr,2040ctx: &mut SolverCtx,2041assumptions: Vec<SExpr>,2042) -> VerificationResult {2043// Test code only: test against concrete input/output2044// Check that our expected output is valid2045for (i, a) in assumptions.iter().enumerate() {2046ctx.smt2047.assert(ctx.smt.named(format!("conc{i}"), *a))2048.unwrap();2049}2050for (i, e) in ctx.additional_assertions.iter().enumerate() {2051ctx.smt2052.assert(ctx.smt.named(format!("conc_assert{i}"), *e))2053.unwrap();2054}2055ctx.smt.push().unwrap();2056let eq = ctx2057.smt2058.eq(rhs, ctx.smt.atom(concrete.output.literal.clone()));20592060ctx.smt2061.assert(ctx.smt.named("conceq".to_string(), eq))2062.unwrap();20632064for (i, a) in rulectx.rule_sem.rhs_assertions.iter().enumerate() {2065let p = ctx.vir_expr_to_sexp(a.clone());2066ctx.smt2067.assert(ctx.smt.named(format!("rhs_assert{i}"), p))2068.unwrap();2069}20702071if !matches!(ctx.smt.check(), Ok(Response::Sat)) {2072// Bad! This is a bug!2073// Pop the output assertion2074ctx.smt.pop().unwrap();2075// Try again2076assert!(matches!(ctx.smt.check(), Ok(Response::Sat)));2077// Get the value for what output is to panic with a useful message2078let val = ctx.smt.get_value(vec![rhs]).unwrap()[0].1;2079ctx.display_model(rulectx.termenv, rulectx.typeenv, rulectx.rule, lhs, rhs);2080panic!(2081"Expected {}, got {}",2082concrete.output.literal,2083ctx.display_hex_to_bin(val)2084);2085} else {2086log::debug!(2087"Expected concrete result matched: {}",2088concrete.output.literal2089);2090ctx.smt.pop().unwrap();2091}20922093// Check that there is no other possible output2094ctx.smt.push().unwrap();2095ctx.smt2096.assert(2097ctx.smt.not(2098ctx.smt2099.eq(rhs, ctx.smt.atom(concrete.output.literal.clone())),2100),2101)2102.unwrap();2103if !matches!(ctx.smt.check(), Ok(Response::Unsat)) {2104// Get the value for what output is to panic with a useful message2105let val = ctx.smt.get_value(vec![rhs]).unwrap()[0].1;2106ctx.display_model(rulectx.termenv, rulectx.typeenv, rulectx.rule, lhs, rhs);2107// AVH TODO: should probably elevate back to an error with custom verification condition2108log::error!(2109"WARNING: Expected ONLY {}, got POSSIBLE {}",2110concrete.output.literal,2111ctx.display_hex_to_bin(val)2112);2113}2114ctx.smt.pop().unwrap();2115VerificationResult::Success2116}211721182119