>> Tom Ball: Welcome, everybody, here and in virtual... to have Roman Manevich return to Microsoft Research. He's...

advertisement

>> Tom Ball: Welcome, everybody, here and in virtual reality. I'm really pleased to have Roman Manevich return to Microsoft Research. He's completing his

Ph.D. from Telviv University under the direction of Professor Mooly Sagiv.

Roman is known for doing a lot of interesting work around the area of shape analysis, in particular how to make it more efficient and precise and today he's going to tell us how that applies to both sequential and congruent programs.

Roman, take it away.

>> Roman Manevich: Thank you, Tom.

Is it on, by the way? Can you hear me?

>>: Yeah.

>> Roman Manevich: Okay. So I'm glad to be here. And let me start with this by showing you this program. So this program, along with some properties, posits a very interesting verification problem. What this problem does is an implementation of a concurrent stack in terms of a single ink list.

You can see it has a push operation and a pop operation. And so now what I want to answer some verification, I want to verify some properties.

For example, so we can see it has pointer updates. For example I want to show out that every pointer update is safe. I only want to access legal memory, that

I'm not going to cause any memory leaks and that I'm not going to -- so that the list is going to remain acyclic, which is what is meant by this program.

Another program I'd like to verify is that this stack is linearizable. So I will just, so

I'll come back to this, define this a little bit more precisely later. But right now let me tell you usability is a major criterion for concurrent data structure.

Intuitively it means every concurrent execution of a data structure has a corresponding sequential operation. Some times the operations, push and pop operations you can think of them as atomic operations.

You can see this problem is very challenging. Challenging because of different things. For example, we have dynamic memory allocation, which means that the number of objects is unbounded. Of course, we have the list, which is recourse of data the structure. So we have to reason about it.

As I said, we have a destructive pointer updates. I don't have any assumption on the number of threads. So the push and pop can be executed by any number of threads. And also so this implementation is not blocking. It uses comparant swap for synchronizing the threads, which means that several different threads may be exercising the same objects simultaneously.

Another thing that makes it challenging. The good news is that what I'm going to tell you about are some ideas and techniques that would let me, for example, verify the linearizability and the other properties that I've talked about automatically for an unbound number of threads.

So for now when I talk about concrete states, I'm going to -- so in my analysis concrete states are simply labeled graphs. So and these graphs represent both the dynamically allocated object such as the list elements and also you can see the hexagons actually represent thread objects and they have the program counter written inside.

And they have edges to represent list fields and also edges to represent thread local variables.

So you can just think about so I'm going to present different kinds of shape obstructions. And these obstructions are going to work formally, they're going to handle shapes, these labeled graphs, and handle uniformly objects and control.

>>: If you can go back one slide, I'm sorry. What are the states -- so I program in C# these days and Java and the code like this. Why should I wait?

>> Roman Manevich: Well, maybe you don't write code like this. So this implementation is in C++ but also implementations of these concurrent data structures for Java and C#, and I guess also implementation --

>>: You're using them so you'd like to know they're correct.

>> Roman Manevich: Right. But the idea is to have efficient implementations of all your concurrent data structure. And so really there are two kinds of classes of obstructions that you can think of. And you can think about obstructions as in terms of the invariants they represent or the forms of the invariants.

So we can have, subjunctive school says it should represent invariants in terms of injunctions. So joint operation merges different object states is simply disjunction and this approach is very popular in the model checking community.

On the other hand, there's what I call the partially disjunctive school in which invariants are represented by conjunctions or universal quantification, which is just the infantry version of conjunction.

So in this kind of invariants when you merge different object states, you actually lose information by the join. I'll show you example. This approach is very popular in the objective interpretation community and also the data flow community.

Here's a research problem that interests me. There are various kinds of shape obstructions, and they usually use -- so this highly precise high precision shape obstructions actually use join as, actually use disjunction as their joint operation which means they're very precise but in some sense they're more precise than they need to be, meaning that they actually track more locations that they need in order to prove the properties that we're interested in.

And because they use this junction, that means that the states based very easily blows up. So these obstructions can be either exponential, they're even doubly exponential obstructions and so my take on this is that I'm going to define a different class of shape obstructions that are partial disjunctive, and I'm going to design them in such a way that they're going to be precise enough for the class of programs and properties that I'm interested in proving.

And I'm going to also aim them specifically through using different kinds of exponential structures. You'll see examples later. And the benefit that I get is orders of magnitude of speed up.

So really the challenge is how do you design these partially disjunctive obstructions for the heap. And so this is very challenging, because the objects and the threads are anonymous. You can't actually reason about the correlations between the addresses. The addresses are completely coincidental.

So here's some results. The first one is a framework to create these partially disjunctive obstructions. They're defined in terms of the concept of heap composition, which I talk about which is the main issue in my talk.

And the idea is that in still checking correlations for the global heap, what you do is take a heap and you break it apart. You decompose it into a set of smaller subheaps. Extract the correlations of the object properties inside each of the subheaps. You abstract them from the different subheaps.

And this is good because suppose it takes different full heaps and decompose them. Some of the sub use may actually appear in the different full heaps which means that after you create obstruction you're actually reusing them and that helps you reduce the size of the states base.

And I've created an automatic system that lets the user specify different kinds of the compositions. So these they can specify the compositions and automatic generate an obstruct interpreter, which is static analysis which is it's guaranteed to be sound and the obstructions forms are feasible. And I've applied this technique both to sequential programs that analyze multiple data structures and also to concurrent (inaudible) programs for proving linearizability for an unbound thread type.

So in the rest of the talk I will -- so first I will start talking about I will show you some examples of disjunctive and partially disjunctive obstructions and then I will go on to introduce the concept of heap composition. I'll show you some examples and define more precisely.

Then I'll show you the application to creating kind of a thread modeler analysis for fine grain concurrent programs.

Okay. So let's start with some background. So in static analysis what we usually do is we take an infant states base and we create a fine obstruction. Think about this yellow square as representing the infant number of points inside it. And let's pick some points. These green points that I've marked.

And the way to create an obstruction is by creating, is by creating a finite, we partition the states base into a finite number of equivalent structures represented by the squares. The way the obstruction works is by mapping every one of these green points to the square that covers it.

And that gives you the abstract base state which is just a set of these squares.

