400 likes | 419 Views
Next Steps in Partial Program Synthesis. Rastislav Bodik Shaon Barman, Joel Galenson, Casey Rodarmor, Nicholas Tung Satish Chandra*, Doug Kimelman *, Emina Torlak* University of California, Berkeley *IBM Research ParLab Retreat, Jan 2010. How should synthesis help?.
E N D
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.
Partial Programs originated in aLisp [Andre, Russell, et al, 2002] if (choose) collect wood else build barracks … switch (choose): North: go North South: go South
Synthesis from Partial Programs Part. program P: defines a space of candidate programs A parameter c determines the candidate P[c] Synthesizer picks a candidate that meets the spec: parameter c . inputs x . safeP[c](x) Examples: P1: assert x << ?? == x*2 P2: for (i = ?? to linExpr(N,I,??)) …
verifier/checker Your verifier/checker goes here Synthesizer: CEGIS The CounterExample –Guided Inductive Synthesis algorithm: candidate implementation SAT • Inductive Synthesizer compute candidate implementation from concrete inputs. UNSAT ok buggy fail observation set E add a (bounded) counterexample input Inductive synthesis step implemented with a SAT solver
Synthesizer: Convergence Testing succeed a correct candidate Two candidate implementations c1, c2 + distinguishing input x c1(x)≠c2(x) • Inductive Synthesizer Evaluate spec on x compute candidate implementation from concrete inputs. fail buggy observation set E add an input-output pair (x,spec(x)) [Jha, Gulwani, Seshia, Tiwari, ICSE 2010]
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
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]
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?
What's your memory of Red-Black Tree? 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 examples They 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?
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? ? 1 2 3 3 2 1 1 2 3
An angelic program • Operator !! evaluates to a value yielding a safe trace. • reverse(list) { • while (!!) { • !!(Node).next =!!(Node) • } • reversedList =!!(Node) • assert reversedList is reversal of list • return 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)
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.
Depth-first search with explicit stack vroot = new Node(g.root) push(vroot); current = g.root while (current != vroot) { if (!current.visited) current.visited = true if (current has unvisited children) { current.idx := index of first unvisited child child = current.children[current.idx] push(current) current = child } else { current = pop() } Node idx children
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 semantics parasitic channel: for borrowing/returning storage push(x,(node1,node2,…)) stack can (try to) borrow fields in nodei pop(node1,node2,…)value nodeimay be handy in returning storage Parasitic stack expresses an optimization idea But can DSW be modularized this way? Angels will tell us.
Replace regular stack with parasitic stack vroot = new Node(root) push(null); current = vroot while (current != vroot) { if (!current.visited) current.visited = true if (current has unvisited children) { current.idx := index of first unvisited child child = current.children[current.idx] push(current, (current, child)) current = child } else { current = pop((current)) } Node idx idx idx children children children
Angels perform deep global reasoning Which 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
ParasiticStack.push class 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 restored v = n.children[c] // we are holding 4 values but have only 2 memory locations // select which 2 values to remember, and where e, n.children[c] = angelicallyPermute(x, n, v, e) }
ParasiticStack.pop pop(values) { // ask the angel which location we borrowed at time of push n = !!(e, values) c = !!(0 until n.children.length) // v is the value stored in the borrowed location v = 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 }
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 } }
First we observe what these angels do classParasiticStack { 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 } }
Refinement #1 class ParasiticStack { var e : Node push(x, nodes) { n = !!(nodes) c = !!(0 until n.children.length) e, n.children[c] =x, e } pop(values) { n =e c = !!(0 until n.children.length) v = n.children[c] r, n.children[c],e =e, values[0], v return r } }
Refinement #1 class ParasiticStack { var e : Node push(x, nodes) { n = !!(nodes) c = !!(0 until n.children.length) e, n.children[c] = x, e } pop(values) { n = e c = !!(0 until n.children.length) v = n.children[c] r, n.children[c],e = e, values[0], v return r } }
Refinement #2 classParasiticStack { var e : Node push(x, nodes) { n = nodes[0] c = !!(0 until n.children.length) e, n.children[c] = x, e } pop(values) { n = e c = !!(0 until n.children.length) v = n.children[c] r, n.children[c],e = e, values[0], v return r } }
Refinement #2 classParasiticStack { var e : Node push(x, nodes) { n = nodes[0] c = !!(0 until n.children.length) e, n.children[c] = x, e } pop(values) { n = e c = !!(0 until n.children.length) v = n.children[c] r, n.children[c],e = e, values[0], v return r } }
Refinement #2 classParasiticStack { 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 = e c = !!(0 until n.children.length) v = n.children[c] r, n.children[c],e = e, values[0], v return r } }
Final refinement class 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], v return r } }
We have derived Deutsch-Schorr-Waite Marks reachable graph nodes • using constant space Uses pointer reversal • implicit backtracking structure • encoded by rewriting child pointers
Summary Angels can stand for code that is as yet • unimplemented • not well understood
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
Entanglement-refinement relationship partition of angels all safe traces finer partition refinement 1 refinement 2 refinement 3
Bipartite Graph Classifier root.visited = true root.pol = 0 for (j <- 0 to numedges-1) { val e : Edge = !!(Edge) assert e.src.visited assert not e.traversed e.dest.pol = !!(0,1) e.dest.visited = true e.traversed = true assert 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 synthesis Classical 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))
Summary Partial Programs amenable to inductive synthesis Interactive angelic synthesis frees from syntactic bind Synthesis-backed compilers may package synthesis
Open problems Decision procedures, esp. for undecidable problems Modular synthesis, esp. top-down More interactive usage models, e.g. rapid prototyping More ways of encapsulating synthesis in tools