Program synthesis is possible in rust gas constant

##########

expression in the language. // src/ast.rs pub type NodeId = Id ; pub enum Node { Identifier ( StringId ), Addition ( NodeId , NodeId ), Subtraction ( NodeId , NodeId ), Multiplication ( NodeId , NodeId ), Division ( NodeId , NodeId ), RightShift ( NodeId , NodeId ), LeftShift ( NodeId , NodeId ), Const ( i64 ), Negation ( NodeId ), Conditional ( NodeId , NodeId , NodeId ), }

for the input language. gas weed strain The grammar and actions are given in full below: // src/parser/grammar.lalrpop use crate :: ast ; use std :: str :: FromStr ; grammar ( ctx : & mut ast :: Context ); Integer : i64 = => i64 :: from_str ( s ). unwrap (); Identifier : ast :: NodeId = => ctx . new_identifier ( s ); Sum : ast :: NodeId = { => t , "+" => ctx . new_node ( ast :: Node :: Addition ( l , r )), "-" => ctx . new_node ( ast :: Node :: Subtraction ( l , r )), }; Term : ast :: NodeId = { => i , "*" => ctx . new_node ( ast :: Node :: Multiplication ( l , r )), "/" => ctx . new_node ( ast :: Node :: Division ( l , r )), ">>" => ctx . new_node ( ast :: Node :: RightShift ( l , r )), " => ctx . new_node ( ast :: Node :: LeftShift ( l , r )), }; Item : ast :: NodeId = { => ctx . new_node ( ast :: Node :: Const ( n )), "-" => ctx . new_node ( ast :: Node :: Negation ( i )), => i , "(" ")" => s , }; pub Start : ast :: NodeId = { => s , "?" ":" => ctx . new_node ( ast :: Node :: Conditional ( condition , consequent , alternative )), }; Interpretation

Here is our initial interpreter function: // src/eval.rs pub fn eval ( ctx : & mut ast :: Context , node : ast :: NodeId , lookup : & mut L ) -> Result where L : for FnMut ( & ‘a str ) -> Result , { match * ctx . node_ref ( node ) { Node :: Const ( i ) => Ok ( i ), Node :: Identifier ( s ) => { let s = ctx . interned ( s ); lookup ( s ) } Node :: Addition ( lhs , rhs ) => { let lhs = eval ( ctx , lhs , lookup ) ? ; let rhs = eval ( ctx , rhs , lookup ) ? ; Ok ( lhs + rhs ) } Node :: Subtraction ( lhs , rhs ) => { let lhs = eval ( ctx , lhs , lookup ) ? ; let rhs = eval ( ctx , rhs , lookup ) ? ; Ok ( lhs – rhs ) } Node :: Multiplication ( lhs , rhs ) => { let lhs = eval ( ctx , lhs , lookup ) ? ; let rhs = eval ( ctx , rhs , lookup ) ? ; Ok ( lhs * rhs ) } Node :: Division ( lhs , rhs ) => { let lhs = eval ( ctx , lhs , lookup ) ? ; let rhs = eval ( ctx , rhs , lookup ) ? ; if rhs == 0 { bail ! ( "divide by zero" ); } Ok ( lhs / rhs ) } Node :: RightShift ( lhs , rhs ) => { let lhs = eval ( ctx , lhs , lookup ) ? ; let rhs = eval ( ctx , rhs , lookup ) ? ; Ok ( lhs >> rhs ) } Node :: LeftShift ( lhs , rhs ) => { let lhs = eval ( ctx , lhs , lookup ) ? ; let rhs = eval ( ctx , rhs , lookup ) ? ; Ok ( lhs { let n = eval ( ctx , n , lookup ) ? ; Ok ( – n ) } Node :: Conditional ( condition , consequent , alternative ) => { let condition = eval ( ctx , condition , lookup ) ? ; let consequent = eval ( ctx , consequent , lookup ) ? ; let alternative = eval ( ctx , alternative , lookup ) ? ; Ok ( if condition != 0 { consequent } else { alternative }) } } } From Interpreter to Synthesizer