So it's finite. And on the left side you can see the logical representation of this abstract state, which is as you see it's a disjunction. So this is an example of disjunctive obstruction. It's just a disjunction of these inequations that correlate X and Y.

So now I told you that when you have two abstract states and you merge them together by joints, this is something that happens on control flow pass when control flow pass in the program actually join.

When you put them together, what you do you don't actually lose any information you simply use disjunction. So just keep a set of disjunction that you used to have. So this is very simple. Now here's an example of a partial disjunctive obstruction. This obstruction what we do we take a set of points and cover it by a single rectangle. You can see the abstract states are obstructions that correlate

X and Y.

But in this obstruction actually I'm tracking X and Y independently. When I take two object states and I join them, what do I get? I get a single rectangle that covers all of them. And what you can see is that I get -- so finally I get a single conjunction. So I've lost some correlations between X and Y. And you can see the loss of correlation by these red corners which represent the loss of precision that I get by the joint operation itself.

So here join is obviously across this junction. So let me now compare the dejunctive obstructions to partially dejunctive obstructions in a more general way.

In dejunctive obstructions we don't lose any information when you join abstract states that come from different flow throw passes you see the examples. The downside is because we were using disjunctions we immediately get exponential blowups. Because we can get on super sets of the states.

And now there was an important question with obstructions is given the program and the property, can you actually automatically come up with a good obstruction for this verification problem? And this problem has been studied, there's a large body of work, for example, for (inaudible) obstruction which is a disjunctive obstruction.

So this work about counter example obstruction refinement. That gives you some very good answers by using (inaudible) obstructions for example, and so for partial disjunctive obstructions, we actually lose many correlations when they merge object states.

The benefit that we get is that we can actually reduce many exponential factors.

And now for the question how do you come up with the right obstruction?

So that question has not been studied as much as in the disjunctive case. And it's a bit harder because in some sense you have to -- so in the disjunctive case when you run these counter refinements they're usually considered just one trace in the program. And they try to rule it out by coming up with a more precise obstruction.

For partial disjunctive obstructions you actually have to learn, generalize from multiple choices simultaneously. So it's a bit harder.

Now let's go and -- so now I'm going to talk about how do you come up with these partial disjunctive obstructions for the heap. For the heap scaling is a major issue. So the kind of obstruction we use are costly. They can be doubly exponential and of course the state is infinite and we don't have the bound of number of objects and threads. So when concurrency complicates things, you can have different kinds of connections between the thread and the object.

And so the question is how do you create these partially disjunctive or Cartesian obstructions for the heap that give the right correlations. But as I've shown you in the example with the numbers, the trick, some correlations between the numerical values and we lose others.

So the semantics we use for the heap is tallest we can't actually use the addresses. So we have to do something else. So that's a challenge. And as I told you, there's no bound on the number of objects and threads.

The question is which kind of correlations are actually important for different kinds of progress and properties. So here's these techniques can really help us but I'm not going to address it in this talk.

What I'm going to show you is class of programs and properties and I'm going to show you how to come up with the right obstructions, with the right partially disjunctive obstructions for this.

And another note I want to make is that it's possible that you have obstruction actually -- it's not always the case that if your obstructions are closer then it's more efficient. I actually show you one example where you actually keep less correlations and then the analysis becomes actually less efficient.

So now what is the idea of -- so now I'm going to tell you about partially disjunctive obstruction using partial heap obstruction. What's this idea. You take a heap and compose it into a set of different heaps.

I'm blocking your view. What do these set of subheaps represent? It simply represents the full heap that we started with. We haven't lost any information yet.

Take another heap and decompose it. We get these four subheaps. What they represent is exactly the heap we start with. Again no loss of information.

What happens if we take both of them decompose them both together? Now we get a set of seven heaps, and actually represent the same -- so they represent both of the full heaps that we saw earlier. Of course it's a sound obstruction.

But in addition it does represent these two full heaps, which you get by mixing different subheaps that we've seen earlier.

For example, how do you get this full heap? We'll take X and Y, this subheap from here Z from here and we'll take P and Q from the subheaps over here.

We'll compose them together and get this full subheap.

We get this full heap by taking this subheap and that subheap and the subheap from here. You can take the subheaps and compose them together and create, and they actually represent new full heaps that weren't there in the beginning.

But not all the combinations actually are consistent. For example, if you take X and Y from here and Y and Z from here and you compose them together, then this is actually not consistent. So I haven't defined the right exactly what the composition is that I'm talking about. You'll see the examples showing not all the compositions actually are actually consistent.

So now I'm going to talk about specific instance of this general heap to composition idea which is the composition into connect components.

And now I'll actually contrast three kinds of obstructions. One is disjunctive obstruction. So here's an example of a program. So let me start with this super memory configuration, which has three objects pointed to by X, Y and Z. What this program does it automatically inserts a link between X and Y or Y and Z. So we get these two object states.

When we merge them together we simply get disjunction. And now we can actually and suppose we want to assert that you cannot get from X to Z in two steps. So we simply check it on each of these full heaps.

And the property holds. So that's good. Downside is you can see the states base is going to grow. And usually so this is the cause of exponential blowups to get from disjunctions.

Here's a kind of obstruction that's a partial disjunctive heap obstruction. It starts the same but now when you merge these two states, we start losing correlations between the edges.

So we remember that we have three objects. But we also have correlations between the links. So these are made edges. So we're not actually showing whether it's possible or not possible to get from X to Z in two steps.

So we're not able to to actually prove this property.

So we can think about the space of all possible heap obstructions. So now I've shown you a simple example of a disjunctive heap obstruction and I'm showing

you an example of an independent heap obstruction. Now I'm going to talk about the component composition, I'm going to define and apply to this program and show that it's more precise and in using it we can verify the property for this program.

So the idea as I told you is to take a heap. And decompose it in a special way.

The way to decompose it in the set of disjoint memory regions. So in this case three objects, each of these objects is actually its own memory regions. There are no links between them.

So we split it and you get three subheaps. What do we actually maintain in this obstruction? We maintain all the area simulations and all the iterability situations.

What do we lose? We lose the configuration between the data structures.

So what does each of these subheaps actually represent? So the obstruction works by projecting the heap on just a portion of the heap. So when you think about the subheaps, what it actually represents is all the heaps that have -- so they're the rest of the heap we don't know anything about except there are no edges going from here to there and Y and Z can only point to some other part of the heap. And represent the same idea in logical notations. So this, for example, says there is a connect component. It contains X but it does not contain Y or Z and there are no edges. So we can see there's no filter, for example, from S to X and similar for these two.

