Sumit Gulwani: Okay. Good morning, everyone. It's my immense pleasure to introduce Saurabh Srivastava, who is a Ph.D. candidate at University of Maryland, College Park. So Saurabh's dissertation has been about a new template-based technique to reasonable programs that leverages the engineering advances in SAT and SMT solvers. So Saurabh has used this technique to verify 20 lines long programs, and you might wonder that why is that interesting. Well, it turns out that these techniques can also be used to actually synthesize those 20-line programs, and Saurabh synthesized the first program probably in January last year, and since then he has been synthesizing programs like crazy. So Saurabh is going to tell you more about this wonderful area of work. So off to you, now, Saurabh. Saurabh Srivastava: Okay. Thanks, everybody, for coming. Sumit, for the introduction. Thanks, So I'll be talking about work that I've been doing in my dissertation, and it's titled Satisfiability-Based Program Reasoning and Synthesis. Okay. So let me start by making almost a non-statement. Software is everywhere. We employ teams of developers, Microsoft knows all about that, which put in many man years worth of effort into building these things. Then we send them off to teams of testers, we stack on another some man years worth of effort, and then we put them on almost everything imaginable. But while software is everywhere, correct software, not so much. We have all these instances where our software hits [inaudible] cases and crashes or does something bad, and, well, all these software companies invest a lot of effort trying to debug the software and, well, probably write it in a font that is very visible and push it [inaudible] or something. We have to spend a lot of effort dealing with that. So as we move forward, it won't just be sufficient to find these bugs and tolerate them if one can. We need a process that does not introduce these bugs in the first place. And while we have this issue, what we also have is the resource that we have at our disposal, that is, we have the computing power available to actually get the job done. We have the computing power to build better software. We just need to develop some theory, techniques, and tools that will allow us to harness it. So what do we want to do? What we want to do is, one, we want a mechanically reasonable software, and that will help -- and that will allow us to help the testers, because now what they can do is they can use the computing power that they have at their disposal and use it to automatically verify software. Additionally, it will also alleviate some of the human effort there. Additionally, it will also help the developers because if we can do mechanical program reasoning, we can infer specifications. That might be good for the developers to use as a summary of what they've already built to build other software. The second, probably the more interesting thing, is that we want to automatically generate software. And this will certainly help the developers because they can use their computing power alleviate some of the human effort there, and what we can do is automatically synthesize the software possibly. So towards this end, I've been pursuing the researchers in the last two, three years, and what I claim is that we can build powerful program reasoning and program synthesis techniques that leverage the power of satisfiability solving. And let's dissect that. So program reasoning, as I was talking about, what we want to do is we want to do verification and we want to do property inference. So verification is you have a program and you have a property and you want to infer a certificate that matches the program to the property. That is the [inaudible] proof that the system will generate. Property inference is you're given a program and you want to infer the properties off the program and you want a certificate about it. Again, as I said, more interestingly, we probably want to look at program synthesis. And as the boxes kind of indicate, we'll be kind of linking this all together. I'll be building on program reasoning to design program synthesis tools, and we won't have the time to go into much detail about program reasoning, but let me just state that that is the key technical bit that I've been working on. But program reasoning just builds on top of that. So we want to talk about verified synthesis where what we want to do is we want to just take the property that we have and we want to infer a program. But not only that, we want to infer a certificate about it as well. And a more pragmatic approach that I've been recently pursuing is this approach where you're given just a property, you only infer the program, but you don't really care about the certificate that much. So what we have is a testing-inspired approach to synthesis where the program that is generated is correct and matches up to the property up to the guarantees provided by testing. And that is approximate guarantees. Okay. So the other thing that I want to mention is that we are going to be using satisfiability solving as our core tool to do all of these things. And why is that? Because that allows us to bring available computing power that we have at our disposal to these tasks. And that is facilitated enabled by recent advances in the last decade, and we now have fast solvers, essentially Z3 for my purposes, which can really solve hard instances in the SAT and SMT domain. And if you look at the SMT competition, which is the competition for these solvers, some of the industrial benchmarks are incredibly big. So we have this tool at our disposal, and we want to apply it to our task. So here is the outline for the rest of the talk. I'll be talking about the foundations that we have built, the core technique that we have built, which leverages satisfiability solving for verification, property inference and synthesis, and then we will talk about -- in this case we will mostly concentrate on the synthesis aspect, just breeze through the first two, and then I'll discuss our testing-inspired synthesis approach. And while we're doing that, I'll also mention the practical implications of this thing, how we've been able to infer some very complicated invariants that you might need for verification, all preconditioned for functional correctness or termination for property inference, and I'll discuss in detail our examples for synthesis and also for testing-inspired synthesis. Yes? >>: So you talk a lot about synthesis. How do you weigh the sort of the impact of the work you're doing in verification against the work in synthesis? In other words, which do you think is more important, and why? Saurabh Srivastava: Well, synthesis has the potential to be really important. Verification is sort of needed right now, right? We have developers who write these programs, so in terms of inferring these quantified invariants, which are really required for a lot of programs to actually prove them correct, verification can be very useful. But synthesis right now is at kind of like an experimental stage. We've been able to synthesize a couple of programs, but once we figure out what we can actually synthesize, then we can start talking about how that might be useful in practice. I don't think synthesis is talking about -- at least my work doesn't really talk about [inaudible] applications right now. We're trying to figure out the core techniques and maybe in a couple of years we'll figure out that, oh, here's how they can help the developers, et cetera. Okay. So to talk of the first part, which is the score satisfiability-based program analysis technique, I'll be talking about verification and property inference, as I was talking about. Okay. So let's start with some real basics. Reasoning about -- and this will be very high level, especially for this audience, but just to put everybody on the same page. So reasoning about a straight line code is simple. Well, relatively, if you don't talk off the heap, et cetera. And at least -- so what I mean is that at least for simple domains, it is simple. So, for instance, if you have a fact X is greater than 5 at the beginning, you have an assignment, X is assigned by plus 1, intuitively we know that we should be able to derive X's greater than 6 afterwards, right? And this is easy to mechanize as well, and our way of doing that will be, for the purposes of this talk, will be to say that, okay, you have the fact X is greater than 5, and for that assignment we will put an equality predicate, we'll conjunct an equality predicate which says that the alpha value of X, which I indicate by the prime variable, X prime is equal to the sum expression over the inputs that we're computing. And once you conjunct that, then you have this -- you can prove something about the resulting values. And this is very -- well, this is essentially the way we do it in symbolic execution, RSSA-style reasoning, and you can do it backwards using [inaudible] style reasoning as well doing substitutions, but we'll stick to this because it's, for one, intuitive, and it also facilitates other things. Okay. So before we go further, let me just introduce some notation. I will use the [inaudible] available F to indicate facts in the system, such as y is greater than 5 and X prime is greater than 6, and because statements for us are these traditions between the inputs and out puts, I will use a [inaudible] variable T to indicate that. And because we're talking about reasoning and synthesis, these facts might be unknown or these traditions might be unknown as well, so I'll use this [inaudible] font for that. Okay. So we have straight line code, which is somewhat simple. Acyclic code is also doable because what you need to do is that's just a combination of various straight line fragments, right? You just need to -- as a [inaudible] operation, you just need to -- quote, unquote, just need to define the joined operator. And you essentially just need to combine the fact that you get F1, F2, F3 from straight line reasoning to get this new fact. So that's also kind of doable. But almost everybody would agree that dealing with loops is the tricky bit. So you have this straight line fragment, acyclic fragment, you have a zero at the beginning and then you get a font at the end. And then you have a loop. The problematic bit is this back edge is that bumps [inaudible] into the beginning, so you have this F0 prime, and then you might bump it back and then you get this F0 double prime. Essentially what you need is a fixed point, right? You need a fixed point that if you start with that, then you'll get the same thing at the end. And this is the key difficulty that we have been dealing with. You need a loop summary, a loop invariant, and for automatic program verification, this is the key difficulty. Inferring these fixed points, loop invariants, is the big task. And over the past couple of decades we've been essentially automating this process, trying to figure out mechanical ways of generating these fixed points. And it has been limited to simple techniques. So what we really need is a technique that will infer complicated, by which I mean quantified invariants for the purposes of this talk, and can do it robustly. And for this we have our approach which I call satisfiability-based program analysis, and analysis written in this framework are always composed of two parts. One part is this part that reduces the program into a SAT formula, and then the other part is the one that actually does the fixed [inaudible] computation using the SAT solver. It takes the SAT form that you have computed from the program and then gets a solution out of it which corresponds to invariants that I will talk about in a bit. So the first part is the important part, which is this part that takes the program and generates a boolean formula out of it, right? So let's talk about that. And here what we have is that we make a key assumption. The key facilitator that allows us to get boolean formulas out of programs so that they respect this mandate is the user invariant templates. And this is very similar to the domains that give you a [inaudible] except that here we have these templates which essentially give you more structure. We'd say that, okay, this is the kind of invariant that I'm looking for. And we've considered two domains. For instance, like for linear arithmetic what we say is that you can have invariants that are a particular form. Instead of just being some invariant we say that, okay, it is an invariant which has a particular form, let's say, of F1 disjunctive, with F2 disjunctive, with F3, and these internally are cubes over unknown linear relation that we want to infer. So this is the kind of template that I'm talking about. We have another reduction for a predicate abstraction that essentially takes -- can talk of quantifiers. So you can have some boolean structure in the top and it could involve quantification. Linear arithmetic reduction does not handle quantification. So this is essentially why I put that down there. >>: So the general [inaudible] is that you're going to say rather than searching for arbitrary invariants, we're going to assume the invariants have a certain structure? Saurabh Srivastava: Yeah. >>: And then you're basically going to ask the theorem prover how about this invariant or how about that -- or, no, you're actually going to say within this structure, please search for invariants that may -Saurabh Srivastava: So, yeah, the intuitive idea is more similar to the second one. We reduce it to a boolean formula and then once you solve for that formula, that datacally gives you the model for that formula, it datacally gives you the invariants. >>: Okay. Right. So the FI's are all unknowns, but you've essentially limited the structure, the size. Saurabh Srivastava: Yeah. But we do not numerate -- so our use of the SATs is a live different than traditional approach -- whatever traditional means. So one approach could be that you numerate some candidate invariant using some technique, like use some process that you might have, and then you use the SAT solver to filter them out, correct? But we don't do that. >>: I see. The analyzer thing are holes that [inaudible]. Saurabh Srivastava: >>: Yeah. They're variables. They'll essentially become variables. Saurabh Srivastava: Yeah, essentially become variables. So when you have conjunctions of -- for instance, in this formulation it's easier for linear arithmetic. These would be conjuncts off linear realization, and those linear relations will be over program variables and some unknown constants. >>: Unknown constants, right. Saurabh Srivastava: booleans. And those constants will essentially boil down to Okay. So, again, for the case of predicate abstraction, these unknowns are conjunctions over some predicates. So we won't really have the time to go into the details of how we do the reductions so let me just give you an overview. So what you have is you have these program verification conditions. They're of the form of some unknown invariant followed by some transition should imply some fact somewhere in the program. It could be the unknown invariant if you're going around the loop or it could be some other fact. And by making this assumption about the structure of the invariant, more so than what you do in abstract [inaudible] domains, what we're able to do is we're able to apply a trick from the linear arithmetic literature called Farkas' lemma to actually take this implication, plug those templates in the formula and then, through a couple of steps, eventually get a boolean formula. So I'll be happy to talk about these steps off line, but let me just give you an overview of how the technique works, right? So we have that for linear arithmetic. For predicate abstraction we, again, have a couple of steps that take these verification conditions and get you to a boolean formula. And we have an algorithm that we call optimal solutions, but essentially it's a glorified predicate cover operation that we know from the literature. So that essentially takes you from the verification condition to the boolean formula in this domain. Once you have this boolean formula, you can use a SAT solver to solve for it. Right? And now you get a SAT solution. And what does that SAT solution correspond to? That SAT solution essentially gives you values for what goes inside of those unknowns that you have in the template. And the template has some alpha-level boolean structure, and once you have those values, you can plug those back in, and now you have the new invariant. Okay. So that is sort of the high level of what the technique does. Again, I'll be happy to talk about these two reductions off line. So what have we used this for? We've used this for verification, that is for assertion checking, generate invariants for assertion checking, and to illustrate that, let me talk of an example. So imagine the background here to be a pixilated screen, okay? So these are pixels. Each box indicates a pixel. So what we need -what we want is a program that draws the best fit line corresponding to the real line from 00 to xy, and such that the alpha values are no more than half a pixel away from the real value. So you want to output these pixel values. You can do that by computing the slope, y divided by x, and multiplying it by small x and then taking the [inaudible] as required. But supposing you wanted to do that more efficiently, you wanted to do that only using linear arithmetic operations, well, the graphic simulator does know of a program that does this, and it's this one, but looking at it, we have no idea why this should be computing the best fit line to a real line, right? So we want to verify that. We want to have -- we have a precondition, we have an output that we want to meet. We want to verify this program. We want to infer invariants for the loop of ten. So we can infer invariants for this program that we have. Bresenham's line drawing example. And the actual invariant that you need is this. And using the linear arithmetic tool that we've developed, we can infer invariants of that form. The human program is probably not going to worry about that or even give you that. So that is the kind of automation that we can provide with that linear arithmetic tool. The other interesting sort of standard benchmark is sorting. Sorting is interesting because you have these small complicated programs, you have a couple of them that we're very familiar with, and these are interesting because the reasoning involved is complicated because it involves quantifiers. You have the fact that you want to prove at the end. And for the loops you want to infer complicated quantified invariants, and this distilled reasoning is difficult to automate. And what we can do using our technique is to give it a template or an alpha-level structure that says, okay, you have some quantification, this fact, this is unknown implies some other unknown, and then the tool [inaudible] for this complicated invariant that you need. Okay. So we can infer quantified invariants, and we can also do that in very reasonable time, quote, unquote, reasonable time. So here are the times in seconds on the log scale out here on the y axis. And for the most part we see that we can compute the invariants within 1 to 10 seconds, approximately 1 to 10 seconds, and for one example, which is the line drawing example, we need more time, about 100 seconds. What is interesting is to know that the previous techniques were somewhere there. Like we had one technique that came close, which was insertion sort, for insertion sort, and then there was merge sort and quick sort, and that's a log scale, so that's about an order of magnitude. But that is essentially not the point I'm making. That's not a fair comparison, because those techniques were a couple of years back and stuff like that. The point that I'm making is that we have a robust technique that can infer invariants given these templates for all of the benchmarks that we have as opposed to other techniques which were specialized for one or the other. Yes. >>: I'm not sure I understand what you're comparing against. So what are the previous techniques? What did they do? You're comparing the performance, presumably, but they verified that quick sort was correct. Saurabh Srivastava: >>: They inferred the invariants for proving -- [inaudible]. Saurabh Srivastava: Yes. Yeah. So the times here are for inferring those invariants. Did I answer the question or ->>: Yeah. Saurabh Srivastava: Okay. Cool. Okay. So we can infer invariants for verification. The other interesting thing is that we can also do loop bound computation, which is of interest to someone, for instance. But this is a heavy-weight technique, and in a speed product, I think he uses more lightweight techniques. But we can infer complicated loop bounds if they are needed. More interesting, probably, is probably inference. And what we can do is we can infer preconditions. For instance, we can infer preconditions for functional correctness. So, for instance, consider a program that implements a binary search. What we can do is we can run this program through a tool in this precondition mode, and what it will tell us is that binary search is only correct if you give it sorted array. And that can be kind of useful if a program is entering -- is writing some fragments and then he wants to figure out other fragments that are going to call into this, so what are the conditions that you hold. We can also infer preconditions for bugs or worst case. And what do I mean by that? So consider selection sort. If you run selection sort through a tool in -- by saying that, okay, well, what is -- put in an assertion saying that, okay, here we're swapping. Give me the worst case number of swaps that it does. And the tool in this precondition mode gives you opportunity to say that array should be sorted, pretty much, except that the last element should be smaller than the first element. So this is kind of interesting. Precondition would say that, okay, if you give it this particular input, then it's the worst case number of sorts that it can possibly do. We can also input preconditions for some kinds of determination. Okay. So, again, we have a technique that can input quantified properties of programs, which can potentially be useful. Okay. So this is what I've been talking about in this part. We have this approach for doing fixed-point computation using the SAT and SMT solver, and the key detail that we need to add are these abstraction-specific reductions. Unfortunately, we didn't go into the details of how you actually do that, but, for instance, for linear arithmetic predicate abstraction, we had to design different reductions to convert them to boolean formulas. Okay. >>: I have a question about that. Saurabh Srivastava: Yeah? >>: So sounds like you're not going to go into it in more depth. So one question I have is what are the limitations of that strategy for trying to invariants? You have the two different kinds of strategies, and in both cases you're making approximations or you're creating this template, et cetera. Under what circumstance will that not work? Have you thought about -Saurabh Srivastava: So there is one thing that is -- one limitation kind of in addition to previous -- kind of in addition to what previous techniques had was this notion of template. Right? So if you do not -- if your invariant does not happen to lie in a template that says, okay, for all K1, K2, something implies something, right? If it requires, like, something more complicated than that. Then you're solver will come back and say that that does not exist in invariant. So you have to [inaudible] go ahead and maybe like give it more expressive templates or something like that. So linear arithmetic does not handle quantification, so they're template -- our template would be some number of conjunct, some number of disjuncts, and, again, if it doesn't fall into that template, then the solver will come back and say no solution. Right? >>: [inaudible] you were looking for 100 variables and thousands of disjunctions and you couldn't find them. >>: Is that the only case? matter of numbers? I guess that's the thing. Is it just a Saurabh Srivastava: Well, so there's a trade off there, right? So if you give it a very, very expressive template -- I mean, you can just give it a huge template, right? Any number of quantifiers, you can enumerate tons. But then the solver will take its own sweet time to come back, right, using SAT and SMT solvers at the core. So if the instance is terribly hard, then the state-of-the-art solvers won't be able to handle that. >>: I mean, one thing that could be done would be to look at the solving time versus the complexity of the template so you could artificially create bigger templates. It's just kind of how long they take to fail, essentially. Saurabh Srivastava: Sure. >>: Because it would give a better sense of what the boundaries are between things that are solvable in a reasonable amount of time and things that are just way out there. Saurabh Srivastava: That might be instructive to see, but I don't think that will be very -- very -- what is the word that I should refer -- SATs always have this brittleness in terms of -- like it's not easily quantifiable which instances are hard and which are not. [inaudible] but it doesn't seem like you can just vary the templates and get [inaudible] like this is a harder SAT than the previous one. So, again, like we're -- so that you could say is another limitation. >>: I'm an experimentalist, and I believe doing an experiment would probably give you some insight. Saurabh Srivastava: Yes, certainly, certainly. give us insight. But ->>: It will certainly [inaudible]. >>: So I have a question. I mean, the template-based version, is that really new to you? I mean, I thought that people had been investigating this idea before using -- maybe not using SAT. So can you really tell me much more specifically compared to the related work what so far is a new contribution? Saurabh Srivastava: These reductions -- so if you assume -- so we need to assume a template to actually get a finite status. >>: No, I know that. Saurabh Srivastava: in. >>: So that's essentially where this template comes No, but the idea of the templates. Saurabh Srivastava: The idea of the templates, I don't think that's, like -- that's a [inaudible] idea. I don't think that's the new contribution here. >>: Okay. Saurabh Srivastava: The new contribution are the reductions. Essentially for linear arithmetic, again, we have to -- to be completely honest, the reduction -- the application of Farkas' lemma was used earlier by student [inaudible]. >>: Yeah, yeah, yeah, right. Saurabh Srivastava: Yeah. That's what I -- So that was -- >>: So you're going to tell us more about the actual -- the detail of your reduction? Saurabh Srivastava: synthesis part now. >>: Actually, no, I'm going to jump into the Oh, okay. Saurabh Srivastava: >>: Okay. >>: [inaudible]. But, again, we can talk about that -- >>: No, no, no, no, no. the talk. You're not giving the talk. Let him give Saurabh Srivastava: But the idea of their user templates was based off that thing that, okay, you can reduce it to formula and then I'll apply mathematical solvers to solve then we took it one step further to get it to SAT, okay? linear arithmetic was the additional bit. >>: [inaudible] a linear it. But So that for Okay. Saurabh Srivastava: They did not input preconditions, which was an additional thing that we did. For predicate abstraction, I don't think there was, like, over predicates getting into a boolean formula, et cetera. That's new. >>: Okay. >>: [inaudible]. Saurabh Srivastava: >>: Okay. That's part of this template of -- Okay. [inaudible]. Thanks. Saurabh Srivastava: >>: Yeah. I'm going to make sure you got the first [inaudible]. Saurabh Srivastava: Okay. So now I'll talk about proof-theoretic since, which is builds off of that program reasoning approach that we've talked about. And then we'll talk about how we infer a program and a certificate. Okay. So through program synthesis, stated broadly, is the idea for automatically generating programs for given specifications. And our approach to that is to connect it to program reasoning. And let me just motivate that. So what we have is that in program reasoning, what we had was we had unknown invariants and then we had those transitions for the statements, and that allowed us to go to other facts. But if we write it out, some of the transitions look exactly like some of the facts at the end, right? So in this particular case, there's no reason why this transition is any special, right? It just looks exactly like you have -- like the fact that you have at the end. So why should they be given special status? And essentially what we decided was that let's try to see if we can just make that a known as well. So what we have is we have our known invariants, as we did earlier, and now we have these transitions which are known as well. They probably should have a particular form, as we will see, but once we make this unknown, our approach would be, well, let's see if we can create synthesis generalized verification and hopefully infer the transitions and the transitions as well -- and the invariants as well. So this is the certificate of correctness that we need. So we have our example that we were talking about earlier. It has an input and output preconditions. We want to generate a program now with only linear operations. And our approach builds on the previous work. So we want to encode synthesis generalized verification and we want to use existing verifiers. But right at the onset we have a problem, because in verification what we had was we had a program, and we were generating a proof for it. Now we don't have a program, so what are we going to do with it? What is the input of the synthesizer? And we need to talk about that. And the input to us is this thing is that we call a scaffold. And what does the scaffold contain? The scaffold contains three parts. The first is the function of specification. You need to know what you're computing, right? So you need -- this is kind of reasonable. You need to know what the pre and post conditions are. So the function of specification is what we need first. The second bit is the resource constraint. So this is a little more non-trivial, if we may, because this allows the programmer to constrain the space of programs that he's looking at or that he wants and additionally allows us to build a synthesizer, essentially. So the resource constraint consists of two parts. The first is a looping structure. That is, does the program contain a nested loop, does it contain two loops in a sequence, and so on. This in some sense very vaguely says what the time taken by the program is. It's not the asymptotic complexity, but in some sense it's like, you know, some indicator of the time taken. The other is the stacked template, which says how many variables are there that the program can manipulate, it can manipulate one variable, thousands of them, or something. So how many local variables are there for the procedure. So these are the two bits of the resource constraint. The other thing that we need is a specification of the domain. So over those variables inside that control flow structure, what operations can you have. Can you have quadratic expressions, can you have linear expressions or something like that. So we need a specification of the domain. So once we have those, we will actually be able to -- once we have these three, which are part of a scaffold, we will actually be able to set up a system so that we can look at it as verification. And our approach to that uses what we call synthesis conditions, and those I'll describe next. So what you have is you have a scaffold which has three parts: The function specification, the resource constraints, and the domains. And from that what we can do is we can write out some basic formulas. You have the controls and structure, so you can write down some verification conditions. But the problem there will be that everything now is unknown. The only thing known are the pre and post conditions, right? But at least when we write them out in this form, we have some tools that can digest implications of this form which is our standard verifiers that we have constructed. They're not used to everything unknown. They're used to having the loop guards known, they're used to having the transitions known and so on. But at least they can digest them, right? So we can be optimistic, send them out to a verifier, these are safety constraints that we have with a whole bunch of extra unknowns, and send it over to the verifier and see what it gives us. And as you might expect, because the system is so horribly under-constrained, the synthesizer will probably just come back and give you a trivial solution. So in this case it says if I assign everything defaults, it just works, right? All the implications are discharged because they have a false on the left-hand side, and that is fine. But that does not correspond to a real program. Defaults do not correspond to a real program. So what went wrong up there? What went wrong is that we did not impose the semantics of the unknowns that we were inserting. We did not say that the statements were of a particular form, the guards were of a particular form. So we need some well-formedness constraints. And we have found that we can impose them in the same manner as safety constraints, so we get these two. You get save the and well-formedness, and now you can ask the solver for solutions again. But there will still be a problem here. So let play a game. So let's say that you are the person who's generating the save the and well-formedness constraints from the scaffold and I'm the synthesizer, the core solver, that is solving these things. And now you've given me these two, so I have to give you a program that meets the pre and post condition and has to be a develop-formed program. It has to be a valid program. But what I'll do is I'll give you back a program that always goes into an infinite loop, right? A program that does not terminate meets every pre and post condition. So it's a real program. It meets the pre and post condition, but it's not interesting. So what we need is we need that the output should be reasonable so that it does some real computation. So once you add those two, the last one, which is the termination constraints, now what we get are these three parts to what we call the synthesis conditions, and solving those actually gives us valid non-trivial programs. And this is the systematic approach to verification that we were kind of looking at. So in verification -- did I say verification? The systematic approach to synthesis that we want, in verification what you had was you had a program using [inaudible] verification conditions from it, and we have engineered these verifiers that can engineer the prover. Now what we want to do is we want to take the scaffold, generate synthesis conditions from it, and use the exact same verifier that we had from the verification domain and apply it to solve these synthesis conditions to get the program plus the proof. And that is possible because these are all encodable in the format that is digestible by these verifiers. Yeah? >>: So the scaffold is imposing some -- like a template, like a constraint on the size of the program you're going to? Sumit Gulwani: If you don't have the scaffold, then there will be no way to put invariants anywhere by having, let's say -- saying that, okay, it has a nested loop. Now you have two points -- you don't know anything inside those loops, but you have this, like, [inaudible] structure where you can put the invariants and you can write out the verification conditions. >>: So I could also just give you the context [inaudible] of the programming language and say don't build me a tree with more than N nodes. You want a little more structure than that. Saurabh Srivastava: Yeah. I mean, that's the system that we've built. But we can discuss the version that you're talking about. don't know how [inaudible]. I >>: But it's a finitization, right? It creates a -- I mean the really key property of the scaffold is not just its structure that you want -- that you say I want this followed by that but also the finite size? Saurabh Srivastava: Finite size in the sense that, well, we allow loops and we allow a loop invariant. >>: You can express unbounded computations. Saurabh Srivastava: >>: You're not going to search over an unbounded space [inaudible]. Saurabh Srivastava: >>: Yeah. Yeah. Sure. Sure. [inaudible]. >>: No, no, but the -- I understand that. But that's what the [inaudible]. I mean, but you could just say, well, because you know perfectly well because of the proof of the halting problem that we can encode anything that's constants, right? We can encode [inaudible] expressions of up to some bound as a constant, so why not search for those too? Saurabh Srivastava: >>: So it gets down to [inaudible]. Yeah, I know, I know. >>: In both this case and with the scaffold here and the template in the verification case -- let's just take the template case. When you specify a template with a given number of disjuncts, is there any advantage to be gained in imposing a higher level of search where I'm going to try all the templates up to the permitted complexity in some increasing complexity order? Saurabh Srivastava: That's certainly doable. The instance is constructed out of those, but using [inaudible] higher things, right? Yeah, that's essentially what our -- like, that was the interface to me as the user of this tool, like I try this template, it doesn't work, I try something more expressive. Like you don't want to go to something that's, like, you know, 10,000 disjuncts because the [inaudible] would be insanely difficult to solve. Right? >>: What I'm asking is for -- if it turned out it was eight disjuncts, would there be any advantage -- I'm sorry, let's say it was five, and you gave it search up to ten. Would there be any practical advantage to having it do try 1, 2, 3, 4, 5 in terms of more efficiently finding it at five versus just try the 10 first? Saurabh Srivastava: Yeah. Essentially I think the ideal scenario would be do a binary research, like go to some high-level thing. Because it's not -- the SAT's not always entirely deterministic, and, like, 10 would be more difficult than 5. Not necessarily. So you could probably just assume that all of them will take some amount of time and you can try 10, 1, and then by new search to find the right amount. But 10 would give you the solution, right? Yeah, [inaudible] increasing is just one approach. You can just give it some higher bound as well. I don't see anybody [inaudible] to that. >>: The idea that the cost is not monotonic in the size of the expression of the template, or in this case the scaffold, is a little disturbing, because it means that -- I mean, if it was, then the strategy that Dave outlined would be very logical, right? You'd start at the smallest and you'd work your way up because it would take less -- each would take successively more time, right? So you'd do the fast one first. Saurabh Srivastava: Yeah. >>: Since it's not, I guess the question becomes how do you know as a person writing one of these things that you're not going to pick some template that's going to be very expensive and sort of fall into a hole, essentially? Even if it's very simple, you're saying -Saurabh Srivastava: This is probably just a usage concern, right? Like so eventually you could have different versions of this, trying out different things, right? The problem is that when the template is not expressive enough, it has to say [inaudible], which takes a lot of time, so that could be problematic. If you go to something that is really high up, like, you know, 10,000 disjuncts, the SAT instance could be incredibly complicated. That's problematic. So you want to be somewhere in the middle. You want to try some thing. You can do it, like, sequentially as I did with my human experiments. Like I [inaudible] tried more, but you can go the other way around or you can do it in parallel if you had a system like 10,000 threads just running the different kind of template structures. That is a space that I have not explored at all. Like the coding [inaudible], well, we found that we could verify these programs and all of that, but actually using it over real life [inaudible], that I haven't really done. And probably part of the motivation why I want to come here. >>: Okay. Saurabh Srivastava: Okay. Going back to synthesis, this actually works, so you can give it a scaffold for, for instance, a line-drawing program and generate those conditions from it, send it out to the satisfiability-based verifier that we built in the first part, and then out comes a program and, in addition, comes invariants. So if you wanted to formally check whether that program is actually correct, you have the invariant there as well. Question? Okay. Okay. So we tried it over some linear arithmetic programs. For instance, we can automatically synthesize Strassen's matrix multiplication, which is kind of interesting. Everybody has seen Strassen's matrix multiplication in the undergrad, and everybody knows it can be done. The key idea was instead of using eight multiplications, you use seven multiplications, and therefore the asymptotic complexity comes down from n cubed to n2.78, but nobody remembers -- well, unless you have a photographic memory, nobody remembers what the actual computation was, right? Now you don't need to because you can just give it a scaffold, you need the output that we have proved up, you have some looping structure which is some indication that it's acyclic, and then you give it seven variables to work with, not out, and then out comes Strassen's matrix multiplication. Well, one of the solutions is that. It generates others which are equivalent and different. We can also generate a program which it's the user verifiers that that has a loop, and, for instance, to compute the integral square root of the given number, and you can give it a stacked template and it will automatically generate, for instance, the binary search, or if you give it a less expressive, it can generate linear search. We can do the line drawing example. We can automatically generate the Bresenham's line drawing as opposed to just checking that invariant as opposed to just generating the invariants for its correctness. More interesting -- well, quote, unquote, more interesting would be looking at sorting examples. So we have a bunch of sorting examples, but it's interesting for synthesis because there's one specification. You have [inaudible] and you want a sorted area of the output. So where do all the different versions come from? Well, they come from the resource constraint. So if you give it a nested loop and zero variables, out comes bubble sort and insertion sort. If you give it a necessary loop and one variable, selection sort. Of course, a template with zero variables, merge sort. Of course, a template with one variable, quick sort. And now we can generate the invariant for it and additionally generate the statements that all of them match up to the pre and post condition. Okay. >>: [inaudible] Saurabh Srivastava: Nested loop with two variables -- well, all of those would be in the template, and as I was pointing out, the first one is two solutions and iterate over those two solutions. So if you have two variables, you'll just iterate and you can generate all of them. >>: [inaudible]. Saurabh Srivastava: >>: Huh. [inaudible]. Saurabh Srivastava: things so -- yeah. Well, I wasn't really trying to get different >>: On the previous one, with Strassen's algorithms, I remember it was recursive. Saurabh Srivastava: Oh, yeah. Sure, sure, sure. No, we don't synthesize the top level structure, the recursive divide and conquer structure. We're just synthesizing the core SAT fragment which just does the computation using seven multiplications as opposed to eight. You have to wrap this around with a recursive divide and conquer algorithm which divides the matrix into four and then you do the multiplication. >>: But given that you relied on beginning pre and post conditions -- Saurabh Srivastava: Yeah, so these could be [inaudible] matrices, right? I'm just saying that this multiplied them in some order and add them in some order. So these are not elements, essentially. So the recursive step just takes these as some -- from end by end and goes to -- by two by end [inaudible] and so on. So I'm just [inaudible] the core inside there. >>: So what makes you think about this is that you are abstracting, but in the real algorithm would be [inaudible] and you're abstracting it with just a product of scalars. >>: [inaudible]. >>: Yeah. It still cares about whether or not there are other undiscovered [inaudible]. Seems like that you're that close -Saurabh Srivastava: Yeah, yeah. Well, okay, one thing that is kind of hidden in here is that this is over predicate abstraction. So that's what I meant by I didn't try other random, like -- so I didn't try the space of all possible predicates and so on. Like linear arithmetic generates any linear relation, so once we put quantification over linear arithmetic, then I would be in a much better position to say that, yeah, these algorithms are completely different. But the one thing is that it does generate -- well, insertions [inaudible] generated not the standard insertion sort. The standard insertion sort uses one extra variable. The version that is generated here conceptually does the same thing. It is not exactly the version that you know. So there will be tons of solutions that are generated which do the same kind of operations as the standard ones, but, yeah, there might be something in the space. I'm not willing to commit to that there is nothing else right now. Yeah? >>: I don't understand why you cannot generate an algorithm with, say, 00 or 11 or 22 because -Saurabh Srivastava: Like an input/output table? >>: No, no. Just because you don't need [inaudible]. Saurabh Srivastava: Oh, the accommodation. Yeah. Okay. That's a -- Sure, sure, sure. Yeah. >>: Because once you can generate a simple algorithm with just, say, 0, 1, 2, 3, 4, 5 -Saurabh Srivastava: Certainly. >>: Yeah, yeah, yeah. Certainly. Certainly. And it's very fast. Saurabh Srivastava: Yeah, yeah. So, yeah, this specification, as you're pointing out, is missing the fact that the output -- the output should be [inaudible] of the input, and that could be added, right? And then the invariants would get correspondingly more complicated and for the most part our technique would not be able to handle that. The way I got around to doing that -- got around that problem was by imposing restrictions on the operations that I was talking about, like the operation that can be used. So I said that you can only swap. I did not have [inaudible] copies in the area. So if you can only swap, you don't lose -- so if you can only swap, then it can't generate the program that you're talking about. It can't copy elements into the area, right? So if you have A0 something, it can't copy it into A0A0A0. If it can only swap, then the input/output computation thing is taken care of, and then imposing this gives you -- so you're right. If you did not have those operation things, if you did not impose that resource constraint before the operation, then you would have to impose additional things about the permutation and then the invariants would be correspondingly complicated and so on and so forth. >>: [inaudible]. Saurabh Srivastava: Huh? >>: It's also possible that [inaudible] constraints like that for programs [inaudible]. Saurabh Srivastava: the ->>: So that constraint does not talk about, like, No, I'm saying that [inaudible]. Saurabh Srivastava: Oh, sure, sure, sure. You're saying that as opposed to having unbounded quantification, you have, like, bounded, and then it actually will find kind of like the right program for the most part. Yes. And I'm sure you guys are pursuing that approach in the big practice. No? Okay. Okay. Going ahead, so here are the times taken for synthesis. The y axis is the time in seconds on the log scale, and we have two sets of benchmarks. Reason why they're similar to the verification is because we had a verifier for this and we had a verifier for that when we were building a synthesizer out of it. So correspondingly, it would be interesting to see how it compares against verification, right? So if we overlay the chart of verification on top of this, you see that for the most part in order of magnitude or two orders of magnitude for the case of this and that, you can synthesize the program in addition to verifying it, right? So you generate invariants plus the program, which is interesting because now we can focal our energies on developing better verifiers and correspondingly, we might have better synthesizers for that domain. Okay. So we were talking about this approach for proof-theoretic synthesis which essentially is leveraging the idea that synthesis is just verification with additional unknowns. And what we realized is that safety by itself is not sufficient. You need to talk about well-formedness and termination to actually get good programs out. Yeah? >>: Can you give me a couple examples of well-formedness? you're swapping, is that part of the well-formedness? Like when Saurabh Srivastava: No, no, no. That is part of the scaffold. That is the input. The well-formedness constraints are essentially, for instance -- in the example we had the solution getting the statement being fault, right? So if you have statements as traditions from output variables equal to some function over the input, that can never be fault, right? A conjunction of those where the left-hand side is prime and the right-hand side is some expression, you conjunct all of them together, it can never equal fault, right? So that was something that we need to preclude. And for that we have a constraint that says statements can -- the conjunction that you put in the statements cannot be fault. If you put that constraint in, then the statements would be of the right order, then you need to put something about the guards as well, because for it to be a real program, the guards have to be translatable if the analysis of a particular font is some constrained error and so on. So those are the kind of constraints that I'm talking about. They're not very complicated. They're just like guards and statements. Yeah? >>: So without these constraints, for example, could you quit the synthesizer generating, say, a program that generates values out of thin air non-deterministically to make things work correctly? Is that one of the constraints that you need to impose or is that -Saurabh Srivastava: So well-formedness does not talk about that. Well-formedness -- if you go and impose well-formedness, the solution that you get from the solver can't even be translated to any program. >>: Right. Saurabh Srivastava: For the most part. So are you going to look at that as a program generating values out of thin air or ->>: just work it's So it's the kind of thing that generate -- magically generate correctly. So the values have a cost [inaudible] or it reads Saurabh Srivastava: you expect a real program doesn't the values that will happen to to come from somewhere. Either it from somewhere else. Uh-huh. >>: So it just seems that that would be one thing that this constraint is probably proving out. Saurabh Srivastava: That might be one way of looking at that. Yeah, I should talk with you more to figure out what exactly that means. >>: [inaudible]. Saurabh Srivastava: Okay. So now I'm going to talk a little bit about some ongoing work where we're trying to do synthesis using our testing-inspired approach. And here we don't care about certificate that much, so that's the key goal. And the motivation for this came from program inverse. And what do we mean by that? Well, program inversion is this problem where what you want to do is you have a program, let's say a compressor for a certain input, and then you want to synthesize the inverse for it, let's say the decompressor. So program inversion was something we need to look at, and we made two observations there. The first one was, well, if you look at a compressor -- for instance, we looked at the core algorithm that goes inside the [inaudible] format, right? If you look at the compressor for that, that generates a online dictionary. While it's parsing the input, it computes a dictionary, and it doesn't output the dictionary, but the compressed output has enough information so instead the decompressor can generate the same dictionary. So if you were to look at these two separately, they have very complicated input/output specifications, right? But if you put them together, that was a key idea that if you put them together, that is, you take original program, that is, the compressor, you concatenate it with the inverse, the decompressor, then this combined program has a specification of identity. It might have very complicated things in the middle, but if you put them together and only work with the concatenation, then you are essentially get a specification of identity. So that was one key idea. The other was looking at the structure of the inverse. So it turns out that for almost everything that we tried, typically the inverse, well, somewhat looks like what the original program is, and so much so that Dykstra [phonetic] proposed that we can just read the program backwards and get the inverse. It doesn't really work all the time, it works for two examples that he talked about, but essentially that can be a good starting point, right? So what we do is we automatically mine the template using this observation. So what we're going to do here is we're going to take a given program, we're going to mine the template, concatenate these two, and that should be the identity. Right? This should be the identity program. The other thing that we wondered was, well, we're not talking -- the specifications are not very simple. So we shouldn't really talk of the invariants as well. Like if [inaudible] we just apply proof-theoretic [inaudible] which is possible to do somewhat, at least the core effect. That would involve invariants. So if the specification is complicated, the invariants are going to be very complicated too. So we don't want to talk about the invariants, so can we get a technique that does not use invariants and essentially has approximate guarantees. And for this we have this approach to -- the approach using the testing-type thing. And this is what we call Path-based Inductive Synthesis. The first two will be inspired and sort of related to what we've been talking about. We're using a satisfiability-based approach. And the last part will be this interesting thing that we'll have to add because of this bit about testing. So let's talk about the first two. So what we have, we have a template program, and for inversion, this will be partially known and partially unknown. So you have the known fragment and then you have the mined unknown fragment, and let me say that this template is both of those together. Okay? And we want that this program should have the specification identity, right? So what we do now is that instead of generating formal verification conditions, we use symbolic execution. We say that we'll run through some path in the program. So, for instance, in this case the path that goes to the bending of the loop and then just exits. And because this template program has this specification identity for everything, then it should be identity that part too. Right? So we generate through one part, we can go through another part, one that goes inside the loop once and then comes out or we can go around twice, doing the first iteration going on the right-hand side and then second on the left-hand side. And we know that for all of these traces, it should meet identity, right? So we can -- instead of the verification conditions, we can use these as kind of proxies, do the same reductions that we were doing earlier, generate a SAT instance from it, dump it out to a SAT solver and get a solution for it. So what is happening out here? What is happening is that we're generating these traces, and when you generate more and more traces, you're kind of constraining the space of programs that can possibly be there. Right? So it's some kind of pigeonholing principle going on, and if you generate enough traces, if you explore enough traces then essentially the remaining programs will be the ones that are correct. And here -- okay, that is because we're using these that could potentially be out. How do we deal with may. the core approach, but the problem is that paths, there's an infinite number of paths there, right? So that we need to figure -- how do we explore the good parts, if you And for that we have this third bit about directed path exploration. I'll talk about the high-level way of doing that, what it means. So what you have is you've generated some traces, okay? So you have a template program. You've generated some, like, one or two traces, you get a SAT instance out of it, and then you send it over so the SAT solver and that solves for some things that go inside those templates. Right? But it might not be just one solution. It might be a ton of solutions. These are candidates that work for these traces. If you were to plug these solutions inside, then they work for these templates, these traces. And essentially if you look at just these two, then nothing has been explored on this side, right? So the solution can assign anything to the left-hand side, it can assign any equality predicate. It can assign x is equal to x plus one for all you care, even if it doesn't work, right? Because that will work for those two traces. >>: Did you mention -- is this the [inaudible]? Saurabh Srivastava: No. That's not my table. >>: It sounds very -- I'm sorry, because it's ringing all sorts of bells. It sounds very similar to what [inaudible] is doing on these examples using -- generalizing from examples on that table. Saurabh Srivastava: So I have not read that paper in its entirety so I'm not the best person to comment on that. But this is over paths. >>: Okay. You get a path from giving an input and running your program. So the path is just a scenario you get from an example. Saurabh Srivastava: Sure. But like the input -- in our case the input is not constrained. It's whatever inputs take that particular path, right? So if you just give it one input, then that will take the path, but another input could take the exact same path. We're generating constraints for all those possible things that go with that. So in some sense it's a broader generalization. And it is related. I'll talk about the spectrum of things. But Sumit's technique is on one extreme of it, and this is a generalization. Every input that goes through that path has to have [inaudible]. >>: Okay. of stop. But you still have some issue of how you're going to sort Saurabh Srivastava: >>: Yeah. Yeah. That's coming up in a second. Okay. Saurabh Srivastava: So we have these different candidates which work for the traces that we have. And essentially what we really want to do is we want to prune the candidate set. We're not concerned about generating like 10,000 traces, as many as possible, we're just interested in prune this candidate set to this one candidate which actually works. Right? So what we do is we take one of those candidate solutions and we instantiate this template with that candidate solution. Okay? So that candidate solution might say that, okay, on this part you should have x is equal to x plus 1 or whatever. Now you instantiate this template by putting that x is equal to x plus 1 there and you have a real program. And now what we do is we do some kind of parallel symbolic execution over this and this, essentially just do symbolic execution over this real program, but generate a trace over the unknown. Okay? So we do this thing where we make our decisions on which side to branch on this instantiated program, but generate the trace over the unknown, and now what we get is a new trace which is relevant to this instantiation. We get a trace that is relevant to program 2 and if the program 2, the solution, is not a correct inverse, then that trace will add constraints to the system which will eliminate that candidate from the system. And we keep doing this. We take each solution that we have in the space remaining, instantiate the template with that, and then keep iterating until we get only one candidate. At that point in time we can terminate, but we can keep going and essentially that will boil down to sort of like a testing-based verifier where you keep generating new and new parts which refute or just -- or just reinforce the program that you have left. Okay. So we have applied this to program inversion. We've been able to synthesize inverses for a bunch of compression benchmarks. So, for instance, random encoding is the trivial simple example where what it does is instead of -- if you have an input that says 200 zeros, instead of writing 0, 0, 0, 200 times, it writes 200, comma, zero, and the inverse for that would be fairly straightforward, but it involves a different -- a little bit of a different structure, and therefore it was interesting to use as a starting point. LZ77 and LZW are the core algorithms that go inside the [inaudible] format, and we were able to synthesize decompressors for those. We were able to synthesize inverses for formatting benchmarks and also for some linear arithmetic -- these are kind of image manipulation benchmarks which rotate, scale, or shift image, and that is a matrix computation and therefore is a linear arithmetic template, and we can synthesize the inverse for that. Yeah? >>: Does the performance of the synthesizing versus -- is it comparable to the original, or is it different? Saurabh Srivastava: So I put a core off the algorithm that goes inside. So the performance -- yeah, those are -- like the actual algorithm is 10, 20 lines. The thing that actually goes inside, if you look at the library or something, that's probably 100 to 200 lines. I mean, that has been optimized way beyond what we synthesized. So, yeah, we're not doing any [inaudible] rolling, we're not doing any, like -- yeah, we're not doing anything for performance right now. >>: [inaudible]. Saurabh Srivastava: Huh? >>: [inaudible]. Saurabh Srivastava: I manually check that it's the correct one. did not actually run it. >>: [inaudible]. Saurabh Srivastava: >>: Oh, you mean run it to check performance? Yeah. Saurabh Srivastava: >>: I Oh, no, I didn't do that. [inaudible]. Saurabh Srivastava: Possibly. Okay. So the other thing that we tried was -- so program inversion was this thing where I was talking about taking a known program, having a template, and sequentially concatenating it. What we also wondered was can we do that for parallel composition. So client server synthesis. Can we -- it has somewhat of the same flavor. You have a client, you have a server, they run in parallel, they do communication in between. If you look at them separately, they have this complicated specification because of this communication that happens at the end of this, but if you combine them, then the messages communicated are all internal, and you can use a testing-based approach to say that, okay, the combined specification is very simple. That is, like, you know, some value from the client goes to the server or some area which represents the disc contents goes from the server to the client or someplace [inaudible]. And we run it over the components of the FTP client and we've been able to synthesize the client from the server. So this is approach, a testing-inspired approach, to synthesis which uses symbolic testing as a proxy for formal constraints, and the detail here is this path exploration that you need to make sure that the path that you explore are the relevant ones to pruning the space. So in the remaining time I just want to go over some future directions for the short-term future that I want that -- that I'm interested in. So one thing that I've been thinking about is using synthesis as sort of, quote, unquote, auditing compilation where you have a program and it communicates with the environment, and there is a specification for what the interface should look like. And what we might be able to do is say that, you know what, if a communication does not meet the interface specification, can we synthesize a wrapper that allows us to take that program, and if you wrap it inside this thing, then it will meet the specification. And what I'm thinking about here is probably in the context of security. For instance, information flow. So if it, let's say, leaks some information, the interface verification could say that the variable's high level should never go to the output. So can we synthesize a wrapper that sanitizes the output or obfuscation or tamper protection. Can we synthesize a wrapper that obfuscates the execution such that the environment cannot infers some properties about it. So that is some potential that I might be interested in exploring. For instance, for the case of information flow, Merlin, which is Ben, Aditya, Sriram and Anindya, that tells us paths in the execution that do not have a sanitizer on it. So instead of just out putting that, oh, this program is unsafe now because that part does not have a sanitizer, can we synthesize a sanitizer that we can insert now. So that's one thing. The other interesting bit might be can we use a synthesis to take a program that is kind of generic and then add some architectural constraints or limitations and parameterize the synthesizer using it to get an object-specific program. And what am I thinking about here? Well, for instance, these constraints could be over memory. For instance, you have embedded systems which are memory constraints. Can we synthesize -- just from the C program, can we synthesize a program that runs on the memory-constrained system or some process configurations. Well, for the cell processor, can we take a C program and generate a program that has two components, one for the PP and one for the SP, can we add some constraints about that to network communication. The client server synthesis is some preliminary stuff that we've done in that context. Can we do it better or synthesize from a sequential program or distribute a version for it with the same semantics. We can keep -- first three, well, they look like they might be doable. We can keep going. Can we add constraints about operations. Map-reduce computation has this domain where everything has to be key value pairs. Well, can we impose that. Can an algorithm be taken from a sequential C program, can we take it to a map-reduce program. Energy efficiency. Now it's getting really vague. So energy efficiency, can we add constraints for that. Or performance. Actually, I'm trying to do that as part of a course project. Let's see where that goes or -- well, keep adding. So essentially the big question here is can we have formal constraints that encode those things. Well, memory, we kind of did that for the stack. Operations, they're kind of like -- they're not exactly process configurations, but they're telling you something about the computation that's happening. It might not be possible to do that for everything, right? And taking [inaudible] work, we can do -- if we can do formal constraint encoding, we can possibly do post-generation filtering. So we compute some candidate programs, and then based on whatever criteria we have, we might want to filter them out. Okay. So that is one thing. The others, domains -- I'm most interested in applying this to other domains, and that is -- well, yeah. So, for instance, functional programming. So [inaudible] Appel says that SSA style is functional programming. So does that mean that synthesis over SSA style implies that we have synthesis for functional programs? Well, I don't know. The heap is certainly something that I have not addressed even for reasoning or for synthesis, so I want to talk about the heap. Because I use SMT solvers, well, it might be reasonable to talk of the approaches that have come out of MSR to reason about the heap. For concurrency, again, a non-explored domain. And I'm talk to Viktor about using a satisfiability-based approach to infer the assumes and guarantees that he has in his deny guarantee work. Certainly interesting is probably modular synthesis. And here what we want to do is we want to talk a top-level specification that we have, we want to synthesize some functions based on some interface that is provided by a lower level and then keep doing this until we get to the bottom. Now, that in general will be difficult because you do not know what the interface will be. So can we talk about co-synthesis of hardware and software? Because there the interface is very well specified. The hardware has a particular interface that it can give you. Can we synthesize on top of that? And also can we mix this verified and testing-based approach? So let me just draw a diagram for what the spectrum looks like. On the one hand, you have an inductive synthesis which kind of generalizes from instances as you were pointing out. And on the other you have deductive synthesis which refines from specifications. And deductive synthesis is essentially -- all of that stems from Manna and Waldinger's work from the '70s and '80s, and essentially that is pretty much manual. Proof-theoretic synthesis probably lies there. It is an automated version of that. On the other end of the spectrum you certainly have a sketching, [inaudible] work, and so much work I think falls there. Path-based inductive synthesis, somewhere in the middle. And what I'm wondering is that can we explore this space, you know, combine this and this or that and that, you know. Okay. So just to conclude, I think satisfiability-based -- a satisfiability-based approach is very -- has a lot of potential for expressive program verification instances, for building expressive tools for program verification instances. And all of what I talked about here is available on my web page, and -- yeah, with that, I'd like to conclude. Thank you. [applause]. Saurabh Srivastava: Yeah? >>: So you didn't mention in the future work sort of any focus on trying to understand the scale of the existing stuff you've done. In other words, sort of if you took what you're doing now, say inverse, and, you know, you wanted to make it useful, so what would it take, are you interested in doing that? Saurabh Srivastava: Yeah, yeah, certainly. I did not have a lot of concrete stuff to say that, so that's why I removed it. It was in my early version of SAT. So one thing that I sort of glossed over is that this approach for reasoning that we have is massively parallelizable. So one approach for scalability could be can we just do the reductions that I'm talking about, each one of those verification conditions reducing to a boolean, parallel boolean formula, can we do that in parallel. So that could be one approach to addressing scalability. But certainly that's very much on my list. I'm interested in doing that. We will have to explore ideas about how this needs to go forward. Sumit Gulwani: [applause] Any other questions? Okay. Let's thank our speaker.