Program Synthesis with Sketching Rastislav Bodik UC Berkeley
Program Synthesis with Sketching Rastislav Bodik UC Berkeley
2
From Verification to Synthesis Done Insight Implementation Mechanics Validation Synthesis Fix the mechanics Fix the Insight 3
Two key problems How to communicate insight? How to synthesize the mechanics? 4
Our vision What: Give the synthesis directly to programmers not just to algorithm designers and compiler writers How: Divide programming into insight and mechanics Hamming: People should think. Machines should work. Corollaries: – Automatic: heed lessons from model checking vs. deductive verification – Don’t rely too much on axiomatizing a domain: algorithmic rather than deductive 5
Synthesis with Sketching 6
Key Observation Insight and mechanics are both reflected in the source code, and can often be distinguished. With sketching, the programmer ideally – writes only the code corresponding to insight – synthesizer produces the mechanics 7
Merge sort: first, by hand int[] merge. Sort (int[] input, int n) { if ( n == 1 ) return input; return merge( merge. Sort (input[0: n/2 -1]), merge. Sort (input[n/2: n-1]), n); } int[] merge (int[] a, int b[], int n) { int j=0; int k=0; for (int i = 0; i < n; i++) if ( a[j] < b[k] ) looks simple to code, result[i] = a[j++]; but there is a bug else result[i] = b[k++]; return result; } 8
Merge sort: corrected, by hand int[] merge. Sort (int[] input, int n) { if ( n == 1 ) return input; return merge( merge. Sort (input[0: n/2 -1]), merge. Sort (input[n/2: n-1]), n); } int[] merge (int[] a, int b[], int n) { int j=0; int k=0; for (int i = 0; i < n; i++) if ( j<n/2 && ( !(k<n-n/2) || a[j]<b[k]) ) result[i] = a[j++]; else result[i] = b[k++]; return result; } 9
Merge sort: sketched int[] merge. Sort (int[] input, int n) { if ( n == 1 ) return input; return merge( merge. Sort (input[0: n/2 -1]), merge. Sort (input[n/2: n-1]), n); } int[] merge (int[] a, int b[], int n) { int j=0; int k=0; for (int i = 0; i < n; i++) if ( j<n/2 && ( !(k<n-n/2) hole || a[j]<b[k]) ) result[i] = a[j++]; else result[i] = b[k++]; return result; } 10
The sketching experience spec + specification sketch implementation (completed sketch) 11
Sketching synthesis is constraint solving sketch(H) spec 12
The spec: bubble sort int[] sort (int[] input, int n) { for (int i=0; i<n; ++i) for (int j=i+1; j<n; ++j) if (input[j] < input[i]) swap(input, j, i); return input; } 13
Merge sort: sketched int[] merge. Sort (int[] input, int n) { if ( n == 1 ) return input; and can help return merge( merge. Sort (input[0: n/2 -1]), scalability merge. Sort (input[n/2: n-1]), n); programmer controls the synthesized } code (int[] a, int b[], int n) { int[] merge int j=0; int k=0; for (int i = 0; i < n; i++) if ( expr(<, &&, ||, !, -, [])(a, hole b, j, k, n, n/2 ) ) result[i] = a[j++]; else result[i] = b[k++]; return result; } 14
Merge sort: synthesized int[] merge. Sort (int[] input, int n) { if ( n == 1 ) return input; return merge( merge. Sort (input[0: n/2 -1]), merge. Sort (input[n/2: n-1]), n); } int[] merge (int[] a, int b[], int n) { int j=0; int k=0; for (int i = 0; i < n; i++) if ( j<n/2 && ( !(k<n-n/2) || a[j]<b[k]) ) result[i] = a[j++]; else result[i] = b[k++]; return result; } 15
Deductive synthesis vs. Sketching Deductive Synthesis [Burstall & Darlington ’ 76, … ] Axioms, Theorems, Rewrite rules Spec Implementation Sketching Automated Validation Spec = Implementation Sketch 16
Example 2: a concurrent list data structure The data structure: – linked list of Nodes – sorted by Node. key – with sentries at head and tail -∞ a b c +∞ The problem: implement a concurrent remove() method 17
Thinking about the problem Sequential remove (): -∞ a b c +∞ • Insight 1: for efficiency, use fine-grain locking – lock individual Nodes rather than the whole list • Insight 2: Maintain a sliding window with two locks 18
On to mechanics … ? -∞ a b c +∞ 19
Capture the insight in a sketch #define comp {| ((cur|prev)(. next)? | null) (== | !=) ((cur|prev)(. next)? | null) |} #define loc {| {cur | prev | tprev) (. next)? |} void Remove(int in){ Node cur = this. head, prev = null; lock({| cur(. next)? |}); while( cur. val < in){ Node tprev = prev; reorder { if(comp) lock( loc ); if(comp) unlock( loc ); prev = cur; cur = cur. next; Plausible conditions: Under some conditions, lock and/or equality/inequality of unlock memory locations. [Regular-expression generators: “interesting” variables restricted regular grammar of And advance the sliding window. expression language] Plausible memory locations: “interesting” and correctly. And order variables the operations fields } } if( cur. val == in ){ prev. next = cur. next; } unlock( {| cur(. next)? |} ); unlock( {| prev(. next)? |} ); } 20
SKETCH generates a correct program void Remove(int in){ Node cur = this. head, prev = null; lock( cur ); while( cur. val < in){ Node tprev = prev; if(prev != null){ unlock(prev); } prev = cur; cur = cur. next; SKETCH finds correct conditions and expressions SKETCH orders these statements correctly lock(cur); } if( cur. val == in ){ prev. next = cur. next; } unlock( cur ); unlock( prev ); } 21
The SKETCH Language 22
Language design goals Learnable Embed without confusion into an existing language Expressive Allow programmer to express a range of insights about holes Induces a simple synthesis problem Ideally, permitting domain-independent synthesizers 23
SKETCH: two simple constructs spec: sketch: int foo (int x) { return x + x; } int bar (int x) implements foo { return x << ? ? ; } result: int bar (int x) implements foo { return x << 1; } Assertions can also be used to state safety properties 24
Beyond synthesis of constants Sometimes the insight is “I want to complete the hole with an of particular syntactic form. ” – Array index expressions: A[ ? ? *i+? ? *j+? ? ] – Polynomial of degree 2 over x: ? ? *x*x + ? ? Primitive holes can be used synthesize arbitrary expressions, statements, … – we also can make these “generators” reusable 28
Reusable expression generators Following function synthesizes to one of a, b, a+b, a-b, a+b+a, …, a-a+b-b, … inline int expr(int a, int b){ // generator switch(? ? ) { case 0: return a; case 1: return b; case 2: return expr(a, b) + expr(a, b); case 3: return expr(a, b) - expr(a, b); } } 29
Synthesizing polynomials int spec (int x) { return 2*x*x + 3*x*x*x + 7*x*x + 10; } int p (int x) implements spec { return (x+1)*(x+2)*poly(3, x); } of e c sen s. The b a he riable t e c n Noti eta-va ly() is a m any rator po tion. c gene ary fun n ordi inline int poly(int n, int x) { if (n==0) return ? ? ; else return x * poly(n-1, x) + ? ? ; } Here, SKETCH performs polynomial division. Result of division is what poly(3, x) is synthesized into. 30
More Synthesis Constructs Easy to add new constructs as syntactic sugar reorder{ s 1; s 2; … ; sn; } x = {| ( a | b | c )(. next)? |} 31
Counterexample-Guided Inductive Synthesis (CEGIS) 32
Candidate space A sketch syntactically describes a set of candidate programs. – The ? ? operator is modeled as a special input, called control: int f(int x) { … ? ? … } int f(int x, int c 1, c 2) { … c 1 … c 2 … } Our goal is to find suitable control parameters – Can’t search naively as the candidate spaces are too large – 1 sec/validation searching through 108 elements takes 3 years – our spaces are sometimes 10800 33
Sketch synthesis is constraint satisfaction Synthesis reduces to solving this satisfiability problem – synthesized program is determined by c A c. x. spec(x) = sketch(x, c) E Quantifier alternation is challenging. Our idea is to turn to inductive synthesis 34
Counter. Example –Guided Inductive Synthesis The CEGIS algorithm: candidate implementation succeed Inductive Synthesizer compute candidate implementation from concrete inputs. verifier/checker fail buggy ok Your verifier/checker goes here fail observation set E add a (bounded) counterexample input Inductive synthesis step implemented with a SAT solver 37
Number of counterexample vs. log(C) C = size of candidate space = exp(bits of controls) 43
Number of counterexample vs. log(C) C = size of candidate space = exp(bits of controls) C = 102400 log(C) 44
Selected benchmarks Benchmark AES control bits solution time 32769 bits 15 min 24 ints + 64 bits 20 min 8192 bits 13. 5 min 60 bits < 1 sec Enqueue 271 int & bit 1 min 16 bit Morton Numbers 332 int & bit 20 min 45 bits 11 sec 363 int & bit 3. 5 min SSE Matrix Transpose CRC Doubly Linked List Remove 32 bit fast parity Sort (bounded) 46
Synthesis for Infinite Programs [PLDI 2007] By default, synthesizer finitizes the sketch, reducing to SAT – unrolls loops, recursion, and turns programs into circuits For a class of non-finite programs, we use semantic reduction – both spec and sketch are reduced into bounded SKETCH programs f(int[N]): int[N] f(int, int[4]): int – the reduction computes a symbolic slice of the program – without loss of precision: all holes are preserved; complete, sound Case study: stencils (structured grid computations) – synthesized complex 3 D stencil kernels [PLDI 2007] Admittedly, the reduction is a domain theory – but it’s a simple procedure, works for a whole class of programs – it embeds no insight about implementation tricks Plus, the reduction itself might be sketchable 47
Synthesis of concurrent programs [PLDI 2008] Counterexamples are multi-threaded traces, not inputs Problem: a trace is relevant only to the program it came from Trace on program P’ Solution: Trace Projection t. P ⊳ P’ Trace on program P Desired property: – if P’ shares the bug exposed in P by tp then tp ⊳ P’ should expose that bug too – allows inductive synthesis through constraint solving 48
Inductive Synthesis from Traces Sequential programs A c. xi in E. safe( Sk[c](xi) ) E where E = {x 1, x 2, …, xk} Concurrent programs A c. tci in T. safe( tci ⊳ (Sk[c]) ) E where T = {tc 1, tc 2, …, tck} A c. tci in T. safe( (tci ⊳ Sk)[c] ) where T = {tc 1, tc 2, …, tck} 49 E
Bibliography PLDI 2005: bitvector streaming programs – deductive synthesis with sketching in rewrite rules ASPLOS 2006: bounded programs (crypto etc) – algorithmic synthesis PLDI 2007: stencils (aka structured grids) – full behavioral verification – via reduction from unbounded domain to an bounded one PLDI 2008: concurrent programs – safety properties, bounded checking (bounded ops, threads) – using the SPIN model checker 50
Summary Programming the synthesizer: domain theory vs. sketch – hypothesis: insight is syntactic, exists in the program – sketch is a syntactic description of candidate space Algorithmic synthesis vs. deductive synthesis – akin to model checking vs. deductive verification – applies to: finite and finitized programs; programs with domain reductions CEGIS: counterexample-guided inductive synthesis – inductive synthesis simplifies the exists/forall problem – CEGIS takes advantage of efficient decision procedures, in both inductive synthesis and in counterexample generation 51
Credits Students – – – Gilad Arnold Chris Jones (Mozilla) Joel Galenson Lexin Shan Liviu Tancau (Google) Armando Solar-Lezama (MIT) Professors – Bob Brayton – Koushik Sen – Sanjit Seshia Other Collaborators – – – – – Satish Chandra (IBM) Kemal Ebcioglu (IBM) Alan Mischenko (UCB) Rodric Rabbah (IBM) Mooly Sagiv (Tel Aviv) Vijay Saraswat (IBM) Vivek Sarkar (Rice) Martin Vechev (IBM) Eran Yahav (IBM) 52
Some History: Deductive Synthesis Two kinds. In both, synthesis is a search for a suitable derivation. Transformational: domain theory transforms a specification program with a sequence of transformations, producing an optimized program. Ex: FFTW, Spiral, Denali. Prover-based: specification is a theorem; the synthesized program is obtained from the (constructive) proof of theorem. Ex: KIDS, Nu. Prl, Manna-Waldinger. Notable industrial successes: FFTW, SPIRAL, KIDS 54
Domain Theory Communicates to synthesizer the human insight about the domain. It must say enough to enable derivation of the program. Its development requires expertise and time. Examples of domain theory fragments: SPIRAL Cooley/Tukey FFT: DFT 4 = (DFT 2 I 2) T 42 (I 2 DFT 2) L 42 KIDS divide and conquer: Dec(x 0, x 1, x 2) O(x 1, z 1) O(x 2, z 2) Compose(z 0, z 1, z 2) O(x 0, z 0) 55
Some challenges in deductive synthesis Domain theory – debugging: is theory correct, complete? – generality: can it generate programs of interest? – accessibility to programmers: can one broaden theory? Control over synthesized code – how to coax the synthesizer to include a particular trick? 56
Algorithmic Synthesis? Instead of deriving a program, can we search for a correct program in a space of candidate programs? This is analogous to the model checking approach to verification: rather than obtaining a proof of correctness, model checking algorithmically explores the state space. All we need is a checker of program correctness and a description of the candidate space. In situations that mirror model checking (finite state; domain reductions), we sidestep the need for a domain theory. 58
A stencil: spec void sten 1 d (float[4, N] X) { t for (int t=1; t<4; ++t) i for (int i=1; i<N-1; ++i) X[t, i] = X[t-1, i-1] + X[t-1, i+1]; } 59
Fast implementation void sten 1 d. SK (float[4, N] X) { assume ( N >= 3 ) t for (int i= 0; i<4 ; ++i) i for (int t=1; t<i ; ++t) X[t, i-t] = X[t-1, i-1 -t] + X[t-1, i+1 -t]; for (int i=4; i<N; ++i) for (int t=1; t<4; ++t) X[t, i-t] = X[t-1, i-1 -t] + X[t-1, i+1 -t]; for (int i=N; i<N+4; ++i) for (int t=i-N+2; t<4; ++t) X[t, i-t] = X[t-1, i-1 -t] + X[t-1, i+1 -t]; } 60
What are the hard fragments? void sten 1 d. SK (float[4, N] X) { assume ( N >= 3 ); t for (int i= 0; i<4 ; ++i) i for (int t=1; t<i ; ++t) X[t, i-t] = X[t-1, i-1 -t] + X[t-1, i+1 -t]; for (int i=4; i<N; ++i) for (int t=1; t<4; ++t) X[t, i-t] = X[t-1, i-1 -t] + X[t-1, i+1 -t]; for (int i=N; i<N+4; ++i) for (int t=i-N+2; t<4; ++t) X[t, i-t] = X[t-1, i-1 -t] + X[t-1, i+1 -t]; } 61
Sketch the hard fragments void sten 1 d. SK (float[4, N] X) { assume ( N >= 3 ); t for (int i= ; i< 4 ; ++i) i for (int t= ; t< ; ++t) X[t, i-t] = X[t-1, i-1 -t] + X[t-1, i+1 -t]; for (int i= ; i< ; ++i) for (int t= ; t< ; ++t) X[t, i-t] = X[t-1, i-1 -t] + X[t-1, i+1 -t]; for (int i= ; i< 4; ++i) for (int t=i ; t< ; ++t) X[t, i-t] = X[t-1, i-1 -t] + X[t-1, i+1 -t]; } 62
The final sketch void sten 1 d. SK (float[4, N] X) implements sten 1 d { assume ( N >= 3 ); for (int i=linexp. G(N, 4); i<linexp. G(N, 4); ++i) for (int t=linexp. G(N, 4, i); t<linexp. G(N, 4, i); ++t) X[t, i-t] = X[t-1, i-1 -t] + X[t-1, i+1 -t]; } 63
Concurrent synthesis Counterexamples are multi-threaded traces not inputs Problem: a trace is relevant only to the program it came from Trace on program P’ Solution: Trace Projection tp ⊳ P’ Trace on program P Desired property: – if P’ shares the bug exposed in P by tp then tp ⊳ P’ should expose that bug too – allows inductive synthesis through constraint solving 66
Inductive Synthesis from Traces Sequentially A c. xi in E. safe( Sk[c](xi) ) E where E = {x 1, x 2, …, xk} Concurrently A c. tci in T. safe( tci ⊳ (Sk[c]) ) E where T = {tc 1, tc 2, …, tck} A c. tci in T. safe( (tci ⊳ Sk)[c] ) where T = {tc 1, tc 2, …, tck} 67 E
Bibliography PLDI 2005: bitvector streaming programs – deductive synthesis with sketching in rewrite rules ASPLOS 2006: bounded programs (crypto etc) – algorithmic synthesis PLDI 2007: stencils (aka structured grids) – full behavioral verification – via reduction from unbounded domain to an bounded one PLDI 2008: concurrent programs – safety properties, bounded checking (bounded ops, threads) – using the SPIN model checker 69
Summary Algorithmic synthesis vs. deductive synthesis – akin to model checking vs. deductive verification – applies to: finite and finitized programs; unbounded programs with domain reductions Programming the synthesizer: domain theory vs. sketch – hypothesis: insight is syntactic, exists in the program – sketch is a syntactic description of candidate space CEGIS: counterexample-guided inductive synthesis – inductive synthesis simplifies the exists/forall problem – CEGIS takes advantage of efficient decision procedures, in both inductive synthesis and in counterexample creation 70
The two key problems, still open How to communicate insight to a synthesizer How to use a verifier/checker for synthesis 71
Concurrency SKETCH Language What is sketching Concurrency Future Work SKETCH Synthesis Algorithm 72
Works for concurrent programs too Ex: Concurrent Enqueue using Atomic. Swap Object Atomic. Swap(ref Object loc, Object entry) atomic { Object old = loc; loc = entry; return old; } class Queue { Queue. Entry prev. Head = new Queue. Entry(null); Queue. Entry tail = prev. Head; } void Enqueue(Object newobject) { Node tmp = null; new. Entry = new Queue. Entry(newobject); reorder{ tmp = Atomic. Swap(tail tmp. next = new. Entry, ; new. Entry ); tmp. next = new. Entry ; tmp = Atomic. Swap(tail , new. Entry ); } } 73
Concurrency (PLDI 2008) How to use traces (schedules) as observations? For effective pruning, counterexample should apply to all candidates as much as possible But candidates have different set of atomic statements Solution: program representation that preserves ordering of statements common to a trace (or aborts the trace) 74
Summary ASPLOS 2006: bounded programs (crypto etc) – full behavioral verification PLDI 2007: stencils (aka structured grids) – full behavioral verification – via reduction from unbounded domain to an bounded one PLDI 2008: concurrent programs – safety properties, bounded checking (bounded ops, threads) – using SPIN 75
Future work • Dynamic synthesis – CUTE as checker – a new dynamic synthesizer • Replace counterexample-guided inductive synthesis with abstract interpreter – sketching and synthesis of abstractions? 76
Concurrent programs Ex: Concurrent Enqueue using Atomic. Swap Object Atomic. Swap(ref Object loc, Object entry) atomic { Object old = loc; loc = entry; return old; } class Queue { Queue. Entry prev. Head = new Queue. Entry(null); Queue. Entry tail = prev. Head; } void Enqueue(Object newobject) { Node tmp = null; new. Entry = new Queue. Entry(newobject); reorder{ tmp = Atomic. Swap(tail tmp. next = new. Entry, ; new. Entry ); tmp. next = new. Entry ; tmp = Atomic. Swap(tail , new. Entry ); } } prev. Head tmp tail new. Entry 77
Generalization to Parallelism Output now dependent on thread interleaving – Make interleaving schedule part of the observations – Most verifiers can provide a counterexample trace Using the observations becomes harder – Schedule generated as witness for a given candidate – How do we use it to rule out other incorrect candidates? 78
Representation pitfalls Thread i=1 x = 5; fork(2, i){ s 0: if(? ? ) t = 5; s 1: int l = x; s 2: x = l + 1; if(? ? == if(i &&0){ i == 0){ s 3: l = x; s 4: x = lx++1; 1; } } assert x = x + 1 || x = x+2; 1 s 1: l=x 1 s 2: x=l + 1 Thread i=2 2 s 1: l=x 2 s 2: x=l + 1 1 s 3: l=x 1 s 4: x=l + 1 79
Representation pitfalls Thread i=1 x = 5; fork(2, i){ s 0: tif(? ? ) = 5; t = 5; s 1: int l = x; s 2: x = l + 1; if(? ? if( i == && 0){ i == 0){ s 3: l = x; s 4: x = lx++1; 1; } } assert x = x + 1 || x = x+2; 1 s 0: t=5 1 s 1: l=x Thread i=2 2 s 0: t=5 2 s 1: l=x 1 s 2: x=l + 1 1 s 3: l=x s 4: x=l + 1 s 2: x=l + 1 80
Define observation in terms of dataflow Observation must eliminate all candidates with the same bug Identify dataflow that lead to assertion failure – the dataflow defines the bug Observation defined in terms of buggy dataflow 81
We have synthesized… Benchmark Lock Free linked list enqueue Observations Solution Time 2 540 sec. Lock Free linked list dequeue Herlihy’s hand-overhand set remove() 2 194 sec. 5 325 sec. doubly linked list add() 2 245 sec. – Most of the time was spent on the final verification. – Expect these numbers to be much better in the final PLDI paper. 82
Future Work SKETCH Language What is sketching Concurrency Future Work SKETCH Synthesis Algorithm 83
Domain Specific Insight Sketching allows users to provide their own insight – insight is specific to individual program What if we have general insight about a specific domain – can we incorporate it into the solver – maintain the ability to write sketches sketch => instance specific insight specialized synthesizer => domain specific insight We’ve done this already for the domain of Stencils (PLDI 07) 84
The key idea Specialization through problem reduction spec = sketch(c) Encode Domain specific insight as a transformation T : (f: in out) (f’: in out) Such that – T[spec] = T[sketch](c) spec = sketch(c) for all c – T[spec] = T[sketch](c) is a simpler problem to solve • for stencils T[spec] and T[sketc] don’t have loops • they are easier to handle by the SAT based verifier 85
Future work: Generalize Reduction Strategy worked great for stencils Can we generalize it to other domains – sketches for advanced architectures (CELL, GPU) – more scientific computation Can we make it more flexible – can we sketch these reductions – need a formal framework for reasoning about them 86
Programmer Feedback We haven’t solved the problem of programmer feedback – What happens when the insight is wrong – Can’t debug like a regular program A c. x in E. Spec(x) = Sk(x, c) where E = {x 1, x 2, …, xk} E We are left with – Set of witnesses – An UNSAT core 87
Programmer Feedback Can we minimize the set of witnesses? Can we use the UNSAT core to explain error to the human? What kind of feedback would be more useful for a human? 88
Sketching for a 1, 000 line program Scenario: “ You have a 1 M line program, and there is one routine that modifies the heap and which you have to rewrite so its more efficient, but the program must still work” Can we sketch such modifications How do we derive the right specification How do we handle the rest of the program without analyzing all of it 89
Questions?
- Slides: 74