So here's a set of subheaps that have decompose subheaps. What do they actually represent? So you can see that this, for example, has a component that contains X, Y. It does not contain Z. The set of edges that it has is just a single edge from X to Y.

And this one has it's a connect component. It does not have X or Y. It has Z.

There are no edges. Similarly for the other two. And what does this set of subheaps represent? So remember that the meaning of a set of subheaps is given by composition.

>>: One question. Do you need this close assumption that you know would hold

X, Y and Z, or is this sort of open for any other object that I might know about that's different from X? So make it explicit, not just Z. Is that for your slides or is that something you need to know from the universe?

>> Roman Manevich: Do you mean whether I know that the set of variables is, whether I know the variables in advance or not?

>>: Yes.

>> Roman Manevich: So for the kind of analysis I've used I do know the set of variables. But I have to think what it means for -- I think the same idea. I could actually use it for library where I don't have any assumption on the entire set of variables. But I have to think about it a bit more. In the setting I've applied I've used it for programs where the set of variables is known in advance. And so the meaning of a set of subheaps is given by composition.

So a set of subheaps represents all the full heaps by composing the subheaps.

For example I can take the two on the left and compose them together and they represent the full heap. I can take the two on the right, compose them together and I get this full heap.

And when I compose it I have to take care all the variables appeared there. So, for example, if I tried to compose these two, you can see that I have X and Z. Is the logical notation. But Y doesn't appear there. So this heap does not represent -- it's not a full heap because it doesn't contain all the program variables.

And also I cannot -- if I try to compose different subheaps that have different variables in common then I get inconsistent heaps. For example, if I take these two and I try to compose them together, they're actually inconsistent because of the composition that I'm using.

So if we look at the logical notation we can see it. Suppose we try -- so when I compose actually tell you the conjunction of these two. Suppose I try to take the conjunction of them. So this is the invariant that I get. And let's see what it says.

It says the heap contains a connect component that contains X and Y and definitely not Z. And simultaneously it contains a connect component that does not contain X but does contain Y and Z. Which is impossible. So this is not represented.

And this is actually going to be important. So let's go back to the example we saw. Let us apply this composition. So we started with this memory configuration, spread it apart. Go to the left and to the right we get these subheaps. We join them together. We get a set of four subheaps I've just shown you and what they represent. Exactly these two full heaps. You can see it for each one of them the property holds.

So I'm actually able to verify this property for this program.

>>: Can you go back and just do that one more time just a little more slowly.

>> Roman Manevich: Sure. So I started with this memory configuration. So the full memory configuration used to have these three objects simultaneously. I spread it apart. I apply the composition. So when I go to the left this is something that I actually went over quickly. What I do I temporarily compose some of the subheaps. So I try to install a link between X and Y. I join these two subheaps. And then I try to decompose and I get these two subheaps. And I do the same thing for the right.

>>: So there is always -- so you always sort of recompose after.

>> Roman Manevich: Right. So during this I temporarily compose some of the subheaps. I try not to compose, if I try to compose all of them to get the set of full heaps, of course, I get all the exponential blowups that I tried to avoid.

>>: So this is something like a focus operation?

>> Roman Manevich: It's a (inaudible). So I temporarily refine my obstruction.

So in this case for example for this I temporarily find my obstruction by saying I'm going to decompose the heap except if it contains X and Y simultaneously, for the subheap that contains X and Y not going to decompose it. What I'll will happen I'll get a subheap that looks exactly like this except X and Y are in the same subheap. Then I'm going to apply distance former. I'm going to install the link. I'm going to get this and then I'm going to decompose. And because these are -- because I installed a link this becomes a single component. And the same for the right side. And then I just put them together.

As I said this is their meaning and I'm able to --

>>: So you might want to talk about how do you verify the property of the full heaps without actually (inaudible) the full heaps.

>> Roman Manevich: Right. What I'm going to do, I'm going to apply just a small number of subheaps that are going to, for example, suppose I have this set of four of four subheaps. And I want to verify you cannot get from X to Z. How would I do it? I would start from the subheaps that contain X. So these two.

And then I'm going to look for the rest of the heap. So actually in this case I don't really have to do it. So suppose I start with this subheap and I ask, can it possibly be a part of a full heap which contains a path from X to Z? That's not possible because this is a single connect component. Remember that what the connect component represents is a heap that contains this part and there are no edges between this part and the rest of the heap.

So I know it's impossible to get from X to Z from here. I can actually apply this very local reasoning for each of the subheaps. But sometimes I have to do some composition. So suppose I'm actually, I don't use this reason on the connect components.

So what I might try to do is actually try to take this one and look for Z and compose them together to get the slightly larger subheap.

So the idea is to get the smallest subheaps to compose the smallest subheaps that would allow me to verify the property.

>>: So suppose on the heap on the right if you had X points to Y, Y points to Z and on the left you had X points to Y and Z and then Z. You merge these two, what do you get?

>> Roman Manevich: You're changing --

>>: On the right side you actually have an extra inch from X to Y.

>> Roman Manevich: Okay.

>>: And then if you want to merge, you still have two connecter components.

One which contains just X and Y, another one that contains Y and Z.

>> Roman Manevich: I have this component and this component and this will be a single component that has X, Y, Z simultaneously in the same component.

>>: So you formally can get as large as the number as you can in your universe.

>> Roman Manevich: Right.

>>: That can be very large.

>> Roman Manevich: Right.

>>: Exponential or a factor.

>> Roman Manevich: No, I mean it can -- no. It's linear in the number of program variables. So a connect component contains a subset of the variables.

So it must have all the program variables. So suppose the number of variables is

N.

>>: So each component can be -- we can have different components.

>>: No. If you're not blowing garbage, then every connecter component starts with some program variant --

>> Roman Manevich: The number of subheaps is indeed exponential. You're right. So I mean the size -- in the worst case I haven't reduced the height of the lattice. It's a smaller exponential but it's exponential, you're right. It doesn't magically become (inaudible) just the composition. No.

>>: Single.

