Functional SMT Solving with Z3 and Racket
Total Page:16
File Type:pdf, Size:1020Kb
Functional SMT Solving with Z3 and Racket Siddharth Agarwal∗y Amey Karkarey [email protected] [email protected] ∗Facebook Inc, yDepartment of Computer Science & Engineering Menlo Park, CA, USA Indian Institute of Technology Kanpur, India Abstract—Satisfiability Modulo Theories (SMT) solvers are can attack range from simple puzzles like Sudoku and n- powerful tools that can quickly solve complex constraints queens, to planning and scheduling, program analysis [8], involving Booleans, integers, first-order logic predicates, lists, whitebox fuzz testing [9] and bounded model checking [10]. and other data types. They have a vast number of potential Yet SMT solvers are only used by a small number of experts. It applications, from constraint solving to program analysis and isn’t hard to see why: the standard way for programs to interact verification. However, they are so complex to use that their power with SMT solvers like Z3 [4], Yices [5] and CVC3 [11] is via is inaccessible to all but experts in the field. We present an attempt to make using SMT solvers simpler by integrating the powerful but relatively arcane C APIs that require the users Z3 solver into a host language, Racket. The system defines a to know the particular solver’s internals. For example, here programmer’s interface in Racket that makes it easy to harness is a C program that asks Z3 whether the simple proposition the power of Z3 to discover solutions to logical constraints. The p ^ :p is satisfiable. interface, although in Racket, retains the structure and brevity Z3_config cfg = Z3_mk_config(); of the SMT-LIB format. This system is expected to be useful for Z3_context ctx = Z3_mk_context(cfg); a wide variety of applications, from simple constraint solving Z3_del_config(cfg); to writing tools for debugging, verification, and automatic test Z3_sort bool_srt = Z3_mk_bool_sort(ctx); generation for functional programs. Z3_symbol sym_p = Z3_mk_int_symbol(ctx, 0); I. INTRODUCTION Z3_ast p = Z3_mk_const(ctx, sym_p, bool_srt) The Boolean satisfiability or SAT problem asks: Given ; a Boolean formula with a set of variables in it, is there a Z3_ast not_p = Z3_mk_not(ctx, p); way to assign each variable a value such that the formula becomes true? The SAT problem is one of the cornerstones Z3_ast args[2] = {p, not_p}; Z3_ast conjecture = Z3_mk_and(ctx, 2, args); of computer science, with enormous theoretical and practical Z3_assert_cnstr(ctx, conjecture); implications. Indeed, it was the very first problem to be proved NP-complete [1]. Yet, interest in efficiently solving so-called Z3_lbool sat = Z3_check(ctx); “natural” or “real-world” instances of SAT has remained. This Z3_del_context(ctx); is at least partly because a large number of practical problems return sat; are also NP-complete and can be reduced to SAT [2]. Typically, program analysis tools that used SAT solvers Simultaneously, most SMT solvers also feature interaction would have to find a way to translate variables found in via the standard input language SMT-LIB [12]. SMT-LIB is programs to Boolean ones. For example, a 32-bit integer significantly easier to use in isolation. The same program in could be encoded as a set of 32 Boolean variables1. It was SMT-LIB would look something like soon realized that pushing this step into the SAT solver would ; Declare a Boolean variable help. Since users would still be asking whether formulas were (declare-fun p () Bool) satisfiable, except with variables from more complex domains ; Try to find a contradiction or theories, this approach was dubbed Satisfiability Modulo (assert (and p (not p))) Theories (SMT). Several popular SMT solvers have been (check-sat) developed : Z3 [4], Yices [5] and CVC4 [6] to name a few. ; Prints "unsat", meaning "unsatisfiable" These solvers let programmers specify constraints over However, the SMT-LIB interfaces are generally hard to Booleans, integers, pure functions and other types, and either use directly from programs and often not as full-featured or come up with assignments that satisfy these constraints, or, if extensible as corresponding C APIs2. Importantly, it is difficult possible, a proof that the constraints aren’t satisfiable. Over to write programs that interact with the solver in some way, the last few years, SMT solvers using DPLL(T) [7] and other for example by adding assertions based on generated models. frameworks have come into their own and can solve a wide This makes it difficult to build new abstractions to enhance variety of problems using efficient heuristics. Problems they functionality. 1It is also possible to represent integers as Boolean variables via predicate 2Z3, for instance, supports plugging in external theories via the C API, abstraction [3], which is more efficient but potentially loses information. but not via the textual SMT-LIB interface. 978-1-4673-6292-4/13 c 2013 IEEE 15 FormaliSE 2013, San Francisco, CA, USA Accepted for publication by IEEE. c 2013 IEEE. Personal use of this material is permitted. Permission from IEEE must be obtained for all other uses, in any current or future media, including reprinting/ republishing this material for advertising or promotional purposes, creating new collective works, for resale or redistribution to servers or lists, or reuse of any copyrighted component of this work in other works. To overcome these difficulties, we decided to implement (if (eq? sat ’sat) an SMT-LIB-like interface to Z3 in a way that allowed ; Retrieve the values from the model for the same power as the C interface while appearing (for/list ([x (in-range 0 81)]) naturally integrated into a host language. Since SMT-LIB (smt:eval (select/s sudoku-grid x))) is s-expression-based, for the host language a Lisp dialect was #f))) a natural choice. We chose Racket [13] for our implementation, z3.rkt, because of its extensive facilities for implementing Here we omit a couple of function definitions: new languages [14], not just for the interface to the solver, add-sudoku-grid-rules asserts the standard Sudoku but also for the resulting tools that the solver would make grid rules, and add-grid reads a partially filled grid in a possible. particular format and creates assertions based on it. We note that the function (select/s arr x) retrieves the value Using this system, the program above becomes almost as at x from the array arr, and that this can be used to add brief as the SMT-LIB version. constraints on the array (for instance, (smt:assert (=/s (smt:with-context (select/s arr x) y))). We also note that if a set of (smt:new-context) constraints is satisfiable, Z3 can generate a model showing (smt:declare-fun p () Bool) this; values can be extracted out of this model using the (smt:assert (and/s p (not/s p))) smt:eval command. (smt:check-sat)) However, simply finding a solution isn’t enough for a good Sudoku solver: it must also verify that there aren’t any other It is important to note that we are neither increasing the solutions. The usual way to do that for a constraint solver is power of the Z3 SMT solver, nor adding any new features by retrieving a generated model, adding assertions such that to it. We are providing a new interface in Racket so that the this model cannot be generated again, and then asking the solver can be used from within the Racket language with solver whether the system of assertions is still satisfiable. If much ease. This itself is an interesting, challenging and useful it is, a second solution exists and the puzzle is considered task as the rest of the paper demonstrates. invalid. II. INTERACTIVE SMT SOLVING In such situations, the interactivity offered by z3.rkt becomes useful: it lets the programmer add dynamically To demonstrate the value in integrating a language with an discovered constraints on the fly. The last part of the program SMT solver, we turn our attention to a pair of classic logical then becomes puzzles. .. if (eq? sat ’sat) A. Sudoku ; Make sure no other solution exists We first turn our attention to a problem that demonstrates (let ([result-grid how the interaction of a language with an SMT solver is (for/list ([x (in-range 0 81)]) useful. A Sudoku puzzle asks the player to complete a partially (smt:eval (select/s sudoku-grid x)))]) ; Assert that we want a new solution pre-filled 9×9 grid with the numbers 1 through 9 such that no ; by asserting (not <current solution>) row, column, or 3×3 box has two instances of a number. This (smt:assert is a classic constraint satisfaction problem, and any constraint (not/s solver can handle it with ease. (apply and/s z3.rkt (for/list A Racket program using to solve Sudoku would ([(x i) (in-indexed result-grid)]) look like the following: (=/s (select/s sudoku-grid i) x))) (define (solve-sudoku grid) )) (smt:with-context (if (eq? (smt:check-sat) ’sat) (smt:new-context) #f ; Multiple solutions ; Declare a scalar datatype (finite domain result-grid)) ; type) with 9 entries #f))) (smt:declare-datatypes () ((Sudoku S1 S2 S3 S4 S5 S6 S7 S8 S9))) This part can even be abstracted out into a function that ; Represent the grid as an array from returns a lazily-generated sequence of satisfying assignments ; integers to this type (smt:declare-fun sudoku-grid () for any given set of constraints. (Array Int Sudoku)) ; Assert the standard grid rules B. Number Mind ; (row, column, box) (add-sudoku-grid-rules) The deductive game Bulls and Cows, commercialized as ; Add pre-filled entries Master Mind [15], is popular all around the world. The rules (add-grid grid) may vary slightly, but their essence stays the same: Two players (define sat (smt:check-sat)) play the game.