106
Programming with Decision Procedures Philippe Suter École Polytechnique Fédérale de Lausanne, Switzerland

Programming with Decision Procedures

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

Page 1: Programming with Decision Procedures

Programming with Decision Procedures

Philippe SuterÉcole Polytechnique Fédérale de Lausanne, Switzerland

Page 2: Programming with Decision Procedures

What’s a Decision Procedure?

• 30’000 ft view, known uses

Page 3: Programming with Decision Procedures

This Talk

• High-level plan; from decision procedures to applications.

Page 4: Programming with Decision Procedures

Verification of functional programs

proof

counterexample(input, trace)

Page 5: Programming with Decision Procedures

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

Page 6: Programming with Decision Procedures

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

Page 7: Programming with Decision Procedures

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)⇒ ∪ ∪ }}

Page 8: Programming with Decision Procedures

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)⇒ ∪ ∪}

?

Page 9: Programming with Decision Procedures

Our contribution

Decision procedures for extensions of algebraic data types with certain recursive functions

Page 10: Programming with Decision Procedures

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

Page 11: Programming with Decision Procedures

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

Page 12: Programming with Decision Procedures

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

Page 13: Programming with Decision Procedures

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 }

Page 14: Programming with Decision Procedures

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

=

Page 15: Programming with Decision Procedures

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

Page 16: Programming with Decision Procedures

What we have seen is a simple correct algorithm

But is it complete?

Page 17: Programming with Decision Procedures

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

Page 18: Programming with Decision Procedures

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

Page 19: Programming with Decision Procedures

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

Page 20: Programming with Decision Procedures

What about other content functions?

Tree content abstraction, as a:Set

Multiset

List

Tree size, height, min

Invariants (sortedness,…)

Page 21: Programming with Decision Procedures

Sufficient Surjectivity

How and when we can havea complete algorithm

Page 22: Programming with Decision Procedures

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

Page 23: Programming with Decision Procedures

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) ⇔

Page 24: Programming with Decision Procedures

‘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})| = ∞

Page 25: Programming with Decision Procedures

In-order traversal

2

1 7

4

[ 1, 2, 4, 7 ]inorder-

Page 26: Programming with Decision Procedures

‘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))

Page 27: Programming with Decision Procedures

|inorder-1(list)|

length(list)

More trees map to longer lists

Page 28: Programming with Decision Procedures

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.

Page 29: Programming with Decision Procedures

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

Page 30: Programming with Decision Procedures

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]

Page 31: Programming with Decision Procedures

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!

Page 32: Programming with Decision Procedures

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!

Page 33: Programming with Decision Procedures

»«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[ ]

Page 34: Programming with Decision Procedures

• 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

Page 35: Programming with Decision Procedures

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)⇒

Page 36: Programming with Decision Procedures

Satisfiability ModuloRecursive Programs

…of quantifier-free formulas in a decidable base theory…

…pure, total, deterministic, first-order and terminating on all inputs…

Page 37: Programming with Decision Procedures

• 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

Page 38: Programming with Decision Procedures

Proving/Disproving Modulo Recursive Programs

Page 39: Programming with Decision Procedures

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.

Page 40: Programming with Decision Procedures

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.

Page 41: Programming with Decision Procedures

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], …)

Page 42: Programming with Decision Procedures

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)

Page 43: Programming with Decision Procedures

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.

Page 44: Programming with Decision Procedures

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

Page 45: Programming with Decision Procedures

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 :

Page 46: Programming with Decision Procedures

Termination for Sufficiently Surjective Abstraction Functions

Page 47: Programming with Decision Procedures

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))}

Page 48: Programming with Decision Procedures

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.

Page 49: Programming with Decision Procedures

Verification System

Page 50: Programming with Decision Procedures

(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.

Page 51: Programming with Decision Procedures

Try it online http://lara.epfl.ch/leon/

Page 52: Programming with Decision Procedures

• 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

Page 53: Programming with Decision Procedures

“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.« »

Page 54: Programming with Decision Procedures

• 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.« »

Page 55: Programming with Decision Procedures

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.

Page 56: Programming with Decision Procedures

• 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

Page 57: Programming with Decision Procedures

Kaplan in Action

Page 58: Programming with Decision Procedures

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)

Page 59: Programming with Decision Procedures

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))))

Page 60: Programming with Decision Procedures

Language Integration

Page 61: Programming with Decision Procedures

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…

Page 62: Programming with Decision Procedures

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

Page 63: Programming with Decision Procedures

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)

Page 64: Programming with Decision Procedures

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

Page 65: Programming with Decision Procedures

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

Page 66: Programming with Decision Procedures

Constraint Language and Solver

Page 67: Programming with Decision Procedures

Constraint Language: Primitive Types

• Booleans

• Integers

• Sets

• Maps

||, &&, !, ==, !=

+, -, *, /, >, <, ==, !=

s(x), ++, --, **, Set(…), subsetOf

m(x), m.isDefinedAt(x), ++, Map(x -> i)

Page 68: Programming with Decision Procedures

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

Page 69: Programming with Decision Procedures

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())))

Page 70: Programming with Decision Procedures

Underlying Constraint Solver

Page 71: Programming with Decision Procedures

Applications and Conclusion

Page 72: Programming with Decision Procedures

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!

Page 73: Programming with Decision Procedures

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]

Page 74: Programming with Decision Procedures

• 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

Page 75: Programming with Decision Procedures

The Compilation Approach

Page 76: Programming with Decision Procedures

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

Page 77: Programming with Decision Procedures

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)…

Page 78: Programming with Decision Procedures

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

Page 79: Programming with Decision Procedures

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.

Page 80: Programming with Decision Procedures

• 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

Page 81: Programming with Decision Procedures

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”

Page 82: Programming with Decision Procedures

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

Page 83: Programming with Decision Procedures

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:

Page 84: Programming with Decision Procedures

((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)

Page 85: Programming with Decision Procedures

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)

Page 86: Programming with Decision Procedures

• 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

Page 87: Programming with Decision Procedures

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.

Page 88: Programming with Decision Procedures

Summary Slide

• Same as first, perhaps with paper references.• a

Page 89: Programming with Decision Procedures

Thank you.

Page 90: Programming with Decision Procedures

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

Page 91: Programming with Decision Procedures

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”

Page 92: Programming with Decision Procedures

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.

Page 93: Programming with Decision Procedures

Thank you.

Page 94: Programming with Decision Procedures

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.

Page 95: Programming with Decision Procedures

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

Page 96: Programming with Decision Procedures

Higher-Order Theorem Proving

• w/ N. Bjorner at MSR

Page 97: Programming with Decision Procedures

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

Page 98: Programming with Decision Procedures

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

Page 99: Programming with Decision Procedures

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

Page 100: Programming with Decision Procedures

Logical Variables

Page 101: Programming with Decision Procedures

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.

Page 102: Programming with Decision Procedures

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.

Page 103: Programming with Decision Procedures

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”

Page 104: Programming with Decision Procedures

Semantics with Constraint Store

t1 | μ1 →S t2 | μ2Hostt1 | ⟨ μ1, κ ⟩ → t2 | ⟨ μ2, κ ⟩t1 | ⟨ μ1, κ ⟩ → t2 | ⟨ μ2, κ ⟩

Constraint store: a formula over all logical variables.

Page 105: Programming with Decision Procedures

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 ⟩

Page 106: Programming with Decision Procedures

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.