>> Roman Manevich: Actually, okay. So I haven't shown a particular kind of obstruction. So actually for the particular kind of obstruction that I'm going to use, that I'm going to apply connect component to the composition, the obstruction that I start with is also exponential. After I decompose I still have an exponential factor. But empirically it's better. I'm going to lose some correlations.

So for the more general kind of decomposition, you can actually tune them to have polynomial states, in the worst case. But here I haven't done it. For example, I can try to further decompose the heap and try, so, for example, I could take this subheap and try to use the correlation between Y and Z.

Or I might even have an object that's pointed to by several variables and I can actually try compose it using the correlation using the (inaudible) relations but in practice that turns out to be a bad idea. So I'm not going to do it. You can think about the finer composition. I'm going to discuss finite compositions but in this scheme I'm not going to do it.

This is as fine as I get. A single component is the smallest subheap that I get.

Okay. So we've had this. And now you might ask me how do you actually get the finite obstruction? So using the composition alone, you don't get the finite obstruction.

So I'm actually going to combine the idea of heap to composition and other kind of bounding binary heap obstruction. So there's conical heap obstruction and there's a kind of obstruction I devised for possibly cyclic list. So let me show you just one kind of, let me show you this obstruction, it's pretty simple.

Suppose you have a heap that has set of program variables. The idea of this obstruction is I'm going to maintain important objects, these blue objects either pointed to by program variables or have more than one incoming edge.

What the obstruction does it maintains them but it's going to obstruct the length of the sub lists that's connected. So I get this bounded heap. So this heap may be unbounded and this heap remembers that the length of the list is either 1 or greater than 1 so it becomes bounded. In the rest of the talk I'm not really going to make a distinction between the concrete heaps and these bounded heaps. So just think about them as different kinds of labeled graphs.

So the idea of the composition works well for both concrete heaps. For these concrete labeled graphs and also for these bounded label graphs. So it's not a distinction that is going to be important for us. So the idea of the composition is for this bounding obstruction.

Now really to get a finite obstruction, what I do I can compose these obstructions in different ways. For example I can start with full heaps and then decompose them. So now I get a set of these subheaps that are actually not bounded then apply this bounding obstruction to them. So this might be a very, very long list, and then after I bound it, I get this bounded subheap.

And the meaning is given by first exploding these heaps and then composing into a set of full heaps. Or I can go the other way around. I can take a heap and then bound it. And then decompose this abstract heap.

>>: So does that mean you can't deal with data obstructions in which the number of minutes were (inaudible) edges that were unbounded? Redacted, the structure?

>> Roman Manevich: Not for this, not in this obstruction. This obstruction is specifically aimed for single link list but there are actually extensions of this work that using very similar ideas, keeping important objects and then obstructing away some paths between them that so people actually use this ideas and extend them to trees and other data structures.

But I'm going to use this obstruction. I mention it because meta data, because which is one of my results but I'll use it in my slides.

So for us right now let's just think about single link list. And so actually in the kind of the compositions I'm using, these operations commute. So it really doesn't actually -- it's really not important. So in practice the order actually doesn't matter. I get the same result.

So I showed you an example. Let's see a more real example that shows what kind of exponential factors do I aim to reduce.

In this example, I have a precondition, don't actually have to read it in order to read the precondition, but what it says is that we have this one element list that are pointed to by H1 and T1, and H2 and T 2 respectively. What it proves is every iteration of the loop, the invariant looks like this. I have these two lists that are disjoint, acyclic. H1 points to the first element and T1 to the left one and the same for the second list. Simple program.

What does it do? It creates an object. It puts it in the list element and then it

(inaudible) inserts this element into the first list or the second list.

If I want to verify that this loop invariant holds, the observation is that actually I don't have to track the correlations between the properties of the different lists. I can actually track them separately, using connect component composition.

And so this is what I'm going to do. And so this is actually very common pattern in device drivers where, so this might be an IFP object which you insert into a list.

Sorry, is there a question?

So suppose we're on an disjunctive obstruction, what I get is a loop head. What I get is -- yeah? You seem to be confused.

>>: You're going debug the program.

>> Roman Manevich: No, there's no bug. This program doesn't have a bug. I want to verify that it's actually.

>>: Okay.

>> Roman Manevich: I want to verify that this property holds and indeed it holds.

I don't have an example with a bug.

>>: Trivially holds.

>> Roman Manevich: It trivially holds? In what sense? I mean you have to reason about across the data structures, what does trivially hold. I don't see how you can actually verify this locally.

>>: Okay.

>> Roman Manevich: When you actually try to dereference, you have to attract the relationships you have to know which regions exist on the heap. You have to know the sub regions actually disjoint. For example if you use a flow sensitive

pointer analysis you can't actually -- all this is going to be clumped together into one single points node. And so if you run a disjunctive obstruction what you get is the disjunction of these nine abstract states. Each of the states is made of a conjunction of the properties of each of the lists.

So, for example, for the first list it can either be of size one, two or more than two.

And the same for the second list. One, two, or more than two and you get combinations of all these other properties. So you get nine abstract states.

So actually what we see here is that you can see that in each one of the rows are the subheaps for the second list, they keep reappearing. These are common sub graphs. And the same for the rows and the list number one. You can see in the rows the sub graphs first number one are also common.

I actually want to exploit this. So now suppose I apply connect component to the composition to these heaps. So what do I get? I get just these six heaps. And in this case I don't actually lose any information, because so what they represent.

So if you take any one of these three subheaps and you compose it with any one of these three subheaps you get exactly one of these nine abstract states that I'm showing here.

But you can see I went down from nine to six states, which is nice. And if I use this kind of obstruction for this program it's still precise enough to prove the property that I wanted to. But actually lost the correlations between the two lists.

So when I can think about the generalization of this program that has any number of lists I'm careless, for example, and if I use this kind of obstruction, compare it to disjunctive heap obstruction, I go down from 3K to 2K, three times

K abstract states.

This shows the exponential reduction in the states that I want to have.

>>: Going back to Nicolai's point?

>>: No, I understand. T is the tail of this (inaudible).

>>: No, no, I guess. But I guess the main is not so much about -- there's a difficulty in updating, maintaining the broadest reach in disjoint lists under these assignments, destructive updates.

>> Roman Manevich: Right.

>>: And fundamentally that's a difficult problem, but what you're addressing is really efficiency, maintain be efficiency and precision. So there's a problem of precise updates then there's the problem of sort of efficiency.

