program synthesis with sketching rastislav bodik uc berkeley
TRANSCRIPT
3
From Verification to Synthesis
Insight ImplementationMechanics
Validation
Done
Fix the mechanics
Fix the Insight
Synthesis
Our vision
What: Give the synthesis directly to programmersnot just to algorithm designers and compiler writers
How: Divide programming into insight and mechanicsHamming: 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
7
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
int[] mergeSort (int[] input, int n) {if ( n == 1 ) return input;return merge( mergeSort (input[0:n/2-1]),
mergeSort (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] ) result[i] = a[j++]; else result[i] = b[k++]; return result;}
8
Merge sort: first, by hand
looks simple to code, but there is a bug
9
Merge sort: corrected, by handint[] mergeSort (int[] input, int n) {
if ( n == 1 ) return input;return merge( mergeSort (input[0:n/2-1]),
mergeSort (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;}
10
Merge sort: sketchedint[] mergeSort (int[] input, int n) {
if ( n == 1 ) return input;return merge( mergeSort (input[0:n/2-1]),
mergeSort (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;}
hole
13
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;}
The spec: bubble sort
int[] mergeSort (int[] input, int n) {if ( n == 1 ) return input;return merge( mergeSort (input[0:n/2-1]),
mergeSort (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 ( expr(<,&&,||,!,-,[])(a, b, j, k, n, n/2
) ) result[i] = a[j++]; else result[i] = b[k++]; return result;}
14
Merge sort: sketched
hole
and can help scalabilityand can help scalability
programmer controls the synthesized codeprogrammer controls the synthesized code
15
Merge sort: synthesizedint[] mergeSort (int[] input, int n) {
if ( n == 1 ) return input;return merge( mergeSort (input[0:n/2-1]),
mergeSort (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;}
16
Deductive synthesis vs. Sketching
Spec
Spec
Axioms, Theorems, Rewrite rules
Deductive Synthesis [Burstall & Darlington ’76, … ]
Sketching
Implementation
=
Automated Validation
SketchImplementation
17
Example 2: a concurrent list data structure
The data structure:– linked list of Nodes– sorted by Node.key– with sentries at head and tail
The problem: implement a concurrent remove() method
c-∞ +∞ba
18
Thinking about the problem
Sequential remove ():
• 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
c-∞ +∞ba
20
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; } } if( cur.val == in ){ prev.next = cur.next; } unlock( {| cur(.next)? |} ); unlock( {| prev(.next)? |} );}
Under some conditions, lock and/or unlock memory locations.
And advance the sliding window.
And order the operations correctly.
Plausible conditions: equality/inequality of
“interesting” variables
Plausible memory locations:
“interesting” variables and fields
[Regular-expression generators: restricted regular
grammar of expression language]
21
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; lock(cur); } if( cur.val == in ){ prev.next = cur.next; } unlock( cur ); unlock( prev );}
SKETCH finds correct conditionsand expressions
SKETCH ordersthese statements
correctly
23
Language design goals
23
LearnableEmbed without confusion into an existing language
ExpressiveAllow programmer to express a range of insights about
holes
Induces a simple synthesis problemIdeally, permitting domain-independent synthesizers
24
SKETCH: two simple constructs
24
spec:
int foo (int x) { return x + x;}
sketch:
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
2828
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 + ??*x + ??
Primitive holes can be used synthesize arbitrary expressions, statements, …– we also can make these “generators” reusable
2929
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);
} }
3030
Synthesizing polynomials
int spec (int x) {return 2*x*x*x*x + 3*x*x*x + 7*x*x + 10;
}
int p (int x) implements spec {return (x+1)*(x+2)*poly(3,x);
}
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.
Notice the absence of
any meta-variables. The
generator poly() is an
ordinary function.
31
More Synthesis Constructs
Easy to add new constructs as syntactic sugar
reorder{ s1; s2; … ; sn; }
x = {| ( a | b | c )(.next)? |}
33
Candidate space
A sketch syntactically describes a set of candidate programs.– The ?? operator is modeled as a special input, called control:
Our goal is to find suitable control parameters – Can’t search naively as the candidate spaces are too large– 1sec/validation searching through 108 elements takes 3
years– our spaces are sometimes 10800
int f(int x) {… ?? … ?? …
}
int f(int x, int c1, c2) { … c1 … c2 …}
34
Sketch synthesis is constraint satisfaction
Synthesis reduces to solving this satisfiability problem – synthesized program is determined by c
Quantifier alternation is challenging. Our idea is to turn to inductive synthesis
34
spec(x) = sketch(x, c)c. x.
A E
37
CounterExample –Guided Inductive Synthesis
Inductive Synthesizer
buggy
candidate implementation
add a (bounded) counterexample input
succeed
fail
fail
observation set E
okverifier/checker
Your verifier/checker goes here
compute candidate implementation from concrete inputs.
The CEGIS algorithm:
Inductive synthesis step implemented with a SAT solver
4444log(C)
Number of counterexample vs. log(C)C = size of candidate space = exp(bits of controls)
C = 102400C = 102400
46
Selected benchmarks
Benchmark control bits solution time
AES 32769 bits 15 min
SSE Matrix Transpose 24 ints + 64 bits 20 min
CRC 8192 bits 13.5 min
Doubly Linked List Remove
60 bits < 1 sec
Enqueue 271 int & bit 1 min
16 bit Morton Numbers 332 int & bit 20 min
32 bit fast parity 45 bits 11 sec
Sort (bounded) 363 int & bit 3.5 min
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 3D 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
48
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
Solution: Trace Projection tP ⊳ 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
Trace on program P
Trace on program P’
49
Inductive Synthesis from TracesSequential programs
Concurrent programs
safe( Sk[c](xi) )c. xi in E.
A E
where E = {x1, x2, …, xk}
safe( tci ⊳ (Sk[c]) )c. tci in T.
A E
where T = {tc1, tc2, …, tck}
safe( (tci ⊳ Sk)[c] )c. tci in T.
A E
where T = {tc1, tc2, …, tck}
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 generation51
52
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)
Some History: Deductive SynthesisTwo 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 the theorem. Ex: KIDS, NuPrl, 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:
DFT4 = (DFT2 I2) T42 (I2 DFT2) L4
2
KIDS divide and conquer:
Dec(x0,x1,x2) O(x1,z1) O(x2,z2) Compose(z0,z1,z2) O(x0,z0)55
Some challenges in deductive synthesis
Domain theory – debugging: is the theory correct, complete?– generality: can it generate programs of interest?– accessibility to programmers: can one
broaden the 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
59
A stencil: spec
void sten1d (float[4,N] X) { for (int t=1; t<4; ++t) for (int i=1; i<N-1; ++i) X[t, i] = X[t-1, i-1] + X[t-1, i+1];}
it
60
Fast implementation
void sten1dSK (float[4,N] X) { assume ( N >= 3 ) for (int i= 0; i<4 ; ++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];}
it
61
void sten1dSK (float[4,N] X) { assume ( N >= 3 ); for (int i= 0; i<4 ; ++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];}
What are the hard fragments?
it
62
void sten1dSK (float[4,N] X) { assume ( N >= 3 ); for (int i= ; i<4 ; ++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];}
Sketch the hard fragments
it
63
The final sketch
void sten1dSK (float[4,N] X) implements sten1d { assume ( N >= 3 ); for (int i=linexpG(N, 4); i<linexpG(N, 4); ++i) for (int t=linexpG(N, 4, i); t<linexpG(N, 4, i); ++t) X[t, i-t] = X[t-1, i-1-t] + X[t-1, i+1-t];
for (int i=linexpG(N, 4); i<linexpG(N, 4); ++i) for (int t=linexpG(N, 4, i); t<linexpG(N, 4, i); ++t) X[t, i-t] = X[t-1, i-1-t] + X[t-1, i+1-t];
for (int i=linexpG(N, 4); i<linexpG(N, 4); ++i) for (int t=linexpG(N, 4, i); t<linexpG(N, 4, i); ++t) X[t, i-t] = X[t-1, i-1-t] + X[t-1, i+1-t];}
66
Concurrent synthesis
Counterexamples are multi-threaded traces not inputs
Problem: a trace is relevant only to the program it came from
Solution: Trace Projection tp ⊳ 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
Trace on program P
Trace on program P’
67
Inductive Synthesis from TracesSequentially
Concurrently
safe( Sk[c](xi) )c. xi in E.
A E
where E = {x1, x2, …, xk}
safe( tci ⊳ (Sk[c]) )c. tci in T.
A E
where T = {tc1, tc2, …, tck}
safe( (tci ⊳ Sk)[c] )c. tci in T.
A E
where T = {tc1, tc2, …, tck}
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
72
Concurrency
What is sketchin
g SKETCH Synthesis Algorithm
SKETCH Language Concurren
cyFuture Work
73
class Queue { QueueEntry prevHead = new QueueEntry(null); QueueEntry tail = prevHead;
void Enqueue(Object newobject) { Node tmp = null; newEntry = new QueueEntry(newobject); reorder{ tmp.next = newEntry ; tmp = AtomicSwap(tail , newEntry ); } }}
Works for concurrent programs tooEx: Concurrent Enqueue
using AtomicSwap
Object AtomicSwap(ref Object loc, Object entry) atomic { Object old = loc; loc = entry; return old;}
class Queue { QueueEntry prevHead = new QueueEntry(null); QueueEntry tail = prevHead;
void Enqueue(Object newobject) { Node tmp = null; newEntry = new QueueEntry(newobject);
tmp = AtomicSwap(tail , newEntry ); tmp.next = newEntry ;
}}
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
77
class Queue { QueueEntry prevHead = new QueueEntry(null); QueueEntry tail = prevHead;
void Enqueue(Object newobject) { Node tmp = null; newEntry = new QueueEntry(newobject); reorder{ tmp.next = newEntry ; tmp = AtomicSwap(tail , newEntry ); } }}
Concurrent programs
Ex: Concurrent Enqueue using AtomicSwap
Object AtomicSwap(ref Object loc, Object entry) atomic { Object old = loc; loc = entry; return old;}
class Queue { QueueEntry prevHead = new QueueEntry(null); QueueEntry tail = prevHead;
void Enqueue(Object newobject) { Node tmp = null; newEntry = new QueueEntry(newobject);
tmp = AtomicSwap(tail , newEntry ); tmp.next = newEntry ;
}}
prevHead tail newEntrytmp newEntrytailprevHead newEntry
78
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?
79
Representation pitfalls
x = 5;fork(2, i){ if(??) t = 5; int l = x; x = l + 1; if(?? && i == 0){ l = x; x = x + 1; }}assert x = x + 1 || x = x+2;
x = 5;fork(2, i){
int l = x; x = l + 1; if(i == 0){ l = x; x = l + 1; }}assert x = x + 1 || x = x+2;
s0:s1:s2:
s3:s4:
s1: l=x
s2: x=l + 1
s3: l=x
s4: x=l + 1
s1: l=x
s2: x=l + 1
Thread i=1 Thread i=2
1
1
2
2
1
1
80
Representation pitfalls
x = 5;fork(2, i){ if(??) t = 5; int l = x; x = l + 1; if(?? && i == 0){ l = x; x = x + 1; }}assert x = x + 1 || x = x+2;
s0:s1:s2:
s3:s4:
x = 5;fork(2, i){ t = 5; int l = x; x = l + 1; if( i == 0){ l = x; x = l + 1; }}assert x = x + 1 || x = x+2;
s0: t=5
s0: t=5
s1: l=x
s2: x=l + 1
s3: l=x
s4: x=l + 1
s1: l=x
s2: x=l + 1
Thread i=1 Thread i=2
1
1
2
2
1
1
81
Define observation in terms of dataflowObservation 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
82
We have synthesized…
Benchmark Observations
Solution Time
Lock Free linked list enqueue
2 540 sec.
Lock Free linked list dequeue
2 194 sec.
Herlihy’s hand-over-hand set remove()
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.
83
Future Work
What is sketchin
g SKETCH Synthesis Algorithm
SKETCH Language Concurren
cyFuture Work
84
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 insightspecialized synthesizer => domain specific insight
We’ve done this already for the domain of Stencils (PLDI 07)
85
Specialization through problem reduction
Encode Domain specific insight as a transformation
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
The key idea
spec = sketch(c)
T : (f: in out) (f’: in out)
86
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
87
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
We are left with– Set of witnesses– An UNSAT core
Spec(x) = Sk(x,c)c. x in E.A E
where E = {x1, x2, …, xk}
88
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?
89
Sketching for a 1,000,000 line programScenario:
“ You have a 1M 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