├── README.md ├── sire ├── Cargo.lock ├── Cargo.toml └── src │ ├── analysis.rs │ ├── eval.rs │ ├── eval │ ├── memory.rs │ └── util.rs │ ├── lib.rs │ ├── sir.rs │ └── sir │ ├── display.rs │ ├── optimize.rs │ ├── ty.rs │ ├── visitor.rs │ └── visitor_mut.rs ├── sire_run ├── Cargo.lock ├── Cargo.toml ├── code.rs └── src │ └── main.rs └── sire_smt ├── Cargo.lock ├── Cargo.toml ├── src ├── lib.rs ├── smtlib.rs └── z3.rs └── tests └── lib.rs /README.md: -------------------------------------------------------------------------------- 1 | # Sire 2 | Sire (which is a WIP) intends to be an small symbolic evaluator for Rust's MIR. 3 | 4 | ## How does it work? 5 | 6 | Sire takes the optimized MIR of your code and evaluates its functions into small [expressions](https://github.com/christianpoveda/sire/blob/8b8a9f94398ac68b3b2b2b902c7980b3f0d7e647/src/interpreter.rs#L10). It also allows to export such expressions to the [smt-lib](http://smtlib.cs.uiowa.edu/) language, then you can reason more about them using a theorem prover. 7 | So for example if you have a file `some_code.rs` containing: 8 | ```rust 9 | fn main() { 10 | 11 | } 12 | 13 | fn sum(n: u64, m: u64) -> u64 { 14 | if m > 0 { 15 | sum(n + 1, m - 1) 16 | } else { 17 | n 18 | } 19 | } 20 | ``` 21 | you can evaluate it cloning this repo and running 22 | ```bash 23 | $ cargo run some_code.rs -C opt-level=3 24 | ``` 25 | then Sire should print something like 26 | ``` 27 | (declare-fun sum ((_ BitVec 64) (_ BitVec 64)) (_ BitVec 64)) 28 | (assert (forall ((x1 (_ BitVec 64)) (x2 (_ BitVec 64))) (= (sum x1 x2) (ite (bvugt x2 (_ bv0 64)) (sum (bvadd x1 (_ bv1 64)) (bvsub x2 (_ bv1 64))) x1)))) 29 | ``` 30 | you can use this code to reason about the `sum` function using [z3](https://rise4fun.com/Z3/sl8wn) for example. 31 | 32 | ## Coverage 33 | 34 | Right now, just an small set of Rust functions can be evaluated with Sire (basically any recursive function without side effects nor loops) and I am working to expand this. To be more specific, the following are allowed: 35 | 36 | - Statements: 37 | - `Assign` 38 | - `StorageLive` 39 | - `StorageDead` 40 | 41 | - Terminators: 42 | - `Return` 43 | - `Goto` 44 | - `Call` (only if the function returns) 45 | - `SwitchInt` 46 | 47 | - Rvalues: 48 | - `BinaryOp` 49 | - `Ref` (only shared references) 50 | - `Use` 51 | 52 | - Operands: 53 | - `Move` and `Copy` 54 | - `Constant` (only scalars) 55 | 56 | Additionally, just the integer (both signed and unsigned) and boolean types are supported. 57 | 58 | If you have any suggestions or questions feel free to open an issue/write me an email :) 59 | 60 | ## Installing 61 | 62 | This project depends on nightly Rust, the preferred (only?) method is using 63 | [`rustup`](https://rustup.rs/). Please check the `rustup` documentation on how 64 | to get nightly. Now you will need to clone this repository: 65 | 66 | ```bash 67 | $ git clone https://github.com/christianpoveda/sire 68 | ``` 69 | 70 | Now to execute a `code.rs` file using `sire`, run the following inside the 71 | repository folder 72 | 73 | ```bash 74 | cargo run code.rs -O 75 | ``` 76 | 77 | This should throw a symbolic representation of every function in `code.rs` and 78 | its `smt-lib` counterpart. 79 | -------------------------------------------------------------------------------- /sire/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "sire" 5 | version = "0.1.0" 6 | -------------------------------------------------------------------------------- /sire/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sire" 3 | version = "0.1.0" 4 | authors = ["Christian Poveda "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rustc = {path = "../../librustc"} 11 | -------------------------------------------------------------------------------- /sire/src/analysis.rs: -------------------------------------------------------------------------------- 1 | use rustc::mir::*; 2 | 3 | use crate::sir::*; 4 | 5 | pub fn find_loop<'tcx>(mir: &'tcx Body<'tcx>) -> Option> { 6 | get_loop_start(mir, BasicBlock::from_u32(0), Vec::new()) 7 | } 8 | 9 | fn get_loop_start<'tcx>( 10 | mir: &'tcx Body<'tcx>, 11 | block: BasicBlock, 12 | mut visited: Vec, 13 | ) -> Option> { 14 | match visited.iter().enumerate().find(|(_, b)| **b == block) { 15 | Some((i, _)) => Some(visited.split_off(i)), 16 | None => { 17 | let blk = mir.basic_blocks().get(block)?; 18 | visited.push(block); 19 | match blk.terminator().kind { 20 | TerminatorKind::Goto { target } => get_loop_start(mir, target, visited), 21 | TerminatorKind::SwitchInt { ref targets, .. } => { 22 | let mut result = None; 23 | for target in targets { 24 | result = get_loop_start(mir, *target, visited.clone()); 25 | if result.is_some() { 26 | break; 27 | } 28 | } 29 | result 30 | } 31 | TerminatorKind::Call { destination: Some((_, target)), .. } => { 32 | get_loop_start(mir, target, visited) 33 | } 34 | _ => None, 35 | } 36 | } 37 | } 38 | } 39 | 40 | impl Expr { 41 | pub fn find_datatype_instances(&self) -> Vec { 42 | Instanced::find_types(self) 43 | } 44 | } 45 | 46 | #[derive(Default)] 47 | struct Instanced { 48 | inner: Vec, 49 | } 50 | 51 | impl Instanced { 52 | fn find_types(expr: &Expr) -> Vec { 53 | let mut this = Self::default(); 54 | this.visit_expr(expr); 55 | this.inner 56 | } 57 | } 58 | 59 | impl Visitor for Instanced { 60 | fn visit_expr(&mut self, expr: &Expr) { 61 | self.super_expr(expr); 62 | let ty = expr.ty(); 63 | 64 | match ty { 65 | Ty::Tuple(_) => { 66 | if !self.inner.contains(&ty) { 67 | self.inner.push(ty); 68 | } 69 | } 70 | _ => (), 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /sire/src/eval.rs: -------------------------------------------------------------------------------- 1 | use rustc::hir::def_id::DefId; 2 | use rustc::mir::interpret::{ConstValue, InterpResult}; 3 | use rustc::mir::*; 4 | use rustc::ty::{self, layout::Size, TyCtxt}; 5 | use rustc::{err_unsup, err_unsup_format}; 6 | 7 | use crate::analysis::find_loop; 8 | use crate::sir::*; 9 | 10 | use self::memory::*; 11 | use self::util::*; 12 | 13 | mod memory; 14 | mod util; 15 | 16 | #[derive(Clone)] 17 | pub struct Evaluator<'tcx> { 18 | location: Location, 19 | memory: Memory<'tcx>, 20 | def_id: Option, 21 | tcx: TyCtxt<'tcx>, 22 | } 23 | 24 | impl<'tcx> Evaluator<'tcx> { 25 | pub fn from_tcx(tcx: TyCtxt<'tcx>) -> Self { 26 | Evaluator { location: Location::START, memory: Default::default(), def_id: None, tcx } 27 | } 28 | 29 | pub fn eval_mir(&mut self, def_id: DefId) -> InterpResult<'tcx, FuncDef> { 30 | let mir = self.tcx.optimized_mir(def_id); 31 | 32 | if find_loop(mir).is_some() { 33 | return Err(err_unsup_format!("The function {:?} contains loops", def_id).into()); 34 | } 35 | 36 | let args_ty = mir 37 | .local_decls 38 | .iter() 39 | .take(mir.arg_count + 1) 40 | .map(|ld| self.transl_ty(&ld.ty)) 41 | .collect::>>()?; 42 | 43 | self.memory.insert(Place::return_place(), Expr::Uninitialized); 44 | 45 | for (i, arg_ty) in args_ty.iter().enumerate().skip(1) { 46 | self.memory.insert_from_int(i, Expr::Value(Value::Arg(i, arg_ty.clone()))); 47 | } 48 | 49 | let params = ExtractParams::run(self, &mir); 50 | 51 | let locals_len = mir.local_decls.len(); 52 | let (live, dead) = CheckStorage::run(&mir); 53 | 54 | for i in args_ty.len()..locals_len { 55 | let local = Local::from_usize(i); 56 | if !live.contains(&local) { 57 | self.memory.insert_from_int(i, Expr::Uninitialized); 58 | } 59 | } 60 | 61 | self.def_id = Some(def_id); 62 | 63 | self.run()?; 64 | 65 | for i in 1usize..args_ty.len() { 66 | self.memory.remove_from_int(i)?; 67 | } 68 | 69 | for i in args_ty.len()..locals_len { 70 | let local = Local::from_usize(i); 71 | if !dead.contains(&local) { 72 | self.memory.remove(&local.into())?; 73 | } 74 | } 75 | 76 | let mut body = self.memory.remove(&Place::return_place())?; 77 | 78 | body.optimize(); 79 | 80 | assert_eq!(args_ty[0], body.ty()); 81 | 82 | if self.memory.is_empty() { 83 | Ok(FuncDef { body, def_id, ty: Ty::Func(args_ty.clone(), params) }) 84 | } else { 85 | Err(err_unsup_format!("Memory is not empty after execution").into()) 86 | } 87 | } 88 | 89 | fn run(&mut self) -> InterpResult<'tcx> { 90 | while self.step()? {} 91 | Ok(()) 92 | } 93 | 94 | fn step(&mut self) -> InterpResult<'tcx, bool> { 95 | let block_data = self 96 | .tcx 97 | .optimized_mir(self.def_id.expect("Bug: DefId should be some")) 98 | .basic_blocks() 99 | .get(self.location.block) 100 | .ok_or_else(|| err_unsup_format!("Basic block not found"))?; 101 | 102 | match block_data.statements.get(self.location.statement_index) { 103 | Some(statement) => self.eval_statement(statement), 104 | None => self.eval_terminator(block_data.terminator()), 105 | } 106 | } 107 | 108 | fn eval_statement(&mut self, statement: &Statement<'tcx>) -> InterpResult<'tcx, bool> { 109 | match statement.kind { 110 | StatementKind::Assign(box (ref place, ref rvalue)) => { 111 | self.eval_rvalue_into_place(rvalue, place)?; 112 | } 113 | StatementKind::StorageLive(local) => { 114 | self.memory.insert(local.into(), Expr::Uninitialized); 115 | } 116 | StatementKind::StorageDead(local) => { 117 | self.memory.remove(&local.into())?; 118 | } 119 | ref sk => { 120 | return Err(err_unsup_format!("StatementKind {:?} is unsupported", sk).into()); 121 | } 122 | }; 123 | self.location = self.location.successor_within_block(); 124 | Ok(true) 125 | } 126 | 127 | fn eval_terminator(&mut self, terminator: &Terminator<'tcx>) -> InterpResult<'tcx, bool> { 128 | match terminator.kind { 129 | TerminatorKind::Return => { 130 | self.location = Location::START; 131 | Ok(false) 132 | } 133 | TerminatorKind::Goto { target } => { 134 | self.location = target.start_location(); 135 | Ok(true) 136 | } 137 | TerminatorKind::Call { ref func, ref args, ref destination, .. } => match destination { 138 | Some((place, block)) => { 139 | let func_expr = self.eval_operand(func)?; 140 | let mut args_expr = Vec::new(); 141 | for op in args { 142 | args_expr.push(self.eval_operand(op)?); 143 | } 144 | *self.memory.get_mut(place)? = Expr::Apply(Box::new(func_expr), args_expr); 145 | self.location = block.start_location(); 146 | Ok(true) 147 | } 148 | None => Err(err_unsup_format!("Call terminator does not assign").into()), 149 | }, 150 | TerminatorKind::SwitchInt { 151 | ref discr, ref switch_ty, ref values, ref targets, .. 152 | } => { 153 | let discr_expr = self.eval_operand(&discr)?; 154 | let mut values_expr = Vec::new(); 155 | let mut targets_expr = Vec::new(); 156 | 157 | for (&bytes, &block) in values.iter().zip(targets) { 158 | let mut target_expr = self.fork_eval(block)?; 159 | 160 | let value_expr = Expr::Value(Value::Const(bytes, self.transl_ty(switch_ty)?)); 161 | 162 | target_expr.replace(&discr_expr, &value_expr); 163 | 164 | values_expr.push(value_expr); 165 | targets_expr.push(target_expr); 166 | } 167 | 168 | self.location = targets.last().unwrap().start_location(); 169 | self.run()?; 170 | 171 | targets_expr.push(self.memory.get(&Place::return_place())?.clone()); 172 | 173 | *self.memory.get_mut(&Place::return_place())? = 174 | Expr::Switch(Box::new(discr_expr), values_expr, targets_expr); 175 | 176 | self.location = Location::START; 177 | Ok(false) 178 | } 179 | // TerminatorKind::Assert { ref cond, ref expected, ref target, .. } => { 180 | // let cond_expr = self.eval_operand(cond)?; 181 | // let just_expr = Expr::Just(Box::new(self.fork_eval(*target)?)); 182 | // let maybe_ty = just_expr.ty(); 183 | // 184 | // let nothing_expr = Expr::Nothing(maybe_ty); 185 | // let values_expr = vec![Expr::Value(Value::Const(0, Ty::Bool))]; 186 | // let targets_expr = if *expected { 187 | // vec![nothing_expr, just_expr] 188 | // } else { 189 | // vec![just_expr, nothing_expr] 190 | // }; 191 | // *self.memory.get_mut(&Place::return_place())? = 192 | // Expr::Switch(Box::new(cond_expr), values_expr, targets_expr); 193 | // self.location = Location::START; 194 | // Ok(false) 195 | // } 196 | ref tk => Err(err_unsup_format!("TerminatorKind {:?} is not supported", tk).into()), 197 | } 198 | } 199 | 200 | fn eval_rvalue_into_place( 201 | &mut self, 202 | rvalue: &Rvalue<'tcx>, 203 | place: &Place<'tcx>, 204 | ) -> InterpResult<'tcx> { 205 | let value = match rvalue { 206 | Rvalue::BinaryOp(bin_op, op1, op2) => Expr::BinaryOp( 207 | *bin_op, 208 | Box::new(self.eval_operand(op1)?), 209 | Box::new(self.eval_operand(op2)?), 210 | ), 211 | Rvalue::CheckedBinaryOp(bin_op, op1, op2) => Expr::Tuple(vec![ 212 | Expr::BinaryOp( 213 | *bin_op, 214 | Box::new(self.eval_operand(op1)?), 215 | Box::new(self.eval_operand(op2)?), 216 | ), 217 | // FIXME: Check the operation 218 | Expr::Value(Value::Const(0, Ty::Bool)), 219 | ]), 220 | Rvalue::Ref(_, BorrowKind::Shared, place) => self.memory.get(place)?.clone(), 221 | Rvalue::Use(op) => self.eval_operand(op)?, 222 | ref rv => return Err(err_unsup_format!("Rvalue {:?} unsupported", rv).into()), 223 | }; 224 | 225 | *self.memory.get_mut(place)? = value; 226 | 227 | Ok(()) 228 | } 229 | 230 | fn eval_operand(&self, operand: &Operand<'tcx>) -> InterpResult<'tcx, Expr> { 231 | Ok(match operand { 232 | Operand::Move(Place { base, projection }) 233 | | Operand::Copy(Place { base, projection }) => { 234 | let expr = 235 | self.memory.get(&Place { base: base.clone(), projection: box [] })?.clone(); 236 | if let box [.., ProjectionElem::Field(field, _)] = projection { 237 | Expr::Projection(Box::new(expr), field.index()) 238 | } else { 239 | expr 240 | } 241 | } 242 | 243 | Operand::Constant(constant) => { 244 | let const_ty = &constant.literal.ty; 245 | let ty = self.transl_ty(const_ty)?; 246 | Expr::Value(match ty { 247 | Ty::Func(_, _) => match const_ty.kind { 248 | ty::FnDef(def_id, _) => Value::Function(def_id, ty), 249 | _ => unreachable!(), 250 | }, 251 | 252 | _ => match constant.literal.val { 253 | ConstValue::Scalar(scalar) => Value::Const( 254 | scalar.to_bits(Size::from_bits(ty.bits().unwrap() as u64))?, 255 | ty, 256 | ), 257 | ConstValue::Param(param) => { 258 | Value::ConstParam(Param(param.index as usize, ty)) 259 | } 260 | val => { 261 | return Err( 262 | err_unsup_format!("Unsupported ConstValue: {:?}", val).into() 263 | ); 264 | } 265 | }, 266 | }) 267 | } 268 | }) 269 | } 270 | #[allow(rustc::usage_of_qualified_ty)] 271 | fn transl_ty(&self, ty: ty::Ty<'tcx>) -> InterpResult<'tcx, Ty> { 272 | match ty.kind { 273 | ty::Bool => Ok(Ty::Bool), 274 | ty::Int(int_ty) => { 275 | Ok(Ty::Int(int_ty.bit_width().unwrap_or(8 * std::mem::size_of::()))) 276 | } 277 | ty::Uint(uint_ty) => { 278 | Ok(Ty::Uint(uint_ty.bit_width().unwrap_or(8 * std::mem::size_of::()))) 279 | } 280 | ty::FnDef(def_id, _) => self 281 | .tcx 282 | .optimized_mir(def_id) 283 | .local_decls 284 | .iter() 285 | .map(|ld| self.transl_ty(&ld.ty)) 286 | .collect::>>() 287 | .map(|args_ty| Ty::Func(args_ty, Vec::new())), 288 | _ => Err(err_unsup_format!("Unsupported ty {:?}", ty).into()), 289 | } 290 | } 291 | 292 | fn fork_eval(&self, block: BasicBlock) -> InterpResult<'tcx, Expr> { 293 | let mut fork = Evaluator { 294 | memory: self.memory.clone(), 295 | location: block.start_location(), 296 | def_id: self.def_id, 297 | tcx: self.tcx, 298 | }; 299 | 300 | fork.run()?; 301 | 302 | fork.memory.get(&Place::return_place()).map(|e| e.clone()) 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /sire/src/eval/memory.rs: -------------------------------------------------------------------------------- 1 | #![allow(rustc::default_hash_types)] 2 | use std::collections::HashMap; 3 | 4 | use rustc::mir::interpret::InterpResult; 5 | use rustc::mir::*; 6 | use rustc::{err_unsup, err_unsup_format}; 7 | 8 | use crate::sir::*; 9 | 10 | #[derive(Default, Clone)] 11 | pub struct Memory<'tcx> { 12 | map: HashMap, Expr>, 13 | } 14 | 15 | impl<'tcx> Memory<'tcx> { 16 | pub fn is_empty(&self) -> bool { 17 | self.map.is_empty() 18 | } 19 | 20 | pub fn get(&self, place: &Place<'tcx>) -> InterpResult<'tcx, &Expr> { 21 | self.map 22 | .get(place) 23 | .ok_or_else(|| err_unsup_format!("Cannot get from place {:?}", place).into()) 24 | } 25 | 26 | pub fn get_mut(&mut self, place: &Place<'tcx>) -> InterpResult<'tcx, &mut Expr> { 27 | self.map 28 | .get_mut(place) 29 | .ok_or_else(|| err_unsup_format!("Cannot get from place {:?}", place).into()) 30 | } 31 | 32 | pub fn insert(&mut self, place: Place<'tcx>, expr: Expr) { 33 | self.map.insert(place, expr); 34 | } 35 | 36 | pub fn insert_from_int(&mut self, int: usize, expr: Expr) { 37 | self.insert(Local::from_usize(int).into(), expr) 38 | } 39 | 40 | pub fn remove(&mut self, place: &Place<'tcx>) -> InterpResult<'tcx, Expr> { 41 | self.map 42 | .remove(place) 43 | .ok_or_else(|| err_unsup_format!("Cannot remove from place {:?}", place).into()) 44 | } 45 | 46 | pub fn remove_from_int(&mut self, int: usize) -> InterpResult<'tcx, Expr> { 47 | self.remove(&Local::from_usize(int).into()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /sire/src/eval/util.rs: -------------------------------------------------------------------------------- 1 | use std::collections::BTreeSet; 2 | 3 | use rustc::mir::interpret::ConstValue; 4 | use rustc::mir::visit::Visitor; 5 | use rustc::mir::*; 6 | use rustc::ty::{Const, ParamConst}; 7 | 8 | use crate::eval::Evaluator; 9 | use crate::sir::Param; 10 | 11 | #[derive(Default)] 12 | pub struct CheckStorage { 13 | live: Vec, 14 | dead: Vec, 15 | } 16 | 17 | impl<'tcx> CheckStorage { 18 | pub fn run(body: &Body<'tcx>) -> (Vec, Vec) { 19 | let mut check = Self::default(); 20 | check.visit_body(body); 21 | (check.live, check.dead) 22 | } 23 | } 24 | 25 | impl<'tcx> Visitor<'tcx> for CheckStorage { 26 | fn visit_statement(&mut self, statement: &Statement<'tcx>, _location: Location) { 27 | match statement.kind { 28 | StatementKind::StorageLive(local) => self.live.push(local), 29 | StatementKind::StorageDead(local) => self.dead.push(local), 30 | _ => (), 31 | } 32 | } 33 | } 34 | 35 | pub struct ExtractParams<'tcx, 'eval> { 36 | params: BTreeSet, 37 | evaluator: &'eval Evaluator<'tcx>, 38 | } 39 | 40 | impl<'tcx, 'eval> ExtractParams<'tcx, 'eval> { 41 | pub fn run(evaluator: &'eval Evaluator<'tcx>, body: &Body<'tcx>) -> Vec { 42 | let mut extract = ExtractParams { params: Default::default(), evaluator }; 43 | extract.visit_body(body); 44 | extract.params.into_iter().collect() 45 | } 46 | } 47 | 48 | impl<'tcx, 'eval> Visitor<'tcx> for ExtractParams<'tcx, 'eval> { 49 | fn visit_operand(&mut self, op: &Operand<'tcx>, _location: Location) { 50 | match op { 51 | Operand::Constant(box Constant { 52 | literal: Const { ty, val: ConstValue::Param(ParamConst { index, .. }) }, 53 | .. 54 | }) => { 55 | // FIXME: not all rust types are supported 56 | let param = Param(*index as usize, self.evaluator.transl_ty(ty).unwrap()); 57 | self.params.insert(param); 58 | } 59 | _ => (), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /sire/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(box_patterns)] 2 | #![feature(slice_patterns)] 3 | #![feature(box_syntax)] 4 | 5 | pub mod analysis; 6 | pub mod eval; 7 | pub mod sir; 8 | -------------------------------------------------------------------------------- /sire/src/sir.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use std::fmt; 3 | 4 | pub use rustc::hir::def_id::DefId; 5 | pub use rustc::mir::BinOp; 6 | 7 | pub use self::display::*; 8 | pub use self::ty::*; 9 | pub use self::visitor::*; 10 | pub use self::visitor_mut::*; 11 | 12 | mod display; 13 | mod optimize; 14 | mod ty; 15 | mod visitor; 16 | mod visitor_mut; 17 | 18 | #[derive(Clone, Debug, PartialEq, Eq)] 19 | pub struct FuncDef { 20 | pub def_id: DefId, 21 | pub body: Expr, 22 | pub ty: Ty, 23 | } 24 | 25 | impl FuncDef { 26 | pub fn is_recursive(&self) -> bool { 27 | self.body.contains(&Expr::Value(Value::Function(self.def_id, self.ty.clone()))) 28 | } 29 | } 30 | 31 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 32 | pub struct Param(pub usize, pub Ty); 33 | 34 | impl Ord for Param { 35 | fn cmp(&self, other: &Self) -> Ordering { 36 | let Param(a, _) = self; 37 | let Param(b, _) = other; 38 | a.cmp(b) 39 | } 40 | } 41 | 42 | impl PartialOrd for Param { 43 | fn partial_cmp(&self, other: &Self) -> Option { 44 | Some(self.cmp(other)) 45 | } 46 | } 47 | 48 | #[derive(Clone, Debug, PartialEq, Eq)] 49 | pub enum Expr { 50 | Value(Value), 51 | Apply(Box, Vec), 52 | BinaryOp(BinOp, Box, Box), 53 | Switch(Box, Vec, Vec), 54 | Tuple(Vec), 55 | Projection(Box, usize), 56 | Assert(Box, Box), 57 | Uninitialized, 58 | } 59 | 60 | impl Expr { 61 | pub fn contains(&self, target: &Self) -> bool { 62 | *self == *target 63 | || match self { 64 | Expr::Apply(e1, e2) => e1.contains(target) || e2.iter().any(|e| e.contains(target)), 65 | Expr::Switch(e1, e2, e3) => { 66 | e1.contains(target) 67 | || e2.iter().any(|e| e.contains(target)) 68 | || e3.iter().any(|e| e.contains(target)) 69 | } 70 | Expr::BinaryOp(_, e1, e2) => e1.contains(target) || e2.contains(target), 71 | Expr::Tuple(e1) => e1.iter().any(|e| e.contains(target)), 72 | _ => false, 73 | } 74 | } 75 | 76 | pub fn replace(&mut self, target: &Self, substitution: &Self) { 77 | if *self == *target { 78 | *self = substitution.clone(); 79 | } else { 80 | match self { 81 | Expr::Apply(e1, e2) => { 82 | e1.replace(target, substitution); 83 | for e in e2 { 84 | e.replace(target, substitution); 85 | } 86 | } 87 | Expr::Switch(e1, e2, e3) => { 88 | e1.replace(target, substitution); 89 | for e in e2 { 90 | e.replace(target, substitution); 91 | } 92 | for e in e3 { 93 | e.replace(target, substitution); 94 | } 95 | } 96 | Expr::BinaryOp(_, e1, e2) => { 97 | e1.replace(target, substitution); 98 | e2.replace(target, substitution); 99 | } 100 | Expr::Tuple(e1) => { 101 | for e in e1 { 102 | e.replace(target, substitution); 103 | } 104 | } 105 | _ => (), 106 | } 107 | } 108 | } 109 | } 110 | 111 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 112 | pub enum Value { 113 | Arg(usize, Ty), 114 | Const(u128, Ty), 115 | Function(DefId, Ty), 116 | ConstParam(Param), 117 | } 118 | -------------------------------------------------------------------------------- /sire/src/sir/display.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | impl fmt::Display for FuncDef { 4 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 5 | let params = match &self.ty { 6 | Ty::Func(_, params) => { 7 | params.iter().map(|p| p.to_string()).collect::>().join(" ") 8 | } 9 | _ => unreachable!(), 10 | }; 11 | 12 | write!(f, "(defun {:?}[{}] {} {})", self.def_id, params, self.ty, self.body) 13 | } 14 | } 15 | 16 | impl fmt::Display for Ty { 17 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 18 | match self { 19 | Ty::Int(n) => write!(f, "(int {})", n), 20 | Ty::Uint(n) => write!(f, "(uint {})", n), 21 | Ty::Bool => write!(f, "bool"), 22 | Ty::Func(args_ty, _) => { 23 | write!(f, "{}", args_ty.iter().map(|x| x.to_string()).collect::>().join(" "),) 24 | } 25 | Ty::Tuple(fields_ty) => write!( 26 | f, 27 | "({})", 28 | fields_ty.iter().map(|x| x.to_string()).collect::>().join(", "), 29 | ), 30 | } 31 | } 32 | } 33 | 34 | impl fmt::Display for Param { 35 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 | let Param(index, ty) = self; 37 | write!(f, "(p{} {})", index, ty) 38 | } 39 | } 40 | 41 | impl fmt::Display for Expr { 42 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 43 | match self { 44 | Expr::Value(value) => write!(f, "{}", value), 45 | Expr::Apply(func, args) => write!( 46 | f, 47 | "({} {})", 48 | func, 49 | args.iter().map(|x| x.to_string()).collect::>().join(" ") 50 | ), 51 | Expr::BinaryOp(op, e1, e2) => { 52 | let op_string = match op { 53 | BinOp::Add => "+", 54 | BinOp::Sub => "-", 55 | BinOp::Mul => "*", 56 | BinOp::Div => "/", 57 | BinOp::Rem => "%", 58 | BinOp::Eq => "=", 59 | BinOp::Lt => "<", 60 | BinOp::Le => "<=", 61 | BinOp::Ne => "!=", 62 | BinOp::Ge => ">=", 63 | BinOp::Gt => ">", 64 | _ => unreachable!(), 65 | }; 66 | write!(f, "({} {} {})", op_string, e1, e2) 67 | } 68 | Expr::Switch(value, branches, targets) => write!( 69 | f, 70 | "(switch {} {} (else -> {}))", 71 | value, 72 | branches 73 | .iter() 74 | .zip(targets.iter()) 75 | .map(|(b, t)| format!("({} -> {})", b, t)) 76 | .collect::>() 77 | .join(" "), 78 | targets.last().unwrap() 79 | ), 80 | Expr::Tuple(fields) => write!( 81 | f, 82 | "(tuple {})", 83 | fields.iter().map(|x| x.to_string()).collect::>().join(" "), 84 | ), 85 | Expr::Projection(e1, i) => write!(f, "(proj {} {})", e1, i), 86 | Expr::Assert(e1, e2) => write!(f, "(assert {} {})", e1, e2), 87 | Expr::Uninitialized => write!(f, "uninitialized"), 88 | } 89 | } 90 | } 91 | 92 | impl fmt::Display for Value { 93 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 94 | match self { 95 | Value::Arg(n, _) => write!(f, "_{}", n), 96 | Value::Const(value, ty) => write!(f, "(const {} {})", ty, value), 97 | Value::Function(def_id, _) => write!(f, "{:?}", def_id), 98 | Value::ConstParam(Param(index, _)) => write!(f, "p{}", index), 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /sire/src/sir/optimize.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | impl Expr { 4 | pub fn optimize(&mut self) { 5 | Optimizer::optimize(self); 6 | } 7 | } 8 | 9 | #[derive(Default)] 10 | struct Optimizer { 11 | expr: Option, 12 | } 13 | 14 | impl Optimizer { 15 | fn optimize(expr: &mut Expr) { 16 | Self::default().visit_mut_expr(expr); 17 | } 18 | } 19 | 20 | impl VisitorMut for Optimizer { 21 | fn visit_mut_expr(&mut self, expr: &mut Expr) { 22 | self.super_mut_expr(expr); 23 | 24 | if let Some(new_expr) = self.expr.take() { 25 | *expr = new_expr; 26 | } 27 | } 28 | 29 | fn visit_mut_projection(&mut self, tuple: &mut Expr, index: usize) { 30 | self.visit_mut_expr(tuple); 31 | 32 | if let Expr::Tuple(fields) = tuple { 33 | if let Some(field) = fields.get(index) { 34 | self.expr = Some(field.clone()); 35 | } 36 | } else { 37 | unreachable!() 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sire/src/sir/ty.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 4 | pub enum Ty { 5 | Int(usize), 6 | Uint(usize), 7 | Bool, 8 | Func(Vec, Vec), 9 | Tuple(Vec), 10 | } 11 | 12 | impl Ty { 13 | pub fn bits(&self) -> Option { 14 | match self { 15 | Ty::Int(n) | Ty::Uint(n) => Some(*n), 16 | Ty::Bool => Some(8), 17 | Ty::Func(_, _) => None, 18 | Ty::Tuple(fields_ty) => { 19 | let mut total = 0; 20 | for ty in fields_ty { 21 | total += ty.bits()?; 22 | } 23 | Some(total) 24 | } 25 | } 26 | } 27 | 28 | pub fn bytes(&self) -> Option { 29 | self.bits().map(|x| x / 8) 30 | } 31 | } 32 | 33 | pub trait Typed { 34 | fn ty(&self) -> Ty; 35 | } 36 | 37 | impl Typed for Param { 38 | fn ty(&self) -> Ty { 39 | let Param(_, ty) = self; 40 | ty.clone() 41 | } 42 | } 43 | 44 | impl Typed for Expr { 45 | fn ty(&self) -> Ty { 46 | match self { 47 | Expr::Value(value) => value.ty(), 48 | Expr::Apply(e1, _) => match e1.ty() { 49 | Ty::Func(args_ty, _) => args_ty.first().unwrap().clone(), 50 | _ => unreachable!(), 51 | }, 52 | Expr::BinaryOp(op, e1, _) => match op { 53 | BinOp::Eq | BinOp::Lt | BinOp::Le | BinOp::Ne | BinOp::Ge | BinOp::Gt => Ty::Bool, 54 | _ => e1.ty(), 55 | }, 56 | Expr::Switch(_, _, e1) => e1.first().unwrap().ty(), 57 | Expr::Tuple(e1) => Ty::Tuple(e1.iter().map(|e| e.ty()).collect()), 58 | Expr::Projection(e1, i) => match **e1 { 59 | Expr::Tuple(ref fields) => fields.get(*i).unwrap().ty(), 60 | _ => unreachable!(), 61 | }, 62 | Expr::Assert(_, e1) => e1.ty(), 63 | Expr::Uninitialized => unreachable!(), 64 | } 65 | } 66 | } 67 | 68 | impl Typed for Value { 69 | fn ty(&self) -> Ty { 70 | match self { 71 | Value::Arg(_, ty) => ty.clone(), 72 | Value::Const(_, ty) => ty.clone(), 73 | Value::Function(_, ty) => ty.clone(), 74 | Value::ConstParam(param) => param.ty(), 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /sire/src/sir/visitor.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub trait Visitor { 4 | fn visit_expr(&mut self, expr: &Expr) { 5 | self.super_expr(expr) 6 | } 7 | 8 | fn visit_value(&mut self, value: &Value) { 9 | self.super_value(value) 10 | } 11 | 12 | fn visit_apply(&mut self, func: &Expr, args: &[Expr]) { 13 | self.super_apply(func, args) 14 | } 15 | 16 | fn visit_binary_op(&mut self, bin_op: &BinOp, e1: &Expr, e2: &Expr) { 17 | self.super_binary_op(bin_op, e1, e2) 18 | } 19 | 20 | fn visit_switch(&mut self, expr: &Expr, values: &[Expr], results: &[Expr]) { 21 | self.super_switch(expr, values, results) 22 | } 23 | 24 | fn visit_tuple(&mut self, fields: &[Expr]) { 25 | self.super_tuple(fields) 26 | } 27 | 28 | fn visit_projection(&mut self, tuple: &Expr, index: usize) { 29 | self.super_projection(tuple, index) 30 | } 31 | 32 | fn visit_assert(&mut self, condition: &Expr, result: &Expr) { 33 | self.super_assert(condition, result) 34 | } 35 | 36 | fn super_expr(&mut self, expr: &Expr) { 37 | match expr { 38 | Expr::Value(e) => self.visit_value(e), 39 | Expr::Apply(e1, e2) => self.visit_apply(e1, e2), 40 | Expr::BinaryOp(op, e1, e2) => self.visit_binary_op(op, e1, e2), 41 | Expr::Switch(e1, e2, e3) => self.visit_switch(e1, e2, e3), 42 | Expr::Tuple(e1) => self.visit_tuple(e1), 43 | Expr::Projection(e1, index) => self.visit_projection(e1, *index), 44 | Expr::Assert(e1, e2) => self.visit_assert(e1, e2), 45 | Expr::Uninitialized => (), 46 | } 47 | } 48 | 49 | fn super_value(&mut self, _: &Value) { 50 | () 51 | } 52 | 53 | fn super_apply(&mut self, func: &Expr, args: &[Expr]) { 54 | self.visit_expr(func); 55 | for arg in args { 56 | self.visit_expr(arg); 57 | } 58 | } 59 | 60 | fn super_binary_op(&mut self, _: &BinOp, e1: &Expr, e2: &Expr) { 61 | self.visit_expr(e1); 62 | self.visit_expr(e2); 63 | } 64 | 65 | fn super_switch(&mut self, expr: &Expr, values: &[Expr], results: &[Expr]) { 66 | self.visit_expr(expr); 67 | for value in values { 68 | self.visit_expr(value); 69 | } 70 | for result in results { 71 | self.visit_expr(result); 72 | } 73 | } 74 | 75 | fn super_tuple(&mut self, fields: &[Expr]) { 76 | for field in fields { 77 | self.visit_expr(field); 78 | } 79 | } 80 | 81 | fn super_projection(&mut self, tuple: &Expr, _: usize) { 82 | self.visit_expr(tuple) 83 | } 84 | 85 | fn super_assert(&mut self, condition: &Expr, result: &Expr) { 86 | self.visit_expr(condition); 87 | self.visit_expr(result); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /sire/src/sir/visitor_mut.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub trait VisitorMut { 4 | fn visit_mut_expr(&mut self, expr: &mut Expr) { 5 | self.super_mut_expr(expr) 6 | } 7 | 8 | fn visit_mut_value(&mut self, value: &mut Value) { 9 | self.super_mut_value(value) 10 | } 11 | 12 | fn visit_mut_apply(&mut self, func: &mut Expr, args: &mut [Expr]) { 13 | self.super_mut_apply(func, args) 14 | } 15 | 16 | fn visit_mut_binary_op(&mut self, bin_op: &mut BinOp, e1: &mut Expr, e2: &mut Expr) { 17 | self.super_mut_binary_op(bin_op, e1, e2) 18 | } 19 | 20 | fn visit_mut_switch(&mut self, expr: &mut Expr, values: &mut [Expr], results: &mut [Expr]) { 21 | self.super_mut_switch(expr, values, results) 22 | } 23 | 24 | fn visit_mut_tuple(&mut self, fields: &mut [Expr]) { 25 | self.super_mut_tuple(fields) 26 | } 27 | 28 | fn visit_mut_projection(&mut self, tuple: &mut Expr, index: usize) { 29 | self.super_mut_projection(tuple, index) 30 | } 31 | 32 | fn visit_mut_assert(&mut self, condition: &mut Expr, result: &mut Expr) { 33 | self.super_mut_assert(condition, result) 34 | } 35 | 36 | fn super_mut_expr(&mut self, expr: &mut Expr) { 37 | match expr { 38 | Expr::Value(e) => self.visit_mut_value(e), 39 | Expr::Apply(e1, e2) => self.visit_mut_apply(e1, e2), 40 | Expr::BinaryOp(op, e1, e2) => self.visit_mut_binary_op(op, e1, e2), 41 | Expr::Switch(e1, e2, e3) => self.visit_mut_switch(e1, e2, e3), 42 | Expr::Tuple(e1) => self.visit_mut_tuple(e1), 43 | Expr::Projection(e1, index) => self.visit_mut_projection(e1, *index), 44 | Expr::Assert(e1, e2) => self.visit_mut_assert(e1, e2), 45 | Expr::Uninitialized => (), 46 | } 47 | } 48 | fn super_mut_value(&mut self, _: &mut Value) { 49 | () 50 | } 51 | 52 | fn super_mut_apply(&mut self, func: &mut Expr, args: &mut [Expr]) { 53 | self.visit_mut_expr(func); 54 | for arg in args { 55 | self.visit_mut_expr(arg); 56 | } 57 | } 58 | 59 | fn super_mut_binary_op(&mut self, _: &mut BinOp, e1: &mut Expr, e2: &mut Expr) { 60 | self.visit_mut_expr(e1); 61 | self.visit_mut_expr(e2); 62 | } 63 | 64 | fn super_mut_switch(&mut self, expr: &mut Expr, values: &mut [Expr], results: &mut [Expr]) { 65 | self.visit_mut_expr(expr); 66 | for value in values { 67 | self.visit_mut_expr(value); 68 | } 69 | for result in results { 70 | self.visit_mut_expr(result); 71 | } 72 | } 73 | 74 | fn super_mut_tuple(&mut self, fields: &mut [Expr]) { 75 | for field in fields { 76 | self.visit_mut_expr(field); 77 | } 78 | } 79 | 80 | fn super_mut_projection(&mut self, tuple: &mut Expr, _: usize) { 81 | self.visit_mut_expr(tuple) 82 | } 83 | 84 | fn super_mut_assert(&mut self, condition: &mut Expr, result: &mut Expr) { 85 | self.visit_mut_expr(condition); 86 | self.visit_mut_expr(result); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /sire_run/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "rustc-workspace-hack" 5 | version = "1.0.0" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "sire" 10 | version = "0.1.0" 11 | 12 | [[package]] 13 | name = "sire-run" 14 | version = "0.1.0" 15 | dependencies = [ 16 | "rustc-workspace-hack 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 17 | "sire 0.1.0", 18 | "sire-smt 0.1.0", 19 | ] 20 | 21 | [[package]] 22 | name = "sire-smt" 23 | version = "0.1.0" 24 | dependencies = [ 25 | "sire 0.1.0", 26 | ] 27 | 28 | [metadata] 29 | "checksum rustc-workspace-hack 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc71d2faa173b74b232dedc235e3ee1696581bb132fc116fa3626d6151a1a8fb" 30 | -------------------------------------------------------------------------------- /sire_run/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sire-run" 3 | version = "0.1.0" 4 | authors = ["Christian Poveda "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | rustc-workspace-hack = "1.0.0" 9 | sire = { path = "../sire" } 10 | sire-smt = { path = "../sire_smt" } 11 | -------------------------------------------------------------------------------- /sire_run/code.rs: -------------------------------------------------------------------------------- 1 | fn main () { 2 | // bar(1,2); 3 | // foo(1,1); 4 | // distance(0,0); 5 | // alt_dist(0,0); 6 | // rec_dist(0,0); 7 | bound_a(0); 8 | // bound_b(0); 9 | } 10 | 11 | fn bar(n: usize, m: usize) -> usize { 12 | if n >= m { n } else { m } 13 | } 14 | 15 | fn foo(x: i32, y: i32) -> i32 { 16 | 17 | let z = x + 1; 18 | let w = y - 1; 19 | if false { 20 | 0 21 | } else { 22 | z - w + 4 23 | } 24 | } 25 | 26 | fn distance(x: i32, y: i32) -> i32 { 27 | if x > y { 28 | x - y 29 | } else { 30 | y - x 31 | } 32 | } 33 | 34 | fn alt_dist(x: i32, y: i32) -> i32 { 35 | let sign: i32; 36 | if x > y { 37 | sign = 1; 38 | } else { 39 | sign = -1; 40 | } 41 | sign * (x - y) 42 | } 43 | 44 | fn rec_dist(x: i32, y: i32) -> i32 { 45 | if x > y { 46 | x - y 47 | } else { 48 | rec_dist(y, x) 49 | } 50 | } 51 | // 52 | fn bound_a(x: usize) -> bool { 53 | x > 0 54 | } 55 | // 56 | // fn bound_b(x: usize) -> bool { 57 | // x + 1 > 1 58 | // } 59 | -------------------------------------------------------------------------------- /sire_run/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | 3 | extern crate rustc; 4 | extern crate rustc_driver; 5 | extern crate rustc_interface; 6 | extern crate syntax; 7 | 8 | 9 | use rustc::hir::{def_id::LOCAL_CRATE, ItemKind}; 10 | use rustc_driver::{report_ices_to_stderr_if_any, run_compiler, Callbacks, Compilation}; 11 | use rustc_interface::interface; 12 | 13 | use sire::eval::Evaluator; 14 | use sire_smt::smtlib::ToSmtlib; 15 | 16 | fn find_sysroot() -> String { 17 | let home = option_env!("RUSTUP_HOME").or(option_env!("MULTIRUST_HOME")); 18 | let toolchain = option_env!("RUSTUP_TOOLCHAIN").or(option_env!("MULTIRUST_TOOLCHAIN")); 19 | 20 | match (home, toolchain) { 21 | (Some(home), Some(toolchain)) => format!("{}/toolchains/{}", home, toolchain), 22 | _ => option_env!("RUST_SYSROOT") 23 | .expect("could not find sysroot") 24 | .to_owned(), 25 | } 26 | } 27 | 28 | struct SireCompilerCalls; 29 | 30 | impl Callbacks for SireCompilerCalls { 31 | fn after_parsing(&mut self, _compiler: &interface::Compiler) -> Compilation { 32 | Compilation::Continue 33 | } 34 | 35 | fn after_analysis(&mut self, compiler: &interface::Compiler) -> Compilation { 36 | compiler.session().abort_if_errors(); 37 | compiler.global_ctxt().unwrap().peek_mut().enter(|tcx| { 38 | let mut evaluator = Evaluator::from_tcx(tcx).unwrap(); 39 | let mut functions = Vec::new(); 40 | 41 | let (main_id, _) = tcx.entry_fn(LOCAL_CRATE).expect("no main function found!"); 42 | 43 | let hir = tcx.hir(); 44 | 45 | for (&hir_id, item) in &hir.krate().items { 46 | if let ItemKind::Fn(_, _, _, _) = item.node { 47 | let def_id = hir.local_def_id(hir_id); 48 | if def_id != main_id { 49 | functions.push(evaluator.eval_mir(def_id).unwrap()); 50 | } 51 | } 52 | } 53 | 54 | for func in functions { 55 | println!("{}", func); 56 | println!("{}", func.to_smtlib()); 57 | } 58 | }); 59 | 60 | compiler.session().abort_if_errors(); 61 | Compilation::Stop 62 | } 63 | } 64 | 65 | fn main() { 66 | let mut rustc_args = std::env::args().collect::>(); 67 | let sysroot_flag = String::from("--sysroot"); 68 | 69 | if !rustc_args.contains(&sysroot_flag) { 70 | rustc_args.push(sysroot_flag); 71 | rustc_args.push(find_sysroot()); 72 | } 73 | 74 | let result = report_ices_to_stderr_if_any(move || { 75 | run_compiler(&rustc_args, &mut SireCompilerCalls, None, None) 76 | }) 77 | .and_then(|result| result); 78 | 79 | std::process::exit(result.is_err() as i32); 80 | } 81 | -------------------------------------------------------------------------------- /sire_smt/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "sire" 5 | version = "0.1.0" 6 | 7 | [[package]] 8 | name = "sire-smt" 9 | version = "0.1.0" 10 | dependencies = [ 11 | "sire 0.1.0", 12 | ] 13 | -------------------------------------------------------------------------------- /sire_smt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sire-smt" 3 | version = "0.1.0" 4 | authors = ["Christian Poveda "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | sire = { path = "../sire" } 11 | 12 | [dev-dependencies] 13 | rustc = {path = "../../librustc"} 14 | -------------------------------------------------------------------------------- /sire_smt/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(box_patterns)] 2 | 3 | use sire::sir::*; 4 | 5 | use crate::smtlib::ToSmtlib; 6 | 7 | pub mod smtlib; 8 | mod z3; 9 | 10 | pub fn check_equality(a: &FuncDef, b: &FuncDef) -> Result> { 11 | if let (Ty::Func(a_args_ty, a_params), Ty::Func(b_args_ty, b_params)) = (&a.ty, &b.ty) { 12 | if a_args_ty == b_args_ty && a_params == b_params { 13 | let mut instances = a.body.find_datatype_instances(); 14 | for instance in b.body.find_datatype_instances() { 15 | if !instances.contains(&instance) { 16 | instances.push(instance); 17 | } 18 | } 19 | // Datatype declaration 20 | let mut code = vec![ 21 | "(declare-datatypes (T1 T2) ((Tuple (tuple (first T1) (second T2)))))".to_owned(), 22 | "(declare-datatypes () ((Unit (unit))))".to_owned(), 23 | ]; 24 | // Instances of datatypes 25 | code.extend_from_slice( 26 | &instances 27 | .iter() 28 | .map(|ty| format!("(declare-const _ {})", ty.to_smtlib())) 29 | .collect::>(), 30 | ); 31 | // Function declarations and equality assertin 32 | code.extend_from_slice(&[ 33 | a.to_smtlib(), 34 | b.to_smtlib(), 35 | gen_equality_assertion(a.def_id, b.def_id, a_args_ty, a_params), 36 | "(check-sat)".to_owned(), 37 | ]); 38 | let code = code.join("\n"); 39 | println!("{}", code); 40 | return z3::call(&code).map(CheckResult::from_string); 41 | } 42 | } 43 | Ok(CheckResult::Unsat) 44 | } 45 | 46 | #[derive(Debug, PartialEq, Eq)] 47 | pub enum CheckResult { 48 | Sat, 49 | Unsat, 50 | Undecided, 51 | Unknown(String), 52 | } 53 | 54 | impl CheckResult { 55 | fn from_string(s: String) -> Self { 56 | if s == "sat\n" { 57 | CheckResult::Sat 58 | } else if s == "unsat\n" { 59 | CheckResult::Unsat 60 | } else if s == "unknown\n" { 61 | CheckResult::Undecided 62 | } else { 63 | CheckResult::Unknown(s) 64 | } 65 | } 66 | } 67 | 68 | pub fn gen_equality_assertion(a: DefId, b: DefId, args_ty: &[Ty], params: &[Param]) -> String { 69 | if args_ty.len() + params.len() > 1 { 70 | let (args_with_ty, args) = args_ty 71 | .iter() 72 | .enumerate() 73 | .skip(1) 74 | .map(|(i, ty)| (format!("(x{} {})", i, ty.to_smtlib()), format!("x{}", i))) 75 | .chain(params.iter().map(|Param(index, ty)| { 76 | (format!("(p{} {})", index, ty.to_smtlib()), format!("p{}", index)) 77 | })) 78 | .unzip::, Vec>(); 79 | 80 | let args_with_ty = args_with_ty.join(" "); 81 | let args = args.join(" "); 82 | 83 | format!( 84 | "(assert (forall ({}) (= ({} {}) ({} {}))))", 85 | args_with_ty, 86 | a.to_smtlib(), 87 | args, 88 | b.to_smtlib(), 89 | args 90 | ) 91 | } else { 92 | format!("(assert (= {} {}))", a.to_smtlib(), b.to_smtlib(),) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /sire_smt/src/smtlib.rs: -------------------------------------------------------------------------------- 1 | use sire::sir::*; 2 | 3 | pub trait ToSmtlib { 4 | fn to_smtlib(&self) -> String; 5 | } 6 | 7 | impl ToSmtlib for FuncDef { 8 | fn to_smtlib(&self) -> String { 9 | let body = self.body.to_smtlib(); 10 | let (args, params) = match &self.ty { 11 | Ty::Func(args, params) => (args, params), 12 | _ => unreachable!(), 13 | }; 14 | 15 | let ret_ty = args[0].to_smtlib(); 16 | 17 | let args_with_ty = args 18 | .iter() 19 | .enumerate() 20 | .skip(1) 21 | .map(|(i, ty)| format!("(x{} {})", i, ty.to_smtlib())) 22 | .chain(params.iter().map(|Param(index, ty)| format!("(p{} {})", index, ty.to_smtlib()))) 23 | .collect::>() 24 | .join(" "); 25 | 26 | let def = if self.is_recursive() { "define-fun-rec" } else { "define-fun" }; 27 | format!( 28 | "({def} {name} ({args_with_ty}) {ret_ty} {body})", 29 | def = def, 30 | name = self.def_id.to_smtlib(), 31 | ret_ty = ret_ty, 32 | args_with_ty = args_with_ty, 33 | body = body 34 | ) 35 | } 36 | } 37 | 38 | impl ToSmtlib for Param { 39 | fn to_smtlib(&self) -> String { 40 | let Param(index, _) = self; 41 | format!("p{}", index) 42 | } 43 | } 44 | 45 | impl ToSmtlib for Ty { 46 | fn to_smtlib(&self) -> String { 47 | match self { 48 | Ty::Bool => "Bool".to_owned(), 49 | Ty::Tuple(fields) => { 50 | let mut fields = fields.iter().rev(); 51 | if let Some(first) = fields.next() { 52 | let mut buffer = first.to_smtlib(); 53 | for field in fields { 54 | buffer = format!("(Tuple {} {})", field, buffer); 55 | } 56 | buffer 57 | } else { 58 | "Unit".to_owned() 59 | } 60 | } 61 | _ => format!("(_ BitVec {})", self.bits().unwrap()), 62 | } 63 | } 64 | } 65 | 66 | impl ToSmtlib for DefId { 67 | fn to_smtlib(&self) -> String { 68 | format!("func_{}_{}", self.krate.as_u32(), self.index.as_u32()) 69 | } 70 | } 71 | 72 | impl ToSmtlib for Value { 73 | fn to_smtlib(&self) -> String { 74 | match self { 75 | Value::Arg(n, _) => format!("x{}", n), 76 | Value::Const(b, ty) => match ty { 77 | Ty::Bool => format!("{}", *b != 0), 78 | ty => format!("(_ bv{} {})", b, ty.bits().unwrap()), 79 | }, 80 | Value::Function(d, _) => d.to_smtlib(), 81 | Value::ConstParam(p) => p.to_smtlib(), 82 | } 83 | } 84 | } 85 | 86 | impl ToSmtlib for Expr { 87 | fn to_smtlib(&self) -> String { 88 | match self { 89 | Expr::Value(value) => value.to_smtlib(), 90 | Expr::BinaryOp(op, e1, e2) => { 91 | let smt_op = match e1.ty() { 92 | Ty::Bool => match op { 93 | BinOp::Eq => "=", 94 | BinOp::Ne => "!=", 95 | _ => unreachable!(), 96 | }, 97 | Ty::Int(_) => match op { 98 | BinOp::Add => "bvadd", 99 | BinOp::Sub => "bvsub", 100 | BinOp::Mul => "bvmul", 101 | BinOp::Div => "bvsdiv", 102 | BinOp::Rem => "bvsrem", 103 | BinOp::Eq => "=", 104 | BinOp::Lt => "bvslt", 105 | BinOp::Le => "bvsle", 106 | BinOp::Ne => "!=", 107 | BinOp::Ge => "bvsge", 108 | BinOp::Gt => "bvsgt", 109 | _ => unreachable!(), 110 | }, 111 | Ty::Uint(_) => match op { 112 | BinOp::Add => "bvadd", 113 | BinOp::Sub => "bvsub", 114 | BinOp::Mul => "bvmul", 115 | BinOp::Div => "bvudiv", 116 | BinOp::Rem => "bvurem", 117 | BinOp::Eq => "=", 118 | BinOp::Lt => "bvult", 119 | BinOp::Le => "bvule", 120 | BinOp::Ne => "!=", 121 | BinOp::Ge => "bvuge", 122 | BinOp::Gt => "bvugt", 123 | _ => unreachable!(), 124 | }, 125 | _ => unreachable!(), 126 | }; 127 | format!("({} {} {})", smt_op, e1.to_smtlib(), e2.to_smtlib()) 128 | } 129 | Expr::Apply(f, es) => format!( 130 | "({} {})", 131 | f.to_smtlib(), 132 | es.iter().map(ToSmtlib::to_smtlib).collect::>().join(" ") 133 | ), 134 | Expr::Switch(val, cs, bs) => { 135 | if let Ty::Bool = val.ty() { 136 | format!("(ite {} {} {})", val.to_smtlib(), bs[1].to_smtlib(), bs[0].to_smtlib()) 137 | } else { 138 | let mut cond = bs.last().unwrap().to_smtlib(); 139 | for i in (0..cs.len()).rev() { 140 | cond = format!( 141 | "(ite (= {} {}) {} {})", 142 | val.to_smtlib(), 143 | cs[i].to_smtlib(), 144 | bs[i].to_smtlib(), 145 | cond 146 | ); 147 | } 148 | cond 149 | } 150 | } 151 | Expr::Tuple(fields) => { 152 | let mut fields = fields.iter().rev(); 153 | if let Some(first) = fields.next() { 154 | let mut buffer = first.to_smtlib(); 155 | for field in fields { 156 | buffer = format!("(tuple {} {})", field.to_smtlib(), buffer); 157 | } 158 | buffer 159 | } else { 160 | "unit".to_owned() 161 | } 162 | } 163 | Expr::Projection(box tuple, index) => { 164 | let mut buffer = tuple.to_smtlib(); 165 | match index { 166 | 0 => buffer = format!("(first {})", buffer), 167 | 1 => buffer = format!("(second {})", buffer), 168 | // FIXME: Support larger tuples 169 | _ => unimplemented!(), 170 | } 171 | buffer 172 | } 173 | // FIXME: Handle assertions correctly 174 | Expr::Assert(_, result) => result.to_smtlib(), 175 | _ => unimplemented!(), 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /sire_smt/src/z3.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Write}; 2 | use std::process::{Command, Stdio}; 3 | 4 | pub fn call(code: &str) -> Result> { 5 | let mut buffer = String::new(); 6 | 7 | let child = 8 | Command::new("z3").arg("-in").stdin(Stdio::piped()).stdout(Stdio::piped()).spawn()?; 9 | 10 | child.stdin.expect("stdin is none").write(code.as_bytes())?; 11 | 12 | child.stdout.expect("stdout is none").read_to_string(&mut buffer)?; 13 | 14 | Ok(buffer) 15 | } 16 | -------------------------------------------------------------------------------- /sire_smt/tests/lib.rs: -------------------------------------------------------------------------------- 1 | use rustc::hir::def_id::{DefIndex, CrateNum}; 2 | 3 | use sire_smt::check_equality; 4 | use sire::sir::*; 5 | 6 | #[test] 7 | fn test_equality_sat() -> Result<(), Box> { 8 | let a = FuncDef { 9 | def_id: DefId { krate: CrateNum::new(0), index: DefIndex::from_usize(1)}, 10 | body: Expr::BinaryOp( 11 | BinOp::Add, 12 | Box::new(Expr::Value(Value::Arg(1, Ty::Uint(32)))), 13 | Box::new(Expr::Value(Value::Arg(1, Ty::Uint(32)))), 14 | ), 15 | ty: Ty::Func(vec![Ty::Uint(32), Ty::Uint(32)]), 16 | }; 17 | 18 | let b = FuncDef { 19 | def_id: DefId { krate: CrateNum::new(0), index: DefIndex::from_usize(2)}, 20 | body: Expr::BinaryOp( 21 | BinOp::Mul, 22 | Box::new(Expr::Value(Value::Const(2, Ty::Uint(32)))), 23 | Box::new(Expr::Value(Value::Arg(1, Ty::Uint(32)))), 24 | ), 25 | ty: Ty::Func(vec![Ty::Uint(32), Ty::Uint(32)]), 26 | }; 27 | 28 | assert_eq!(sire_smt::CheckResult::Sat, check_equality(&a, &b)?); 29 | 30 | Ok(()) 31 | } 32 | 33 | #[test] 34 | fn test_equality_unsat() -> Result<(), Box> { 35 | let a = FuncDef { 36 | def_id: DefId { krate: CrateNum::new(0), index: DefIndex::from_usize(1)}, 37 | body: Expr::BinaryOp( 38 | BinOp::Add, 39 | Box::new(Expr::Value(Value::Arg(1, Ty::Uint(32)))), 40 | Box::new(Expr::Value(Value::Arg(1, Ty::Uint(32)))), 41 | ), 42 | ty: Ty::Func(vec![Ty::Uint(32), Ty::Uint(32)]), 43 | }; 44 | 45 | let b = FuncDef { 46 | def_id: DefId { krate: CrateNum::new(0), index: DefIndex::from_usize(2)}, 47 | body: Expr::Value(Value::Arg(1, Ty::Uint(32))), 48 | ty: Ty::Func(vec![Ty::Uint(32), Ty::Uint(32)]), 49 | }; 50 | 51 | assert_eq!(sire_smt::CheckResult::Unsat, check_equality(&a, &b)?); 52 | 53 | Ok(()) 54 | } 55 | --------------------------------------------------------------------------------