So really here what you're showing is that like we could do it sort of the normal way with maybe just some verification condition generation and some checking in a theorem prover suitably enriched with these predicates. But we might have to

explore unnecessary correlations here showing a decomposition that retains the efficiency.

>> Roman Manevich: Exactly.

>>: So really your technique is not -- it's not even the question of the precise product.

>> Roman Manevich: Right.

>>: In some sense.

>> Roman Manevich: Yeah.

>>: Do you feel the partial disjunction is bigger than the smaller, or you can think of cases where the partial would be even larger?

>> Roman Manevich: It could be larger. Suppose I take this heap and you decompose it you get two subheaps. If you compare the number of heaps and subheaps, then, yeah, it can be larger. It can be even worse in practice. You might actually lose important information and start exploring all sorts of impossible situations. That could happen.

>>: (Inaudible).

>> Roman Manevich: Right. So for the class of the product I'm interested in, for example, these device drivers have a bunch of list then manipulate them. If you want to prove cleanest properties that you don't destroy the list and don't do null nonpointer updates memory leaks you don't have to track the correlations between the lists. That's the observations that are used.

>>: When you say correlations between your list, do you mean actual values in the list?

>> Roman Manevich: No, the correlations between --

>>: Then you would actually (inaudible) because you're not in the divide them. If they're pointed correlations then the subdivision rule would have connected them so you won't be able to use that (inaudible) right? So what correlations are you talking about?

>> Roman Manevich: These are the correlations between the abstract properties. For example, so here the correlation I have is that simultaneously the first list has a length greater than 1. And the second list has a length exactly 1.

>>: For example, (inaudible) properties are size properties?

>> Roman Manevich: Right, exactly.

>>: I think you're using the fact that many times it's composed of different independent things and then there's no reason to always talk about holding them all together.

>> Roman Manevich: Right.

>>: That's in practice.

>> Roman Manevich: In practice. Yes, this is what interests me.

>>: Purely disjunctive approach for that which would not eagerly expand and

(inaudible) structure could also take advantage of this case, right? You will talk about that later?

>> Roman Manevich: (Inaudible) going to.

>>: If you have basically local properties and these subheaps are kind of independent.

>> Roman Manevich: They're not -- sorry. So they're not statically independent.

It's not like you can look at the program and say, for example, maybe this is a trivial example. But temp is going to use, is going to be used in both of these lists it's not like you can run the point analysis and say these are going to be forever disjoint and so let's just check them, let's separate the problem into a set of sub problems.

So this example is just temporarily available but there are examples where some of the pointers are actually going to be used in one list and then they're going to be used for the other list. So you can't actually statically determine the right kind of decomposition. So the decomposition I'm using is determined dynamically during the analysis, as the connect components get connected and disconnected.

So it's dynamically determined, the composition. Okay?

>>: So I have another question. So in programs you have lots of feeds and you have the next feed and you have a clean field. So when you're doing this analysis you can really focus on one field at a time?

>> Roman Manevich: In this example, in this specific example for the examples I have here of just one field. But this is an interesting idea. So this is not the idea that I explored here. But so what you're suggesting is a different kind of decomposition, which sounds interesting.

>>: Exactly if you just focus on one field, right, the connector competence you get would be different than if you just focus on another field.

>> Roman Manevich: Right.

>>: So usually these ink lists they may not be reachable through the next field but they might be connected in other ways. Usually they're part of some bigger data structure.

>> Roman Manevich: Right. You can consider different kinds of generalizations of reachability properties to determine the decomposition. So maybe you can take the union of all fields and then create your connect components. But this is one -- right now what I want to show you is one specific instance that shows how this idea of the composition is interesting why it's worth considering.

Later I'm going to tell you about more general kinds of decompositions. But here

I'm just exploring just one decomposition. So now there's also the question of in abstract interpretation we have the question which kind of obstructions do you use. And also the question of how do you approximate the concrete semantics of the program in your obstruction.

And we call it how do you actually compute obstruction transformers, transformers that overapproximate program statements under the obstruction.

And so for this specifically the composition are considered this problem theoretically and I found out it's actually anti complete. You can't actually -- so basic to compute you can't avoid doing something which is sub exponential which is better than maybe just composing all the heaps.

And so what I did is I compromised. I devised a set of assumption transformers that are polynomial. Intuitively they only compose as much of the small number of subheaps, no more than two and three. As much as we need to compute the problem statements. I find that these are actually very useful for the kind of analysis that I have.

And the overanalysis when I applied it it was precise enough for a class of programs. I'm going to show you some experimental results and I got very nice speed-ups.

So here are some experimental results.

So I applied this analysis to both artificial examples and also Windows device drivers. So this is for the serial device driver and so this is for kind of a slice of the serial device driver, just the firewall driver. These are programs that take a list and split it into five lists or take five lists and join them together and you can see, so this goes to your question you can't actually statically determine the decomposition, because at some point all these actually interact with all the other lists. At some point actually all in the same component.

And so this is actually an artificial example that I created to show that you can actually lose important correlations.

So in this program you -- so this program maintains a queue by two stacks. Two single ink lists.

>>: What's the property you are changing?

>> Roman Manevich: I was checking the cleanest property you don't have memory leaks. Now pointer updates. Some properties about disjointness of different lists, reachability between program variables.

So I have a prototype that lets you write assertions in terms of reachability, cyclicisity and acyclicisity?

>>: (Inaudible).

>> Roman Manevich: No. Automatically infers the invariants as is done standard in abstract interpretation. It runs the obstruction. Explores the abstract state space and automatically proves it. They're notations you can write your assertions but it's only for checking. And so this is the queue that's maintained by two lists.

So the thing is here that in order to know the queue is empty you have to simultaneously know the two lists are empty.

If you actually lose the correlations between them you can't tell that. That's why in this example actually losing important information I can't actually prove important properties, and if I compare the number of shape graphs that we get for the dejunctive obstruction for the number of subgraphs I get for the component obstruction, you can see usually have nice reduction.

So I was actually able to reduce the size of the reachable state space usually by order of magnitude. But in this case the state is larger because I'm exploring impossible situations.

In terms of the speed-ups I actually have nice speed-ups for but for this example again it runs twice as slow. So this goes back to what I told you in the beginning that sometimes losing correlations doesn't mean you get more efficient analysis.

