Upload
lillian-schroeder
View
32
Download
1
Embed Size (px)
DESCRIPTION
Programming with Decision Procedures. Philippe Suter. École Polytechnique Fédérale de Lausanne, Switzerland. What’s a Decision Procedure?. 30’000 ft view, known uses. This Talk. High-level plan; from decision procedures to applications. Verification of functional programs. proof. - PowerPoint PPT Presentation
Citation preview
Programming with Decision Procedures
Philippe SuterÉcole Polytechnique Fédérale de Lausanne, Switzerland
What’s a Decision Procedure?
• 30’000 ft view, known uses
This Talk
• High-level plan; from decision procedures to applications.
Verification of functional programs
proof
counterexample(input, trace)
sealed abstract class Treecase class Node(left: Tree, value: Int, right: Tree) extends Treecase class Leaf() extends Tree
object BST { def add(tree: Tree, element: Int): Tree = tree match { case Leaf() Node(Leaf(), element, Leaf())⇒ case Node(l, v, r) if v > element Node(add(l, element), v, r)⇒ case Node(l, v, r) if v < element Node(l, v, add(r, element))⇒ case Node(l, v, r) if v == element tree⇒ } ensuring (result ≠ Leaf())}
(tree = Node(l, v, r) v > element result ≠ Leaf())∧ ∧ ⇒ Node(result, v, r) ≠ Leaf()
We know how to generate verification conditions for functional programs
Proving verification conditions
(tree = Node(l, v, r) v > element result ≠ ∧ ∧Leaf()) ⇒ Node(result, v, r) ≠ Leaf()
D.C. Oppen, Reasoning about Recursively Defined Data Structures, POPL ’78
G. Nelson, D.C. Oppen, Simplification by Cooperating Decision Procedure, TOPLAS ’79
Previous work gives decision procedures that can handle certain verification conditions
sealed abstract class Treecase class Node(left: Tree, value: Int, right: Tree) extends Treecase class Leaf() extends Tree
object BST { def add(tree: Tree, element: Int): Tree = tree match { case Leaf() Node(Leaf(), element, Leaf())⇒ case Node(l, v, r) if v > element Node(add(l, element), v, r)⇒ case Node(l, v, r) if v < element Node(l, v, add(r, element))⇒ case Node(l, v, r) if v == element tree⇒ } ensuring (content(result) == content(tree) { element })∪
def content(tree: Tree) : Set[Int] = tree match { case Leaf() ⇒∅ case Node(l, v, r) content(l) { v } content(r)⇒ ∪ ∪ }}
Complex verification conditionSet Expressions
Recursive FunctionAlgebraic Data Types
t1 = Node(t2, e1, t3) ∧ content(t4) = content(t2) { e∪ 2 } ∧ content(Node(t4, e1, t3)) ≠ content(t1) { e∪ 2 }where def content(tree: Tree) : Set[Int] = tree match { case Leaf() ⇒∅ case Node(l, v, r) content(l) { v } content(r)⇒ ∪ ∪}
?
Our contribution
Decision procedures for extensions of algebraic data types with certain recursive functions
Formulas we aim to proveQuantifier-free Formula
Generalized Fold Function
where def content(tree: Tree) : Set[Int] = tree match { case Leaf() ⇒∅ case Node(l, v, r) content(l) { v } content(r)⇒ ∪ ∪}
t1 = Node(t2, e1, t3) ∧ content(t4) = content(t2) { e∪ 2 } ∧ content(Node(t4, e1, t3)) ≠ content(t1) { e∪ 2 }
Domain with a Decidable Theory
def α(tree: Tree) : C = tree match { case Leaf() ⇒ empty case Node(l, v, r) ⇒ combine(α(l), v, α(r))}
General form of our recursive functions
def content(tree: Tree) : Set[Int] = tree match { case Leaf() ⇒∅ case Node(l, v, r) content(l) { v } content(r)⇒ ∪ ∪}
empty : Ccombine : (C, E, C) → C
How do we prove such formulas?Quantifier-free Formula
Generalized Fold Function
where def content(tree: Tree) : Set[Int] = tree match { case Leaf() ⇒∅ case Node(l, v, r) content(l) { v } content(r)⇒ ∪ ∪}
t1 = Node(t2, e1, t3) ∧ content(t4) = content(t2) { e∪ 2 } ∧ content(Node(t4, e1, t3)) ≠ content(t1) { e∪ 2 }
Domain with a Decidable Theory
Separate the Conjuncts
c1 = content(t1) … c∧ ∧ 5 = content(t5)
t1 = Node(t2, e1, t3) t∧ 5 = Node(t4, e1, t3) ∧c4 = c2 { e∪ 2 } c∧ 5 ≠ c1 { e∪ 2 } ∧
t1 = Node(t2, e1, t3) ∧ content(t4) = content(t2) { e∪ 2 } ∧ content(Node(t4, e1, t3)) ≠ content(t1) { e∪ 2 }
1
4
2 t2
t3
t1
1
7
0
t5
t4=4
2 t2
t3
t4
c2
c3
∪∪
∅
4
2c4 =
c4 = { 4 } { 2 } c∪ ∪∅∪ 3 c∪ 2
content=
t1 7
0
t5
=
Overview of the decision procedure
c4 = c2 { e∪ 2 } c∧ 5 ≠ c1 { e∪ 2 }t1 = Node(t2, e1, t3)t5 = Node(t4, e1, t3) ∧
The resulting formula is in the decidable theory of sets
c1 = c2 { e∪ 1 } c∪ 3
c5 = c4 { e∪ 1 } c∪ 3∧
additional derived constraints
set constraints from the input formula
c4 = c2 { e∪ 2 }c5 ≠ c1 { e∪ 2 }c1 = c2 { e∪ 1 } c∪ 3
c5 = c4 { e∪ 1 } c∪ 3
∧∧∧
resulting formula
ci = content(ti), i { 1, …, ∈5 }
tree constraints from the input formula
mappings from the input formula
Decision Procedure for Sets
What we have seen is a simple correct algorithm
But is it complete?
A verifier based on such procedure
val c1 = content(t1) val c2 = content(t2)if (t1 ≠ t2) { if (c1 == ) {∅ assert(c2 ≠ )∅ x = c2.chooseElement }}
c1 = content(t1) c∧ 2 = content(t2) t∧ 1 ≠ t2 c∧ 1 = c∅∧ 2 = ∅
Warning: possible assertion violation
…
Source of incompleteness
c1 = c∅∧ 2 = ∅ !
Models for the formula in the logic of sets must not contradict the disequalities over trees
c1 = content(t1) c∧ 2 = content(t2) t∧ 1 ≠ t2 c∧ 1 = c∅∧ 2 = ∅
t1 ≠ t2
∅
How to make the algorithm complete
• Case analysis for each tree variable:– is it Leaf ?– Is it not Leaf ?
c1 = content(t1) c∧ 2 = content(t2) t∧ 1 ≠ t2 c∧ 1 = c∅∧ 2 = ∅
This gives a complete decision procedure for the content function that maps to sets
∧ t1 = Leaf t∧ 2 = Node(t3, e, t4) ∧ t1 = Leaf t∧ 2 = Leaf ∧ t1 = Node(t3, e1, t4) t∧ 2 = Node(t5, e2, t6) ∧ t1 Node(t3, e, t4) t∧ 2 = Leaf
What about other content functions?
Tree content abstraction, as a:Set
Multiset
List
Tree size, height, min
Invariants (sortedness,…)
Sufficient Surjectivity
How and when we can havea complete algorithm
Decision Procedure for Sets
Choice of trees is constrained by sets
c4 = c2 { e∪ 2 } c∧ 5 ≠ c1 { e∪ 2 }t1 = Node(t2, e1, t3)t5 = Node(t4, e1, t3) ∧
c1 = c2 { e∪ 1 } c∪ 3
c5 = c4 { e∪ 1 } c∪ 3∧
c4 = c2 { e∪ 2 }c5 ≠ c1 { e∪ 2 }c1 = c2 { e∪ 1 } c∪ 3
c5 = c4 { e∪ 1 } c∪ 3
∧∧∧
additional derived constraints
set constraints from the input formula
resulting formula
ci = content(ti), i { 1, …, ∈5 }
tree constraints from the input formula
mappings from the input formula
Inverse images
• When we have a model for c1, c2, … how can we pick distinct values for t1, t2,… ?
α
α-1
The cardinality of α-1 (ci) is what matters.
ci = content(ti)ti content∈ -1 (ci) ⇔
‘Surjectivity’ of set abstraction
{ 1, 5 } 5
1
1
5
5
5 1
1…
∅ content-1
content-1
|content-1( )| = 1∅|content-1({1, 5})| = ∞
In-order traversal
2
1 7
4
[ 1, 2, 4, 7 ]inorder-
‘Surjectivity’ of in-order traversal
[ 1, 5 ] 5
1
1
5
[ ] inorder-1
inorder-1
|inorder-1(list)| =
(number of trees of size n = length(list))
…
…
|inorder-1(list)|
length(list)
More trees map to longer lists
An abstraction function α (e.g. content, inorder) is sufficiently surjective if and only if, for each number p > 0, there exist, computable as a function of p:
such that, for every term t, Mp(α(t)) or š(t) in Sp.
a finite set of shapes Sp
a closed formula Mp in the collection theory such that Mp(c) implies |α-1(c)| > p
--
Pick p sufficiently large.Guess which trees have a problematic shape.
Guess their shape and their elements.By construction values for all other trees can be found.
For a conjunction of n disequalities over tree terms, if for each term we can pick a value from a set of trees of size at least n+1, then we can pick values that satisfy all disequalities.
We can make sure there will be sufficiently many trees to choose from.
Generalization of the Independence of Disequations Lemma
Scope of our result - Examples
Tree content abstraction, as a:Set
Multiset
List
Tree size, height, min
Invariants (sortedness,…)
[Kuncak,Rinard’07]
[Piskac,Kuncak’08]
[Plandowski’04]
[Papadimitriou’81]
[Nelson,Oppen’79]
def content(tree: Tree) : Set[Int] = tree match { case Leaf() Set.empty⇒ case Node(l, v, r) (content(l) ++ content(r)) + v⇒}
def allEven(list: List) : Boolean = list match { case Nil() ⇒ true case Cons(x, xs) (x % 2 == 0) && allEven(list)⇒}
def max(tree: Tree) : Option[Int]= tree match { case Leaf() None⇒ case Node(l, v, r) Some(max(l), max(r)) ⇒ match { case (None, None) Some(v)⇒ case (Some(lm), None) max(lm, v)⇒ case (None, Some(rm)) max(rm, v)⇒ case (Some(lm), Some(rm)) (max(lm, rm), v)⇒})}
…as well as any catamorphism
that maps to a finite domain!
Sufficiently Surjectivity Holds in Practice
Theorem: For every sufficiently surjective abstraction our procedure is complete.
Theorem: The following abstractions are sufficiently surjective: set content, multiset content, list (any-order), tree height, tree size, minimum, sortedness
A complete decision procedure for all these cases!
»«In the context of a project to reason about a
domain specific language called Guardol for XML message processing applications […the] procedure has allowed us to prove a number of interesting things […], so I'm extremely grateful for it.
Mike Whalen (University of Minnesota & Rockwell Collins)
Sufficiently Surjectivity Holds in Practice
K. Slind, D. Hardin, M. Whalen, T.H. Pham, The Guardol Language and Verification System, TACAS 2012[ ]
• Reasoning about functional programs reduces to proving formulas
• Decision procedures always find a proof or a counterexample
• Previous decision procedures handle recursion-free formulas
• We introduced decision procedures for formulas with recursive fold functions
Decision Procedures for Recursive Abstractions Functions
Leon(Online)
• A verifier for Scala programs.• The programming and specification languages
are the same purely functional subset.
def content(t: Tree) = t match { case Leaf ⇒ Set.empty case Node(l, v, r) ⇒ (content(l) ++ content(r)) + e}
def insert(e: Int, t: Tree) = t match { case Leaf ⇒ Node(Leaf, e, Leaf) case Node(l, v, r) if e < v ⇒ Node(insert(e, l), v, r) case Node(l, v, r) if e > v ⇒ Node(l, v, insert(e, r)) case _ t⇒} ensuring( res content(res) == content(t) + e)⇒
Satisfiability ModuloRecursive Programs
…of quantifier-free formulas in a decidable base theory…
…pure, total, deterministic, first-order and terminating on all inputs…
• Semi-decidable problem of great interest:– applications in verification, testing, theorem
proving, constraint logic programming, …• Our algorithm always finds counter-examples
when they exist, and will also find assume/guarantee style proofs.
• It is a decision procedure for infinitely and sufficiently surjective abstraction functions.
• We implemented it as a part of a verification system for a purely functional subset of Scala.
Satisfiability Modulo Recursive Programs
Proving/Disproving Modulo Recursive Programs
Proving by Inliningdef size(lst: List) = lst match { case Nil ⇒ 0 case Cons(_, _) ⇒ 1} ensuring(res ⇒ res ≥ 0)
(if(lst = Nil) 0 else 1) < 0
Convert pattern-matching,negate.
Unsatisfiable.
Proving by Inliningdef size(lst: List) = lst match { case Nil ⇒ 0 case Cons(x, xs) ⇒ 1 + size(xs)} ensuring(res ⇒ res ≥ 0)
(if(lst = Nil) 0 else 1 + size(lst.tail)) < 0
Convert pattern-matching,negate,treat functions as uninterpreted.
Satisfiable.(e.g. for size(Nil) = -2)
Inline post-condition.
(if(lst = Nil) 0 else 1 + size(lst.tail)) < 0 ∧size(lst.tail) ≥ 0
Unsatisfiable.
Disproving by Inliningdef size(lst: List) = lst match { case Nil ⇒ -1 case Cons(x, xs) ⇒ 1 + size(xs)}
size(lst) < 0
Negate,treat functions as uninterpreted.
Satisfiable.(e.g. lst = [0], size([0]) = -1)
def prop(lst: List) = { size(lst) ≥ 0} ensuring(res ⇒ res)
Inline definition of size(lst).
size(lst) < 0 ∧size(lst) = (if(lst = Nil) -1 else 1 + size(lst.tail))
Satisfiable.(e.g. lst = [0,1], size([1]) = -2)
size(lst) < 0 ∧size(lst) = (if(lst = Nil) -1 else 1 + size(lst.tail)) ∧size(lst.tail) = (if(lst.tail = Nil) -1 else 1 + size(lst.tail.tail))
Satisfiable.(e.g. lst = [0,1,2], …)
Disproving with Inlining
size(lst)
?
There are always unknown branches in the evaluation tree.We can never be sure that there exists no smaller solution.
?
?
size(lst.tail)
size(lst.tail.tail)
…
Branch Rewritingsize(lst) = if(lst = Nil) { -1} else { 1 + size(lst.tail)}
size(lst) = ite1
∧ p1 lst = Nil⇔ ∧ p1 ite⇒ 1 = -1 ∧ ¬p1 ite⇒ 1 = 1 + size(lst.tail)
size(lst)
p1 ¬p1
… ∧ p1
?
• Satisfiable assignments do not depend on unknown values.
• Unsatisfiable answers have two possible meanings.
Algorithm(φ, B) = unroll(φ, _)while(true) {
solve(φ ∧ B) match { case “SAT” ⇒ return “SAT”
case “UNSAT” solve(⇒ φ) match { case “UNSAT” ⇒ return “UNSAT”
case “SAT” (⇒ φ, B) = unroll(φ, B) } }}
Inlines some postconditions and bodies of function applications that were guarded by a literal in B, returns the new formula and new set of guards B.
“I’m feeling lucky”
Some literals in B may be implied by φ : no need to unroll what they guard.
Inlining must be fair
Completeness for Counter-Examples• Let a1,… an be the free variables of φ, and c1,…,cn
be a counter-example to φ.• Let T be the evaluation tree of φ[a1→c1, …, an→cn].
• Eventually, the algorithm will reach a point where T is covered.
?
?
?
?
T :
Termination for Sufficiently Surjective Abstraction Functions
Sufficiently Surjective Abstractions• The algorithm terminates, and is thus a
decision procedure for the case of infinitely and sufficiently surjective abstraction functions.
def α(tree: Tree) : C = tree match { case Leaf() ⇒ empty case Node(l, v, r) ⇒ combine(α(l), v, α(r))}
A Tale of Two Techniques
φ ≡ … α(t) …
t ≡ t ≡
t ≡
t ≡
t ≡
∨ ψ(α(t))• For catamorphisms, unrolling α
corresponds to enumerating potential counter-examples by the size of their tree.
• When all required trees have been considered, a formula at least as strong as ψ(α(t)) is derived by the solver.
Verification System
(Some) Experimental ResultsBenchmark LoC #Funs. #VCs. Time (s)
ListOperations 107 15 27 0.76
AssociativeList 50 5 11 0.23
InsertionSort 99 6 15 0.42
RedBlackTrees 117 11 24 3.73
PropositionalLogic 86 9 23 2.36
AmortizedQueue 124 14 32 3.37
VSComp 94 11 23 0.79
677 71 155 11.66
Functional correctness properties of data structures: red-black trees implement a set and maintain height invariants, associative list has read-over-write property, insertion sort returns a sorted list of identical content, amortized queue implements a balanced queue, etc.
Try it online http://lara.epfl.ch/leon/
• Semi-decidable problem of great interest:– applications in verification, testing, theorem
proving, constraint logic programming, …• Our algorithm always finds counter-examples
when they exist, and will also find assume/guarantee style proofs.
• It is a decision procedure for infinitely and sufficiently surjective abstraction functions.
• We implemented it as a part of a verification system for a purely functional subset of Scala.
Satisfiability Modulo Recursive Programs
“The smallest positive integer that is a multiple
of both a and b.” “All binary search trees whose content is a
given set.”
“The choice of items that maximize total value given
a bound on weight.”
That’s easier said than done.« »
• Long history of constraint solving languages
CLP(X) …
• Can we bring similar benefits to mainstream languages by building on existing tools?
That’s easier said than done.« »
Pragmatic Constraint Programming
Programming by constraints is appropriate for some but not all tasks.• The performance of regular, imperative, code
should not be affected.• From the programmer’s point of view, changes
to the language should be minimal.Constraints should be a feature, not necessarily the foundation.
• We present Kaplan, an extension of Scala with first-class constraints.
• Programmers can manipulate constraints like other values and combine them.
• Constraints can be solved eagerly, or lazily with logical variables.
• At run time, Kaplan invokes a constraint solver supporting user-defined recursive functions.
Constraints as Control
Kaplan in Action
Kaplan in Action
((x : Int) ⇒ x > 0).find> Some(1)
((x : Int) ⇒ x != x).find> None
val it = ((x : Int) ⇒ 4 < x).findAll> Non-empty iterator.
it.take(6).toList> List(5, 6, 7, 8, 10, 9)
Kaplan in Action
val (a, b) = (12, 8)val (lcm, _, _) = ((m, fa, fb) ⇒ m > 0 && m == fa * a && m == fb * b) .minimizing((m,fa,fb) ⇒ m).find> Some(24)
((t : Tree) ⇒ isBST(t) && elems(t) == Set(1, 2, 3)) .findAll.toList> List(Node(Node(L, 1, L), 2, Node(L, 3, L)), Node(Node(Node(L, 1, L), 2, L), 3, L), Node(L, 1, Node(L, 2, Node(L, 3, L))))
Language Integration
class Constraint[T] extends Function[T,Boolean] { def find : Option[T]}
Potentially a tuple type. Scala representation of a first-class function.
All you Need is…
Semantics is Intuitive
t1 | μ1 →S t2 | μ2
t1 | μ1 → t2 | μ2
Host
¬∃M . M ⊨ φ
(λxn . φ(xn)).find | μ → None | μS-Unsat
M ⊨ φ
(λxn . φ(xn)).find | μ → Some(M(xn))| μS-Sat
Derived Constructs
• findAll creates an iterator that stores and negates values returned by find.
• minimizing runs a search by dichotomy.
• Constraints can be combined:((x : Int) ⇒ x > 0) && ((x : Int) ⇒ x < 10)
A SAT Solver
def solve(problem : Seq[Seq[Int]]) : Option[Map[Int,Boolean]] = { problem.map(clause ⇒ clause.map(lit ⇒ ((m : Map[Int,Boolean]) ⇒ m(abs(lit)) == (lit > 0)).c ).reduceLeft(_ || _)).reduceLeft(_ && _).find}
Problem is given as a DIMACS-like list of clauses
Constraint for one literal
Constraint for one clause
Constraint for the conjunction
Result is an assignment, or None
Embedding: Lifting Syntax Trees• We need access to the ASTs of constraints.• We rely on implicit type conversions.
• We check that the constraint falls in the supported fragment and extracts the AST.
implicit def lift[A](f : Function[A,Boolean]) : Constraint[A]
((x : Int) ⇒ x > 0).find
lift[Int](((x : Int) ⇒ x > 0)).find λ
>
$1 0
Constraint Language and Solver
Constraint Language: Primitive Types
• Booleans
• Integers
• Sets
• Maps
||, &&, !, ==, !=
+, -, *, /, >, <, ==, !=
s(x), ++, --, **, Set(…), subsetOf
m(x), m.isDefinedAt(x), ++, Map(x -> i)
Constraint Language: Recursive Functions@spec def noneDivides(from : Int, j : Int) : Boolean = { if(from == j) { true } else { !divides(from, j) && noneDivides(from + 1, j) }}@spec def isPrime(i : Int) = (i >= 2 && noneDivides(2, i))
((x : Int) ⇒ isPrime(x)).find> Some(2)
((x : Int) ⇒ isPrime(x)).minimizing(x ⇒ x).findAll.take(10)> 2, 3, 5, 7, 11, 13, 17, 19, 23, 29
Constraint Language: Data Types@spec sealed abstract class List@spec case class Cons(head : Int, tail : List) extends List@spec case class Nil() extends List
@spec def content(lst : List) = lst match { case Nil() ⇒ Set.empty case Cons(x, xs) ⇒ Set(x) ++ content(xs)}@spec def isSorted(lst : List) = ...
val s = Set(0, 1, -3)((l : List) ⇒ isSorted(lst) && content(lst) == s).solve
> Cons(-3, Cons(0, Cons(1, Nil())))
Underlying Constraint Solver
Applications and Conclusion
Initial Experience• Solving puzzles• Enumerating data structures for testing• Executable specifications
– Can be used as method stubs…– …or substitute when a contract check fails
[Samimi et al. ECOOP 2010]
• Simplifies applications that depend on SMT solvers– counter-example guided inductive synthesis
• Ask us for a live demo!
Related Work
• Prolog + CLP(X)
• Functional Logic Programming (Curry)
• Dminor [Bierman et al. ICFP 2010]
• Jeeves [Yang et al. POPL 2012]
• Complete Functional Synthesis [Kuncak et al. PLDI 2010]
• We presented Kaplan, an extension of Scala with first-class constraints.
• Programmers can manipulate constraints like other values and combine them.
• Constraints can be solved eagerly, or lazily with logical variables.
• At run time, Kaplan invokes a constraint solver supporting user-defined recursive functions.
Constraints as Control
The Compilation Approach
Program synthesis
Program specification(implicit)
find y suchThat property(x, y)
Program implementation(explicit, programming language)
z1 = f1(x)z2 = f2(z1, x)y = f3(z1, z2, x)
The generated code is correct by construction
Compilation Approach• Localized invocation of constraint solving.
• Replace, when possible, by explicit computation.
standard code
implicit code
Turn decision procedures into synthesis procedures
… val x = readInteger() + 4
val r = (y ⇒ 5*x+7*y == 31).solve
println(“r^2: “ + r*r)…
Decision vs. synthesis proceduresFor a well-defined class of formulas:
Decision procedure
• Input: a formula
Synthesis procedure
• Input: a formula, with input and output variables
• Output: a modelof the formula
• Output: a program to compute output values from input values
5*x + 7*y = 31
x := 2y := 3
Inputs: { x } outputs: { y }5*x + 7*y = 31
y := (31 – 5*x) / 7
(model-generating)
a theorem prover that always succeeds a synthesizer that always succeeds
def secondsToTime(totalSeconds: Int) : (Int, Int, Int) = ((h: Int, m: Int, s: Int) (⇒ h * 3600 + m * 60 + s == totalSeconds && h ≥ 0 && m ≥ 0 && m < 60 && s ≥ 0 && s < 60 )).solve
An example
def secondsToTime(totalSeconds: Int) : (Int, Int, Int) = val t1 = totalSeconds / 3600 val t2 = totalSeconds + ((-3600) * t1) val t3 = min(t2 / 60, 59) val t4 = totalSeconds + ((-3600) * t1) + (-60 * t3) (t1, t3, t4)
3787 seconds 1 hour, 3 mins. and 7 secs.
• Synthesis: our procedures start from an implicit specification.
• Functional: computes a function that satisfies a given input/output relation.
• Complete: guaranteed to work for all specification expressions from a well-defined class.
Complete Functional Synthesis
Starting point: quantifier elimination
• A call of the form
• Corresponds to constructively solving the quantifier elimination problem
where a is a parameter
r = (x F( a, x )).solve⇒
∃ x . F( a, x )
“let r be x such that F(a, x) holds”
Quantifier elimination
• Converts a formula into an equivalent one with no quantified variables
Observation: we can obtain witness termsfor the eliminated variables
• Prominent application of Q.E.:integer linear arithmetic
• Witness terms become the instructions of the synthesized program
val z = ceil(5*a/12)val x = -7*z + 3*aval y = 5*z + -2*a
((x, y) 5 * x + 7 * y == a && x ≤ y).solve⇒
z = ceil(5*31/12) = 13x = -7*13 + 3*31 = 2y = 5*13 – 2*31 = 3
∃ x y . 5x + 7y = a x ≤ y∃ ∧
x = 3ay = -2a
Use extended Euclid’s algorithm to find particular solution to 5x + 7y = a:
Express general solution of equations for x, y using a new variable z:
x = -7z + 3ay = 5z - 2a
Rewrite inequations x ≤ y in terms of z: 5a ≤ 12z z ≥ ceil(5a/12)
Obtain synthesized program:
For a = 31:
Corresponding quantifier elimination problem:
((x, y) 5 * x + 7 * y == a && x ≤ y && x ≥ 0).solve⇒
Express general solution of equations for x, y using a new variable z:
x = -7z + 3ay = 5z - 2a
Rewrite inequations x ≤ y in terms of z: z ≥ ceil(5a/12)
assert(ceil(5*a/12) ≤ floor(3*a/7))val z = ceil(5*a/12)val x = -7*z + 3*aval y = 5*z + -2*a
Obtain synthesized program:
z ≤ floor(3a/7)Rewrite x ≥ 0:
ceil(5a/12) ≤ floor(3a/7)Precondition on a:(exact precondition)
Applicability
• In principle, any model-generating decision procedure can be adapted; at the limit, the generated program is the decision procedure.
• Quantifier-elimination decision procedures:– Integer linear (Presburger) arithmetic– Polynomial real arithmetic– Algebraic data types (lists, trees, etc.)– Boolean algebra with Presburger arithmetic (BAPA)
• We proposed a method to devise synthesis procedures from decision procedures.
• Our synthesis algorithms are guaranteed to find a solution when there exists one.
• Our techniques can be integrated into general purpose programming languages.
• We built a Scala compiler plugin integrating integer arithmetic and set synthesis into Scala.
Compilation Approach
Programming with Decision Procedures
• Requires runtime solver.• Language of constraints
is extensible.• First-class constraints,
can be combined.• Can also enumerate
solutions.
• Efficient generated code.• Language of constraints
is syntactically fixed.• Constraints need to be
known at compile time.• Computes a single
solution.
Run time approach Compilation approach
• In both cases:– Constraints are built from existing constructs.– Semantics are intuitive.
Summary Slide
• Same as first, perhaps with paper references.• a
Thank you.
Sets with Cardinality Constraints
• Decision procedure for constraints on sets with the cardinality operator.
• Based on decomposition techniques to scale.• Integrated into Z3, works in combination with all
other supported theories.• Part of verification system for functional
programs.P. Suter, R. Steiger, and V. Kuncak, Sets with Cardinality Constraints in Satisfiability Modulo Theories, VMCAI 2011[ ]
x A B A | B ∈ ∧ ⊆ ∧ ∩ C | ≤ 2
Type Inference for PHP
• Uses abstract interpretation to estimate the set of possible types at all program points.
• Precise abstractions for arrays.• Can use code instrumentation to enhance the
precision of the analysis.
E. Kneuss, P. Suter, and V. Kuncak, Runtime Instrumentation for Precise Flow-Sensitive Type Analysis, RV 2010[ ]
“PHP Analyzer for Type Mismatch”
Interprocedural Effect Analysis
“Interprocedural Static Analyzer for Effects”
with E. Kneuss and V. Kuncak, work in progress.[ ]
• Computes summaries of heap side-effects.• Handling of closures is challenging:
– Analyze “around” problematic calls.– Include callback invocations in effect summaries.
• Targets the whole Scala language.
Thank you.
Concrete Meets Logical• Logical and concrete variables are distinct, but
are inter-convertible.def max(x : L[Int], y : L[Int]) : L[Int] = { ((a : Int) ⇒ a >= x && a >= y && (a == x || a == y)).lazySolve}
> (max(3, 5)) : Int5
implicit def concretize[T](l : L[T]) : T = l.valueimplicit def lift[T](c : T) : L[T] = ...
• Nothing is logical by default; this preserves efficient execution.
for Comprehensions
for((x,y) ← ((x: Int, y: Int) ⇒ x > 0 && y > x && x * 2 + y * 3 <= 40).findAll; if(isPrime(y)); z ← ((z: Int) ⇒ z * x == 3 * y * y).findAll) yield (x, y, z)
Can you find positive x, y, z such that 2x + 3y ≤ 40, xz = 3y2, and y is prime?
> (1,2,12), (1,3,27), (1,5,75), (1,7,147), (1,11,363), (3,11,121), (3,5,25), (3,7,49)
Returned expressionFilter: arbitrary boolean expression.
Generators: sequences, computed lazily
Higher-Order Theorem Proving
• w/ N. Bjorner at MSR
Related Work
G. Nelson, D.C. Oppen, Simplification by Cooperating Decision Procedure, TOPLAS ’79
V. Sofronie-Stokkermans, Locality Results for Certain Extensions of Theories with Bridging Functions, CADE ’09
Some implemented systems: ACL2, Isabelle, Coq, Verifun, Liquid Types
Related Work• SMT Solvers + axioms
– can be very efficient for well-formed axioms– in general, no guarantee than instantiations are fair– cannot in general conclude satisfiability (changing…)
• Interactive verification systems (ACL2, Isabelle, Coq)
– very good at building inductive proofs– could benefit greatly from counter-example
generation (as feedback to the user but also to prune out branches in the search)
• Sinha’s Inertial Refinement technique– similar use of “blocked paths”– no guarantee to find counter-examples
Related Work, continued• DSolve (aka. “Liquid Types”)
– because the proofs are based on type inference rules, cannot immediately be used to prove assertions relating different functions
• Bounded Model Checking– offer similar guarantees on the discovery of
counter-examples– reduces to SAT rather than SMT– unclear when it is complete for proofs
• Finite Model Finding (Alloy, Paradox)– lack of theory reasoning, no proofs
Logical Variables
class L[T] { def value : T}
class Constraint[T] extends Function[T,Boolean] { def find : Option[T] def lazyFind : Option[L[T]]}
Logical Variables
A constraint store is maintained at run time.
Logical Variables
val x = ((x : Int) ⇒ x > 0).lazyFind.get> x : L[Int] = L(?)x.value> 1
val x = ((x : Int) ⇒ x > 0).lazyFind.getval y = ((y : Int) ⇒ 0 < y && y < x).lazyFind.get> y : L[Int] = L(?)x.value> 2y.value> 1 Constrains both x and y.
Ensures existence of x, does not commit to a value.
Branching on Logical Conditions
val a = Console.readIntval x = ((x : Int) ⇒ true).lazyFind.getval y = ((y : Int) ⇒ a / x).lazyFind.get
assuming(a == x * y && x != 1 && x != a) { println(a + “ is composite”) println(x.value + “ * “ + y.value)} otherwise { println(a + “ is prime”)} x is arbitrary
In this branch, x and y are a decompositon of a.
“Biased if-then-else”
Semantics with Constraint Store
t1 | μ1 →S t2 | μ2Hostt1 | ⟨ μ1, κ ⟩ → t2 | ⟨ μ2, κ ⟩t1 | ⟨ μ1, κ ⟩ → t2 | ⟨ μ2, κ ⟩
Constraint store: a formula over all logical variables.
Semantics with Constraint Store
The variables are returned. The model
M is ignored.
Bound variables are mapped to fresh
variables in κ.
Enforces single value semantics.
The existence of M is guaranteed by the execution order.
L-SatM ⊨ κ ∧ φ[xn an↦ f] anf fresh in κ
(λxn . φ(xn,an)).lazyFind | ⟨ μ, κ → Some(an⟩ f)| ⟨ μ, κ ∧ φ[xn ↦anf] ⟩
L-Unsat¬∃M . M ⊨ κ ∧ φ[xn an↦ f] anf fresh in κ
(λxn . φ(xn,an)).lazyFind | ⟨ μ, κ → None | ⟩ ⟨ μ, κ ⟩
ValueM ⊨ κ
a.value | ⟨ μ, κ → ⟩ M(a)| ⟨ μ, κ a = ∧ M(a) ⟩
Liftc a constant af fresh in κ
c.lift | ⟨ μ, κ → a⟩ f| ⟨ μ, κ a∧ f = c ⟩
Implementation of Constraint Store• For each logical variable, we maintain two
variables in the solver:- a value variable and- a guard variable
(y ⇒ x + y >= 4).lazySolve gx g∨ y v⇒ x + vy ≥ 4
• Guard denotes “liveness”. • Works as a garbage collection mechanism.• .value never triggers a new search, last result
is always cached.