├── .gitignore ├── Cargo.toml ├── run.sh ├── src ├── fake_logging.rs ├── operator.rs ├── builder.rs ├── component.rs └── lib.rs ├── notes.md ├── README.md └── examples └── brahma.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | *.log 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "synth-loop-free-prog" 3 | version = "0.1.0" 4 | authors = ["Nick Fitzgerald "] 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 | z3 = "0.4.0" 11 | rand = { version = "0.7.2", features = ["small_rng"] } 12 | log = { version = "0.4.8", optional = true } 13 | 14 | [dev-dependencies] 15 | env_logger = "0.7.1" 16 | structopt = "0.3.7" 17 | 18 | [profile.release] 19 | debug = true 20 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -ex 4 | 5 | cd $(dirname $0) 6 | 7 | # if [[ -f "~/souper/third_party/z3-install/lib/libz3.so" ]]; then 8 | # export LD_LIBRARY_PATH="~/souper/third_party/z3-install/lib" 9 | # fi 10 | 11 | cargo build --example brahma --release --all-features 12 | 13 | echo "###############################################################################" >> run.stdout.log 14 | echo "# Running with '$@'" >> run.stdout.log 15 | echo "###############################################################################" >> run.stdout.log 16 | export RUST_LOG=synth_loop_free_prog=debug 17 | cargo run --example brahma --release --all-features -- $@ 2>> run.stderr.log >> run.stdout.log 18 | -------------------------------------------------------------------------------- /src/fake_logging.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_macros)] 2 | macro_rules! debug { 3 | ( $( $e:expr ),* $(,)* ) => { 4 | if false { 5 | $( 6 | let _ = $e; 7 | )* 8 | } 9 | } 10 | } 11 | 12 | #[allow(unused_macros)] 13 | macro_rules! error { 14 | ( $( $e:expr ),* $(,)* ) => { 15 | if false { 16 | $( 17 | let _ = $e; 18 | )* 19 | } 20 | } 21 | } 22 | 23 | #[allow(unused_macros)] 24 | macro_rules! info { 25 | ( $( $e:expr ),* $(,)* ) => { 26 | if false { 27 | $( 28 | let _ = $e; 29 | )* 30 | } 31 | } 32 | } 33 | 34 | #[allow(unused_macros)] 35 | macro_rules! log { 36 | ( $( $e:expr ),* $(,)* ) => { 37 | if false { 38 | $( 39 | let _ = $e; 40 | )* 41 | } 42 | } 43 | } 44 | 45 | #[allow(unused_macros)] 46 | macro_rules! trace { 47 | ( $( $e:expr ),* $(,)* ) => { 48 | if false { 49 | $( 50 | let _ = $e; 51 | )* 52 | } 53 | } 54 | } 55 | 56 | #[allow(unused_macros)] 57 | macro_rules! warn { 58 | ( $( $e:expr ),* $(,)* ) => { 59 | if false { 60 | $( 61 | let _ = $e; 62 | )* 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | * TODO: try and combine `add` and `const 1` into a single `add1` component, and 2 | do the same for other combos, and see if that helps with synthesizing 3 | popcount. That is what brahma did, iiuc. 4 | 5 | * DONE: actually use the narrowest width for lines that we can 6 | 7 | * DONE: it seems like attempting to synthesize a too-minimal program takes too 8 | long, so it would be faster to go longest -> shortest, keeping only the last, 9 | shortest program we synthesized. 10 | 11 | # Notes from reading Souper source 12 | 13 | * `include/souper/Infer/InstSynthesis.h` 14 | * `lib/Infer/InstSynthesis.cpp` 15 | * DONE: Souper says the final output is either equal to one of the original inputs, or 16 | the location of the output is equal to one of the component's output locations. 17 | * And then lets the `(l_x = l_y) => x = y` handle ensuring that the final 18 | output is correct 19 | * this means you could ask for smaller and smaller programs to be synthesized 20 | by requiring that L[O] be smaller and smaller... 21 | * DONE: Souper does a bunch of work to avoid invalid wirings in the connectivity 22 | constraint and elsewhere. This should cut down on the number of clauses / 23 | constraints given to the solver a bunch. 24 | * DONE: souper creates 4 initial concrete inputs for synthesis via asking the 25 | solver to find some sort of counter examples 26 | * see `InstSynthesis::getInitialConcreteInputs` 27 | * but I'm not totally sure what the query is... I think it is the negation of 28 | the spec 29 | * DONE: after synthesizing a wiring that the solver finds a counter example for, 30 | the particular wiring is forbidden in new synthesis queries 31 | * see `InstSynthesis::forbidInvalidCandWiring` 32 | * but it only does this if there are no synthesized constants in the program! 33 | if there are synthesized constants, then it only forbids that wiring with 34 | that particular constant value (and has a limit before forbidding this 35 | wiring altogether) 36 | * has a loop around the CEGIS for constraining to use fewer components, and then 37 | more and more until we successfully synthesize a program 38 | * also builds cost/benefit model into loop around CEGIS 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `synth-loop-free-prog` 2 | 3 | Implementing [*Synthesis of Loop-free Programs* by Gulwani et 4 | al](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/12/pldi11-loopfree-synthesis.pdf) 5 | in Rust, using the [Z3][] solver. 6 | 7 | I explain the paper and walk through this implementation in my blog post 8 | [*Synthesizing Loop-Free Programs with Rust and Z3*](https://fitzgeraldnick.com/2020/01/13/synthesizing-loop-free-programs.html). 9 | 10 | ## Example 11 | 12 | ```rust 13 | use synth_loop_free_prog::*; 14 | 15 | let mut builder = ProgramBuilder::new(); 16 | // ...build an unoptimized program... 17 | let spec_program = builder.finish(); 18 | 19 | // Define a library of components that the synthesized program can use. 20 | let library = Library { 21 | components: vec![ 22 | component::add(), 23 | component::sub(), 24 | component::xor(), 25 | component::shl(), 26 | // etc... 27 | ], 28 | }; 29 | 30 | let config = z3::Config::new(); 31 | let context = z3::Context::new(&config); 32 | 33 | // Synthesize an optimized program! 34 | let optimized_program = Synthesizer::new(&context, &library, &spec_program) 35 | // One hour timeout. 36 | .set_timeout(60 * 60 * 1000) 37 | // Synthesize optimally small programs. 38 | .should_synthesize_minimal_programs(true) 39 | // Start synthesis! 40 | .synthesize()?; 41 | 42 | println!("Synthesized program:\n\n{}", optimized_program); 43 | ``` 44 | 45 | ## Build 46 | 47 | First, ensure that you have [Z3][] installed on your system: 48 | 49 | ```shell 50 | # Something like this, depending on your OS. 51 | $ sudo apt install libz3-dev 52 | ``` 53 | 54 | Then run 55 | 56 | ```shell 57 | $ cargo build 58 | ``` 59 | 60 | ## Testing 61 | 62 | ```shell 63 | $ cargo test 64 | ``` 65 | 66 | ## Running the Example Benchmarks 67 | 68 | Run the all 25 benchmark programs from the paper (originally taken from 69 | [Hacker's Delight](https://www.goodreads.com/book/show/276079.Hacker_s_Delight)) 70 | like this: 71 | 72 | ```shell 73 | $ cargo run --example brahma 74 | ``` 75 | 76 | You can also run only the ones that finish pretty quickly like this: 77 | 78 | ```shell 79 | $ cargo run --example brahma -- --only-fast 80 | ``` 81 | 82 | You can see a full listing of the available options with: 83 | 84 | ```shell 85 | $ cargo run --example brahma -- --help 86 | ``` 87 | 88 | ## Logging 89 | 90 | Logging requires incoking `cargo` with `--features log` when building, running, 91 | or testing. 92 | 93 | At the `debug` log level, information about the progress of synthesis, the bit 94 | width we're synthesizing at, and the example inputs is logged: 95 | 96 | ```shell 97 | $ export RUST_LOG=synth_loop_free_prog=debug 98 | ``` 99 | 100 | At the `trace` level, every SMT query is additionally logged: 101 | 102 | ```shell 103 | $ export RUST_LOG=synth_loop_free_prog=trace 104 | ``` 105 | 106 | [Z3]: https://github.com/Z3Prover/z3 107 | -------------------------------------------------------------------------------- /src/operator.rs: -------------------------------------------------------------------------------- 1 | use crate::Id; 2 | use std::fmt::{self, Display}; 3 | 4 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 5 | pub enum Operator { 6 | // Declare a new variable. 7 | Var, 8 | 9 | // A constant value. 10 | Const(u64), 11 | 12 | // Unary operators. 13 | Eqz(Id), 14 | Clz(Id), 15 | Ctz(Id), 16 | Popcnt(Id), 17 | 18 | // Binary relations. 19 | Eq(Id, Id), 20 | Ne(Id, Id), 21 | LtS(Id, Id), 22 | LtU(Id, Id), 23 | GtS(Id, Id), 24 | GtU(Id, Id), 25 | LeS(Id, Id), 26 | LeU(Id, Id), 27 | GeS(Id, Id), 28 | GeU(Id, Id), 29 | 30 | // Binary arithmetic. 31 | Add(Id, Id), 32 | Sub(Id, Id), 33 | Mul(Id, Id), 34 | DivS(Id, Id), 35 | DivU(Id, Id), 36 | RemS(Id, Id), 37 | RemU(Id, Id), 38 | And(Id, Id), 39 | Or(Id, Id), 40 | Xor(Id, Id), 41 | Shl(Id, Id), 42 | ShrS(Id, Id), 43 | ShrU(Id, Id), 44 | Rotl(Id, Id), 45 | Rotr(Id, Id), 46 | 47 | // If-then-else. 48 | Select(Id, Id, Id), 49 | } 50 | 51 | impl Operator { 52 | pub fn arity(&self) -> usize { 53 | match self { 54 | Operator::Var | Operator::Const(_) => 0, 55 | Operator::Eqz(_) | Operator::Clz(_) | Operator::Ctz(_) | Operator::Popcnt(_) => 1, 56 | Operator::Eq(_, _) 57 | | Operator::Ne(_, _) 58 | | Operator::LtS(_, _) 59 | | Operator::LtU(_, _) 60 | | Operator::GtS(_, _) 61 | | Operator::GtU(_, _) 62 | | Operator::LeS(_, _) 63 | | Operator::LeU(_, _) 64 | | Operator::GeS(_, _) 65 | | Operator::GeU(_, _) 66 | | Operator::Add(_, _) 67 | | Operator::Sub(_, _) 68 | | Operator::Mul(_, _) 69 | | Operator::DivS(_, _) 70 | | Operator::DivU(_, _) 71 | | Operator::RemS(_, _) 72 | | Operator::RemU(_, _) 73 | | Operator::And(_, _) 74 | | Operator::Or(_, _) 75 | | Operator::Xor(_, _) 76 | | Operator::Shl(_, _) 77 | | Operator::ShrS(_, _) 78 | | Operator::ShrU(_, _) 79 | | Operator::Rotl(_, _) 80 | | Operator::Rotr(_, _) => 2, 81 | Operator::Select(_, _, _) => 3, 82 | } 83 | } 84 | 85 | pub fn immediates(&self, mut f: impl FnMut(u64)) { 86 | if let Operator::Const(c) = *self { 87 | f(c); 88 | } 89 | } 90 | 91 | pub fn operands(&self, mut f: impl FnMut(Id)) { 92 | match *self { 93 | Operator::Var | Operator::Const(_) => {} 94 | Operator::Eqz(a) | Operator::Clz(a) | Operator::Ctz(a) | Operator::Popcnt(a) => f(a), 95 | Operator::Eq(a, b) 96 | | Operator::Ne(a, b) 97 | | Operator::LtS(a, b) 98 | | Operator::LtU(a, b) 99 | | Operator::GtS(a, b) 100 | | Operator::GtU(a, b) 101 | | Operator::LeS(a, b) 102 | | Operator::LeU(a, b) 103 | | Operator::GeS(a, b) 104 | | Operator::GeU(a, b) 105 | | Operator::Add(a, b) 106 | | Operator::Sub(a, b) 107 | | Operator::Mul(a, b) 108 | | Operator::DivS(a, b) 109 | | Operator::DivU(a, b) 110 | | Operator::RemS(a, b) 111 | | Operator::RemU(a, b) 112 | | Operator::And(a, b) 113 | | Operator::Or(a, b) 114 | | Operator::Xor(a, b) 115 | | Operator::Shl(a, b) 116 | | Operator::ShrS(a, b) 117 | | Operator::ShrU(a, b) 118 | | Operator::Rotl(a, b) 119 | | Operator::Rotr(a, b) => { 120 | f(a); 121 | f(b); 122 | } 123 | Operator::Select(a, b, c) => { 124 | f(a); 125 | f(b); 126 | f(c); 127 | } 128 | } 129 | } 130 | 131 | pub fn operands_mut(&mut self, mut f: impl FnMut(&mut Id)) { 132 | match self { 133 | Operator::Var | Operator::Const(_) => {} 134 | Operator::Eqz(a) | Operator::Clz(a) | Operator::Ctz(a) | Operator::Popcnt(a) => f(a), 135 | Operator::Eq(a, b) 136 | | Operator::Ne(a, b) 137 | | Operator::LtS(a, b) 138 | | Operator::LtU(a, b) 139 | | Operator::GtS(a, b) 140 | | Operator::GtU(a, b) 141 | | Operator::LeS(a, b) 142 | | Operator::LeU(a, b) 143 | | Operator::GeS(a, b) 144 | | Operator::GeU(a, b) 145 | | Operator::Add(a, b) 146 | | Operator::Sub(a, b) 147 | | Operator::Mul(a, b) 148 | | Operator::DivS(a, b) 149 | | Operator::DivU(a, b) 150 | | Operator::RemS(a, b) 151 | | Operator::RemU(a, b) 152 | | Operator::And(a, b) 153 | | Operator::Or(a, b) 154 | | Operator::Xor(a, b) 155 | | Operator::Shl(a, b) 156 | | Operator::ShrS(a, b) 157 | | Operator::ShrU(a, b) 158 | | Operator::Rotl(a, b) 159 | | Operator::Rotr(a, b) => { 160 | f(a); 161 | f(b); 162 | } 163 | Operator::Select(a, b, c) => { 164 | f(a); 165 | f(b); 166 | f(c); 167 | } 168 | } 169 | } 170 | } 171 | 172 | impl Display for Operator { 173 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 174 | match self { 175 | Operator::Var => write!(f, "var"), 176 | Operator::Const(c) => write!(f, "const {:#X}", c), 177 | Operator::Eqz(id) => write!(f, "eqz {}", id), 178 | Operator::Clz(id) => write!(f, "clz {}", id), 179 | Operator::Ctz(id) => write!(f, "ctz {}", id), 180 | Operator::Popcnt(id) => write!(f, "popcnt {}", id), 181 | Operator::Eq(a, b) => write!(f, "eq {}, {}", a, b), 182 | Operator::Ne(a, b) => write!(f, "ne {}, {}", a, b), 183 | Operator::LtS(a, b) => write!(f, "lt_s {}, {}", a, b), 184 | Operator::LtU(a, b) => write!(f, "lt_u {}, {}", a, b), 185 | Operator::GtS(a, b) => write!(f, "gt_s {}, {}", a, b), 186 | Operator::GtU(a, b) => write!(f, "gt_u {}, {}", a, b), 187 | Operator::LeS(a, b) => write!(f, "le_s {}, {}", a, b), 188 | Operator::LeU(a, b) => write!(f, "le_u {}, {}", a, b), 189 | Operator::GeS(a, b) => write!(f, "ge_s {}, {}", a, b), 190 | Operator::GeU(a, b) => write!(f, "ge_u {}, {}", a, b), 191 | Operator::Add(a, b) => write!(f, "add {}, {}", a, b), 192 | Operator::Sub(a, b) => write!(f, "sub {}, {}", a, b), 193 | Operator::Mul(a, b) => write!(f, "mul {}, {}", a, b), 194 | Operator::DivS(a, b) => write!(f, "div_s {}, {}", a, b), 195 | Operator::DivU(a, b) => write!(f, "div_u {}, {}", a, b), 196 | Operator::RemS(a, b) => write!(f, "rem_s {}, {}", a, b), 197 | Operator::RemU(a, b) => write!(f, "rem_u {}, {}", a, b), 198 | Operator::And(a, b) => write!(f, "and {}, {}", a, b), 199 | Operator::Or(a, b) => write!(f, "or {}, {}", a, b), 200 | Operator::Xor(a, b) => write!(f, "xor {}, {}", a, b), 201 | Operator::Shl(a, b) => write!(f, "shl {}, {}", a, b), 202 | Operator::ShrS(a, b) => write!(f, "shr_s {}, {}", a, b), 203 | Operator::ShrU(a, b) => write!(f, "shr_u {}, {}", a, b), 204 | Operator::Rotl(a, b) => write!(f, "rotl {}, {}", a, b), 205 | Operator::Rotr(a, b) => write!(f, "rotr {}, {}", a, b), 206 | Operator::Select(a, b, c) => write!(f, "select {}, {}, {}", a, b, c), 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/builder.rs: -------------------------------------------------------------------------------- 1 | use crate::{Id, Instruction, Operator, Program}; 2 | 3 | #[derive(Debug)] 4 | pub struct ProgramBuilder { 5 | program: Program, 6 | } 7 | 8 | impl ProgramBuilder { 9 | pub fn new() -> ProgramBuilder { 10 | ProgramBuilder { 11 | program: Program { 12 | instructions: vec![], 13 | }, 14 | } 15 | } 16 | 17 | pub fn finish(self) -> Program { 18 | self.program 19 | } 20 | 21 | fn next_id(&self) -> Id { 22 | Id(self.program.instructions.len() as u32) 23 | } 24 | 25 | pub fn var(&mut self) -> Id { 26 | assert!( 27 | self.program 28 | .instructions 29 | .iter() 30 | .all(|inst| inst.operator == Operator::Var), 31 | "All `var`s must be at the start of the program" 32 | ); 33 | 34 | let result = self.next_id(); 35 | self.program.instructions.push(Instruction { 36 | result, 37 | operator: Operator::Var, 38 | }); 39 | result 40 | } 41 | 42 | pub fn const_(&mut self, c: u64) -> Id { 43 | let result = self.next_id(); 44 | self.program.instructions.push(Instruction { 45 | result, 46 | operator: Operator::Const(c), 47 | }); 48 | result 49 | } 50 | 51 | pub fn eqz(&mut self, a: Id) -> Id { 52 | let result = self.next_id(); 53 | self.program.instructions.push(Instruction { 54 | result, 55 | operator: Operator::Eqz(a), 56 | }); 57 | result 58 | } 59 | 60 | pub fn clz(&mut self, a: Id) -> Id { 61 | let result = self.next_id(); 62 | self.program.instructions.push(Instruction { 63 | result, 64 | operator: Operator::Clz(a), 65 | }); 66 | result 67 | } 68 | 69 | pub fn ctz(&mut self, a: Id) -> Id { 70 | let result = self.next_id(); 71 | self.program.instructions.push(Instruction { 72 | result, 73 | operator: Operator::Ctz(a), 74 | }); 75 | result 76 | } 77 | 78 | pub fn popcnt(&mut self, a: Id) -> Id { 79 | let result = self.next_id(); 80 | self.program.instructions.push(Instruction { 81 | result, 82 | operator: Operator::Popcnt(a), 83 | }); 84 | result 85 | } 86 | 87 | pub fn eq(&mut self, a: Id, b: Id) -> Id { 88 | let result = self.next_id(); 89 | self.program.instructions.push(Instruction { 90 | result, 91 | operator: Operator::Eq(a, b), 92 | }); 93 | result 94 | } 95 | 96 | pub fn ne(&mut self, a: Id, b: Id) -> Id { 97 | let result = self.next_id(); 98 | self.program.instructions.push(Instruction { 99 | result, 100 | operator: Operator::Ne(a, b), 101 | }); 102 | result 103 | } 104 | 105 | pub fn lt_s(&mut self, a: Id, b: Id) -> Id { 106 | let result = self.next_id(); 107 | self.program.instructions.push(Instruction { 108 | result, 109 | operator: Operator::LtS(a, b), 110 | }); 111 | result 112 | } 113 | 114 | pub fn lt_u(&mut self, a: Id, b: Id) -> Id { 115 | let result = self.next_id(); 116 | self.program.instructions.push(Instruction { 117 | result, 118 | operator: Operator::LtU(a, b), 119 | }); 120 | result 121 | } 122 | 123 | pub fn gt_s(&mut self, a: Id, b: Id) -> Id { 124 | let result = self.next_id(); 125 | self.program.instructions.push(Instruction { 126 | result, 127 | operator: Operator::GtS(a, b), 128 | }); 129 | result 130 | } 131 | 132 | pub fn gt_u(&mut self, a: Id, b: Id) -> Id { 133 | let result = self.next_id(); 134 | self.program.instructions.push(Instruction { 135 | result, 136 | operator: Operator::GtU(a, b), 137 | }); 138 | result 139 | } 140 | 141 | pub fn le_s(&mut self, a: Id, b: Id) -> Id { 142 | let result = self.next_id(); 143 | self.program.instructions.push(Instruction { 144 | result, 145 | operator: Operator::LeS(a, b), 146 | }); 147 | result 148 | } 149 | 150 | pub fn le_u(&mut self, a: Id, b: Id) -> Id { 151 | let result = self.next_id(); 152 | self.program.instructions.push(Instruction { 153 | result, 154 | operator: Operator::LeU(a, b), 155 | }); 156 | result 157 | } 158 | 159 | pub fn ge_s(&mut self, a: Id, b: Id) -> Id { 160 | let result = self.next_id(); 161 | self.program.instructions.push(Instruction { 162 | result, 163 | operator: Operator::GeS(a, b), 164 | }); 165 | result 166 | } 167 | 168 | pub fn ge_u(&mut self, a: Id, b: Id) -> Id { 169 | let result = self.next_id(); 170 | self.program.instructions.push(Instruction { 171 | result, 172 | operator: Operator::GeU(a, b), 173 | }); 174 | result 175 | } 176 | 177 | pub fn add(&mut self, a: Id, b: Id) -> Id { 178 | let result = self.next_id(); 179 | self.program.instructions.push(Instruction { 180 | result, 181 | operator: Operator::Add(a, b), 182 | }); 183 | result 184 | } 185 | 186 | pub fn sub(&mut self, a: Id, b: Id) -> Id { 187 | let result = self.next_id(); 188 | self.program.instructions.push(Instruction { 189 | result, 190 | operator: Operator::Sub(a, b), 191 | }); 192 | result 193 | } 194 | 195 | pub fn mul(&mut self, a: Id, b: Id) -> Id { 196 | let result = self.next_id(); 197 | self.program.instructions.push(Instruction { 198 | result, 199 | operator: Operator::Mul(a, b), 200 | }); 201 | result 202 | } 203 | 204 | pub fn div_s(&mut self, a: Id, b: Id) -> Id { 205 | let result = self.next_id(); 206 | self.program.instructions.push(Instruction { 207 | result, 208 | operator: Operator::DivS(a, b), 209 | }); 210 | result 211 | } 212 | 213 | pub fn div_u(&mut self, a: Id, b: Id) -> Id { 214 | let result = self.next_id(); 215 | self.program.instructions.push(Instruction { 216 | result, 217 | operator: Operator::DivU(a, b), 218 | }); 219 | result 220 | } 221 | 222 | pub fn rems(&mut self, a: Id, b: Id) -> Id { 223 | let result = self.next_id(); 224 | self.program.instructions.push(Instruction { 225 | result, 226 | operator: Operator::RemS(a, b), 227 | }); 228 | result 229 | } 230 | 231 | pub fn remu(&mut self, a: Id, b: Id) -> Id { 232 | let result = self.next_id(); 233 | self.program.instructions.push(Instruction { 234 | result, 235 | operator: Operator::RemU(a, b), 236 | }); 237 | result 238 | } 239 | 240 | pub fn and(&mut self, a: Id, b: Id) -> Id { 241 | let result = self.next_id(); 242 | self.program.instructions.push(Instruction { 243 | result, 244 | operator: Operator::And(a, b), 245 | }); 246 | result 247 | } 248 | 249 | pub fn or(&mut self, a: Id, b: Id) -> Id { 250 | let result = self.next_id(); 251 | self.program.instructions.push(Instruction { 252 | result, 253 | operator: Operator::Or(a, b), 254 | }); 255 | result 256 | } 257 | 258 | pub fn xor(&mut self, a: Id, b: Id) -> Id { 259 | let result = self.next_id(); 260 | self.program.instructions.push(Instruction { 261 | result, 262 | operator: Operator::Xor(a, b), 263 | }); 264 | result 265 | } 266 | 267 | pub fn shl(&mut self, a: Id, b: Id) -> Id { 268 | let result = self.next_id(); 269 | self.program.instructions.push(Instruction { 270 | result, 271 | operator: Operator::Shl(a, b), 272 | }); 273 | result 274 | } 275 | 276 | pub fn shr_s(&mut self, a: Id, b: Id) -> Id { 277 | let result = self.next_id(); 278 | self.program.instructions.push(Instruction { 279 | result, 280 | operator: Operator::ShrS(a, b), 281 | }); 282 | result 283 | } 284 | 285 | pub fn shr_u(&mut self, a: Id, b: Id) -> Id { 286 | let result = self.next_id(); 287 | self.program.instructions.push(Instruction { 288 | result, 289 | operator: Operator::ShrU(a, b), 290 | }); 291 | result 292 | } 293 | 294 | pub fn rotl(&mut self, a: Id, b: Id) -> Id { 295 | let result = self.next_id(); 296 | self.program.instructions.push(Instruction { 297 | result, 298 | operator: Operator::Rotl(a, b), 299 | }); 300 | result 301 | } 302 | 303 | pub fn rotr(&mut self, a: Id, b: Id) -> Id { 304 | let result = self.next_id(); 305 | self.program.instructions.push(Instruction { 306 | result, 307 | operator: Operator::Rotr(a, b), 308 | }); 309 | result 310 | } 311 | 312 | pub fn select(&mut self, a: Id, b: Id, c: Id) -> Id { 313 | let result = self.next_id(); 314 | self.program.instructions.push(Instruction { 315 | result, 316 | operator: Operator::Select(a, b, c), 317 | }); 318 | result 319 | } 320 | } 321 | -------------------------------------------------------------------------------- /examples/brahma.rs: -------------------------------------------------------------------------------- 1 | use structopt::*; 2 | use synth_loop_free_prog::{Result as SynthResult, *}; 3 | 4 | macro_rules! benchmarks { 5 | ( $($name:ident,)* ) => { 6 | vec![ 7 | $( 8 | (stringify!($name), $name as _), 9 | )* 10 | ] 11 | } 12 | } 13 | 14 | fn main() { 15 | env_logger::init(); 16 | 17 | let mut opts = Options::from_args(); 18 | if opts.only_fast { 19 | opts.problems = vec![ 20 | "p1".to_string(), 21 | "p2".to_string(), 22 | "p3".to_string(), 23 | "p4".to_string(), 24 | "p5".to_string(), 25 | "p6".to_string(), 26 | "p7".to_string(), 27 | "p10".to_string(), 28 | "p18".to_string(), 29 | ]; 30 | } 31 | 32 | let mut config = z3::Config::new(); 33 | config.set_bool_param_value("auto_config", false); 34 | config.set_model_generation(true); 35 | 36 | let context = z3::Context::new(&config); 37 | 38 | let problems: Vec<( 39 | &'static str, 40 | fn(&z3::Context, &Options) -> SynthResult, 41 | )> = benchmarks! { 42 | p1, 43 | p2, 44 | p3, 45 | p4, 46 | p5, 47 | p6, 48 | p7, 49 | p8, 50 | p9, 51 | p10, 52 | p11, 53 | p12, 54 | p13, 55 | p14, 56 | p15, 57 | p16, 58 | p17, 59 | p18, 60 | p19, 61 | p20, 62 | p21, 63 | p22, 64 | p23, 65 | p24, 66 | p25, 67 | }; 68 | 69 | for (name, p) in problems { 70 | if !opts.should_run_problem(name) { 71 | continue; 72 | } 73 | 74 | println!("==================== {} ====================", name); 75 | let then = std::time::Instant::now(); 76 | let program = p(&context, &opts); 77 | let elapsed = then.elapsed(); 78 | 79 | println!( 80 | "\nElapsed: {}.{:03}s\n", 81 | elapsed.as_secs(), 82 | elapsed.subsec_millis() 83 | ); 84 | match program { 85 | Ok(prog) => { 86 | println!("Synthesized:\n\n{}", prog); 87 | } 88 | Err(e) => { 89 | println!("Error: {:?}\n", e); 90 | } 91 | } 92 | } 93 | } 94 | 95 | #[derive(StructOpt)] 96 | struct Options { 97 | /// Set a timeout, in milliseconds. 98 | #[structopt(short = "t", long = "timeout")] 99 | timeout: Option, 100 | 101 | /// Synthesize the optimally smallest programs. 102 | #[structopt(short = "m", long = "minimal")] 103 | minimal: bool, 104 | 105 | /// Run only the problems that we can solver pretty fast. 106 | #[structopt(short = "f", long = "only-fast", conflicts_with = "problems")] 107 | only_fast: bool, 108 | 109 | /// Should constants be given or synthesized? It isn't always clear which 110 | /// they did in the paper, and sort seems like they did a mix depending on 111 | /// the benchmark problem. 112 | #[structopt(short = "c", long = "synthesize-constants")] 113 | synthesize_constants: bool, 114 | 115 | /// When supplied, run only these problems instead of all problems. 116 | #[structopt(last = true)] 117 | problems: Vec, 118 | } 119 | 120 | impl Options { 121 | fn should_run_problem(&self, problem: &str) -> bool { 122 | self.problems.is_empty() || self.problems.iter().position(|p| p == problem).is_some() 123 | } 124 | } 125 | 126 | fn synthesize( 127 | opts: &Options, 128 | context: &z3::Context, 129 | spec: &dyn Specification, 130 | library: &Library, 131 | ) -> SynthResult { 132 | Synthesizer::new(context, library, spec)? 133 | .set_timeout(opts.timeout) 134 | .should_synthesize_minimal_programs(opts.minimal) 135 | .synthesize() 136 | } 137 | 138 | fn p1(context: &z3::Context, opts: &Options) -> SynthResult { 139 | let mut library = Library::brahma_std(); 140 | library 141 | .components 142 | .push(component::const_(if opts.synthesize_constants { 143 | None 144 | } else { 145 | Some(1) 146 | })); 147 | 148 | let mut builder = ProgramBuilder::new(); 149 | let a = builder.var(); 150 | let b = builder.const_(1); 151 | let c = builder.sub(a, b); 152 | let _ = builder.and(a, c); 153 | let spec = builder.finish(); 154 | 155 | synthesize(opts, context, &spec, &library) 156 | } 157 | 158 | fn p2(context: &z3::Context, opts: &Options) -> SynthResult { 159 | let mut library = Library::brahma_std(); 160 | library 161 | .components 162 | .push(component::const_(if opts.synthesize_constants { 163 | None 164 | } else { 165 | Some(1) 166 | })); 167 | 168 | let mut builder = ProgramBuilder::new(); 169 | let a = builder.var(); 170 | let b = builder.const_(1); 171 | let c = builder.add(a, b); 172 | let _ = builder.and(a, c); 173 | let spec = builder.finish(); 174 | 175 | synthesize(opts, context, &spec, &library) 176 | } 177 | 178 | fn p3(context: &z3::Context, opts: &Options) -> SynthResult { 179 | let library = Library::brahma_std(); 180 | 181 | let mut builder = ProgramBuilder::new(); 182 | let a = builder.var(); 183 | let b = builder.const_(0); 184 | let c = builder.sub(b, a); 185 | let _ = builder.and(a, c); 186 | let spec = builder.finish(); 187 | 188 | synthesize(opts, context, &spec, &library) 189 | } 190 | 191 | fn p4(context: &z3::Context, opts: &Options) -> SynthResult { 192 | let mut library = Library::brahma_std(); 193 | library 194 | .components 195 | .push(component::const_(if opts.synthesize_constants { 196 | None 197 | } else { 198 | Some(1) 199 | })); 200 | 201 | let mut builder = ProgramBuilder::new(); 202 | let a = builder.var(); 203 | let b = builder.const_(1); 204 | let c = builder.sub(a, b); 205 | let _ = builder.xor(a, c); 206 | let spec = builder.finish(); 207 | 208 | synthesize(opts, context, &spec, &library) 209 | } 210 | 211 | fn p5(context: &z3::Context, opts: &Options) -> SynthResult { 212 | let mut library = Library::brahma_std(); 213 | library 214 | .components 215 | .push(component::const_(if opts.synthesize_constants { 216 | None 217 | } else { 218 | Some(1) 219 | })); 220 | 221 | let mut builder = ProgramBuilder::new(); 222 | let a = builder.var(); 223 | let b = builder.const_(1); 224 | let c = builder.sub(a, b); 225 | let _ = builder.or(a, c); 226 | let spec = builder.finish(); 227 | 228 | synthesize(opts, context, &spec, &library) 229 | } 230 | 231 | fn p6(context: &z3::Context, opts: &Options) -> SynthResult { 232 | let mut library = Library::brahma_std(); 233 | library 234 | .components 235 | .push(component::const_(if opts.synthesize_constants { 236 | None 237 | } else { 238 | Some(1) 239 | })); 240 | 241 | let mut builder = ProgramBuilder::new(); 242 | let a = builder.var(); 243 | let b = builder.const_(1); 244 | let c = builder.add(a, b); 245 | let _ = builder.or(a, c); 246 | let spec = builder.finish(); 247 | 248 | synthesize(opts, context, &spec, &library) 249 | } 250 | 251 | fn p7(context: &z3::Context, opts: &Options) -> SynthResult { 252 | let mut library = Library::brahma_std(); 253 | library 254 | .components 255 | .push(component::const_(if opts.synthesize_constants { 256 | None 257 | } else { 258 | Some(1) 259 | })); 260 | library 261 | .components 262 | .push(component::const_(if opts.synthesize_constants { 263 | None 264 | } else { 265 | Some(std::u64::MAX) 266 | })); 267 | 268 | let mut builder = ProgramBuilder::new(); 269 | let x = builder.var(); 270 | // o1 = bvnot(x) = xor(x, MAX) 271 | let a = builder.const_(std::u64::MAX); 272 | let o1 = builder.xor(x, a); 273 | // o2 = bvadd(x, 1) 274 | let b = builder.const_(1); 275 | let o2 = builder.add(x, b); 276 | let _ = builder.and(o1, o2); 277 | let spec = builder.finish(); 278 | 279 | synthesize(opts, context, &spec, &library) 280 | } 281 | 282 | fn p8(context: &z3::Context, opts: &Options) -> SynthResult { 283 | let mut library = Library::brahma_std(); 284 | library 285 | .components 286 | .push(component::const_(if opts.synthesize_constants { 287 | None 288 | } else { 289 | Some(1) 290 | })); 291 | 292 | let mut builder = ProgramBuilder::new(); 293 | let a = builder.var(); 294 | let b = builder.const_(1); 295 | let c = builder.sub(a, b); 296 | let d = builder.const_(std::u64::MAX); 297 | let e = builder.xor(a, d); 298 | let _ = builder.and(c, e); 299 | let spec = builder.finish(); 300 | 301 | synthesize(opts, context, &spec, &library) 302 | } 303 | 304 | fn p9(context: &z3::Context, opts: &Options) -> SynthResult { 305 | let mut library = Library::brahma_std(); 306 | library 307 | .components 308 | .push(component::const_(if opts.synthesize_constants { 309 | None 310 | } else { 311 | Some(31) 312 | })); 313 | 314 | let mut builder = ProgramBuilder::new(); 315 | let a = builder.var(); 316 | let b = builder.const_(31); 317 | let c = builder.shr_u(a, b); 318 | let d = builder.xor(a, c); 319 | let _ = builder.sub(d, c); 320 | let spec = builder.finish(); 321 | 322 | synthesize(opts, context, &spec, &library) 323 | } 324 | 325 | fn p10(context: &z3::Context, opts: &Options) -> SynthResult { 326 | let library = Library::brahma_std(); 327 | 328 | let mut builder = ProgramBuilder::new(); 329 | let x = builder.var(); 330 | let y = builder.var(); 331 | let a = builder.and(x, y); 332 | let b = builder.xor(x, y); 333 | let _ = builder.le_u(b, a); 334 | let spec = builder.finish(); 335 | 336 | synthesize(opts, context, &spec, &library) 337 | } 338 | 339 | fn p11(context: &z3::Context, opts: &Options) -> SynthResult { 340 | let library = Library::brahma_std(); 341 | 342 | let mut builder = ProgramBuilder::new(); 343 | 344 | let x = builder.var(); 345 | let y = builder.var(); 346 | 347 | // not y 348 | let a = builder.const_(std::u64::MAX); 349 | let b = builder.xor(a, y); 350 | 351 | let c = builder.and(x, b); 352 | let _ = builder.gt_u(c, y); 353 | 354 | let spec = builder.finish(); 355 | 356 | synthesize(opts, context, &spec, &library) 357 | } 358 | 359 | fn p12(context: &z3::Context, opts: &Options) -> SynthResult { 360 | let library = Library::brahma_std(); 361 | 362 | let mut builder = ProgramBuilder::new(); 363 | let x = builder.var(); 364 | let y = builder.var(); 365 | 366 | // not y 367 | let a = builder.const_(std::u64::MAX); 368 | let b = builder.xor(a, y); 369 | 370 | let c = builder.and(x, b); 371 | let _ = builder.le_u(c, y); 372 | let spec = builder.finish(); 373 | 374 | synthesize(opts, context, &spec, &library) 375 | } 376 | 377 | fn p13(context: &z3::Context, opts: &Options) -> SynthResult { 378 | let mut library = Library::brahma_std(); 379 | library 380 | .components 381 | .push(component::const_(if opts.synthesize_constants { 382 | None 383 | } else { 384 | Some(31) 385 | })); 386 | 387 | let mut builder = ProgramBuilder::new(); 388 | let x = builder.var(); 389 | 390 | let a = builder.const_(31); 391 | let o1 = builder.shr_s(x, a); 392 | 393 | // neg(x) = 0 - x 394 | let b = builder.const_(0); 395 | let o2 = builder.sub(b, x); 396 | 397 | let o3 = builder.shr_u(o2, a); 398 | let _ = builder.or(o1, o3); 399 | let spec = builder.finish(); 400 | 401 | synthesize(opts, context, &spec, &library) 402 | } 403 | 404 | fn p14(context: &z3::Context, opts: &Options) -> SynthResult { 405 | let mut library = Library::brahma_std(); 406 | library 407 | .components 408 | .push(component::const_(if opts.synthesize_constants { 409 | None 410 | } else { 411 | Some(1) 412 | })); 413 | 414 | let mut builder = ProgramBuilder::new(); 415 | let x = builder.var(); 416 | let y = builder.var(); 417 | let o1 = builder.and(x, y); 418 | let o2 = builder.xor(x, y); 419 | let a = builder.const_(1); 420 | let o3 = builder.shr_u(o2, a); 421 | let _ = builder.add(o1, o3); 422 | let spec = builder.finish(); 423 | 424 | synthesize(opts, context, &spec, &library) 425 | } 426 | 427 | fn p15(context: &z3::Context, opts: &Options) -> SynthResult { 428 | let mut library = Library::brahma_std(); 429 | library 430 | .components 431 | .push(component::const_(if opts.synthesize_constants { 432 | None 433 | } else { 434 | Some(1) 435 | })); 436 | 437 | let mut builder = ProgramBuilder::new(); 438 | let x = builder.var(); 439 | let y = builder.var(); 440 | let o1 = builder.or(x, y); 441 | let o2 = builder.xor(x, y); 442 | let a = builder.const_(1); 443 | let o3 = builder.shr_u(o2, a); 444 | let _ = builder.sub(o1, o3); 445 | let spec = builder.finish(); 446 | 447 | synthesize(opts, context, &spec, &library) 448 | } 449 | 450 | fn p16(context: &z3::Context, opts: &Options) -> SynthResult { 451 | let library = Library::brahma_std(); 452 | 453 | let mut builder = ProgramBuilder::new(); 454 | let x = builder.var(); 455 | let y = builder.var(); 456 | let o1 = builder.xor(x, y); 457 | let a = builder.ge_u(x, y); 458 | 459 | // o2 = neg(a) 460 | let b = builder.const_(0); 461 | let o2 = builder.sub(b, a); 462 | 463 | let o3 = builder.and(o1, o2); 464 | let _ = builder.xor(o3, y); 465 | let spec = builder.finish(); 466 | 467 | synthesize(opts, context, &spec, &library) 468 | } 469 | 470 | fn p17(context: &z3::Context, opts: &Options) -> SynthResult { 471 | let mut library = Library::brahma_std(); 472 | library 473 | .components 474 | .push(component::const_(if opts.synthesize_constants { 475 | None 476 | } else { 477 | Some(1) 478 | })); 479 | 480 | let mut builder = ProgramBuilder::new(); 481 | let x = builder.var(); 482 | let a = builder.const_(1); 483 | let o1 = builder.sub(x, a); 484 | let o2 = builder.or(x, o1); 485 | let o3 = builder.add(o2, a); 486 | let _ = builder.and(o3, x); 487 | let spec = builder.finish(); 488 | 489 | synthesize(opts, context, &spec, &library) 490 | } 491 | 492 | // The `brahma_std` library should be good for `p <= 17`. From here on out, we 493 | // require custom libraries. 494 | 495 | fn p18(context: &z3::Context, opts: &Options) -> SynthResult { 496 | // In the paper, redor(x) seems to produce booleans, which have their own 497 | // operations. Turns out if we use zero and non-zero as 32-bit wide false 498 | // and true, we can solve the whole thing with our `brahma_std` library. 499 | let library = Library::brahma_std(); 500 | 501 | let mut builder = ProgramBuilder::new(); 502 | let x = builder.var(); 503 | let a = builder.const_(1); 504 | let o1 = builder.sub(x, a); 505 | let o2 = builder.and(o1, x); 506 | 507 | // o3 = redor(x) = "reduce bits with or" 508 | // = 1 if any bit is set, otherwise 0 509 | // = `gt_u x, 0` 510 | let b = builder.const_(0); 511 | let o3 = builder.gt_u(x, b); 512 | 513 | // o4 = redor(o2) 514 | let o4 = builder.gt_u(o2, b); 515 | 516 | // o5 = bool-not(o4) 517 | let o5 = builder.select(o4, b, a); 518 | 519 | // res = bool-and(o5, o3) 520 | let _ = builder.and(o5, o3); 521 | 522 | let spec = builder.finish(); 523 | 524 | synthesize(opts, context, &spec, &library) 525 | } 526 | 527 | fn p19(context: &z3::Context, opts: &Options) -> SynthResult { 528 | let library = Library { 529 | components: vec![ 530 | component::shr_u(), 531 | component::xor(), 532 | component::and(), 533 | component::shl(), 534 | component::xor(), 535 | component::xor(), 536 | ], 537 | }; 538 | 539 | let mut builder = ProgramBuilder::new(); 540 | let x = builder.var(); 541 | let m = builder.var(); 542 | let k = builder.var(); 543 | let o1 = builder.shr_u(x, k); 544 | let o2 = builder.xor(x, o1); 545 | let o3 = builder.and(o2, m); 546 | let o4 = builder.shl(o3, k); 547 | let o5 = builder.xor(o4, o3); 548 | let _ = builder.xor(o5, x); 549 | let spec = builder.finish(); 550 | 551 | synthesize(opts, context, &spec, &library) 552 | } 553 | 554 | fn p20(context: &z3::Context, opts: &Options) -> SynthResult { 555 | let library = Library { 556 | components: vec![ 557 | component::const_(if opts.synthesize_constants { 558 | None 559 | } else { 560 | Some(0) 561 | }), 562 | component::sub(), 563 | component::and(), 564 | component::add(), 565 | component::xor(), 566 | component::const_(if opts.synthesize_constants { 567 | None 568 | } else { 569 | Some(2) 570 | }), 571 | component::shr_u(), 572 | component::div_u(), 573 | component::or(), 574 | ], 575 | }; 576 | 577 | let mut builder = ProgramBuilder::new(); 578 | let x = builder.var(); 579 | 580 | // o1 = neg(x) 581 | let a = builder.const_(0); 582 | let o1 = builder.sub(a, x); 583 | 584 | let o2 = builder.and(x, o1); 585 | let o3 = builder.add(x, o2); 586 | let o4 = builder.xor(x, o2); 587 | let b = builder.const_(2); 588 | let o5 = builder.shr_u(o4, b); 589 | let o6 = builder.div_u(o5, o2); 590 | let _ = builder.or(o6, o3); 591 | let spec = builder.finish(); 592 | 593 | synthesize(opts, context, &spec, &library) 594 | } 595 | 596 | fn p21(context: &z3::Context, opts: &Options) -> SynthResult { 597 | let library = Library { 598 | components: vec![ 599 | component::eq(), 600 | component::const_(if opts.synthesize_constants { 601 | None 602 | } else { 603 | Some(0) 604 | }), 605 | component::sub(), 606 | component::xor(), 607 | component::eq(), 608 | component::sub(), 609 | component::xor(), 610 | component::and(), 611 | component::and(), 612 | component::xor(), 613 | component::xor(), 614 | ], 615 | }; 616 | 617 | let mut builder = ProgramBuilder::new(); 618 | let x = builder.var(); 619 | let a = builder.var(); 620 | let b = builder.var(); 621 | let c = builder.var(); 622 | 623 | // o1 = neg(eq(x, c)) 624 | let d = builder.eq(x, c); 625 | let e = builder.const_(0); 626 | let o1 = builder.sub(e, d); 627 | 628 | let o2 = builder.xor(a, c); 629 | 630 | // o3 = neg(eq(x, a)) 631 | let f = builder.eq(x, a); 632 | let o3 = builder.sub(e, f); 633 | 634 | let o4 = builder.xor(b, c); 635 | let o5 = builder.and(o1, o2); 636 | let o6 = builder.and(o3, o4); 637 | let o7 = builder.xor(o5, o6); 638 | let _ = builder.xor(o7, c); 639 | 640 | let spec = builder.finish(); 641 | 642 | synthesize(opts, context, &spec, &library) 643 | } 644 | 645 | fn p22(context: &z3::Context, opts: &Options) -> SynthResult { 646 | let library = Library { 647 | components: vec![ 648 | component::const_(if opts.synthesize_constants { 649 | None 650 | } else { 651 | Some(1) 652 | }), 653 | component::shr_u(), 654 | component::xor(), 655 | component::const_(if opts.synthesize_constants { 656 | None 657 | } else { 658 | Some(2) 659 | }), 660 | component::shr_u(), 661 | component::xor(), 662 | component::const_(if opts.synthesize_constants { 663 | None 664 | } else { 665 | Some(0x11111111) 666 | }), 667 | component::and(), 668 | component::mul(), 669 | component::const_(if opts.synthesize_constants { 670 | None 671 | } else { 672 | Some(28) 673 | }), 674 | component::shr_u(), 675 | component::const_(if opts.synthesize_constants { 676 | None 677 | } else { 678 | Some(1) 679 | }), 680 | component::and(), 681 | ], 682 | }; 683 | 684 | let mut builder = ProgramBuilder::new(); 685 | let x = builder.var(); 686 | let a = builder.const_(1); 687 | let o1 = builder.shr_u(x, a); 688 | let o2 = builder.xor(o1, x); 689 | let b = builder.const_(2); 690 | let o3 = builder.shr_u(o2, b); 691 | let o4 = builder.xor(o2, o3); 692 | let c = builder.const_(0x11111111); 693 | let o5 = builder.and(o4, c); 694 | let o6 = builder.mul(o5, c); 695 | let d = builder.const_(28); 696 | let o7 = builder.shr_u(o6, d); 697 | let e = builder.const_(1); 698 | let _ = builder.and(o7, e); 699 | let spec = builder.finish(); 700 | 701 | synthesize(opts, context, &spec, &library) 702 | } 703 | 704 | fn p23(context: &z3::Context, opts: &Options) -> SynthResult { 705 | let library = Library { 706 | components: vec![ 707 | component::const_(if opts.synthesize_constants { 708 | None 709 | } else { 710 | Some(1) 711 | }), 712 | component::shr_u(), 713 | component::const_(if opts.synthesize_constants { 714 | None 715 | } else { 716 | Some(0x55555555) 717 | }), 718 | component::and(), 719 | component::sub(), 720 | component::const_(if opts.synthesize_constants { 721 | None 722 | } else { 723 | Some(0x33333333) 724 | }), 725 | component::and(), 726 | component::const_(if opts.synthesize_constants { 727 | None 728 | } else { 729 | Some(2) 730 | }), 731 | component::shr_u(), 732 | component::and(), 733 | component::add(), 734 | component::const_(if opts.synthesize_constants { 735 | None 736 | } else { 737 | Some(4) 738 | }), 739 | component::add(), 740 | component::const_(if opts.synthesize_constants { 741 | None 742 | } else { 743 | Some(0x0F0F0F0F) 744 | }), 745 | component::and(), 746 | ], 747 | }; 748 | 749 | let mut builder = ProgramBuilder::new(); 750 | let x = builder.var(); 751 | let a = builder.const_(1); 752 | let o1 = builder.shr_u(x, a); 753 | let b = builder.const_(0x55555555); 754 | let o2 = builder.and(o1, b); 755 | let o3 = builder.sub(x, o2); 756 | let c = builder.const_(0x33333333); 757 | let o4 = builder.and(o3, c); 758 | let d = builder.const_(2); 759 | let o5 = builder.shr_u(o3, d); 760 | let o6 = builder.and(o5, c); 761 | let o7 = builder.add(o4, o6); 762 | let e = builder.const_(4); 763 | let o8 = builder.shr_u(o7, e); 764 | let o9 = builder.add(o8, o7); 765 | let f = builder.const_(0x0F0F0F0F); 766 | let _ = builder.and(o9, f); 767 | let spec = builder.finish(); 768 | 769 | synthesize(opts, context, &spec, &library) 770 | } 771 | 772 | fn p24(context: &z3::Context, opts: &Options) -> SynthResult { 773 | let library = Library { 774 | components: vec![ 775 | component::const_(if opts.synthesize_constants { 776 | None 777 | } else { 778 | Some(1) 779 | }), 780 | component::sub(), 781 | component::shr_u(), 782 | component::or(), 783 | component::const_(if opts.synthesize_constants { 784 | None 785 | } else { 786 | Some(2) 787 | }), 788 | component::shr_u(), 789 | component::or(), 790 | component::const_(if opts.synthesize_constants { 791 | None 792 | } else { 793 | Some(4) 794 | }), 795 | component::shr_u(), 796 | component::or(), 797 | component::const_(if opts.synthesize_constants { 798 | None 799 | } else { 800 | Some(8) 801 | }), 802 | component::shr_u(), 803 | component::or(), 804 | component::const_(if opts.synthesize_constants { 805 | None 806 | } else { 807 | Some(16) 808 | }), 809 | component::shr_u(), 810 | component::or(), 811 | component::add(), 812 | ], 813 | }; 814 | 815 | let mut builder = ProgramBuilder::new(); 816 | let x = builder.var(); 817 | let a = builder.const_(1); 818 | let o1 = builder.sub(x, a); 819 | let o2 = builder.shr_u(o1, a); 820 | let o3 = builder.or(o1, o2); 821 | let b = builder.const_(2); 822 | let o4 = builder.shr_u(o3, b); 823 | let o5 = builder.or(o3, o4); 824 | let c = builder.const_(4); 825 | let o6 = builder.shr_u(o5, c); 826 | let o7 = builder.or(o5, o6); 827 | let d = builder.const_(8); 828 | let o8 = builder.shr_u(o7, d); 829 | let o9 = builder.or(o7, o8); 830 | let e = builder.const_(16); 831 | let o10 = builder.shr_u(o9, e); 832 | let o11 = builder.or(o9, o10); 833 | let _ = builder.add(o11, a); 834 | let spec = builder.finish(); 835 | 836 | synthesize(opts, context, &spec, &library) 837 | } 838 | 839 | fn p25(context: &z3::Context, opts: &Options) -> SynthResult { 840 | let library = Library { 841 | components: vec![ 842 | component::const_(if opts.synthesize_constants { 843 | None 844 | } else { 845 | Some(0xFFFF) 846 | }), 847 | component::and(), 848 | component::const_(if opts.synthesize_constants { 849 | None 850 | } else { 851 | Some(16) 852 | }), 853 | component::shr_u(), 854 | component::and(), 855 | component::shr_u(), 856 | component::mul(), 857 | component::mul(), 858 | component::mul(), 859 | component::mul(), 860 | component::shr_u(), 861 | component::add(), 862 | component::and(), 863 | component::shr_u(), 864 | component::add(), 865 | component::shr_u(), 866 | component::add(), 867 | component::add(), 868 | ], 869 | }; 870 | 871 | let mut builder = ProgramBuilder::new(); 872 | let x = builder.var(); 873 | let y = builder.var(); 874 | let a = builder.const_(0xFFFF); 875 | let o1 = builder.and(x, a); 876 | let b = builder.const_(16); 877 | let o2 = builder.shr_u(x, b); 878 | let o3 = builder.and(y, a); 879 | let o4 = builder.shr_u(y, b); 880 | let o5 = builder.mul(o1, o3); 881 | let o6 = builder.mul(o2, o3); 882 | let o7 = builder.mul(o1, o4); 883 | let o8 = builder.mul(o2, o4); 884 | let o9 = builder.shr_u(o5, b); 885 | let o10 = builder.add(o6, o9); 886 | let o11 = builder.and(o10, a); 887 | let o12 = builder.shr_u(o10, b); 888 | let o13 = builder.add(o7, o11); 889 | let o14 = builder.shr_u(o13, b); 890 | let o15 = builder.add(o14, o12); 891 | let _ = builder.add(o15, o8); 892 | let spec = builder.finish(); 893 | 894 | synthesize(opts, context, &spec, &library) 895 | } 896 | -------------------------------------------------------------------------------- /src/component.rs: -------------------------------------------------------------------------------- 1 | use crate::{Id, Operator}; 2 | use std::fmt::Debug; 3 | use z3::ast::{Ast, BV as BitVec}; 4 | 5 | fn bit_vec_from_u64(context: &z3::Context, val: u64, bit_width: u32) -> BitVec { 6 | BitVec::from_i64(context, val as i64, bit_width) 7 | } 8 | 9 | fn zero(context: &z3::Context, bit_width: u32) -> BitVec { 10 | bit_vec_from_u64(context, 0, bit_width) 11 | } 12 | 13 | fn one(context: &z3::Context, bit_width: u32) -> BitVec { 14 | bit_vec_from_u64(context, 1, bit_width) 15 | } 16 | 17 | pub trait Component: Debug { 18 | fn operand_arity(&self) -> usize; 19 | 20 | fn make_operator(&self, immediates: &[u64], operands: &[Id]) -> Operator; 21 | 22 | fn make_expression<'a>( 23 | &self, 24 | context: &'a z3::Context, 25 | immediates: &[BitVec<'a>], 26 | operands: &[BitVec<'a>], 27 | bit_width: u32, 28 | ) -> BitVec<'a>; 29 | 30 | /// How many immediates does this component require? 31 | fn immediate_arity(&self) -> usize { 32 | 0 33 | } 34 | } 35 | 36 | #[derive(Debug)] 37 | struct Const(Option); 38 | 39 | impl Component for Const { 40 | fn operand_arity(&self) -> usize { 41 | 0 42 | } 43 | 44 | fn make_operator(&self, immediates: &[u64], _operands: &[Id]) -> Operator { 45 | if let Some(val) = self.0 { 46 | Operator::Const(val) 47 | } else { 48 | Operator::Const(immediates[0]) 49 | } 50 | } 51 | 52 | fn make_expression<'a>( 53 | &self, 54 | context: &'a z3::Context, 55 | immediates: &[BitVec<'a>], 56 | _operands: &[BitVec<'a>], 57 | bit_width: u32, 58 | ) -> BitVec<'a> { 59 | if let Some(val) = self.0 { 60 | BitVec::from_i64(context, val as i64, bit_width) 61 | } else { 62 | immediates[0].clone() 63 | } 64 | } 65 | 66 | fn immediate_arity(&self) -> usize { 67 | if self.0.is_some() { 68 | 0 69 | } else { 70 | 1 71 | } 72 | } 73 | } 74 | 75 | pub fn const_(val: Option) -> Box { 76 | Box::new(Const(val)) as _ 77 | } 78 | 79 | #[derive(Debug)] 80 | struct Eqz; 81 | 82 | impl Component for Eqz { 83 | fn operand_arity(&self) -> usize { 84 | 1 85 | } 86 | 87 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 88 | Operator::Eqz(operands[0]) 89 | } 90 | 91 | fn make_expression<'a>( 92 | &self, 93 | context: &'a z3::Context, 94 | _immediates: &[BitVec<'a>], 95 | operands: &[BitVec<'a>], 96 | bit_width: u32, 97 | ) -> BitVec<'a> { 98 | zero(context, bit_width) 99 | ._eq(&operands[0]) 100 | .ite(&one(context, bit_width), &zero(context, bit_width)) 101 | } 102 | } 103 | 104 | pub fn eqz() -> Box { 105 | Box::new(Eqz) as _ 106 | } 107 | 108 | #[derive(Debug)] 109 | struct Clz; 110 | 111 | impl Component for Clz { 112 | fn operand_arity(&self) -> usize { 113 | 1 114 | } 115 | 116 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 117 | Operator::Clz(operands[0]) 118 | } 119 | 120 | fn make_expression<'a>( 121 | &self, 122 | context: &'a z3::Context, 123 | _immediates: &[BitVec<'a>], 124 | operands: &[BitVec<'a>], 125 | bit_width: u32, 126 | ) -> BitVec<'a> { 127 | fn clz<'a>( 128 | context: &'a z3::Context, 129 | input: &BitVec<'a>, 130 | one_bit: &BitVec<'a>, 131 | bit_width: u32, 132 | i: u32, 133 | ) -> BitVec<'a> { 134 | if i == bit_width { 135 | bit_vec_from_u64(context, i as u64, bit_width) 136 | } else { 137 | input 138 | .extract(bit_width - 1 - i, bit_width - 1 - i) 139 | ._eq(&one_bit) 140 | .ite( 141 | &bit_vec_from_u64(context, i as u64, bit_width), 142 | &clz(context, input, one_bit, bit_width, i + 1), 143 | ) 144 | } 145 | } 146 | 147 | let one_bit = BitVec::from_i64(context, 1, 1); 148 | clz(context, &operands[0], &one_bit, bit_width, 0) 149 | } 150 | } 151 | 152 | pub fn clz() -> Box { 153 | Box::new(Clz) as _ 154 | } 155 | 156 | #[derive(Debug)] 157 | struct Ctz; 158 | 159 | impl Component for Ctz { 160 | fn operand_arity(&self) -> usize { 161 | 1 162 | } 163 | 164 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 165 | Operator::Ctz(operands[0]) 166 | } 167 | 168 | fn make_expression<'a>( 169 | &self, 170 | context: &'a z3::Context, 171 | _immediates: &[BitVec<'a>], 172 | operands: &[BitVec<'a>], 173 | bit_width: u32, 174 | ) -> BitVec<'a> { 175 | fn ctz<'a>( 176 | context: &'a z3::Context, 177 | input: &BitVec<'a>, 178 | one_bit: &BitVec<'a>, 179 | bit_width: u32, 180 | i: u32, 181 | ) -> BitVec<'a> { 182 | if i == bit_width { 183 | bit_vec_from_u64(context, i as u64, bit_width) 184 | } else { 185 | input.extract(i, i)._eq(&one_bit).ite( 186 | &bit_vec_from_u64(context, i as u64, bit_width), 187 | &ctz(context, input, one_bit, bit_width, i + 1), 188 | ) 189 | } 190 | } 191 | 192 | let one_bit = BitVec::from_i64(context, 1, 1); 193 | ctz(context, &operands[0], &one_bit, bit_width, 0) 194 | } 195 | } 196 | 197 | pub fn ctz() -> Box { 198 | Box::new(Ctz) as _ 199 | } 200 | 201 | #[derive(Debug)] 202 | struct Popcnt; 203 | 204 | impl Component for Popcnt { 205 | fn operand_arity(&self) -> usize { 206 | 1 207 | } 208 | 209 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 210 | Operator::Popcnt(operands[0]) 211 | } 212 | 213 | fn make_expression<'a>( 214 | &self, 215 | _context: &'a z3::Context, 216 | _immediates: &[BitVec<'a>], 217 | operands: &[BitVec<'a>], 218 | bit_width: u32, 219 | ) -> BitVec<'a> { 220 | let mut bits: Vec<_> = (0..bit_width) 221 | .map(|i| operands[0].extract(i, i).zero_ext(bit_width - 1)) 222 | .collect(); 223 | let initial = bits.pop().unwrap(); 224 | bits.iter().fold(initial, |a, b| a.bvadd(b)) 225 | } 226 | } 227 | 228 | pub fn popcnt() -> Box { 229 | Box::new(Popcnt) as _ 230 | } 231 | 232 | #[derive(Debug)] 233 | struct Eq; 234 | 235 | impl Component for Eq { 236 | fn operand_arity(&self) -> usize { 237 | 2 238 | } 239 | 240 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 241 | Operator::Eq(operands[0], operands[1]) 242 | } 243 | 244 | fn make_expression<'a>( 245 | &self, 246 | context: &'a z3::Context, 247 | _immediates: &[BitVec<'a>], 248 | operands: &[BitVec<'a>], 249 | bit_width: u32, 250 | ) -> BitVec<'a> { 251 | operands[0] 252 | ._eq(&operands[1]) 253 | .ite(&one(context, bit_width), &zero(context, bit_width)) 254 | } 255 | } 256 | 257 | pub fn eq() -> Box { 258 | Box::new(Eq) as _ 259 | } 260 | 261 | #[derive(Debug)] 262 | struct Ne; 263 | 264 | impl Component for Ne { 265 | fn operand_arity(&self) -> usize { 266 | 2 267 | } 268 | 269 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 270 | Operator::Ne(operands[0], operands[1]) 271 | } 272 | 273 | fn make_expression<'a>( 274 | &self, 275 | context: &'a z3::Context, 276 | _immediates: &[BitVec<'a>], 277 | operands: &[BitVec<'a>], 278 | bit_width: u32, 279 | ) -> BitVec<'a> { 280 | operands[0] 281 | ._eq(&operands[1]) 282 | .ite(&zero(context, bit_width), &one(context, bit_width)) 283 | } 284 | } 285 | 286 | pub fn ne() -> Box { 287 | Box::new(Ne) as _ 288 | } 289 | 290 | #[derive(Debug)] 291 | struct LtS; 292 | 293 | impl Component for LtS { 294 | fn operand_arity(&self) -> usize { 295 | 2 296 | } 297 | 298 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 299 | Operator::LtS(operands[0], operands[1]) 300 | } 301 | 302 | fn make_expression<'a>( 303 | &self, 304 | context: &'a z3::Context, 305 | _immediates: &[BitVec<'a>], 306 | operands: &[BitVec<'a>], 307 | bit_width: u32, 308 | ) -> BitVec<'a> { 309 | operands[0] 310 | .bvslt(&operands[1]) 311 | .ite(&one(context, bit_width), &zero(context, bit_width)) 312 | } 313 | } 314 | 315 | pub fn lt_s() -> Box { 316 | Box::new(LtS) as _ 317 | } 318 | 319 | #[derive(Debug)] 320 | struct LtU; 321 | 322 | impl Component for LtU { 323 | fn operand_arity(&self) -> usize { 324 | 2 325 | } 326 | 327 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 328 | Operator::LtU(operands[0], operands[1]) 329 | } 330 | 331 | fn make_expression<'a>( 332 | &self, 333 | context: &'a z3::Context, 334 | _immediates: &[BitVec<'a>], 335 | operands: &[BitVec<'a>], 336 | bit_width: u32, 337 | ) -> BitVec<'a> { 338 | operands[0] 339 | .bvult(&operands[1]) 340 | .ite(&one(context, bit_width), &zero(context, bit_width)) 341 | } 342 | } 343 | 344 | pub fn lt_u() -> Box { 345 | Box::new(LtU) as _ 346 | } 347 | 348 | #[derive(Debug)] 349 | struct GtS; 350 | 351 | impl Component for GtS { 352 | fn operand_arity(&self) -> usize { 353 | 2 354 | } 355 | 356 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 357 | Operator::GtS(operands[0], operands[1]) 358 | } 359 | 360 | fn make_expression<'a>( 361 | &self, 362 | context: &'a z3::Context, 363 | _immediates: &[BitVec<'a>], 364 | operands: &[BitVec<'a>], 365 | bit_width: u32, 366 | ) -> BitVec<'a> { 367 | operands[0] 368 | .bvsgt(&operands[1]) 369 | .ite(&one(context, bit_width), &zero(context, bit_width)) 370 | } 371 | } 372 | 373 | pub fn gt_s() -> Box { 374 | Box::new(GtS) as _ 375 | } 376 | 377 | #[derive(Debug)] 378 | struct GtU; 379 | 380 | impl Component for GtU { 381 | fn operand_arity(&self) -> usize { 382 | 2 383 | } 384 | 385 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 386 | Operator::GtU(operands[0], operands[1]) 387 | } 388 | 389 | fn make_expression<'a>( 390 | &self, 391 | context: &'a z3::Context, 392 | _immediates: &[BitVec<'a>], 393 | operands: &[BitVec<'a>], 394 | bit_width: u32, 395 | ) -> BitVec<'a> { 396 | operands[0] 397 | .bvugt(&operands[1]) 398 | .ite(&one(context, bit_width), &zero(context, bit_width)) 399 | } 400 | } 401 | 402 | pub fn gt_u() -> Box { 403 | Box::new(GtU) as _ 404 | } 405 | 406 | #[derive(Debug)] 407 | struct LeS; 408 | 409 | impl Component for LeS { 410 | fn operand_arity(&self) -> usize { 411 | 2 412 | } 413 | 414 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 415 | Operator::LeS(operands[0], operands[1]) 416 | } 417 | 418 | fn make_expression<'a>( 419 | &self, 420 | context: &'a z3::Context, 421 | _immediates: &[BitVec<'a>], 422 | operands: &[BitVec<'a>], 423 | bit_width: u32, 424 | ) -> BitVec<'a> { 425 | operands[0] 426 | .bvsle(&operands[1]) 427 | .ite(&one(context, bit_width), &zero(context, bit_width)) 428 | } 429 | } 430 | 431 | pub fn le_s() -> Box { 432 | Box::new(LeS) as _ 433 | } 434 | 435 | #[derive(Debug)] 436 | struct LeU; 437 | 438 | impl Component for LeU { 439 | fn operand_arity(&self) -> usize { 440 | 2 441 | } 442 | 443 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 444 | Operator::LeU(operands[0], operands[1]) 445 | } 446 | 447 | fn make_expression<'a>( 448 | &self, 449 | context: &'a z3::Context, 450 | _immediates: &[BitVec<'a>], 451 | operands: &[BitVec<'a>], 452 | bit_width: u32, 453 | ) -> BitVec<'a> { 454 | operands[0] 455 | .bvule(&operands[1]) 456 | .ite(&one(context, bit_width), &zero(context, bit_width)) 457 | } 458 | } 459 | 460 | pub fn le_u() -> Box { 461 | Box::new(LeU) as _ 462 | } 463 | 464 | #[derive(Debug)] 465 | struct GeS; 466 | 467 | impl Component for GeS { 468 | fn operand_arity(&self) -> usize { 469 | 2 470 | } 471 | 472 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 473 | Operator::GeS(operands[0], operands[1]) 474 | } 475 | 476 | fn make_expression<'a>( 477 | &self, 478 | context: &'a z3::Context, 479 | _immediates: &[BitVec<'a>], 480 | operands: &[BitVec<'a>], 481 | bit_width: u32, 482 | ) -> BitVec<'a> { 483 | operands[0] 484 | .bvsge(&operands[1]) 485 | .ite(&one(context, bit_width), &zero(context, bit_width)) 486 | } 487 | } 488 | 489 | pub fn ge_s() -> Box { 490 | Box::new(GeS) as _ 491 | } 492 | 493 | #[derive(Debug)] 494 | struct GeU; 495 | 496 | impl Component for GeU { 497 | fn operand_arity(&self) -> usize { 498 | 2 499 | } 500 | 501 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 502 | Operator::GeU(operands[0], operands[1]) 503 | } 504 | 505 | fn make_expression<'a>( 506 | &self, 507 | context: &'a z3::Context, 508 | _immediates: &[BitVec<'a>], 509 | operands: &[BitVec<'a>], 510 | bit_width: u32, 511 | ) -> BitVec<'a> { 512 | operands[0] 513 | .bvuge(&operands[1]) 514 | .ite(&one(context, bit_width), &zero(context, bit_width)) 515 | } 516 | } 517 | 518 | pub fn ge_u() -> Box { 519 | Box::new(GeU) as _ 520 | } 521 | 522 | #[derive(Debug)] 523 | struct Add; 524 | 525 | impl Component for Add { 526 | fn operand_arity(&self) -> usize { 527 | 2 528 | } 529 | 530 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 531 | Operator::Add(operands[0], operands[1]) 532 | } 533 | 534 | fn make_expression<'a>( 535 | &self, 536 | _context: &'a z3::Context, 537 | _immediates: &[BitVec<'a>], 538 | operands: &[BitVec<'a>], 539 | _bit_width: u32, 540 | ) -> BitVec<'a> { 541 | operands[0].bvadd(&operands[1]) 542 | } 543 | } 544 | 545 | pub fn add() -> Box { 546 | Box::new(Add) as _ 547 | } 548 | 549 | #[derive(Debug)] 550 | struct Sub; 551 | 552 | impl Component for Sub { 553 | fn operand_arity(&self) -> usize { 554 | 2 555 | } 556 | 557 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 558 | Operator::Sub(operands[0], operands[1]) 559 | } 560 | 561 | fn make_expression<'a>( 562 | &self, 563 | _context: &'a z3::Context, 564 | _immediates: &[BitVec<'a>], 565 | operands: &[BitVec<'a>], 566 | _bit_width: u32, 567 | ) -> BitVec<'a> { 568 | operands[0].bvsub(&operands[1]) 569 | } 570 | } 571 | 572 | pub fn sub() -> Box { 573 | Box::new(Sub) as _ 574 | } 575 | 576 | #[derive(Debug)] 577 | struct Mul; 578 | 579 | impl Component for Mul { 580 | fn operand_arity(&self) -> usize { 581 | 2 582 | } 583 | 584 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 585 | Operator::Mul(operands[0], operands[1]) 586 | } 587 | 588 | fn make_expression<'a>( 589 | &self, 590 | _context: &'a z3::Context, 591 | _immediates: &[BitVec<'a>], 592 | operands: &[BitVec<'a>], 593 | _bit_width: u32, 594 | ) -> BitVec<'a> { 595 | operands[0].bvmul(&operands[1]) 596 | } 597 | } 598 | 599 | pub fn mul() -> Box { 600 | Box::new(Mul) as _ 601 | } 602 | 603 | #[derive(Debug)] 604 | struct DivS; 605 | 606 | impl Component for DivS { 607 | fn operand_arity(&self) -> usize { 608 | 2 609 | } 610 | 611 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 612 | Operator::DivS(operands[0], operands[1]) 613 | } 614 | 615 | fn make_expression<'a>( 616 | &self, 617 | _context: &'a z3::Context, 618 | _immediates: &[BitVec<'a>], 619 | operands: &[BitVec<'a>], 620 | _bit_width: u32, 621 | ) -> BitVec<'a> { 622 | operands[0].bvsdiv(&operands[1]) 623 | } 624 | } 625 | 626 | pub fn div_s() -> Box { 627 | Box::new(DivS) as _ 628 | } 629 | 630 | #[derive(Debug)] 631 | struct DivU; 632 | 633 | impl Component for DivU { 634 | fn operand_arity(&self) -> usize { 635 | 2 636 | } 637 | 638 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 639 | Operator::DivU(operands[0], operands[1]) 640 | } 641 | 642 | fn make_expression<'a>( 643 | &self, 644 | _context: &'a z3::Context, 645 | _immediates: &[BitVec<'a>], 646 | operands: &[BitVec<'a>], 647 | _bit_width: u32, 648 | ) -> BitVec<'a> { 649 | operands[0].bvudiv(&operands[1]) 650 | } 651 | } 652 | 653 | pub fn div_u() -> Box { 654 | Box::new(DivU) as _ 655 | } 656 | 657 | #[derive(Debug)] 658 | struct RemS; 659 | 660 | impl Component for RemS { 661 | fn operand_arity(&self) -> usize { 662 | 2 663 | } 664 | 665 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 666 | Operator::RemS(operands[0], operands[1]) 667 | } 668 | 669 | fn make_expression<'a>( 670 | &self, 671 | _context: &'a z3::Context, 672 | _immediates: &[BitVec<'a>], 673 | operands: &[BitVec<'a>], 674 | _bit_width: u32, 675 | ) -> BitVec<'a> { 676 | operands[0].bvsrem(&operands[1]) 677 | } 678 | } 679 | 680 | pub fn rem_s() -> Box { 681 | Box::new(RemS) as _ 682 | } 683 | 684 | #[derive(Debug)] 685 | struct RemU; 686 | 687 | impl Component for RemU { 688 | fn operand_arity(&self) -> usize { 689 | 2 690 | } 691 | 692 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 693 | Operator::RemU(operands[0], operands[1]) 694 | } 695 | 696 | fn make_expression<'a>( 697 | &self, 698 | _context: &'a z3::Context, 699 | _immediates: &[BitVec<'a>], 700 | operands: &[BitVec<'a>], 701 | _bit_width: u32, 702 | ) -> BitVec<'a> { 703 | operands[0].bvurem(&operands[1]) 704 | } 705 | } 706 | 707 | pub fn rem_u() -> Box { 708 | Box::new(RemU) as _ 709 | } 710 | 711 | #[derive(Debug)] 712 | struct And; 713 | 714 | impl Component for And { 715 | fn operand_arity(&self) -> usize { 716 | 2 717 | } 718 | 719 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 720 | Operator::And(operands[0], operands[1]) 721 | } 722 | 723 | fn make_expression<'a>( 724 | &self, 725 | _context: &'a z3::Context, 726 | _immediates: &[BitVec<'a>], 727 | operands: &[BitVec<'a>], 728 | _bit_width: u32, 729 | ) -> BitVec<'a> { 730 | operands[0].bvand(&operands[1]) 731 | } 732 | } 733 | 734 | pub fn and() -> Box { 735 | Box::new(And) as _ 736 | } 737 | 738 | #[derive(Debug)] 739 | struct Or; 740 | 741 | impl Component for Or { 742 | fn operand_arity(&self) -> usize { 743 | 2 744 | } 745 | 746 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 747 | Operator::Or(operands[0], operands[1]) 748 | } 749 | 750 | fn make_expression<'a>( 751 | &self, 752 | _context: &'a z3::Context, 753 | _immediates: &[BitVec<'a>], 754 | operands: &[BitVec<'a>], 755 | _bit_width: u32, 756 | ) -> BitVec<'a> { 757 | operands[0].bvor(&operands[1]) 758 | } 759 | } 760 | 761 | pub fn or() -> Box { 762 | Box::new(Or) as _ 763 | } 764 | 765 | #[derive(Debug)] 766 | struct Xor; 767 | 768 | impl Component for Xor { 769 | fn operand_arity(&self) -> usize { 770 | 2 771 | } 772 | 773 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 774 | Operator::Xor(operands[0], operands[1]) 775 | } 776 | 777 | fn make_expression<'a>( 778 | &self, 779 | _context: &'a z3::Context, 780 | _immediates: &[BitVec<'a>], 781 | operands: &[BitVec<'a>], 782 | _bit_width: u32, 783 | ) -> BitVec<'a> { 784 | operands[0].bvxor(&operands[1]) 785 | } 786 | } 787 | 788 | pub fn xor() -> Box { 789 | Box::new(Xor) as _ 790 | } 791 | 792 | #[derive(Debug)] 793 | struct Shl; 794 | 795 | impl Component for Shl { 796 | fn operand_arity(&self) -> usize { 797 | 2 798 | } 799 | 800 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 801 | Operator::Shl(operands[0], operands[1]) 802 | } 803 | 804 | fn make_expression<'a>( 805 | &self, 806 | _context: &'a z3::Context, 807 | _immediates: &[BitVec<'a>], 808 | operands: &[BitVec<'a>], 809 | _bit_width: u32, 810 | ) -> BitVec<'a> { 811 | operands[0].bvshl(&operands[1]) 812 | } 813 | } 814 | 815 | pub fn shl() -> Box { 816 | Box::new(Shl) as _ 817 | } 818 | 819 | #[derive(Debug)] 820 | struct ShrS; 821 | 822 | impl Component for ShrS { 823 | fn operand_arity(&self) -> usize { 824 | 2 825 | } 826 | 827 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 828 | Operator::ShrS(operands[0], operands[1]) 829 | } 830 | 831 | fn make_expression<'a>( 832 | &self, 833 | _context: &'a z3::Context, 834 | _immediates: &[BitVec<'a>], 835 | operands: &[BitVec<'a>], 836 | _bit_width: u32, 837 | ) -> BitVec<'a> { 838 | operands[0].bvashr(&operands[1]) 839 | } 840 | } 841 | 842 | pub fn shr_s() -> Box { 843 | Box::new(ShrS) as _ 844 | } 845 | 846 | #[derive(Debug)] 847 | struct ShrU; 848 | 849 | impl Component for ShrU { 850 | fn operand_arity(&self) -> usize { 851 | 2 852 | } 853 | 854 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 855 | Operator::ShrU(operands[0], operands[1]) 856 | } 857 | 858 | fn make_expression<'a>( 859 | &self, 860 | _context: &'a z3::Context, 861 | _immediates: &[BitVec<'a>], 862 | operands: &[BitVec<'a>], 863 | _bit_width: u32, 864 | ) -> BitVec<'a> { 865 | operands[0].bvlshr(&operands[1]) 866 | } 867 | } 868 | 869 | pub fn shr_u() -> Box { 870 | Box::new(ShrU) as _ 871 | } 872 | 873 | #[derive(Debug)] 874 | struct Rotl; 875 | 876 | impl Component for Rotl { 877 | fn operand_arity(&self) -> usize { 878 | 2 879 | } 880 | 881 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 882 | Operator::Rotl(operands[0], operands[1]) 883 | } 884 | 885 | fn make_expression<'a>( 886 | &self, 887 | _context: &'a z3::Context, 888 | _immediates: &[BitVec<'a>], 889 | operands: &[BitVec<'a>], 890 | _bit_width: u32, 891 | ) -> BitVec<'a> { 892 | operands[0].bvrotl(&operands[1]) 893 | } 894 | } 895 | 896 | pub fn rotl() -> Box { 897 | Box::new(Rotl) as _ 898 | } 899 | 900 | #[derive(Debug)] 901 | struct Rotr; 902 | 903 | impl Component for Rotr { 904 | fn operand_arity(&self) -> usize { 905 | 2 906 | } 907 | 908 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 909 | Operator::Rotr(operands[0], operands[1]) 910 | } 911 | 912 | fn make_expression<'a>( 913 | &self, 914 | _context: &'a z3::Context, 915 | _immediates: &[BitVec<'a>], 916 | operands: &[BitVec<'a>], 917 | _bit_width: u32, 918 | ) -> BitVec<'a> { 919 | operands[0].bvrotr(&operands[1]) 920 | } 921 | } 922 | 923 | pub fn rotr() -> Box { 924 | Box::new(Rotr) as _ 925 | } 926 | 927 | #[derive(Debug)] 928 | struct Select; 929 | 930 | impl Component for Select { 931 | fn operand_arity(&self) -> usize { 932 | 3 933 | } 934 | 935 | fn make_operator(&self, _immediates: &[u64], operands: &[Id]) -> Operator { 936 | Operator::Select(operands[0], operands[1], operands[2]) 937 | } 938 | 939 | fn make_expression<'a>( 940 | &self, 941 | context: &'a z3::Context, 942 | _immediates: &[BitVec<'a>], 943 | operands: &[BitVec<'a>], 944 | bit_width: u32, 945 | ) -> BitVec<'a> { 946 | operands[0] 947 | ._eq(&zero(context, bit_width)) 948 | .ite(&operands[2], &operands[1]) 949 | } 950 | } 951 | 952 | pub fn select() -> Box { 953 | Box::new(Select) as _ 954 | } 955 | 956 | macro_rules! with_operator_component { 957 | ( $me:expr , |$c:ident| $body:expr ) => { 958 | match $me { 959 | Operator::Var => panic!("`Var` operators do not have a component"), 960 | Operator::Const(c) => { 961 | let $c = Const(Some(*c)); 962 | $body 963 | } 964 | Operator::Eqz(_) => { 965 | let $c = Eqz; 966 | $body 967 | } 968 | Operator::Clz(_) => { 969 | let $c = Clz; 970 | $body 971 | } 972 | Operator::Ctz(_) => { 973 | let $c = Ctz; 974 | $body 975 | } 976 | Operator::Popcnt(_) => { 977 | let $c = Popcnt; 978 | $body 979 | } 980 | Operator::Eq(_, _) => { 981 | let $c = Eq; 982 | $body 983 | } 984 | Operator::Ne(_, _) => { 985 | let $c = Ne; 986 | $body 987 | } 988 | Operator::LtS(_, _) => { 989 | let $c = LtS; 990 | $body 991 | } 992 | Operator::LtU(_, _) => { 993 | let $c = LtU; 994 | $body 995 | } 996 | Operator::GtS(_, _) => { 997 | let $c = GtS; 998 | $body 999 | } 1000 | Operator::GtU(_, _) => { 1001 | let $c = GtU; 1002 | $body 1003 | } 1004 | Operator::LeS(_, _) => { 1005 | let $c = LeS; 1006 | $body 1007 | } 1008 | Operator::LeU(_, _) => { 1009 | let $c = LeU; 1010 | $body 1011 | } 1012 | Operator::GeS(_, _) => { 1013 | let $c = GeS; 1014 | $body 1015 | } 1016 | Operator::GeU(_, _) => { 1017 | let $c = GeU; 1018 | $body 1019 | } 1020 | Operator::Add(_, _) => { 1021 | let $c = Add; 1022 | $body 1023 | } 1024 | Operator::Sub(_, _) => { 1025 | let $c = Sub; 1026 | $body 1027 | } 1028 | Operator::Mul(_, _) => { 1029 | let $c = Mul; 1030 | $body 1031 | } 1032 | Operator::DivS(_, _) => { 1033 | let $c = DivS; 1034 | $body 1035 | } 1036 | Operator::DivU(_, _) => { 1037 | let $c = DivU; 1038 | $body 1039 | } 1040 | Operator::RemS(_, _) => { 1041 | let $c = RemS; 1042 | $body 1043 | } 1044 | Operator::RemU(_, _) => { 1045 | let $c = RemU; 1046 | $body 1047 | } 1048 | Operator::And(_, _) => { 1049 | let $c = And; 1050 | $body 1051 | } 1052 | Operator::Or(_, _) => { 1053 | let $c = Or; 1054 | $body 1055 | } 1056 | Operator::Xor(_, _) => { 1057 | let $c = Xor; 1058 | $body 1059 | } 1060 | Operator::Shl(_, _) => { 1061 | let $c = Shl; 1062 | $body 1063 | } 1064 | Operator::ShrS(_, _) => { 1065 | let $c = ShrS; 1066 | $body 1067 | } 1068 | Operator::ShrU(_, _) => { 1069 | let $c = ShrU; 1070 | $body 1071 | } 1072 | Operator::Rotl(_, _) => { 1073 | let $c = Rotl; 1074 | $body 1075 | } 1076 | Operator::Rotr(_, _) => { 1077 | let $c = Rotr; 1078 | $body 1079 | } 1080 | Operator::Select(_, _, _) => { 1081 | let $c = Select; 1082 | $body 1083 | } 1084 | } 1085 | }; 1086 | } 1087 | 1088 | impl Component for Operator { 1089 | fn operand_arity(&self) -> usize { 1090 | Operator::arity(self) 1091 | } 1092 | 1093 | fn make_operator(&self, immediates: &[u64], operands: &[Id]) -> Operator { 1094 | with_operator_component!(self, |c| c.make_operator(immediates, operands)) 1095 | } 1096 | 1097 | fn make_expression<'a>( 1098 | &self, 1099 | context: &'a z3::Context, 1100 | immediates: &[BitVec<'a>], 1101 | operands: &[BitVec<'a>], 1102 | bit_width: u32, 1103 | ) -> BitVec<'a> { 1104 | with_operator_component!(self, |c| { 1105 | c.make_expression(context, immediates, operands, bit_width) 1106 | }) 1107 | } 1108 | 1109 | fn immediate_arity(&self) -> usize { 1110 | with_operator_component!(self, |c| c.immediate_arity()) 1111 | } 1112 | } 1113 | 1114 | #[cfg(test)] 1115 | mod tests { 1116 | use super::*; 1117 | 1118 | #[test] 1119 | fn ctz_test() { 1120 | let _ = env_logger::try_init(); 1121 | let cfg = z3::Config::new(); 1122 | let ctx = z3::Context::new(&cfg); 1123 | 1124 | // 0000 0000 0000 0000 0000 0000 0000 0010 1125 | assert!(ctz() 1126 | .make_expression(&ctx, &vec![], &vec![bit_vec_from_u64(&ctx, 2, 32)], 32) 1127 | ._eq(&bit_vec_from_u64(&ctx, 1, 32)) 1128 | .simplify() 1129 | .as_bool() 1130 | .unwrap()); 1131 | // all zeroes 1132 | assert!(ctz() 1133 | .make_expression(&ctx, &vec![], &vec![bit_vec_from_u64(&ctx, 0, 32)], 32) 1134 | ._eq(&bit_vec_from_u64(&ctx, 32, 32)) 1135 | .simplify() 1136 | .as_bool() 1137 | .unwrap()); 1138 | // all ones 1139 | assert!(ctz() 1140 | .make_expression( 1141 | &ctx, 1142 | &vec![], 1143 | &vec![z3::ast::BV::from_i64(&ctx, -1, 32)], 1144 | 32 1145 | ) 1146 | ._eq(&bit_vec_from_u64(&ctx, 0, 32)) 1147 | .simplify() 1148 | .as_bool() 1149 | .unwrap()); 1150 | // 00 1010 1151 | assert!(ctz() 1152 | .make_expression(&ctx, &vec![], &vec![bit_vec_from_u64(&ctx, 10, 6)], 6) 1153 | ._eq(&bit_vec_from_u64(&ctx, 1, 6)) 1154 | .simplify() 1155 | .as_bool() 1156 | .unwrap()); 1157 | } 1158 | 1159 | #[test] 1160 | fn clz_test() { 1161 | let _ = env_logger::try_init(); 1162 | let cfg = z3::Config::new(); 1163 | let ctx = z3::Context::new(&cfg); 1164 | 1165 | // 0000 0000 0000 0000 0000 0000 0000 0010 1166 | assert!(clz() 1167 | .make_expression(&ctx, &vec![], &vec![bit_vec_from_u64(&ctx, 2, 32)], 32) 1168 | ._eq(&bit_vec_from_u64(&ctx, 30, 32)) 1169 | .simplify() 1170 | .as_bool() 1171 | .unwrap()); 1172 | // all zeroes 1173 | assert!(clz() 1174 | .make_expression(&ctx, &vec![], &vec![bit_vec_from_u64(&ctx, 0, 32)], 32) 1175 | ._eq(&bit_vec_from_u64(&ctx, 32, 32)) 1176 | .simplify() 1177 | .as_bool() 1178 | .unwrap()); 1179 | // all ones 1180 | assert!(clz() 1181 | .make_expression( 1182 | &ctx, 1183 | &vec![], 1184 | &vec![z3::ast::BV::from_i64(&ctx, -1, 32)], 1185 | 32 1186 | ) 1187 | ._eq(&bit_vec_from_u64(&ctx, 0, 32)) 1188 | .simplify() 1189 | .as_bool() 1190 | .unwrap()); 1191 | // 00 1010 1192 | assert!(clz() 1193 | .make_expression(&ctx, &vec![], &vec![bit_vec_from_u64(&ctx, 10, 6)], 6) 1194 | ._eq(&bit_vec_from_u64(&ctx, 2, 6)) 1195 | .simplify() 1196 | .as_bool() 1197 | .unwrap()); 1198 | } 1199 | } 1200 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_debug_implementations)] 2 | 3 | #[cfg(feature = "log")] 4 | #[macro_use] 5 | extern crate log; 6 | 7 | #[cfg(not(feature = "log"))] 8 | #[macro_use] 9 | mod fake_logging; 10 | 11 | mod builder; 12 | pub mod component; 13 | mod operator; 14 | 15 | pub use builder::ProgramBuilder; 16 | pub use component::Component; 17 | pub use operator::Operator; 18 | 19 | use std::collections::{HashMap, HashSet}; 20 | use std::fmt::{self, Display}; 21 | use std::iter::FromIterator; 22 | use std::ops::Range; 23 | use std::time; 24 | use z3::ast::{Ast, Bool, BV as BitVec}; 25 | 26 | const FULL_BIT_WIDTH: u32 = 32; 27 | 28 | fn and<'a, 'b>(context: &'a z3::Context, exprs: impl IntoIterator>) -> Bool<'a> 29 | where 30 | 'a: 'b, 31 | { 32 | let exprs: Vec<&_> = exprs.into_iter().collect(); 33 | Bool::from_bool(context, true).and(&exprs) 34 | } 35 | 36 | fn or<'a, 'b>(context: &'a z3::Context, exprs: impl IntoIterator>) -> Bool<'a> 37 | where 38 | 'a: 'b, 39 | { 40 | let exprs: Vec<&_> = exprs.into_iter().collect(); 41 | Bool::from_bool(context, false).or(&exprs) 42 | } 43 | 44 | fn fresh_immediate(context: &z3::Context, bit_width: u32) -> BitVec { 45 | BitVec::fresh_const(context, "immediate", bit_width) 46 | } 47 | 48 | fn fresh_param(context: &z3::Context, bit_width: u32) -> BitVec { 49 | BitVec::fresh_const(context, "param", bit_width) 50 | } 51 | 52 | fn fresh_result(context: &z3::Context, bit_width: u32) -> BitVec { 53 | BitVec::fresh_const(context, "result", bit_width) 54 | } 55 | 56 | fn fresh_input(context: &z3::Context, bit_width: u32) -> BitVec { 57 | BitVec::fresh_const(context, "input", bit_width) 58 | } 59 | 60 | fn fresh_output(context: &z3::Context, bit_width: u32) -> BitVec { 61 | BitVec::fresh_const(context, "output", bit_width) 62 | } 63 | 64 | fn eval_bitvec(model: &z3::Model, bv: &BitVec) -> u64 { 65 | model.eval(bv).unwrap().as_u64().unwrap() 66 | } 67 | 68 | fn eval_bitvecs<'a, I>(model: &'a z3::Model, bvs: I) -> Vec 69 | where 70 | I: IntoIterator>, 71 | { 72 | bvs.into_iter() 73 | .map(move |bv| eval_bitvec(model, bv)) 74 | .collect() 75 | } 76 | 77 | fn eval_line(model: &z3::Model, line: &Line) -> u32 { 78 | eval_bitvec(model, line) as u32 79 | } 80 | 81 | fn eval_lines<'a, I>(model: &'a z3::Model, lines: I) -> Vec 82 | where 83 | I: IntoIterator>, 84 | { 85 | lines 86 | .into_iter() 87 | .map(move |l| eval_line(model, l)) 88 | .collect() 89 | } 90 | 91 | #[derive(Debug)] 92 | pub enum Error { 93 | NoComponents, 94 | SynthesisUnsatisfiable, 95 | SynthesisUnknown, 96 | } 97 | 98 | pub type Result = std::result::Result; 99 | 100 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 101 | pub struct Id(u32); 102 | 103 | impl Display for Id { 104 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 105 | let mut s = String::new(); 106 | let mut x = self.0; 107 | loop { 108 | let y = x % 26; 109 | x = x / 26; 110 | 111 | s.insert(0, ('a' as u32 + y) as u8 as char); 112 | 113 | if x == 0 { 114 | break; 115 | } 116 | 117 | x -= 1; 118 | } 119 | write!(f, "{}", s) 120 | } 121 | } 122 | 123 | #[derive(Debug)] 124 | pub struct Instruction { 125 | result: Id, 126 | operator: Operator, 127 | } 128 | 129 | impl Display for Instruction { 130 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 131 | write!(f, "{} ← {}", self.result, self.operator) 132 | } 133 | } 134 | 135 | pub trait Specification: fmt::Debug { 136 | fn arity(&self) -> usize; 137 | 138 | fn make_expression<'a>( 139 | &self, 140 | context: &'a z3::Context, 141 | inputs: &[BitVec<'a>], 142 | output: &BitVec<'a>, 143 | bit_width: u32, 144 | ) -> Bool<'a>; 145 | } 146 | 147 | /// A collection of components. 148 | /// 149 | /// Multiple copies of a particular component may exist in the library, allowing 150 | /// synthesis to use it multiple times. 151 | #[derive(Debug)] 152 | pub struct Library { 153 | pub components: Vec>, 154 | } 155 | 156 | impl Library { 157 | /// Create a library of components that is roughly equivalent to the Brahma 158 | /// standard library. 159 | pub fn brahma_std() -> Self { 160 | Library { 161 | // 7.3 Choice of Multi-set of Base Components: 162 | // 163 | // > The standard library included 12 components, one each for 164 | // > performing standard operations, such as bitwise-and, 165 | // > bitwise-or, bitwise-not, add-one, bitwise-xor, shift-right, 166 | // > comparison, add, and subtract operations." 167 | // 168 | // They don't actually spell out exactly what's included, but here 169 | // are the set of components used in the benchmark problems they say 170 | // are solved with just the standard components: 171 | // 172 | // 1. add 173 | // 2. and 174 | // 3. neg 175 | // 4. not 176 | // 5. or 177 | // 6. shr (signed) 178 | // 7. shr (logical) 179 | // 8. sub 180 | // 9. uge 181 | // 10. ugt 182 | // 11. ule 183 | // 12. xor 184 | // 185 | // Note that they only use "bvshr" which doesn't specify if the 186 | // right shift is signed or logical, but `p13` uses two right 187 | // shifts, and for it to be correct the first has to be signed and 188 | // the second has to be logical. It was really annoying to figure 189 | // that out! 190 | // 191 | // Finally, it isn't 100% clear to me if they synthesized the 192 | // various constants that appear in their solutions, or if they 193 | // provided them as components. By my reading, it sort of seems like 194 | // they did a mix. So we leave constants out of this library, and 195 | // kick that problem down the road to callers. 196 | components: vec![ 197 | // 1. 198 | component::add(), 199 | // 2. 200 | component::and(), 201 | // 3. neg(x) = 0 - x 202 | component::const_(Some(0)), 203 | component::sub(), 204 | // 4. not(a) = xor a, MAX 205 | component::const_(Some(std::u64::MAX)), 206 | component::xor(), 207 | // 5. 208 | component::or(), 209 | // 6. 210 | component::shr_s(), 211 | // 7. 212 | component::shr_u(), 213 | // 8. 214 | component::sub(), 215 | // 9. 216 | component::ge_u(), 217 | // 10. 218 | component::gt_u(), 219 | // 11. ule 220 | component::le_u(), 221 | // 12. 222 | component::xor(), 223 | ], 224 | } 225 | } 226 | } 227 | 228 | type Line<'a> = BitVec<'a>; 229 | 230 | fn line_lt<'a>(lhs: &Line<'a>, rhs: &Line<'a>) -> Bool<'a> { 231 | lhs.bvult(rhs) 232 | } 233 | 234 | fn line_le<'a>(lhs: &Line<'a>, rhs: &Line<'a>) -> Bool<'a> { 235 | lhs.bvule(rhs) 236 | } 237 | 238 | #[derive(Debug)] 239 | struct LocationVars<'a> { 240 | inputs: Vec>, 241 | params: Vec>, 242 | results: Vec>, 243 | output: Line<'a>, 244 | line_bit_width: u32, 245 | } 246 | 247 | impl<'a> LocationVars<'a> { 248 | fn new(context: &'a z3::Context, library: &Library, num_inputs: usize) -> Self { 249 | let max_line = num_inputs as u32 250 | + library.components.len() as u32 251 | + library 252 | .components 253 | .iter() 254 | .map(|c| c.operand_arity() as u32) 255 | .sum::(); 256 | let max_pow_2 = (max_line + 1).next_power_of_two(); 257 | // `2^n` are of the form `0b10..0` and `trailing_zeros(2^n)` is `n`. For 258 | // example, `4 = 0b100` and `trailing_zeros(4) = 2`. Since we already 259 | // added one before we took the next power of two, we only need as many 260 | // bits as there are trailing zeros to represent every possible line 261 | // number. 262 | let line_bit_width = max_pow_2.trailing_zeros(); 263 | 264 | let inputs = (0..num_inputs) 265 | .map(|_| Self::fresh_line(context, "input_location", line_bit_width)) 266 | .collect(); 267 | let params = library 268 | .components 269 | .iter() 270 | .flat_map(|c| { 271 | (0..c.operand_arity()) 272 | .map(|_| Self::fresh_line(context, "param_location", line_bit_width)) 273 | }) 274 | .collect(); 275 | let results = library 276 | .components 277 | .iter() 278 | .map(|_| Self::fresh_line(context, "result_location", line_bit_width)) 279 | .collect(); 280 | let output = Self::fresh_line(context, "output_line", line_bit_width); 281 | LocationVars { 282 | inputs, 283 | params, 284 | results, 285 | output, 286 | line_bit_width, 287 | } 288 | } 289 | 290 | fn fresh_line(context: &'a z3::Context, name: &str, line_bit_width: u32) -> Line<'a> { 291 | BitVec::fresh_const(context, name, line_bit_width) 292 | } 293 | 294 | fn line_from_u32(&self, context: &'a z3::Context, line: u32) -> Line<'a> { 295 | assert!(line < (1 << self.line_bit_width)); 296 | BitVec::from_i64(context, line as i64, self.line_bit_width) 297 | } 298 | 299 | fn inputs_range(&self) -> Range { 300 | 0..self.inputs.len() as u32 301 | } 302 | 303 | fn params_range(&self) -> Range { 304 | let start = self.inputs.len() as u32; 305 | let end = start + self.params.len() as u32; 306 | start..end 307 | } 308 | 309 | fn results_range(&self) -> Range { 310 | let start = self.inputs.len() as u32 + self.params.len() as u32; 311 | let end = start + self.results.len() as u32; 312 | start..end 313 | } 314 | 315 | fn output_range(&self) -> Range { 316 | let start = self.inputs.len() as u32 + self.params.len() as u32 + self.results.len() as u32; 317 | let end = start + 1; 318 | start..end 319 | } 320 | 321 | fn invalid_connections(&self, library: &Library) -> HashSet<(u32, u32)> { 322 | let mut invalid_connections = HashSet::new(); 323 | 324 | // We will never assign the output directly to an input. 325 | for a in self.inputs_range() { 326 | for b in self.output_range() { 327 | invalid_connections.insert((a, b)); 328 | } 329 | } 330 | 331 | // We never assign an input's location to another input's location. 332 | for (i, a) in self.inputs_range().enumerate() { 333 | for b in self.inputs_range().skip(i as usize) { 334 | invalid_connections.insert((a, b)); 335 | } 336 | } 337 | 338 | // Similarly, a well-formed program will never assign a param's location 339 | // as another param; it should only be one of the original inputs or the 340 | // result of another component. 341 | for (i, p) in self.params_range().enumerate() { 342 | for q in self.params_range().skip(i as usize) { 343 | invalid_connections.insert((p, q)); 344 | } 345 | } 346 | 347 | // Finally, a well-formed will never have a component with its own 348 | // result as a parameter. 349 | let params = &mut self.params_range(); 350 | for (r, c) in self.results_range().zip(&library.components) { 351 | for p in params.take(c.operand_arity()) { 352 | invalid_connections.insert((r, p)); 353 | } 354 | } 355 | 356 | invalid_connections 357 | } 358 | 359 | /// 5.1 Encoding Well-formed Programs 360 | fn well_formed_program( 361 | &self, 362 | context: &'a z3::Context, 363 | library: &Library, 364 | invalid_connections: &mut HashSet<(u32, u32)>, 365 | ) -> Bool<'a> { 366 | let mut wfp = Vec::with_capacity( 367 | // Acyclic and consistent. 368 | 2 369 | // Assignment of inputs. 370 | + self.inputs.len() 371 | // Lower and upper bounds on params. 372 | + self.params.len() * 2 373 | // Lower and upper bounds on results. 374 | + self.results.len() * 2, 375 | ); 376 | 377 | wfp.push(self.consistent(context, invalid_connections)); 378 | wfp.push(self.acyclic(context, library)); 379 | 380 | let i_len = self.line_from_u32(context, self.inputs.len() as u32); 381 | let m = self.line_from_u32(context, (self.results.len() + self.inputs.len()) as u32); 382 | let zero = self.line_from_u32(context, 0); 383 | 384 | for (i, l) in self.inputs.iter().enumerate() { 385 | let i = self.line_from_u32(context, i as u32); 386 | wfp.push(l._eq(&i)); 387 | } 388 | 389 | for l in &self.params { 390 | // 0 <= l 391 | wfp.push(line_le(&zero, l)); 392 | // l < M 393 | wfp.push(line_lt(l, &m)); 394 | } 395 | 396 | for l in &self.results { 397 | // |i| <= l 398 | wfp.push(line_le(&i_len, l)); 399 | // l < m 400 | wfp.push(line_lt(l, &m)); 401 | } 402 | 403 | and(context, &wfp) 404 | } 405 | 406 | fn consistent( 407 | &self, 408 | context: &'a z3::Context, 409 | invalid_connections: &mut HashSet<(u32, u32)>, 410 | ) -> Bool<'a> { 411 | let mut cons = vec![]; 412 | for (i, (i_x, x)) in self.results_range().zip(&self.results).enumerate() { 413 | for (i_y, y) in self.results_range().zip(&self.results).skip(i + 1) { 414 | invalid_connections.insert((i_x, i_y)); 415 | cons.push(x._eq(y).not()); 416 | } 417 | } 418 | and(context, &cons) 419 | } 420 | 421 | fn acyclic(&self, context: &'a z3::Context, library: &Library) -> Bool<'a> { 422 | let mut acycs = vec![]; 423 | let mut params = self.params.iter(); 424 | let mut results = self.results.iter(); 425 | 426 | for c in &library.components { 427 | let result_location = results.next().unwrap(); 428 | for _ in 0..c.operand_arity() { 429 | let param_location = params.next().unwrap(); 430 | acycs.push(line_lt(param_location, result_location)); 431 | } 432 | } 433 | 434 | and(context, &acycs) 435 | } 436 | } 437 | 438 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 439 | struct Assignments { 440 | immediates: Vec, 441 | // The line in the program where the i^th input is defined (for all inputs 442 | // of all components). 443 | params: Vec, 444 | // The line in the program where the i^th component is located (and 445 | // therefore the i^th output is defined).. 446 | results: Vec, 447 | // The line in the program where the final output is defined. 448 | output: u32, 449 | } 450 | 451 | impl Assignments { 452 | fn to_program(&self, num_inputs: usize, library: &Library) -> Program { 453 | let mut b = ProgramBuilder::new(); 454 | for _ in 0..num_inputs { 455 | b.var(); 456 | } 457 | let mut program = b.finish(); 458 | 459 | let mut immediates = self.immediates.iter().cloned(); 460 | let mut params = self.params.iter().cloned().map(Id); 461 | 462 | program 463 | .instructions 464 | .extend(self.results.iter().zip(&library.components).map(|(&n, c)| { 465 | let imm_arity = c.immediate_arity(); 466 | let immediates: Vec<_> = immediates.by_ref().take(imm_arity).collect(); 467 | 468 | let op_arity = c.operand_arity(); 469 | let operands: Vec<_> = params.by_ref().take(op_arity).collect(); 470 | debug_assert!(operands.iter().all(|op| op.0 < n)); 471 | 472 | let operator = c.make_operator(&immediates, &operands); 473 | let result = Id(n); 474 | Instruction { result, operator } 475 | })); 476 | 477 | program.instructions.sort_unstable_by_key(|i| i.result.0); 478 | program.instructions.truncate(self.output as usize + 1); 479 | program 480 | } 481 | } 482 | 483 | #[derive(Debug)] 484 | pub struct Program { 485 | instructions: Vec, 486 | } 487 | 488 | impl Display for Program { 489 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 490 | for i in &self.instructions { 491 | write!(f, "{}\n", i)?; 492 | } 493 | Ok(()) 494 | } 495 | } 496 | 497 | enum Verification { 498 | WorksForAllInputs, 499 | Counterexample(Vec), 500 | } 501 | 502 | #[derive(Debug, Clone)] 503 | enum Timeout { 504 | Duration(time::Duration), 505 | Instant(time::Instant), 506 | } 507 | 508 | #[derive(Debug)] 509 | pub struct Synthesizer<'a> { 510 | context: &'a z3::Context, 511 | library: &'a Library, 512 | spec: &'a dyn Specification, 513 | locations: LocationVars<'a>, 514 | well_formed_program: Bool<'a>, 515 | invalid_connections: HashSet<(u32, u32)>, 516 | not_invalid_assignments: Bool<'a>, 517 | should_synthesize_minimal_programs: bool, 518 | timeout: Option, 519 | } 520 | 521 | impl<'a> Synthesizer<'a> { 522 | pub fn new( 523 | context: &'a z3::Context, 524 | library: &'a Library, 525 | spec: &'a dyn Specification, 526 | ) -> Result { 527 | if library.components.is_empty() { 528 | return Err(Error::NoComponents); 529 | } 530 | 531 | let locations = LocationVars::new(context, library, spec.arity()); 532 | let mut invalid_connections = locations.invalid_connections(library); 533 | let well_formed_program = 534 | locations.well_formed_program(context, library, &mut invalid_connections); 535 | let not_invalid_assignments = Bool::from_bool(context, true); 536 | Ok(Synthesizer { 537 | context, 538 | library, 539 | spec, 540 | locations, 541 | well_formed_program, 542 | invalid_connections, 543 | not_invalid_assignments, 544 | should_synthesize_minimal_programs: false, 545 | timeout: None, 546 | }) 547 | } 548 | 549 | /// Configure whether we should synthesize the minimal-length program that 550 | /// satisfies the specification. 551 | /// 552 | /// This produces the smallest possible program, but it tends to take 553 | /// more time. 554 | pub fn should_synthesize_minimal_programs(&mut self, should: bool) -> &mut Self { 555 | self.should_synthesize_minimal_programs = should; 556 | self 557 | } 558 | 559 | /// Configure the timeout. 560 | /// 561 | /// No timeout means that we will keep going forever if necessary. Providing 562 | /// a number of milliseconds means we will have a soft maximum runtime of of 563 | /// that many milliseconds before giving up. 564 | pub fn set_timeout(&mut self, milliseconds: Option) -> &mut Self { 565 | self.timeout = 566 | milliseconds.map(|ms| Timeout::Duration(time::Duration::from_millis(ms as u64))); 567 | self 568 | } 569 | 570 | fn solver(&mut self) -> z3::Solver<'a> { 571 | let solver = z3::Solver::new(self.context); 572 | 573 | if let Some(timeout) = self.timeout.clone() { 574 | let millis = match timeout { 575 | Timeout::Duration(d) => { 576 | let millis = d.as_millis(); 577 | self.timeout = Some(Timeout::Instant(time::Instant::now() + d)); 578 | millis as u32 579 | } 580 | Timeout::Instant(instant) => { 581 | let dur = instant.saturating_duration_since(time::Instant::now()); 582 | dur.as_millis() as u32 583 | } 584 | }; 585 | 586 | let mut params = z3::Params::new(self.context); 587 | params.set_u32("timeout", millis); 588 | 589 | solver.set_params(¶ms); 590 | } 591 | 592 | solver 593 | } 594 | 595 | fn is_invalid_connection(&self, i: u32, j: u32) -> bool { 596 | debug_assert!( 597 | i < self.locations.inputs.len() as u32 598 | + self.locations.params.len() as u32 599 | + self.locations.results.len() as u32 600 | + 1 // Output. 601 | ); 602 | debug_assert!( 603 | j < self.locations.inputs.len() as u32 604 | + self.locations.params.len() as u32 605 | + self.locations.results.len() as u32 606 | + 1 // Output. 607 | ); 608 | self.invalid_connections.contains(&(i, j)) || self.invalid_connections.contains(&(j, i)) 609 | } 610 | 611 | fn fresh_immediates(&self, bit_width: u32) -> Vec> { 612 | self.library 613 | .components 614 | .iter() 615 | .flat_map(|c| { 616 | (0..c.immediate_arity()).map(|_| fresh_immediate(self.context, bit_width)) 617 | }) 618 | .collect() 619 | } 620 | 621 | fn fresh_param_vars(&self, bit_width: u32) -> Vec> { 622 | self.library 623 | .components 624 | .iter() 625 | .flat_map(|c| (0..c.operand_arity()).map(|_| fresh_param(self.context, bit_width))) 626 | .collect() 627 | } 628 | 629 | fn fresh_result_vars(&self, bit_width: u32) -> Vec> { 630 | self.library 631 | .components 632 | .iter() 633 | .map(|_| fresh_result(self.context, bit_width)) 634 | .collect() 635 | } 636 | 637 | fn add_invalid_assignment(&mut self, assignments: &Assignments) { 638 | // TODO: like souper, we should have multiple cases here for if we're 639 | // trying to synthesize any constants or not. When we're synthesizing 640 | // constants, allow reusing the same location assignments N times with 641 | // different constants before completely abandoning these location 642 | // assignments. 643 | 644 | let params = and( 645 | self.context, 646 | &assignments 647 | .params 648 | .iter() 649 | .zip(&self.locations.params) 650 | .map(|(assignment, location)| { 651 | location._eq(&self.locations.line_from_u32(self.context, *assignment as _)) 652 | }) 653 | .collect::>(), 654 | ); 655 | 656 | let results = and( 657 | self.context, 658 | &assignments 659 | .results 660 | .iter() 661 | .zip(&self.locations.results) 662 | .map(|(assignment, location)| { 663 | location._eq(&self.locations.line_from_u32(self.context, *assignment as _)) 664 | }) 665 | .collect::>(), 666 | ); 667 | 668 | let not_this_assignment = and(self.context, &[results, params]).not(); 669 | self.not_invalid_assignments = self.not_invalid_assignments.and(&[¬_this_assignment]); 670 | } 671 | 672 | fn reset_invalid_assignments(&mut self) { 673 | self.not_invalid_assignments = Bool::from_bool(self.context, true); 674 | } 675 | 676 | fn finite_synthesis( 677 | &mut self, 678 | inputs: &HashSet>, 679 | output_line: u32, 680 | bit_width: u32, 681 | ) -> Result { 682 | debug!( 683 | "finite synthesis at bit width {} with inputs = {:#018X?}", 684 | bit_width, 685 | { 686 | let mut inputs: Vec<_> = inputs.iter().collect(); 687 | inputs.sort(); 688 | inputs 689 | } 690 | ); 691 | 692 | let immediates = self.fresh_immediates(bit_width); 693 | let mut works_for_inputs = Vec::with_capacity(inputs.len() * 4); 694 | 695 | for input in inputs { 696 | let params = self.fresh_param_vars(bit_width); 697 | let results = self.fresh_result_vars(bit_width); 698 | let inputs: Vec<_> = input 699 | .iter() 700 | .map(|i| BitVec::from_i64(self.context, *i as i64, bit_width)) 701 | .collect(); 702 | let output = fresh_output(self.context, bit_width); 703 | 704 | let lib = self.library(&immediates, ¶ms, &results, bit_width); 705 | works_for_inputs.push(lib); 706 | 707 | let conn = self.connectivity(&inputs, &output, ¶ms, &results); 708 | works_for_inputs.push(conn); 709 | 710 | let spec = self 711 | .spec 712 | .make_expression(self.context, &inputs, &output, bit_width); 713 | works_for_inputs.push(spec); 714 | } 715 | 716 | let works_for_inputs: Vec<&_> = works_for_inputs.iter().collect(); 717 | 718 | assert!(self.spec.arity() <= output_line as usize); 719 | assert!((output_line as usize) < self.spec.arity() + self.library.components.len()); 720 | let output_on_line = self 721 | .locations 722 | .output 723 | ._eq(&self.locations.line_from_u32(self.context, output_line)); 724 | 725 | let query = self 726 | .well_formed_program 727 | .and(&works_for_inputs) 728 | .and(&[&self.not_invalid_assignments, &output_on_line]); 729 | trace!("finite synthesis query =\n{:?}", query); 730 | 731 | let solver = self.solver(); 732 | solver.assert(&query); 733 | 734 | match solver.check() { 735 | z3::SatResult::Unknown => Err(Error::SynthesisUnknown), 736 | z3::SatResult::Unsat => Err(Error::SynthesisUnsatisfiable), 737 | z3::SatResult::Sat => { 738 | let model = solver.get_model(); 739 | 740 | let immediates = eval_bitvecs(&model, &immediates); 741 | 742 | let params = eval_lines(&model, &self.locations.params); 743 | 744 | let results = eval_lines(&model, &self.locations.results); 745 | 746 | let assignments = Assignments { 747 | immediates, 748 | params, 749 | results, 750 | output: output_line, 751 | }; 752 | 753 | debug!( 754 | "finite synthesis generated:\n{}", 755 | assignments.to_program(self.spec.arity(), &self.library) 756 | ); 757 | 758 | Ok(assignments) 759 | } 760 | } 761 | } 762 | 763 | fn verification(&mut self, assignments: &Assignments, bit_width: u32) -> Result { 764 | let inputs: Vec<_> = (0..self.spec.arity()) 765 | .map(|_| fresh_input(self.context, bit_width)) 766 | .collect(); 767 | let output = fresh_output(self.context, bit_width); 768 | 769 | let mut prog = assignments.to_program(self.spec.arity(), self.library); 770 | prog.dce(); 771 | let prog = prog.make_expression(self.context, &inputs, &output, bit_width); 772 | 773 | let spec = self 774 | .spec 775 | .make_expression(self.context, &inputs, &output, bit_width); 776 | let not_spec = spec.not(); 777 | let query = prog.and(&[¬_spec]); 778 | trace!("verification query =\n{:?}", query); 779 | 780 | let solver = self.solver(); 781 | solver.assert(&query); 782 | 783 | match solver.check() { 784 | z3::SatResult::Unknown => Err(Error::SynthesisUnknown), 785 | // There are no more inputs that don't satisfy the spec! We're done! 786 | z3::SatResult::Unsat => { 787 | debug!( 788 | "verified to work for all inputs at bit width = {}", 789 | bit_width 790 | ); 791 | Ok(Verification::WorksForAllInputs) 792 | } 793 | // There still exist inputs for which the synthesized program does 794 | // not fulfill the spec. 795 | z3::SatResult::Sat => { 796 | let model = solver.get_model(); 797 | self.add_invalid_assignment(assignments); 798 | let inputs = eval_bitvecs(&model, &inputs); 799 | debug!("found a counter-example: {:?}", inputs); 800 | Ok(Verification::Counterexample(inputs)) 801 | } 802 | } 803 | } 804 | 805 | /// 5.2 Encoding Dataflow in Programs 806 | fn connectivity( 807 | &self, 808 | inputs: &[BitVec<'a>], 809 | output: &BitVec<'a>, 810 | params: &[BitVec<'a>], 811 | results: &[BitVec<'a>], 812 | ) -> Bool<'a> { 813 | let locs_to_vars: Vec<_> = self 814 | .locations 815 | .inputs 816 | .iter() 817 | .zip(inputs) 818 | .chain(self.locations.params.iter().zip(params)) 819 | .chain(self.locations.results.iter().zip(results)) 820 | .chain(Some((&self.locations.output, output))) 821 | .collect(); 822 | 823 | let mut conn = 824 | Vec::with_capacity(locs_to_vars.len() * locs_to_vars.len() + locs_to_vars.len()); 825 | 826 | for (i, (l_x, x)) in locs_to_vars.iter().enumerate() { 827 | for (j, (l_y, y)) in locs_to_vars.iter().enumerate().skip(i + 1) { 828 | if self.is_invalid_connection(i as u32, j as u32) { 829 | continue; 830 | } 831 | conn.push(l_x._eq(l_y).implies(&x._eq(y))); 832 | } 833 | } 834 | 835 | and(self.context, &conn) 836 | } 837 | 838 | fn library( 839 | &self, 840 | immediates: &[BitVec<'a>], 841 | params: &[BitVec<'a>], 842 | results: &[BitVec<'a>], 843 | bit_width: u32, 844 | ) -> Bool<'a> { 845 | let mut exprs = Vec::with_capacity(self.library.components.len()); 846 | let mut immediates = immediates; 847 | let mut params = params; 848 | let mut results = results.iter(); 849 | 850 | for c in &self.library.components { 851 | let (imms, rest) = immediates.split_at(c.immediate_arity()); 852 | immediates = rest; 853 | 854 | let (inputs, rest) = params.split_at(c.operand_arity()); 855 | params = rest; 856 | 857 | let result = results.next().unwrap(); 858 | 859 | exprs.push( 860 | c.make_expression(self.context, imms, inputs, bit_width) 861 | ._eq(result), 862 | ); 863 | } 864 | 865 | and(self.context, &exprs) 866 | } 867 | 868 | /// Have the solver generate initial concrete inputs for finite synthesis by 869 | /// negating the specification. 870 | /// 871 | /// Originally, I was using an RNG to generate random initial inputs, but I 872 | /// took this technique from Souper. Presumably it lets the solver choose 873 | /// inputs that are more interesting than an RNG would have chosen, which 874 | /// later helps it synthesize better solutions more quickly. 875 | fn initial_concrete_inputs(&mut self) -> Result>> { 876 | // Taken from Souper. 877 | const NUM_INITIAL_INPUTS: usize = 4; 878 | 879 | let mut inputs: HashSet> = HashSet::with_capacity(NUM_INITIAL_INPUTS); 880 | 881 | let input_vars: Vec<_> = (0..self.spec.arity()) 882 | .map(|_| fresh_input(self.context, FULL_BIT_WIDTH)) 883 | .collect(); 884 | let output_var = fresh_output(self.context, FULL_BIT_WIDTH); 885 | let spec = 886 | self.spec 887 | .make_expression(self.context, &input_vars, &output_var, FULL_BIT_WIDTH); 888 | // let not_spec = spec.not(); 889 | 890 | for _ in 0..NUM_INITIAL_INPUTS { 891 | // Make sure that we don't find the same concrete inputs that we've 892 | // already found. 893 | let mut existing_inputs = Vec::with_capacity(inputs.len()); 894 | for input_set in &inputs { 895 | let mut this_input = Vec::with_capacity(self.spec.arity()); 896 | for (inp, var) in input_set.iter().zip(&input_vars) { 897 | let inp = BitVec::from_i64(self.context, *inp as i64, FULL_BIT_WIDTH); 898 | this_input.push(inp._eq(var)); 899 | } 900 | let this_input = and(self.context, &this_input); 901 | existing_inputs.push(this_input); 902 | } 903 | let existing_inputs = or(self.context, &existing_inputs); 904 | let not_existing_inputs = existing_inputs.not(); 905 | 906 | let query = spec.and(&[¬_existing_inputs]); 907 | trace!("initial concrete input synthesis query =\n{:?}", query); 908 | 909 | let solver = self.solver(); 910 | solver.assert(&query); 911 | 912 | match solver.check() { 913 | z3::SatResult::Unknown => return Err(Error::SynthesisUnknown), 914 | z3::SatResult::Unsat => return Err(Error::SynthesisUnsatisfiable), 915 | z3::SatResult::Sat => { 916 | let model = solver.get_model(); 917 | let new_inputs = eval_bitvecs(&model, &input_vars); 918 | let is_new = inputs.insert(new_inputs); 919 | assert!(is_new); 920 | } 921 | } 922 | } 923 | 924 | Ok(inputs) 925 | } 926 | 927 | /// Synthesize a program! 928 | /// 929 | /// The synthesizer has been configured, and we're ready to create a 930 | /// program. 931 | pub fn synthesize(&mut self) -> Result { 932 | let mut inputs = self.initial_concrete_inputs()?; 933 | assert!(!inputs.is_empty()); 934 | 935 | let arity = self.spec.arity(); 936 | assert!(arity > 0); 937 | 938 | let longest = arity as u32 + self.library.components.len() as u32; 939 | let shortest = if self.should_synthesize_minimal_programs { 940 | arity as u32 + 1 941 | } else { 942 | longest 943 | }; 944 | 945 | // In practice, the cost of searching for a program of length `n` and 946 | // failing seems to be much more expensive than when there actually is a 947 | // solution. Therefore, search for the longest programs first and the 948 | // shortest last. Because we have dead code elimination, we can also 949 | // skip ahead a bunch of iterations when we find long solutions that 950 | // contain dead code. 951 | let mut best = Err(Error::SynthesisUnknown); 952 | let mut length = longest; 953 | while length >= shortest { 954 | match self.synthesize_with_length(length, &mut inputs) { 955 | Ok(mut program) => { 956 | program.dce(); 957 | 958 | assert!(program.instructions.len() > arity); 959 | length = program.instructions.len() as u32 - 1; 960 | 961 | best = Ok(program); 962 | 963 | // Reset the invalid-assignments clause, since an assignment 964 | // that was an invalid program of length `i` might be valid 965 | // at length `i-1`. 966 | self.reset_invalid_assignments(); 967 | 968 | continue; 969 | } 970 | err => return best.or_else(|_| err), 971 | } 972 | } 973 | 974 | best 975 | } 976 | 977 | fn synthesize_with_length( 978 | &mut self, 979 | program_length: u32, 980 | inputs: &mut HashSet>, 981 | ) -> Result { 982 | debug!("synthesizing a program of length = {}", program_length); 983 | 984 | let mut bit_width = 2; 985 | 'cegis: loop { 986 | let assignments = self.finite_synthesis(inputs, program_length - 1, bit_width)?; 987 | 988 | let mut verifying_with_more_bits = false; 989 | loop { 990 | debug!("verifying at bit width = {}", bit_width); 991 | match self.verification(&assignments, bit_width)? { 992 | Verification::WorksForAllInputs => { 993 | debug_assert!(bit_width <= FULL_BIT_WIDTH); 994 | debug_assert!(bit_width.is_power_of_two()); 995 | if bit_width == FULL_BIT_WIDTH { 996 | return Ok(assignments.to_program(self.spec.arity(), self.library)); 997 | } else { 998 | bit_width *= 2; 999 | verifying_with_more_bits = true; 1000 | // TODO: if the synthesized assignments use 1001 | // immediate constants, try to extend the constants 1002 | // for the wider bit width in various ways. See 1003 | // *Program Synthesis for Program Analysis*. 1004 | } 1005 | } 1006 | Verification::Counterexample(new_inputs) => { 1007 | let is_new = inputs.insert(new_inputs); 1008 | assert!(is_new || verifying_with_more_bits); 1009 | continue 'cegis; 1010 | } 1011 | } 1012 | } 1013 | } 1014 | } 1015 | } 1016 | 1017 | impl Program { 1018 | pub fn synthesize<'a>( 1019 | context: &'a z3::Context, 1020 | spec: &impl Specification, 1021 | library: &Library, 1022 | ) -> Result { 1023 | let mut synthesizer = Synthesizer::new(context, library, spec)?; 1024 | synthesizer.synthesize() 1025 | } 1026 | 1027 | pub fn dce(&mut self) { 1028 | let mut used: HashSet = HashSet::from_iter( 1029 | self.instructions 1030 | .iter() 1031 | .take_while(|inst| inst.operator == Operator::Var) 1032 | .map(|inst| inst.result) 1033 | .chain(Some(self.instructions.last().unwrap().result)), 1034 | ); 1035 | 1036 | for inst in self.instructions.iter().rev() { 1037 | if !used.contains(&inst.result) { 1038 | continue; 1039 | } 1040 | 1041 | inst.operator.operands(|op| { 1042 | used.insert(op); 1043 | }); 1044 | } 1045 | 1046 | self.instructions.retain(|inst| used.contains(&inst.result)); 1047 | 1048 | let mut renumbering = HashMap::new(); 1049 | for (i, inst) in self.instructions.iter_mut().enumerate() { 1050 | inst.operator.operands_mut(|x| { 1051 | *x = renumbering[x]; 1052 | }); 1053 | 1054 | let old = renumbering.insert(inst.result, Id(i as u32)); 1055 | debug_assert!(old.is_none()); 1056 | inst.result = Id(i as u32); 1057 | } 1058 | } 1059 | } 1060 | 1061 | impl Specification for Program { 1062 | fn arity(&self) -> usize { 1063 | self.instructions 1064 | .iter() 1065 | .take_while(|inst| inst.operator == Operator::Var) 1066 | .count() 1067 | } 1068 | 1069 | fn make_expression<'a>( 1070 | &self, 1071 | context: &'a z3::Context, 1072 | inputs: &[BitVec<'a>], 1073 | output: &BitVec<'a>, 1074 | bit_width: u32, 1075 | ) -> Bool<'a> { 1076 | assert!(self.instructions.len() > inputs.len()); 1077 | 1078 | let mut vars: Vec<_> = inputs.iter().cloned().collect(); 1079 | 1080 | let mut operands = vec![]; 1081 | for instr in self.instructions.iter().skip(inputs.len()) { 1082 | // NB: programs cannot contain unbound constants, so specifications 1083 | // constructed from programs will never require us to synthesize an 1084 | // immediate. 1085 | let immediates = []; 1086 | 1087 | operands.clear(); 1088 | instr 1089 | .operator 1090 | .operands(|Id(x)| operands.push(vars[x as usize].clone())); 1091 | 1092 | vars.push( 1093 | instr 1094 | .operator 1095 | .make_expression(context, &immediates, &operands, bit_width), 1096 | ); 1097 | } 1098 | 1099 | vars.pop().unwrap()._eq(output) 1100 | } 1101 | } 1102 | 1103 | #[cfg(test)] 1104 | mod tests { 1105 | use super::*; 1106 | 1107 | #[test] 1108 | fn display_id() { 1109 | assert_eq!(Id(0).to_string(), "a"); 1110 | assert_eq!(Id(1).to_string(), "b"); 1111 | assert_eq!(Id(2).to_string(), "c"); 1112 | assert_eq!(Id(25).to_string(), "z"); 1113 | 1114 | assert_eq!(Id(26).to_string(), "aa"); 1115 | assert_eq!(Id(27).to_string(), "ab"); 1116 | assert_eq!(Id(28).to_string(), "ac"); 1117 | assert_eq!(Id(51).to_string(), "az"); 1118 | 1119 | assert_eq!(Id(52).to_string(), "ba"); 1120 | assert_eq!(Id(53).to_string(), "bb"); 1121 | assert_eq!(Id(54).to_string(), "bc"); 1122 | 1123 | assert_eq!(Id(0 * 26 + 1 * 26 + 26 * 26 - 1).to_string(), "zz"); 1124 | assert_eq!(Id(0 * 26 + 1 * 26 + 26 * 26).to_string(), "aaa"); 1125 | } 1126 | 1127 | #[test] 1128 | fn display_operator() { 1129 | assert_eq!(Operator::Mul(Id(1), Id(2)).to_string(), "mul b, c"); 1130 | } 1131 | 1132 | #[test] 1133 | fn display_instruction() { 1134 | let instr = Instruction { 1135 | result: Id(3), 1136 | operator: Operator::And(Id(1), Id(2)), 1137 | }; 1138 | assert_eq!(instr.to_string(), "d ← and b, c"); 1139 | } 1140 | 1141 | #[test] 1142 | fn display_program() { 1143 | let mut builder = ProgramBuilder::new(); 1144 | let a = builder.var(); 1145 | let b = builder.var(); 1146 | let c = builder.const_(1); 1147 | let d = builder.add(a, c); 1148 | let _e = builder.and(b, d); 1149 | let program = builder.finish(); 1150 | assert_eq!( 1151 | program.to_string(), 1152 | "a ← var\n\ 1153 | b ← var\n\ 1154 | c ← const 0x1\n\ 1155 | d ← add a, c\n\ 1156 | e ← and b, d\n\ 1157 | " 1158 | ); 1159 | } 1160 | 1161 | #[test] 1162 | fn synthesize() { 1163 | let mut config = z3::Config::new(); 1164 | config.set_model_generation(true); 1165 | 1166 | let context = z3::Context::new(&config); 1167 | 1168 | let library = Library::brahma_std(); 1169 | let mut builder = ProgramBuilder::new(); 1170 | let a = builder.var(); 1171 | let b = builder.const_(2); 1172 | let _ = builder.mul(a, b); 1173 | let spec = builder.finish(); 1174 | 1175 | let mut p = Program::synthesize(&context, &spec, &library).unwrap(); 1176 | p.dce(); 1177 | println!("{}", p.to_string()); 1178 | } 1179 | 1180 | #[test] 1181 | fn synthesize_select() { 1182 | let mut config = z3::Config::new(); 1183 | config.set_model_generation(true); 1184 | 1185 | let context = z3::Context::new(&config); 1186 | 1187 | let mut library = Library::brahma_std(); 1188 | library.components.push(component::select()); 1189 | 1190 | let mut builder = ProgramBuilder::new(); 1191 | let a = builder.var(); 1192 | let b = builder.var(); 1193 | let c = builder.var(); 1194 | let _ = builder.select(a, b, c); 1195 | let spec = builder.finish(); 1196 | 1197 | let mut p = Program::synthesize(&context, &spec, &library).unwrap(); 1198 | p.dce(); 1199 | println!("{}", p.to_string()); 1200 | } 1201 | } 1202 | --------------------------------------------------------------------------------