So it's actually important to tune your obstruction such that it doesn't lose too many, doesn't lose too many correlations.

Now the question is --

>>: So (inaudible) can you give us examples of whether the single procedure, was it an individual procedure?

>> Roman Manevich: So these examples are interprocedural, but you don't have recursive calls, so I just use the cost string approach.

>>: (Inaudible).

>> Roman Manevich: Yes, I'm using the -- yes. So the abstract space has the list and also maintains the call stack, which is finite.

So another question is this the end of the road? We have a this decomposition, we have the nice results.

As some of you mentioned sometimes you can get, the heap can get very complex. It can have things that are not lists and very often the heap is actually just, it can even be just a single connect component. And that means that you can't actually -- the composition doesn't actually buy you anything. And another question is what happens when you have concurrency, how do you actually define the compositions that allow you to reduce for example interleaving, the object due to interleaving. It's actually sometimes important to keep correlations across different connect components.

One trivial example was this key examples but there are other examples.

So, okay, so this leads me to the idea of more generalized kind of decompositions. So for this purpose I came up with a system so it's a parametric system for heap decomposition, so you can experiment with different kinds of decompositions.

What you do with the system you specify the composition parameter. You have a specification for the abstract performers and the subheaps that you get don't have to be, they can actually overlap. There's no need for them to be disjoint.

And they also apply for concurrent states and some of this is automatically guaranteed.

So you can think about any kind of widely composition you want and any specification of the transformers, and you can never have sound list you can have precision or efficiency.

So let me show you just the kind of specification that I'm using so this is for actually I'm cheating a little bit. I'm showing one kind of specification that we're using. So in this kind of specification what you do is you define a finite set of unit predicates that capture a subset of the objects. So suppose we have the subheap, the full screen circle. V1 holds for a subset of the object. So V ranges over the objects and threads in the heap.

And P1 holds for a subset. And P2 holds for another subset. They may overlap.

What the decomposition does intuitively what it does is actually projects on that portion of the subheaps. So I'm maintaining a set of objects here and all the connections between them. But I'm losing the connection between the subheaps. So these are the parameters I'm using.

And going back to the base of obstruction, it's a more general kind of obstruction.

It's parametric. You can define different kinds, you can use it to describe different kinds of obstructions. And I've applied this obstruction to proving realizability.

>>: How is it? Is it a stack that (inaudible) like this parametric?

>> Roman Manevich: So the unit predicates. There are two classes of unit predicates. The unit predicates they use they use it for the binding obstruction.

Here I'm using it for the composition. So actually the truth is I integrated this idea

into the TVLA system where I have actually two classes of predicates. One class of predicates is used for in order to bound just the heaps and the other kind is used to decompose.

But you can actually theoretically can try to adapt this idea to different kinds of obstructions, not necessarily built in terms of predicates.

>>: Are you going to talk about the post compositions, maybe you have to partition base and reachability for connecting. I.e. it's obvious how you compose again and (inaudible) but now in this more general form it seems much more difficult.

>> Roman Manevich: Right.

>>: Are you going to talk about that?

>> Roman Manevich: Not so much. Okay. I'll go back to this point in various slides and I'll try to supply some answers.

Just the quick answer what I'm actually using, I'm using a meet algorithm for three valid structures which enables me to actually take two subheaps that may overlap and create a set of refind heaps that actually contain both of them. So what I'm actually using properties of the different abstract nodes of the summary nodes and I'm actually trying to see which ones can actually go together.

>>: I see. So you detect during the analysis whether you have an inconsistent heap (inaudible) versus the state decomposition. You know which ones you don't even have to try to --

>> Roman Manevich: Correct. The meet algorithm is actually very simple. You only combine subheaps that don't have variables in common. And that's a simple criteria.

But in the more general case it's a bit more complicated. But I'm not going to talk about it. So I took this idea of heap to composition. And I want to try to see whether I can verify fine grain concurrent programs and proving the realizability.

One of the goals was to create a thread modeler-like analysis. So what's a thread modeler analysis? So there are different kinds of notions. For example, so there are different works. There's one by Franigan Kadeer (phonetic), where the idea is you have different threads. So the hexagons represent different threads. And the resources are represented by the small squares. And the other idea is that you actually create a Cartesian obstruction that maintains the correlation between the local variables, the local state of each of these threads but loses the correlations and other -- and you also track properties of the global state but you lose the correlations between the different local states of the thread and the local state of each thread and the global state.

So but the problem is -- so this actually -- so this approach works well for programs that have, for example, numerical variables. It doesn't work so well for

the heap, because the heap you can think about it's just one single global variable. So if you -- so it tends to be very complicated so it doesn't actually buy you too much.

So now there's another work by Alexi gotman and what it does is it actually, he actually lets you split the heap into a set of disjoint components and he lets you associate local resource invariant with each one of these separated subheaps.

So this is slightly better approach. But the problem with this approach is it only works for cross grain concurrent programs. So if you have different kind of threads, like in the programs I've showed you, can actually act as the same kind of object then you're in trouble. It doesn't actually work.

And using the idea of heap to composition, I have actually a more general approach. And this approach you can actually have longest joint resource invariants.

For example, you can have a resource invariant for these four objects and for these four objects, these four objects and they may actually overlap. This is actually what lets me handle fine grain components.

So let me start with the example. In this example I have a global variable G.

Local variables X and Y. And now what does the program do? It assigns a new object to X. It assigns X to Y, and now I want to sort the thread in local invariant that allows me to get here. X equal to Y. Then I assign X to G and then I want to verify this assertion which is actually not local. Talks about the local table. That says G is not null.

So this is a fine grain concurrent program. And so let's just for example for right now let's run it in one thread. So let's see how it works.

So I start with this memory configuration where I have a single thread represented by the hexagon. I assign an object. I assign one to X. And I assign

G to X. So I have just these -- so I have these four possible memory configurations, and the logical form are represented by this invariant. You can see this is disjunctive invariant.

And here I maintain the correlations between the program counter and the qualities and disqualities of all the variabilities and null. So this is precise enough.

Now the question is how do I actually verify these assertions for an unbounded number of threads. So to do that I need to consider obstructions for programs we don't have any assumption on the number of the threads. This is an example of one concrete state. It's just one state. But it has numerous threads. For example it has two threads in program counter one, two threads executing program two and three and three threads at program four.