Let’s consider the example from the original blog post: // Specification: x * 10 // Template: ( x Self :: Output ; /// `lhs + rhs` fn add ( & mut self , lhs : & Self :: Output , rhs : & Self :: Output ) -> Self :: Output ; /// `lhs – rhs` fn sub ( & mut self , lhs : & Self :: Output , rhs : & Self :: Output ) -> Self :: Output ; /// `lhs * rhs` fn mul ( & mut self , lhs : & Self :: Output , rhs : & Self :: Output ) -> Self :: Output ; /// `lhs / rhs`. 850 gas block Fails on divide by zero. fn div ( & mut self , lhs : & Self :: Output , rhs : & Self :: Output ) -> Result ; /// `lhs >> rhs` fn shr ( & mut self , lhs : & Self :: Output , rhs : & Self :: Output ) -> Self :: Output ; /// `lhs Self :: Output ; /// `-e` fn neg ( & mut self , e : & Self :: Output ) -> Self :: Output ; /// Returns `1` if `lhs == rhs`, returns `0` otherwise. fn eq ( & mut self , lhs : & Self :: Output , rhs : & Self :: Output ) -> Self :: Output ; /// Returns `1` if `lhs != rhs`, returns `0` otherwise. fn neq ( & mut self , lhs : & Self :: Output , rhs : & Self :: Output ) -> Self :: Output ; /// Perform variable lookup for the identifier `var`. fn lookup ( & mut self , var : & str ) -> Result ; }

interpreting conditionals directly to the AbstractIntepret trait. // src/abstract_interpret.rs pub fn interpret ( interpreter : & mut A , ctx : & mut ast :: Context , node : ast :: NodeId , ) -> Result where A : AbstractInterpret , { match * ctx . node_ref ( node ) { Node :: Const ( i ) => Ok ( interpreter . constant ( i )), Node :: Identifier ( s ) => { let s = ctx . interned ( s ); interpreter . lookup ( s ) } Node :: Addition ( lhs , rhs ) => { let lhs = interpret ( interpreter , ctx , lhs ) ? ; let rhs = interpret ( interpreter , ctx , rhs ) ? ; Ok ( interpreter . add ( & lhs , & rhs )) } Node :: Subtraction ( lhs , rhs ) => { let lhs = interpret ( interpreter , ctx , lhs ) ? ; let rhs = interpret ( interpreter , ctx , rhs ) ? ; Ok ( interpreter . sub ( & lhs , & rhs )) } Node :: Multiplication ( lhs , rhs ) => { let lhs = interpret ( interpreter , ctx , lhs ) ? ; let rhs = interpret ( interpreter , ctx , rhs ) ? ; Ok ( interpreter . mul ( & lhs , & rhs )) } Node :: Division ( lhs , rhs ) => { let lhs = interpret ( interpreter , ctx , lhs ) ? ; let rhs = interpret ( interpreter , ctx , rhs ) ? ; interpreter . div ( & lhs , & rhs ) } Node :: RightShift ( lhs , rhs ) => { let lhs = interpret ( interpreter , ctx , lhs ) ? ; let rhs = interpret ( interpreter , ctx , rhs ) ? ; Ok ( interpreter . shr ( & lhs , & rhs )) } Node :: LeftShift ( lhs , rhs ) => { let lhs = interpret ( interpreter , ctx , lhs ) ? ; let rhs = interpret ( interpreter , ctx , rhs ) ? ; Ok ( interpreter . shl ( & lhs , & rhs )) } Node :: Negation ( e ) => { let e = interpret ( interpreter , ctx , e ) ? ; Ok ( interpreter . neg ( & e )) } Node :: Conditional ( condition , consequent , alternative ) => { let condition = interpret ( interpreter , ctx , condition ) ? ; let consequent = interpret ( interpreter , ctx , consequent ) ? ; let alternative = interpret ( interpreter , ctx , alternative ) ? ; let zero = interpreter . constant ( 0 ); let neq_zero = interpreter . neq ( & condition , & zero ); let eq_zero = interpreter . eq ( & condition , & zero ); let consequent = interpreter . mul ( & neq_zero , & consequent ); let alternative = interpreter . mul ( & eq_zero , & alternative ); Ok ( interpreter . add ( & consequent , & alternative )) } } }

