scala z3 integrating smt and programming ali sinan köksal, viktor kuncak, philippe suter École...
TRANSCRIPT
ScalaZ3 Integrating SMT and Programming
Ali Sinan Köksal, Viktor Kuncak, Philippe SuterÉcole Polytechnique Fédérale de Lausanne
• Efficient SMT solver from Microsoft Research• Supports many theories through DPLL(T) and Nelson-
Oppen combination• SMT-LIB standard input format, as well as C, .NET, OCaml,
and Python bindings
• Blending of functional and object-oriented programming• Runs on the Java Virtual Machine (and .NET)
• Rich type system (generics, type classes, implicit conversions, etc.)
• Now used by over 100’000 developers (incl. Twitter, UBS, LinkedIn)
“Scalable programming language”
~$ ./demo
#include "z3.h"…Z3_config cfg = Z3_mk_config();Z3_set_param_value(cfg, "MODEL", "true");Z3_context z3 = Z3_mk_context(cfg);
Z3_sort intSort = Z3_mk_int_sort(z3);
Z3_func_decl f = Z3_mk_func_decl(z3, Z3_mk_string_symbol(z3,"f"), 1, &intSort, intSort);Z3_ast x = Z3_mk_const(z3, Z3_mk_string_symbol(z3,"x"), intSort);Z3_ast y = Z3_mk_const(z3, Z3_mk_string_symbol(z3,"y"), intSort);
Z3_ast ineq = Z3_mk_not(z3, Z3_mk_eq(z3, x, Z3_mk_app(z3, f, 1, &y)));
Z3_assert_cnstr(z3, ineq);
Z3_model m;if(Z3_check_and_get_model(z3, &m)) { printf("%s", Z3_model_to_string(z3, m));}
import z3.scala._…val cfg = new Z3Configcfg.setParamValue("MODEL", "true")val z3 = new Z3Context(cfg)
val intSort : Z3Sort = z3.mkIntSort
val f : Z3FuncDecl = z3.mkFuncDecl( z3.mkStringSymbol("f"), List(intSort), intSort)val x : Z3AST = z3.mkConst( z3.mkStringSymbol("x"), intSort)val y : Z3AST = z3.mkConst( z3.mkStringSymbol("y"), intSort)
val ineq : Z3AST = z3.mkNot( z3.mkEq(x, z3.mkApp(f, y)))
z3.assertCnstr(ineq)
z3.checkAndGetModel match { case (Some(true), m) ⇒ println(m) case _ ⇒ ;}
C and Scala side-by-side
def choose[A,B](p: (Val[A],Val[B])⇒Tree[BoolSort]) : (A,B)
def find[A,B](p: (Val[A],Val[B])⇒Tree[BoolSort]) : Option[(A,B)]
def findAll[A,B](p: (Val[A],Val[B])⇒Tree[BoolSort]) : Iterator[(A,B)]
Anatomy of an Inline Invocation
import z3.scala._import dsl._...choose((y: Val[Int⇒Int], x: Val[Int], y: Val[Int]) ⇒ !(x === f(y)))
Desired return types (actual Scala types).
Domain specific language of formulas resembles
Scala expressions.
Returned values are Scala values (including functions).
imports and Val[_]s only manifestations of
the library
for Comprehensions
for((x,y) ← findAll((x: Val[Int], y: Val[Int]) ⇒ x > 0 && y > x && x * 2 + y * 3 <= 40); if(isPrime(y)); z ← findAll((z: Val[Int]) ⇒ z * x === 3 * y * y)) 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 expression.
Filter: arbitrary boolean expression.
Generators: sequences, computed eagerly or lazily.
Implementation Aspects
class Z3AST(ptr : Long)
abstract class Tree[+T >: Bottom <: Top] { ... def <(other : Tree[_ <: IntSort]) : Tree[BoolSort] = ... ...}
Bottom
Top
IntSort BoolSort … SetSort
implicit def ast2Tree(ast : Z3AST) : Tree[Bottom]implicit def tree2AST(tree : Tree[_]) : Z3AST
• Automatic conversions between the two kinds.• Soft typing for the DSL Trees.
Basic representation:
Tree hierarchy as part of the DSL:
import z3.scala._…val cfg = new Z3Configcfg.setParamValue("MODEL", "true")val z3 = new Z3Context(cfg)
val intSort : Z3Sort = z3.mkIntSort
val f : Z3FuncDecl = z3.mkFuncDecl( z3.mkStringSymbol("f"), List(intSort), intSort)val x : Z3AST = z3.mkConst( z3.mkStringSymbol("x"), intSort)val y : Z3AST = z3.mkConst( z3.mkStringSymbol("y"), intSort)
val ineq : Z3AST = z3.mkNot( z3.mkEq(x, z3.mkApp(f, y)))
DSL in Action
import z3.scala._…val z3 = new Z3Context("MODEL“ -> true)
val intSort = z3.mkIntSort
val f = z3.mkFuncDecl( "f", intSort, intSort)
val x = z3.mkConst("x", intSort)
val y = z3.mkConst("y", intSort)
val ineq : Z3AST = !(x === f(y))
Z3 Sorts
Int BV[32]
Bool
Array[A,B]
…
Scala Types
Int Boolean
Set[A]A⇒B
…
• Users should be able to ignore the underlying representation:
• Different function calls to create constants and to retrieve the models:
ctx.getBool(m.eval(tree))ctx.getNumeralInt(m.eval(tree))m.getArrayValue(tree)...
m.evalAs[Boolean](tree)m.evalAs[Int](tree)m.evalAs[Int⇒Int](tree)
def evalAs[T](tree : Z3AST) : T
trait Evaluator[T] { def eval(model : Z3Model, tree : Z3AST) : T}
implicit object IntEvaluator extends Evaluator[Int] { def eval(model : Z3Model, tree : Z3AST) : Int = ...}
m.evalAs[Int](t) m.evalAs[Int](t)(IntEvaluator)
implicit def lift2Set[T : Evaluator] = new Evaluator[Set[T]] { def eval(model : Z3Model, tree : Z3AST) : Set[T] = ...}
m.evalAs[Set[Set[Int]]](t)
m.evalAs[...](t)(lift2Set(lift2Set(IntEvaluator)))
def evalAs[T : Evaluator](tree : Z3AST) : T
def evalAs[T](tree : Z3AST)(implicit e : Evaluator[T]) : T
@implicitNotFound(“No known model evaluator for type ${T}.”)
Procedural Attachments
import stringTheory._
val s = z3.mkConst(“s”, stringTheory.sort)
z3.assertCnstr(s === “world” || s === “moon”)z3.assertCnstr(evenLength(s))z3.check
> Some(true)
z3.assertCnstr(substr(concat(“hello”, s), “low”))z3.check
> Some(false)
val z3 = new Z3Context(“MODEL” -> true)val stringTheory = new ProceduralAttachment[String](z3) { val concat = function((s1,s2) ⇒ s1 + s2) val substr = predicate((s1,s2) ⇒ s2.contains(s1)) val evenLength = predicate(_.length % 2 == 0)}
Applications
Z3 extension for sets with cardinality constraintsP. Suter, R. Steiger, V. Kuncak, VMCAI 2011
MUNCH: Decision procedure for multisets and setsR. Piskac, V. Kuncak, IJCAR 2010
Leon: Verifier for functional programsP. Suter, A.S. Köksal, V. Kuncak, SAS 2011, http://lara.epfl.ch/leon/
Other users at ETH Zürich, KU Leuven
Kaplan: Constraint programming in ScalaAli Sinan Köksal’s Master Thesis
https://github.com/psuter/ScalaZ3
Availability
http://lara.epfl.ch/w/ScalaZ3
(Tested on Windows and Linux, on 32 and 64 bit architectures.)
Thank you.