So what kind of obstructions can I perform here? So there's an idea that goes back to counter obstruction and what this idea says is that you actually conflate all the information for all the threads for the same program location.

There can only be a finite number of program locations. You summarize together all threads in the same location.

What you get is this bounded heap and now you can see we have these summary thread nodes that says this node represents one or more thread, executing this program location, and have the summary object which puts now more objects in the heap. And then also what you can see is that I also have these dashed edges that are actually may (phonetic) edges. After I perform the obstruction what you see is you can't even verify a thread local invariant. Right?

Because we don't know that X and Y, for every thread here X and Y are actually aliased. We lost this information, which is important.

So that's the correlation between the local variables and we can't prove that X is equal to Y.

What I'm going to use is I'm going to move to a richer class of obstructions for concurrent programs that are going to use the universal quantification. So here I have this thread index that I'm going to add to the predicate. So I'm adding it to the program counter in both the thread local variables. And this is the invariant -- this is actually part of the invariant I get for this program. And so it is in these predicates. And each of these disjuncts represent the obstruction of the state from the point of view of just one thread.

And I'm going to combine this idea of -- so remember that in the beginning I told you that the C and F invariants, they have this infantry version which is just universal quantification. So I'm going to combine this idea with heap to composition.

I'm going to use the marginal kind of heap to compositions that allow me to move. So before I had this finite number of components that I could define.

Using the universal quantification I can have actually an unbounded composition of the heap. So in the information I'm using, it's the number of threads which I want to lose, it's not important. It's usually not important. And this idea can actually -- I actually adopted this idea to conical heap obstruction.

So here's the example of what happens. So I have this concrete state. And suppose I focus on this one thread. So what I do is I decompose the heap from the perspective of this one thread. And I get an abstract state that looks like this which corresponds to this disjunct. And here you can see I actually maintain the correlation between the local variables and the global variables.

What this subject represents, it represents all the heaps. So now remember we have this more general kind of decomposition where we can have actual overlaps. But represents all the heaps that contain this subheap and also some other number of threads and objects and we might actually have -- we might even have links going from local variables from other threads to this object but the important information that we have here are the equalities and disequalities between the local variables and the global variables, which is important for this program.

What we lost is the number of threads and the correlations between the variables of different threads. So here I'm using the more general kind of decomposition, which is indexed by this thread T.

So this would be technical, but if we don't get it, don't worry. So I have this binary predicates indicated by this T. What it says is suppose you choose some object, some object and then the composition predicate captures a subheap, a subheap, relative to this particular thread and then there's another subheap from the perspective of this thread and then we decompose. Again we decompose, but what we get finally the invariant is a universal quantification of the disjunction of all of these subheaps that we can get.

So, okay, so this was for example. Now I'm going to come back to this example I showed you in the beginning and this concurrent second implementation for which I want to prove, I want to prove that this object, this concurrent data structure is linearizable. So let me define, more or less define linearizability.

So linearizability is a major Cartesian for concurrent data structures and it provides the illusion that each operation is atomic. Uses the concept of linearization point. So the push and pop, for example, we can have two threads executing push and pop operations. These operations may take a lot of time and overlap.

But what you think about them happening is atomically. So linearizability says that for every concurrent execution there's a corresponding sequential execution where the order between the operation's maintained. We for example can think about these operations taking zero time at this point in time and so this is the linearized execution.

Okay. And here actually building on top of another analysis that proves linearizability for a bounded number of threads and the idea there is to track a valid number of differences between the concurrent execution and the linearized execution. But because the bound that it's tracking, the different states tracking is bounded it means it can only apply to a valid number of threads. So this is an assumption I want to lift. And the linearization points in the kind of algorithms that I'm considering are actually happening at successful cast (phonetic) operations and also at successful occurrences of acquiring a lock and releasing a lock. So this analysis uses special correlate predicate to track partial realizabilty between the concurrent and sequential execution which must be bounded.

And because it tracks the correlation for all the threads it's feasible only up to two or three or four threads, depending on the program. So in the new analysis that I defined I'm using heap to composition and here actually so what this analysis lets me do it lets me track bound differences between the concurrent execution but the bound is only per thread.

So this is what actually lets me handle an unbound number of threads. I'm using the same linearization points and the kind of correlations that I want to maintain are the ones that are enough to ensure that I actually can successfully detect

successful class operations and to find, to be able to determine the linearization points, which is important for realizabilty, of course. And I'm going to use the correlations between different threads, and this is what is going to bind me to get me an analysis that has thread model characteristics.

So here's an example of a concurrent state. You can see there are four threads executing, and some of the objects are actually correlated and some of the objects, these objects are actually the result of local mutations that the thread's actually doing and they mean it -- so there's actually a difference between the concurrent sequential execution. And the kind of composition I want to define here is the one that maintains for every thread the objects that are pointed to by the local variables and the objects that are pointed to by the global variables.

Intuitively this is because for this example program I want to be able to determine that we have this class operation, actually want to know where the global variable and local variable are pointing to and be able to update the local variable, and also have another variability that helps me get scalability which is the correlated component. Keeps the correlated objects pointed to by the global variables.

If I apply this to composition, I get the following. So for this thread I get this subheap.

You can see that I'm tracking the course between X and T and top. And this is a subheap for this object. For this thread, this is the subheap for that thread and another subheap for the final thread and I also have the correlated part which tracks the correlation between the global object and all the correlated objects.

What you see is that these subheaps overlap. This is actually important. And so if I decompose them, what I get is these four subheaps. So the information I actually maintain are the correlations between the thread local variables where every thread and the thread of the local globals and the thread's local mutation.

For example, I remember that these objects is not actually correlated for this thread. And the information to lose is the number of threads, which when I want to analyze this program for unbound threads and also correlations between the variables of different threads, which actually gets me this thread modeler like analysis.

So let's look at two of these subheaps. What do they actually represent? Again, this cloud picture. What they represent is all the heaps that have any other number of threads and objects. And there may be some links between them. I'm not assuming any disjointedness.

A lot of information. But what I maintain is actually to ensure that for this subheap CAS is going to succeed. So this is going to be a linearization point.

So this object, for example, is going to become a correlated object after the mutation.

For this subheap CAS is actually going to fail.

>>: I missed what the correlated objects are. How are they different from the other? (Inaudible).

>> Roman Manevich: These objects?

