- Home
- Documents
*Next Steps in Partial Program Synthesis Rastislav Bodik Shaon Barman, Joel Galenson, Casey Rodarmor,...*

of 40/40

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

View

218Download

0

Embed Size (px)

- Slide 1
- 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
- Slide 2
- 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
- Slide 3
- 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 3
- Slide 4
- 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. safe P[c](x) Examples: P1: assert x
- 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
- Slide 12
- Jim Demmel's napkin 12
- Slide 13
- 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? 13
- Slide 14
- 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 1 2 2 3 3 3 3 2 2 1 1 ? 1 1 2 2 3 3
- Slide 15
- 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
- Slide 16
- 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
- Slide 17
- Example Problem: Implement zip(x, reverse(y)) Traverse lists x and y exactly once. Use cons, car, cdr. Length of lists unknown a priori. Based on our implementation of angelic programming in Scala 17
- Slide 18
- The spec (input/output pair) val x = List("a", "b", "c", "d") val y = List("1", "2", "3", "4") var r =... assert r == List("a4","b3","c2","d1") 18
- Slide 19
- Refinement 1: obtain a demonstration val x = List("a", "b", "c", "d") val y = List("1", "2", "3", "4") var r = Nil while (!!) { r = cons(!!(x)+!!(y), r) // d 1
- Slide 20
- Refinement 2a (bad): traverse each list once var a = x var b = y var r = Nil while (!!) a = !!({a, a.tail}) b = !!({b, b.tail}) if (!!) r = cons(a.head + b.head, r) ) 20
- Slide 21
- Step 2b (successful): recursion var r = Nil val up = !! // true or false def descent() = { if (!!) return if (!up) r = cons(!!(x) + !!(y), r) descent() if (up) r = cons(!!(x) + !!(y), r) } descent() 21
- Slide 22
- Step 3: recursion with induction variables var r = Nil val up = !! def descent(a, b) : (List,List) = { if (!!) return (a,b) if (!up) r = cons(a.head + b.head, r) val (aa, bb) = descent(!!({a,a.tail}), !!({b,b.tail})) if (up) r = cons(!!({a,aa}).head + !!({b,bb}).head, r) return (!!({a,aa}).tail, !!({b,bb}).tail) } descent(x,y) 22
- Slide 23
- What do angels reveal in this program? Too many safe traces to grasp individually. Abstract the traces! Some cases are easy: in all traces, up = true. So ignore up. Rest may benefit from what we call angelic entanglement. Two instances of !! are entangled if changing the value of one requires changing the value of the other (one angel compensates for the other). 1) Angels handling b,bb are not entangled with angels for a,aa. ==> understand them independently, via subtraces for a/aa, for b/bb. 2) When !!({b,bb}).head always selects bb, then angels for b,bb are not entangled (likely, the desired algorithm) otherwise, angels for bb are entangled (likely, algorithm would require some extra logic and state variables) 23
- Slide 24
- Step 4: nearly final program var r = Nil def descent(a, b) : List = { if (!!) return b val bb = descent(a.tail, b) r = cons(a.head+bb.head, r) return b.tail } descent(x,y) 24
- Slide 25
- 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
- Slide 26
- Modularizing DSW with angels Deutsch-Schorr-Waite (DSW) mark reachable graph nodes use constant extra space Challenging: 150 Lemmas [Abrial]; 50-min lecture (me) Complexity due to pointer reversal backtracking structure, needed for depth-first search, is implicit encoded by manipulating child pointers 26
- Slide 27
- DSW def DSW(g) { vroot = new Node(g.root) current, up = g.root, vroot while (current != vroot) { if (!current.visited) current.visited = true if (current has unvisited children) { current.idx := index of first unvisited child up, current, current.children[current.idx] = current, current.children[current.idx], up } else up, current, up.children[up.idx] = up.children[up.idx], up, current } Can we decouple traversal+graph from the backtracking structure? 27 stores the back- tracking structure stores edges of the graph
- Slide 28
- Recursive depth-first search def DFS(current) { if (!current.visited) current.visited = true while (current has unvisited children) { current.idx := index of first unvisited child child = current.children[current.idx] DFS(child) } return vroot = new Node(g.root) DFS(g.root) Correctness condition: (i) mark all/only reachable nodes; (ii) graph unchanged 28
- Slide 29
- Recursive depth-first search def DFS(current) { if (!current.visited) current.visited = true while (current has unvisited children) { current.idx := index of first unvisited child child = current.children[current.idx] DFS(child) ret:} return vroot = new Node(g.root) DFS(g.root) exit: 29
- Slide 30
- Recursive depth-first search def DFS(current) { if (!current.visited) current.visited = true while (current has unvisited children) { current.idx := index of first unvisited child child = current.children[current.idx] DFS(child) ret:} return if (!!) goto ret else goto exit vroot = new Node(g.root) DFS(g.root) exit: 30
- Slide 31
- Recursive depth-first search def DFS(current) { call:if (!current.visited) current.visited = true while (current has unvisited children) { current.idx := index of first unvisited child child = current.children[current.idx] DFS(child) goto call ret:} return if (!!) goto ret else goto exit // return vroot = new Node(g.root) DFS(g.root) goto call exit: 31
- Slide 32
- Recursive depth-first search def DFS(current) { call:if (!current.visited) current.visited = true while (current has unvisited children) { current.idx := index of first unvisited child child = current.children[current.idx] push(current); current = child // DFS(child) goto call ret:} current = pop() if (!!) goto ret else goto exit // return vroot = new Node(g.root) push(vroot); current = g.root // DFS(g.root) goto call exit: 32
- Slide 33
- Iterative depth-first search, explicit stack call:if (!current.visited) current.visited = true while (current has unvisited children) { current.idx := index of first unvisited child child = current.children[current.idx] push(current); current = child goto call ret:} current = pop() if (!!) goto ret else goto exit vroot = new Node(g.root) push(vroot); current = g.root goto call exit: 33
- Slide 34
- 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() } 34 Node children idx
- Slide 35
- 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,(node 1,node 2,)) stack can (try to) borrow fields in node i pop(node 1,node 2,) value node i may be handy in returning storage Parasitic stack expresses an optimization idea But can DSW be modularized this way? Angels will tell us. 35
- Slide 36
- 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)) } 36 Node children idx children idx children idx
- Slide 37
- 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 37
- Slide 38
- 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) } 38
- Slide 39
- 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 } 39
- Slide 40
- 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
- Slide 41
- 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
- Slide 42
- 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} 42
- Slide 43
- 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} 43
- Slide 44
- Refinement #2 class 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 = e c = !!(0 until n.children.length) v = n.children[c] r, n.children[c],e = e, values[0], v return r} 44
- Slide 45
- Refinement #2 class 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 = e c = !!(0 until n.children.length) v = n.children[c] r, n.children[c],e = e, values[0], v return r} 45
- Slide 46
- Refinement #2 class 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 = e c = !!(0 until n.children.length) v = n.children[c] r, n.children[c],e = e, values[0], v return r} 46
- Slide 47
- 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} 47
- Slide 48
- 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
- Slide 49
- Summary Angels can stand for code that is as yet unimplemented not well understood 49
- Slide 50
- 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
- Slide 51
- Entanglement-refinement relationship 51 all safe traces refinement 2 refinement 1 partition of angels refinement 3 finer partition
- Slide 52
- Bipartite Graph Classifier root.visited = true root.pol = 0 for (j