Next Steps in Partial Program Synthesis
Rastislav Bodik
Shaon Barman, Joel Galenson, Casey Rodarmor, Nicholas TungSatish Chandra*, Doug Kimelman*, Emina Torlak*
University of California, Berkeley *IBM Research
ParLab Retreat, Jan 2010
How should synthesis help?Program synthesis moves formal systems into early stages of program development.
Verification is a back-end activity: quality assurance.Synthesis is a front-end activity: ???.
Synthesis: find problem decomposition & invariants.
Algorithms are too diverse to be encoded in a library domain theory. Which means that the programmer must be able to program the synthesizer.
2
Partial Programs
originated in aLisp [Andre, Russell, et al, 2002]
if (choose) collect woodelse build barracks…switch (choose):North: go NorthSouth: go South
3
Synthesis from Partial Programs
Part. program P: defines a space of candidate programsA parameter c determines the candidate P[c]
Synthesizer picks a candidate that meets the spec: parameter c . inputs x . safe P[c](x)
Examples: P1: assert x << ?? == x*2
P2: for (i = ?? to linExpr(N,I,??)) …
4
5
Synthesizer: CEGIS
Inductive Synthesizer
buggy
candidate implementation
add a (bounded) counterexample input
SAT
UNSAT
fail
observation set E
okverifier/checker
Your verifier/checker goes here
compute candidate implementation from concrete inputs.
The CounterExample –Guided Inductive Synthesis algorithm:
Inductive synthesis step implemented with a SAT solver
6
Synthesizer: Convergence Testing
Inductive Synthesizer
buggy
Two candidate implementations c1, c2 + distinguishing input x c1(x)≠c2(x)
add an input-output pair (x,spec(x))
fail
observation set E
Evaluate spec on x compute candidate
implementation from concrete inputs.
[Jha, Gulwani, Seshia, Tiwari, ICSE 2010]
a correct candidate succeed
Partial programs: benefits
Often conveys insight, decoupling it from mechanics
Replaces rewrite rules: says how the program should look like, not how to derive it
Which allows programmers to program the synthesizer
7
Who writes partial programs
programmers, one p.program per problem– sketch [Solar-Lezama, Bodik, Seshia ASPLOS 2006 ]
– skeleton [Srivastava, Foster, Gulwani POPL 2010]
library writers, one for a class of problems– target language [Itzhaky, Gulwani, Immerman, Sagiv,
SPLASH 2010]
generated from program being repaired– program repair with genetic programming
[Weimer et al, 2009]
8
Partial programs: limitations
Cannot encode vague insight.
Sometimes to come up with the partial program is as hard as coming up with the full program
Q1: How to free ourselves from the syntactic bind?
Q2: How to entirely hide the partial program?9
What's your memory of Red-Black Tree?
11
left_rotate( Tree T, node x ) { node y; y = x->right; /* Turn y's left sub-tree into x's right sub-tree */ x->right = y->left; if ( y->left != NULL ) y->left->parent = x; /* y's new parent was x's parent */ y->parent = x->parent; /* Set the parent to point to y instead of x */ /* First see whether we're at the root */ if ( x->parent == NULL ) T->root = y; else if ( x == (x->parent)->left ) /* x was on the left of its parent */ x->parent->left = y; else /* x must have been on the right */ x->parent->right = y; /* Finally, put x on y's left */ y->left = x; x->parent = y; }
left_rotate( Tree T, node x ) { node y; y = x->right; /* Turn y's left sub-tree into x's right sub-tree */ x->right = y->left; if ( y->left != NULL ) y->left->parent = x; /* y's new parent was x's parent */ y->parent = x->parent; /* Set the parent to point to y instead of x */ /* First see whether we're at the root */ if ( x->parent == NULL ) T->root = y; else if ( x == (x->parent)->left ) /* x was on the left of its parent */ x->parent->left = y; else /* x must have been on the right */ x->parent->right = y; /* Finally, put x on y's left */ y->left = x; x->parent = y; }
http://www.cs.auckland.ac.nz/software/AlgAnim/red_black.html
Programmers often think with examplesThey often design algorithms by devising and studying examples demonstrating steps of algorithm at hand.
If only the programmer could ask for a demonstration of the desired algorithm!
The demonstration (a trace) would reveal the insight.
Could an executable oracle demonstrate it for him?
13
Demonstrations by an oracle
Imagine we want to know whether to reverse a list:
We ask an oracle for a demonstration:
We then mimic the oracle with a deterministic program.
How do we instruct the oracle what to demonstrate?
11 22 33 33 22 11?
11 22 33
An angelic programOperator !! evaluates to a value yielding a safe trace.
reverse(list) {while (!!) {
!!(Node).next = !!(Node)}reversedList = !!(Node)assert reversedList is reversal of listreturn reversedList
}
Each trace of an angelic program is a demonstration.
a Boolean value
pointer to an existing object of type Node
correctness check
Angelic choice
Angelic nondeterminism embedded into Scala.
Oracle makes an angelic (clairvoyant) choice.
!!(S) evaluates to a value chosen from set S such that the execution terminates without violating an assertion
We developed two implementations of oracle:– parallel backtracking– SAT solver (reduction to SKETCH language)
16
First case study
Design DFS traversal that does not use a stack.
Used in garbage collection: when out of memory, you cannot ask for O(N) memory to mark reachable nodes
We want DFS that uses O(1) memory.
25
Depth-first search with explicit stackvroot = new Node(g.root)push(vroot); current = g.root
while (current != vroot) {if (!current.visited) current.visited = trueif (current has unvisited children) {
current.idx := index of first unvisited childchild = current.children[current.idx]push(current)current = child
} else { current = pop()
}
34
Node
children
idx
Parasitic Stack
Borrows storage from its host (the graph)accesses the host graph via pointers present in traversal
code
A two-part interface: stack: usual push and pop semanticsparasitic channel: for borrowing/returning storage
push(x,(node1,node2,…)) stack can (try to) borrow fields in nodei
pop(node1,node2,…) value nodei may be handy in returning storage
Parasitic stack expresses an optimization ideaBut can DSW be modularized this way? Angels will tell us.35
Replace regular stack with parasitic stack
vroot = new Node(root)push(null); current = vroot
while (current != vroot) {if (!current.visited) current.visited = trueif (current has unvisited children) {
current.idx := index of first unvisited childchild = current.children[current.idx]push(current, (current, child))current = child
} else { current = pop((current))
}
36
Node
children
idx
children
idx
children
idx
Angels perform deep global reasoningWhich location to borrow?
traversal must not need it until it is returned
How to restore the value in the borrowed location?the stack does not have enough locations to remember
it
How to use the borrowed location? it must implement a stack
Angels will clairvoyantly made these decisions for us – in principle, human could set up this parasitic
“wiring”, too, but we failed without the help of the angels
37
ParasiticStack.pushclass ParasiticStack { var e // allow ourselves one extra storage location
push(x, nodes) { // borrow memory location n.children[c] n = !!(nodes) c = !!(0 until n.children.length)
// value in the borrowed location; will need to be restoredv = n.children[c]
// we are holding 4 values but have only 2 memory locations// select which 2 values to remember, and wheree, n.children[c] = angelicallyPermute(x, n, v, e)
}
38
ParasiticStack.poppop(values) {
// ask the angel which location we borrowed at time of pushn = !!(e, values) c = !!(0 until n.children.length)
// v is the value stored in the borrowed locationv = n.children[c]
// (1) select return value// (2) restore value in the borrowed location// (3) update the extra location e
r, n.children[c], e = angelicallyPermute(n,v,e,values)
return r}
39
Let's refine the angelic program class ParasiticStack {
var e : Node push(x, nodes) {
n = !!(nodes) c = !!(0 until n.children.length)e, n.children[c] = angelicallyPermute(x,n,v,e)
} pop(values) {
n = !!(e, values) c = !!(0 until n.children.length)v = n.children[c]r, n.children[c],e = angelicallyPermute(n,v,e,values)return r
} }
40
First we observe what these angels do
class ParasiticStack {var e : Node push(x, nodes) {
n = !!(nodes) c = !!(0 until n.children.length)e, n.children[c] = angelicallyPermute(x,n,v,e)
} pop(values) {
n = !!(e, values) c = !!(0 until n.children.length)v = n.children[c]r, n.children[c],e = angelicallyPermute(n,v,e,values)return r
} }
41
Refinement #1class ParasiticStack {
var e : Node push(x, nodes) {
n = !!(nodes) c = !!(0 until n.children.length)e, n.children[c] = x, e
} pop(values) {
n = ec = !!(0 until n.children.length)v = n.children[c]r, n.children[c],e = e, values[0], vreturn r
} }
42
Refinement #1class ParasiticStack {
var e : Node push(x, nodes) {
n = !!(nodes) c = !!(0 until n.children.length)e, n.children[c] = x, e
} pop(values) {
n = ec = !!(0 until n.children.length)v = n.children[c]r, n.children[c],e = e, values[0], vreturn r
} }
43
Refinement #2class ParasiticStack {
var e : Node push(x, nodes) {
n = nodes[0]c = !!(0 until n.children.length)e, n.children[c] = x, e
} pop(values) {
n = ec = !!(0 until n.children.length)v = n.children[c]r, n.children[c],e = e, values[0], vreturn r
} }
44
Refinement #2class ParasiticStack {
var e : Node push(x, nodes) {
n = nodes[0]c = !!(0 until n.children.length)e, n.children[c] = x, e
} pop(values) {
n = ec = !!(0 until n.children.length)v = n.children[c]r, n.children[c],e = e, values[0], vreturn r
} }
45
Refinement #2class ParasiticStack {
var e : Node push(x, nodes) { invariant: c == n.idx
n = nodes[0]c = !!(0 until n.children.length)e, n.children[c] = x, e
} pop(values) {
n = ec = !!(0 until n.children.length)v = n.children[c]r, n.children[c],e = e, values[0], vreturn r
} }
46
Final refinementclass ParasiticStack {
var e : Node push(x, nodes) {
n = nodes[0]
e, n.children[n.idx] = x, e } pop(values) {
n = e
v = n.children[n.idx]r, n.children[n.idx],e = e, values[0], vreturn r
} }
47
We have derived Deutsch-Schorr-Waite
Marks reachable graph nodes – using constant space
Uses pointer reversal – implicit backtracking structure– encoded by rewriting child
pointers
48
Problem
Sometimes too many safe traces
Not all of them follow a plausible algorithm
They abuse clairvoyance: one angel destroys a data structure “knowing” that another will fix it.
Idea: summarize traces with angelic entanglement
50
Entanglement-refinement relationship
51
all safe tracesall safe traces
refinement 2refinement 2
refinement 1refinement 1
partition of angelspartition of angels
refinement 3refinement 3
finer partition
Bipartite Graph Classifier
root.visited = trueroot.pol = 0for (j <- 0 to numedges-1) {
val e : Edge = !!(Edge)assert e.src.visitedassert not e.traversede.dest.pol = !!(0,1)e.dest.visited = truee.traversed = trueassert f . f.traversed f.src.pol ≠ f.dest.pol
}
!!(Edge) !!(Edge) !!(Edge) !!(Edge) !!(Edge) !!(Edge) !!(Edge)
!!(0,1)1
!!(0,1)1
!!(0,1)1
!!(0,1)0
!!(0,1)0
!!(0,1)0
!!(0,1)1
Idea: Compilers based on synthesisClassical compiler: a rewriter based on
– legality analysis– optimizing transformation
Synthesis compiler: source program partial program
loc = E if (??) loc = compile(E) else send(loc,compile(E))
54
Summary
Partial Programs amenable to inductive synthesis
Interactive angelic synthesis frees from syntactic bind
Synthesis-backed compilers may package synthesis
55