>>: (Inaudible) some objects that are.

>> Roman Manevich: Pointed to by the local variables.

>>: Some objects aren't.

>> Roman Manevich: Okay. Okay. Sorry.

So these don't have to do with the composition.

The correlations I'm tracking are between by the global variables, X and T global variables. And what the correlated says is this object -- so I'm tracking a difference -- what I'm actually tracking is a partial isomorphism between the concurrent execution and sequential execution. So some objects isomorphic, isomorphic relation, but items temporary, the two graphs, almost isomorphic up to bound number of a thread. This actually represents two objects. It's somewhat existing in the concurrent and sequential execution. There's an isomorphism edge that connects them. But this is implicit here where for this object it only exists in the concurrent execution. There is no corresponding object in the sequential execution.

These two heaps are automatically out isomorphism. After this CAS operation succeeds, then what it's going to do, it's going to make top point to here and this object is going to become correlated.

So these are successful linearization point. It's going to become part of the list.

It's going to become correlated. Does that make sense?

>>: Yes.

>> Roman Manevich: There's some -- okay. I can describe the details afterwards.

So I apply this analysis for these kinds of implementations. One is this tribal stack that I told you. One is 2Q. This is an implementation with a queue with two locks with benign iterations and one is a blocking iteration of a queue. And I verified cleanest properties such as objects of another reference, memory leaks and also linearization. And so this is actually the first automatic verification of linearizability for an unbound number of threads.

And you can see the running time, number of states. Quite reasonable, actually.

And the properties that actually are utilizing in this analysis that the mutations actually occur very close to the axis points of the data structure. So this CAS operation, for example, for the stack occurs near the global variable top. And the number of mutations, as I told you, per threads is bounded.

>>: You said first verify. What did you say you were verifying? Did you give it a new variant to prove or did you --

>> Roman Manevich: No, there's no variant. So in order to understand what I'm talking about here, there are no annotations except for --

>>: So what do you prove of it? Linearizability.

>> Roman Manevich: Linearizability. So the way it works here is that I tell it -- these actually are small indentations that says there's actually a guess of the linearization point. I can try different guesses but this type of program I say my guess is linearization point, is the CAS operation try to prove it, goes on to explore the object space and it proves it.

>>: It doesn't prove any properties of the queue, for example or memorization.

>> Roman Manevich: Yes, does approve these properties. The list is always an acyclic list, no memory leaks. No -- the references in the sequential properties.

>>: So that is part of this as well?

>> Roman Manevich: Right. All these properties.

>>: So linearization (inaudible).

>> Roman Manevich: Just a second. Let me go back. One small correction, actually.

So there's actually it's not entirely correct. What I prove -- so linearizability is an interesting property. What it lets you do, one of the properties of linearizability, once you -- so once you identify different properties separately. So first you actually verify the sequential specification. If you do it, you don't actually have to do it for the concurrent execution. So actually these times actually represent the proven linearizability but they also prove other references and absence of memory leaks, but I don't check, or the list doesn't become acyclic. This actually

I check on the sequential specification.

And so this is actually quite simple for lists.

>>: So linearizability with respect to the sequential (inaudible).

>> Roman Manevich: Yes.

>>: So you have a pre and post for every function of the option.

>> Roman Manevich: Right. There is a sequential specification, right.

>>: So to my understanding, you assume that there's no, that the topology of the threads is simple in the sense that there are no relations between the threads.

>> Roman Manevich: I don't assume it.

>>: No neighbor relationships who synchronizes? Did you have a ring topology

(inaudible), it would not program into this CAS; is that correct.

>> Roman Manevich: I don't assume anything about the connections between the threads. But if I bet on the wrong side, on the wrong kind of the composition, that then what would happen is I am going to lose precision and not going to actually be able to prove the property. But I never lose soundness.

So if we have this root topology, then you had better come up with the different kind of composition. And, okay, so let me come up for the conclusion.

So what I'm proporting is this idea of heap decomposition that helps you scale complicated shape analysis. It's parametric. It lets you uniformly reduce exponential factors both due to the control, the threads and the objects.

And you can use it to create analysis that have thread model characteristics even for fine grain components with nonbound on the number of threads.

And my thesis I have several other results, so I have another kind of different kind of partially disjunctive obstruction which I have described here, which is partial isomorph physical join (phonetic) and integrated it into TLS (phonetic) and became the choice obstruction. And it also gives you a very significant speed-up. And actually I'm combining these two ideas to actually combine very nicely. Partial isomorphism and decomposition, using them together.

Obstruction for single link list I showed you earlier.

I've actually shown how to compared -- actually shown how to correlate predicate obstruction and correlate obstruction, compared it to. This obstruction has been employed by other researchers and it always, the idea has been extended to trees. And coming back to this picture, so partial isomorphism is a phenomena that we can combine them together, they work very well together. And there are some other results that are not in my thesis.

So in my first internship with (inaudible) I worked on post model symbolic evaluation. And the idea there was to take crash dumps and try to produce error traces that would help the developer actually debug the program, find the reason for the bug.

I also worked on the (inaudible) for structures which we can use to intersect different abstract states. This is what I'm using to define the decompositions between heaps that might actually overlap. And I also have a theoretical framework for current example gather refinement.

And, okay, so there are still open problems that I want to handle. So I told you about this idea. So this whole idea of heap decomposition, it's parametric and very nice because it lets you have, can think about the lattice of different obstruction. For every point of the space obstruction you can have sound analysis. Another question is we have all these multiple obstructions, which one do you want to choose? Given the program property, how do you tune them.

This is something I haven't addressed. I've done it class of programs and properties, but haven't done it automatically. I would like to investigate this and I would also like to try thinking about combining the decomposition and summarization for interpretive analysis for concurrent programs and I would also like to try to use linearizability for more complex data structures, more complex kind of linearization criteria, for example, ones that depend on the history of the execution.

And another thing I would like to do is define kind of local shape analysis and the motivation here is that that would automatically let me, hopefully let me come up with completely automatic transformers for decompositions by inferring the footprints the part that's actually accessed by transformer. So actually having described how I transformed the transformers, so you have to guess which kind of subheaps you have to compose, guess it, then the analysis. It's sound. But the question is how do you come up with the specifications. So I'd like to automate this. And here's some questions that you can ask me.

(Applause)

>>: Does anybody have questions?

1:17:43

Download