i64 associated output type and directly evaluates expressions: // src/eval.rs struct Eval { env : & ‘a HashMap , } impl AbstractInterpret for Eval { type Output = i64 ; fn constant ( & mut self , c : i64 ) -> i64 { c } fn lookup ( & mut self , var : & str ) -> Result { self . env . get ( var ) . cloned () . ok_or_else ( || format_err ! ( "undefined variable: {}" , var )) } fn neg ( & mut self , e : & i64 ) -> i64 { – e } fn add ( & mut self , lhs : & i64 , rhs : & i64 ) -> i64 { lhs + rhs } fn sub ( & mut self , lhs : & i64 , rhs : & i64 ) -> i64 { lhs – rhs } fn mul ( & mut self , lhs : & i64 , rhs : & i64 ) -> i64 { lhs * rhs } fn shr ( & mut self , lhs : & i64 , rhs : & i64 ) -> i64 { lhs >> rhs } fn shl ( & mut self , lhs : & i64 , rhs : & i64 ) -> i64 { lhs Result { if * rhs == 0 { bail ! ( "divide by zero" ); } Ok ( lhs / rhs ) } fn eq ( & mut self , lhs : & i64 , rhs : & i64 ) -> i64 { ( lhs == rhs ) as i64 } fn neq ( & mut self , lhs : & i64 , rhs : & i64 ) -> i64 { ( lhs != rhs ) as i64 } } pub fn eval ( ctx : & mut ast :: Context , node : NodeId , env : & HashMap ) -> Result { let eval = & mut Eval { env }; interpret ( eval , ctx , node ) }

variable. gas vs diesel mpg All other methods map straightforwardly onto Z3 method calls. // src/synthesize.rs struct Synthesize where ‘ctx: ‘a , { ctx : & ‘ctx z3 :: Context , vars : & ‘a mut HashMap > , holes : & ‘a mut HashMap , String > , const_vars : & ‘a mut HashSet > , } impl AbstractInterpret for Synthesize { type Output = z3 :: Ast ; fn lookup ( & mut self , var : & str ) -> Result > { if ! self . vars . contains_key ( var ) { let c = self . ctx . fresh_bitvector_const ( var , 64 ); self . vars . insert ( var . to_string (), c . clone ()); if var . starts_with ( "h" ) { self . holes . insert ( c , var . to_string ()); } else { self . const_vars . insert ( c ); } } Ok ( self . vars [ var ]. clone ()) } fn constant ( & mut self , c : i64 ) -> z3 :: Ast { z3 :: Ast :: bitvector_from_i64 ( self . ctx , c as i64 , 64 ) } fn add ( & mut self , lhs : & z3 :: Ast , rhs : & z3 :: Ast ) -> z3 :: Ast { lhs . bvadd ( rhs ) } fn sub ( & mut self , lhs : & z3 :: Ast , rhs : & z3 :: Ast ) -> z3 :: Ast { lhs . bvsub ( rhs ) } fn mul ( & mut self , lhs : & z3 :: Ast , rhs : & z3 :: Ast ) -> z3 :: Ast { lhs . bvmul ( rhs ) } fn div ( & mut self , lhs : & z3 :: Ast , rhs : & z3 :: Ast ) -> Result > { Ok ( lhs . bvsdiv ( rhs )) } fn shr ( & mut self , lhs : & z3 :: Ast , rhs : & z3 :: Ast ) -> z3 :: Ast { lhs . bvlshr ( & rhs ) } fn shl ( & mut self , lhs : & z3 :: Ast , rhs : & z3 :: Ast ) -> z3 :: Ast { lhs . bvshl ( & rhs ) } fn neg ( & mut self , e : & z3 :: Ast ) -> z3 :: Ast { e . bvneg () } fn eq ( & mut self , lhs : & z3 :: Ast , rhs : & z3 :: Ast ) -> z3 :: Ast { lhs . _eq ( rhs ). ite ( & self . constant ( 1 ), & self . constant ( 0 )) } fn neq ( & mut self , lhs : & z3 :: Ast , rhs : & z3 :: Ast ) -> z3 :: Ast { lhs . _eq ( rhs ). not (). ite ( & self . constant ( 1 ), & self . constant ( 0 )) } }

constraints for each of them. // src/synthesize.rs pub fn synthesize ( z3_ctx : & ‘a z3 :: Context , ast_ctx : & mut ast :: Context , specification : NodeId , template : NodeId , ) -> Result > { let mut vars = HashMap :: new (); let mut holes = HashMap :: new (); let mut const_vars = HashSet :: new (); let synth = & mut Synthesize { ctx : z3_ctx , vars : & mut vars , holes : & mut holes , const_vars : & mut const_vars , }; let specification = interpret ( synth , ast_ctx , specification ) ? ; if ! synth . holes . is_empty () { bail ! ( "the specification cannot have any holes!" ); } let template = interpret ( synth , ast_ctx , template ) ? ; // … }

results as a hash map. let solver = z3 :: Solver :: new ( z3_ctx ); solver . assert ( & goal ); if solver . check () { let model = solver . get_model (); let mut results = HashMap :: new (); for ( hole , name ) in holes { results . insert ( name , model . eval ( & hole ). unwrap (). as_i64 (). unwrap ()); } Ok ( results ) } else { bail ! ( "no solution" ) }