15852 >> Rustan Leino: Good morning, everyone. Welcome to this morning's talk, one of them. And I'm pleased to introduce Aquinos Hobor, who is a Ph.D. student with Andrew Rapel at Princeton. And he's coming here by way of all kinds of corners of the earth, Singapore, Bangalore, even New Jersey. And he's going to tell us about his Ph.D. work that he's in the process of wrapping up. >> Aquinos Hobor: Good morning. This is joint work with Andrew and Francesco Zapo Nordeli (phonetic) at Inryo (phonetic). So why are proofs about code hard? Map proofs are usually of this form, whereas proofs about real programs usually look somewhat more like this. But there are always some exceptions and usually some underlying assumptions. And the real point is there's a lot of detail. So we have two choices. The first choice is we can sort of isolate the core idea and try to prove something about it on paper and then we have to hope that the implementation doesn't make too many mistakes. Of course, it does make some mistakes. And the second choice is we prove something about the actual code. And the problem is proving things about code by hand is really hard. So the solution is to have a machine check proof, use a computer to check your proofs. There are a lot of systems out there. We use a system called Coq, which is out of France. Ideally the computer is going to actually help do some of the proof as well. And an issue starts to emerge which is that these proofs are very large. Very time-consuming to write. And so proof engineering becomes an issue just like in the large software system software engineering becomes an issue. So what's the goal of oracle semantics. We want to add concurrency to a large system in a provably correct way modular way. Usually the systems already exist in some kind of sequential form. We start with a compiler for sequential cold or some proof about some sequential thing. We'd like to reuse the code and in particular the machine checked proofs. Usually the proofs are larger actually than the code. We'd like to be able to reuse that whenever we can. And the key is going to be isolating the sequential and concurrent reasoning from each other. So this is the project that we sort of applied the technique to. This is the Concert Project by Zavy Luoux (phonetic). Basically there's a sequential source program in a language called C minor. C minor, one way to think about it, it's the top-most intermediate representation in a compiler. It's so high that you could imagine actually writing a program in it. You feed the program into the comp cert compiler and out the bottom comes a target program which is in Power PC and the innovative this about this compiler when it was done in 2006 was that the source and the target both have an operational semantics, separate specified in Coq. And there's a correctness proof which says that any time you put a source program into the compiler, the operational semantics of the source is equivalent to the operational semantics of the target. In other words, the compiler hasn't introduced any bugs. So a question that might occur is what's the relationship between your source program and the operational semantics? In particular, this is the work that Apel Gauzee (phonetic) did and the observation is that usually in a compiler, or the way Zavi found it most convenient was to relate operational semantics at the source and the target level but frequently when you have a source program you'd like an axiomatic semantics like a separation logic to reason about your program. And so now in addition to writing the program, the user is going to provide some program verification with respect to the separation logic. And Apel Gauzee (phonetic) designed the separation logic and designed a soundness proof which connects the axiomatic semantics to the operational semantics and then you sort of have an end-to-end result, because you understand what the semantics are at the source level and the compiler guarantees that the semantics are translated to the target level. So we'd like to move to concurrency. So all kinds of things have to change. We're going to have a concurrent source program. Concurrency aware compiler. That's going to produce Power PC with concurrency GU. We have concurrent semantics at the source level and target level. We have a concurrent separation logic for axiomatic semantics. And the soundness proof and the compiler correctness proof both have to be updated to be concurrency aware. Just to review for a second what this would mean we'd have a machine checked end-to-end proof of concurrent program that actually was executing on the machine. So you would know that the actual bits that the chip was reading have the behavior that you specified and it would be machine checked end-to-end. Okay. So I'm going to go through this sort of one component at a time. Now, the source program, of course, is provided by the user. What we do is we add a few new statements to the language of C minor to allow him to program in a concurrent way. We add five new statements. A lock/unlock make lock free lock and fork. This sort of supports the C threads concurrency model that programmers are familiar with. Make lock and free lock are bookkeeping instruction. Lock and unlock and fork do sort of the standard thing. The unusual thing here is make lock takes a resource invariant R. And R describes what resource of a thread is going to acquire when it locks a lock and release when it unlocks. So here's a little example program. We use make lock to indicate that we're turning L from a regular piece of data into a lock and we provided invariant there that says that X points to an even number. It's in blue in a different font because the point is that's a predicate as opposed to code. Our system just lets you intermix them like that. And then what we're going to do is we're going to fork, the child will pass at L and then here we're going to lock L and fiddle with X a little bit and unlock L. And of course we'd like to know that the child isn't going to be upset whether it runs here or whether it runs here. So, again, we want to combine sequential and concurrent features. Here's sequential features, all these control flow statements. I haven't talked much about what's inside C minor but there's sort of a large variety of control flow constructs. We've added a couple of concurrency features we want to combine them. At the language that's the easiest thing in the world it's just syntax and they play nicely together. So moving from there over to the far side. The operational semantics, the concurrent operational semantics is really where sort of the heart of the work is. The goal is to make the semantics that makes the various proofs of that semantics easier and give us the separation that we want. So here's some different kinds of reasoning. Here's a separation logic soundness for sequential and concurrent separation logic soundness and here's a sequential compiler correctness and concurrent compiler correctness and other kinds of things. We'd like to add some magic so we can basically reason over here independently of what's going on over there and reason over there independently of what's going on over here. So how are we going to do it? The first thing is we're only going to consider well-synchronized programs. In other words, we're going to consider programs that are database-free that have Dykstra semifors that kind of thing. And our operational semantics is actually going to get stuck on L synchronized programs. So we're going to start with a small step operational semantics for C minor, essentially. There are some step relation. It takes a sigma and a kappa to another sigma and kappa. Sigma is a state. Row is some local variables and is the memory and kappa is the code, control stack. We're going to add to the state a new thing, W. W is a world. Like memory. It's a map from addresses to something. Unlike memory which is a map from addresses to values, world is a map from addresses to something called ownerships. So there are a variety of ownerships. Here are some basic ones. One, the first kind is none, that means we don't own the address in question. The second kind is Val. That means we own it and it's data. So we can read it, we can write it. The third kind is lock. Lock has to come with an invariant. Where the invariant comes from the semantics is from the make lock instruction. Some of those what make lock does it turns a Val into a lock and adds the invariant there. And the key point is our world aware semantics is going to get stuck if you use memory without ownership. So if you try to read where you have none or if you try to lock a Val or anything like that, you're just going to get stuck. What we're going to do is we're going to enforce that each thread is going to have its own world. And all the worlds are going to be disjoint. In other words, at any time only one thread is going to be access any address. Another thing with sequential instructions in proofs, they don't know about the existence of lock. They know that there are some kinds of ownerships. They know about some of them. They know there could be others. And so, for example, when you're writing to a memory address, you check to see whether it's Val. You don't check to see that it's not lock. Okay. What's the concurrent step relation look like. We have four components. We have omega, which is a scheduler. That's just going to be a list of the threads that are going to execute. Once we've picked a scheduler, everything is deterministic. We're going to quantify on the outside over all schedulers. So we can handle any schedule you might want to throw at it. Then we have a list of threads, K. A thread is just some local variables that could be registers at the low level or just variables at a higher level. A world, which is to say what pieces of memory it has ownership of. And some code. Then there's one memory for the whole machine. And then there's another piece called a global world. Basically everything is owned by something at all times. So if you unlock a lock and you give up some resources, those resources have to go to someone. If there's no one ready to take them, they get put into the global world. So when you want to execute a sequential instruction, what you do is you use the world aware sequential step relation. So you just call it on a procedure and you don't have to worry about what's going on inside. When you want to execute a concurrent instruction, you have to do -- you actually have to do some work. So, for example, the concurrent step relation will update memory at lock to flip the bit from zero to one and back. It will maintain the thread list, in general, but in particular for fork. And the third thing that it does is it transfers world around, typically between threads in the global pool. And at unlock you transfer world to the global pool whereas at lock you transfer world from the global pool. And we have a picture. So this is time going down. And different threads going across and the global world there off on the side. And thread B is going to unlock L. And WR is going to be the unique world that satisfies L's lock invariant. I'll explain a little bit how that can be, but there is one. So after doing that, WR has been transferred from thread B to the global world. Sometime later thread A is going to lock L, which is going to transfer WR from the global world to thread A. And what this means is that memory that used to be visible and changeable by thread B is now visible by thread A. So B has communicated to A. So this is how processes communicate. I thought I would show an actual rule here. This is the unlock rule. And it's going to illustrate a little bit about how we avoid raised conditions. First of all, the held of the scheduler here we're executing thread I. And the thread associated with I is currently an unlock instruction. And its world is W. We're splitting W into two pieces. W prime and W lock. Then we're looking up and we're discovering that L is a lock within invariant P. That's what the squiggly line means is a lock with an invariant. And then what we're going to do we're going to check that P holds on W lock. So, in other words, a property about anything inside a lock is going to be that there is a unique sub world that holds, that it will hold on. So that way W lock will be unique. And there's a strange thing here which is that this semantics actually isn't constructive. You couldn't actually run it. And the reason is we're providing a classical logic to the users and the user is going to be able to write down whatever he wants to write down. He's going to write down some complicated thing in his make lock instruction. And over here we have to actually test whether or not that thing holds. And so we can't. Fortunately we don't actually have to run our semantics. What we have to do is compile it. And having an unconstructive semantics doesn't prevent us from compiling it at all or prevent us from reasoning about cases where it wouldn't get stuck where we can prove that the test would actually pass. Another unusual feature is we interleave only very rarely. In general we only interleave when we reach a concurrent instruction, like a lock or unlock. If we have a long sequence of sequential instructions we'll execute them all without doing any interleaving at all. And the reason we can get away with this is we have well-synchronized programs. The whole reason Dykstra was introducing semifors was so that you could reason in this style without having to worry about each interleaving. So now we want to reason about concurrency and about concurrent execution. And there's an issue, which is that most of the time concurrent programs are actually executing sequential code. And sequential features are usually hard enough to reason about. I sort of illustrated there was a whole bunch of sequential control flow. Those actually can get quite nasty to reason about. And C minor doesn't have things like exceptions which can get nastier still. And one thing we really don't want to have to do is to have the extra complexity about concurrency when you're reasoning about sequential code. So in the middle of returning from your function call, you don't want to worry about a contact switch. So the idea is why don't we just pretend like it's sequential. So here's a little snippet of code. We have two sequential instructions followed by a concurrent instruction and then another sequential instruction. We'd like to sort of reason about this. But when we try to use a sequential way of looking at things, we just get stuck. We're able to make progress until we hit the lock. Then at the lock we can't make any progress. So our idea is we're going to augment our relation with another feature, an oracle. The oracle is going to be constant over sequential steps. And sigma one and kappa one on the left is the same as sigma one and kappa one on the right. So, in other words, from over a sequential instruction, if you know how it behaves over here you know exactly how it behaves over there. That allows you to recover all kinds of sequential reasoning that you might do. When you get to a lock, then what's going to happen is you're going to consult this oracle and it will tell you, you know, what the result will be, what memory will look like after you finish this operation. Afterwards you just continue. This is another sequential instruction. The oracle again is unchanged and you can just continue to reason about this sequence. So how do we do this? An oracle has a number of components. It's got a scheduler. It's got a bunch of other threads. It's got a global world. And what it's doing is it's simulating running all of the other threads until the scheduler returns control. Again, this is a nonconstructive -- this is a classical kind of thing. It reduces the halting problem. But again, what we're trying to do is compile things, not actually run them. >>: You call it oracle, that makes me think that it's going to do something good for you. But it's juicing demonically, is that right? >> Aquinos Hobor: No, it tells you. It does do something good. It says after you finish this lock, this is what memory is going to look like. >>: Right, but suppose that your program works only if memory looks a particular way. >> Aquinos Hobor: You're going to have to show that for any oracle, for any other set of threads that obeys your locking discipline. >>: So the oracle doesn't always return it to the same time that the program would actually ->> Aquinos Hobor: Well, in fact, you won't be able to, for example, you won't be able to prove a hoare triple, because the oracle will force you into a bad state. >>: The methods. >> Aquinos Hobor: I suppose. I think of it as a force for good. Okay. So we need a connection between reasoning in this sort of oracler style and reasoning on the concurrent machine, which is roughly as follows: If a thread executes in a certain way on the oracler machine, then it executes in the same way on the concurrent machine. That means you can reason about each thread independently. And then you can combine your oracler understanding of how each one is executing into an understanding of how the entire machine is executing. Okay. So now talking about axiomatic semantics. This is probably going to -- this is intended for a general audience. I'll go very fast here. We have hoare triples, precondition, command post condition. And our setting these pre and post conditions are just predicates on state. So here's our Coq definition. Hoare logic is a set of axioms for deriving valid triples. Here's the sequence one. A point something to point out is that this rule is usually false in concurrent settings, because you can have a contact switch between the first instruction and the second instruction and so the post condition guaranteed by the first instruction won't actually be true by the time you get to the second instruction. So big problem with hoare logic is dealing with pointers. So if we know that X points to zero and Y points to zero we update X. The problem it's not quite clear what the post condition is. X could be one and Y could be zero, or they could both be one because they're aliased. So Reynolds and O'Hearn introduced the separation operator that says the left half of the separation and the right half of the separation are disjoint pieces of memory and so afterwards, after this you know that X and Y are not aliased. And the nice thing about this is you can have more local reasoning for programs with pointers if you know some hoare triple PCQ you can add a frame to the left and the right and your hoare triple will still hold. Obviously this doesn't hold with "and" because of aliasing concerns. So concurrent separation logic. This is the original concurrent separation logic was by Peter O'Hearn. This is sort of a more advanced version. It includes sequential separation logic sort of as a proper subset. That's sort of sequential separation logic sitting inside concurrent separation logic. And it also has extensions to deal with concurrency. For example, a new predicate, you can write is L as a lock with invariant R. We saw it in operations semantics rules. And we have various rules for various triples for reasoning about hoare, about these lock and unlock rules. So, for example, if we know L is a lock with invariant R and we lock L. Afterwards, we know L is a lock with invariant R and we know R. And there are several ways of formulating an unlock rule but this is one way which is just the reverse of the lock. So if we know L is a lock within invariant R and we know R then we know we've lost the fact that we used to know R. And the beautiful thing about this is that programs proved or verified in concurrent separation logic are well synchronized which means their operational semantics will be well defined on programs you can verify on this. I just thought I would show very briefly I've gotten rid of the global world here. So this is how thread A would prove the lock rule. He's got some frame F and also L is a lock with invariant R. Afterwards he also knows R and here's how by combining the lock rule and the frame rule you can prove this triple. Now I just wanted to talk about the program verification. Here's the example program. I'm going to verify this little piece right here. There's some frame, of course. And then we know L is a lock, such that X points to an even. We lock L. And then we know at this program point not only do we know all that we knew before but we also know that right now X points to an even. That means we can now reason about this instruction. Now, of course, X points to an odd. We increment again. This, of course, is equivalent to this with a slightly larger value of Y. And this lets us prove our unlock rule because now we know the lock invariant. So we can take it out with the unlock rule. Seeing it all together is here. And then here's the whole program. And there are some lessons. The first lesson is there are a whole lot of details that start coming in. Actually, there have been some omitted in this presentation. And what this really leads to is why machine checking is very important. This process has actually been done for some larger example programs in our system in Coq. So fully machine checked. Another point is we had to do this all by hand here. But machine generation would be really helpful. So something that would actually generate all or most of this stuff would be really terrific, and I'll talk a little bit more about that at the end. So now talking about the soundness proof here. This is the connection between the axiomatic semantics and the operational semantics. And what are some difficulties? One difficulty is invariants need to refer to other invariants. So, for example, here's something where the next value in memory points to an even number. So this is a lock that's guarding the next value in memory which must point to an even number. Here's two things like this. And once you create something as cool as this, what you really want to do is tell your friends about it. So what that really means is you need to put it in another lock. So here's another lock. It's guarding a pointer. And if you grab this lock, what you know is that it's pointing to an object of this second kind. And the first thing to say is that method invariants in general are difficult to model. They're a self-referential property which can be very nasty. So one thing you might say is well why don't we just all be first order about it. And the problem is really most programs you can imagine actually have locks that refer to other locks all over the place. That's how communication gets started. That's how you pass function pointers around. Function pointers you want to pass locks to. Locks you want to put inside function pointers. Function pointers you want to put inside locks. Any way you do it to get communication started really you need to have some kind of higher order setting here. So what do we do to solve this sort of nested invariant mess? What we did is we defined a modal logic. And basically when a logical proposition says when and where another logical proposition holds you have a modal logic, we have a modal operator later written with this triangle. You may have noticed the triangle in the unlock rule. Later P means that P is going to hold at all times strictly in the future. It might not hold right now. And the key to that is that it avoids circularity, because we're always talking about right now we may not know much, but in the future we'll know something. So things can become well defined. And it gives us a nice induction principle. We also, in addition to talking about time, we also talk about space, with the separation operator and an operator fashionably, which we write with a circle. Or fashionably P we have a lot of fun. We have lots of these operators, they're great. Fashionably, P means P holds on all words of equal age, which means the only thing you're allowed to assume about the world part of P is its age. So, then we need to construct a model for our modal logic. We use the very modal model of Apel Melies Richards and Verion (phonetic). This was presented in Popolu 7 (phonetic). We had to extend it a bit because we have spatial properties as well as modal properties. So we did that. And a point that we've discovered is that we've discovered that semantic models tend to scale better in large systems. So that's part of why we built up this semantic system. Sort of an engineering point. Basically, our invariants are semantic, as I mentioned, and they're shallowly embedded in Coq. So what this means is that they're easy to use and reason about in in the theorem prover. So we get to use the same tactics at the Coq level and invariant level. We also don't have to reason about binders. So there's this huge Pabo mark challenge and people are submitting their works about, well, maybe you should use locally nameless terms or this or that. We actually don't have to deal with that at all. We use the binders that Coq provides. They're easy to deal with. They're simple. And it really simplifies all the engineering work. There's another modeling difficulty. As I showed in the example program, we actually embed our assertions directly into program syntax. What this means is that the definition for syntax has to depend on the definition for predicate, naturally. On the other hand, one type of predicate we'd like to be able to specify is that something is a function. F is a function with precondition P and post condition Q. That's useful for forking and also if you want to put a function inside a lock. This is actually a predicate about function behavior. So function behavior, of course, is defined in terms of program syntax. So there's a very nasty circularity here where program syntax depends on predicates but there's a predicate that depends on program syntax. So we solve this actually in a similar way to the previous circularity, which is we actually define our hoare triple using our modal logic. Our hoare triple just becomes a predicate on state just like any other predicate. One hypothesis is we may be able to reason about things like a jet or some other self-modifying code with this model as well. That's sort of a little bonus. I thought I would just put it up there just for a second. This is our actual definition of our hoare triple. And I'm putting it up there both because it's nasty, but also just because we actually have to prove all of our hoare rules sound. If we didn't have the shallow embedding, proving something about something this large would be really a nightmare. Because we would be constantly in our proofs worrying about shifting and variable renaming and all this kind of mess and we'd have to have special tactics at the invariant level and it would just be a horrible mess. So the fact that, you know, we're able to define something at the invariant level and then actually reason about it in any kind of reasonable way is actually an illustration that the shallow embedding is a big win. Okay. Now we need to prove our hoare triple sound. And the key is we're going to prove things relative to the oracler machine instead of relative to the concurrent machine. We have all of these Apel and Bazee (phonetic) machine-checked proofs. They had been written before. And the great thing about proving things relative to the oracle machine is that we're able to basically reuse all of those proofs. So proofs that had taken two people six months to write sort of one person with a week was able to get to go through the machine checker. And as I pointed out usually for example like the sequence rule is actually false when you try to prove it in a concurrent setting. And not only is it true in our setting, but it's so true that the previous proof was able to be reused. So that was very nice. We also have to prove the axioms that are unique that are concurrent to separation logic like the axioms about lock. Obviously we can't recycle any other proofs because nobody's done them yet. Or had done them before we got to them. But when we were doing them, at least we were able to ignore all the difficulties of sequential control flow. So when we were reasoning about lock, we got to ignore the fact that the actual concurrent machine had all this complicated sequential control flow and just we were able to focus just on the concurrent behavior. So this is probably a good time to talk about the machine checked proofs. We ended up developing a slogan, which was that, as expected, it took longer than expected. We have got about 62,000 lines of proof. I think when Rustan saw this last it was about 58. So we've been working hard. There's a wonderful quote by Zavi Louiux (phonetic) here. Which is very true, thank God, which is building these things is very addictive in a video game kind of way. >>: (Inaudible). >> Aquinos Hobor: Maybe. Maybe. There's -- I wish. I would even consider paying them. But it is fun, which is good. So what's the status? All the definitions are done in Coq. All the sequential rules are done. The concurrent separation logic rules are all done except for the unlock rule, we've got a little piece left. That's not actually the hard rule. The difficult rule is actually lock, which is done. So there's a little bit of bookkeeping left to do at the end there. There's also the connection between the oracler reasoning and the concurrent reasoning. I currently estimate it's about 90 percent done. As expected it might take longer. But we're pretty confident about where things are. So I want to talk briefly about future work. So we have to modify this compiler to be concurrency aware. Now this is an optimizing compiler but it's not heavily optimizing. And basically the compiler we think can translate lock and unlock basically as a function call. All the intermediate levels of the compiler understand about function call. So it's our hope that the compiler may not need any modification or only very, very minor modification, because it doesn't do very aggressive optimizations that would be difficult for concurrency. There's one issue that one might worry about which is we have these make lock predicates which actually -- these make lock constructions actually have a predicate inside them. So one worry would be that the compiler has to look inside and modify this predicate as it compiles down to intermediate levels. We've actually very carefully designed our predicate so that they look only at memory and at world. World is, of course, a virtual thing, so that's fine. The compiler is not going to change that at all. And the comps for compiler has been carefully designed so each intermediate memory level is preserved. So the compiler shouldn't even have to look at the make lock statement. In fact, as far as the compiler is concerned, it's just going to be unit. Which actually turns out to be useful when you do extraction to take your compiler from Galena, which is the language it's built in, built into Coq, and turn it into O Camel so you have reasonable performance. So then operational semantics here at the target level. There's going to be an oracler machine down here an oracle step down here. One additional feature you have to worry about is the so-called weak memory models which means to say real processors don't interleave the way we do at all. They actually don't interleave at all they have out of order execution with all kinds of constraints, and while sequentially you can't actually tell that this is going on, concurrently you can. We think that for well synchronized programs our interleaving model should be fine but this is something that we have to prove. Then, of course, this correctness proof, the compiler correctness proof has to be updated for concurrency. And it's our hope that the vast majority of it we'll be able to reuse, just like we were able to reuse all the sequential rules from Apel and Blazee (phonetic). So that's future work. But we're reasonably confident. The compiler, of course, will be modified very little. So hopefully the associated correctness proofs won't have to be modified very much. So papers and related work. Peter O'Hearn did the original concurrent separation logic. Steven Brooks had the original soundness proof. The original concurrent separation logic didn't have, well, it didn't have functions or something like fork. It had a different concurrency primitive. It didn't have locks that could refer to other locks. So locks were static, fixed at the beginning of the program. And global to all threads. You didn't have to worry about this sort of self-referential behavior and stuff like that. But anyway Steven Brooks did the original soundness proof. This is our paper. This is work done by people in Cambridge. Alexi is a graduate student at the University of Cambridge and Josh Berdyne and Byron Cooke Brooks and others work at MSR Cambridge. They came up with a similar concurrent separation logic. It's roughly isomorphic to ours which is I think is an indication that the logic that we came up with is sort of the natural way to extend concurrent separation logic to C threads. Now, ours is more powerful in sort of a variety of ways. We have a machine checked proof. But anyway that's definitely good work. Okay. So then there's some talk about semantic models. This is the model for the modal logic. This is actually this has appeared now. This is talk -- so what we discovered engineering-wise was we would get to a point in the proof where things were very nasty. And the solution was stop and back out and try to express the problem at the level of the modal logic. And maybe we have to define new operators like fashionably, or whatever. Lately, all these different kinds of things. And prove things about those operators, reformulate the problem at the level of the modal logic and reason there. And that just, engineering wise, that just made the job a lot -- that was the right technique so we sort of talk about our experience there. This is another paper where we are sort of explaining how semantic methods tend to work and how we found them useful. This just got accepted in PCC. Okay sequential separation logic was in TP Hall last year, and then sort of at the very end I said sort of at the moment we're assuming that the user will actually write all the verification pieces themselves. Alexi Gottman and company at Cambridge developed sort of an initial tool that can try to infer lock invariants and prove, provide these verifications. It sort of works on a simplified, in a simplified setting. For example, I think all the locks have to be declared in the main thread and so you have sort of a fixed number of locks there. But it's great work. And definitely something that helps the user generate that thing is a big win. This is a senior thesis at Princeton where they take Alexi's algorithm and he implements it in Coq. And proves that when it reaches a fixed point that it actually has a proofing concurrent separation logic and there's a verifier. So there's various people working in that direction. A related thing I've done, which is I can actually talk about here, you know, the only place around that I can, is I worked at the Center For Software Excellence, a few buildings over, when Monterey Doss (phonetic) was still running things. Developed this annotation checker to find concurrency bugs in Windows. They don't actually tell me what the story is these days, but I've been told it's running over the Windows code base so the techniques I developed for that are scalable and you can use them on fairly large pieces of software. So that's the concurrency minor project and so I wonder if anybody has any questions. (Applause). >>: So would you give me a rough breakdown of your code size, complexity, you give that (inaudible) communications but you didn't give it for -- if you could give us the definition of the size of the proof (phonetic). >> Aquinos Hobor: The definitions are actually not so simple. But, let's see. So I have 62,000 lines. Now, I should point out that it's in development. So one of the things that you do is you write for a while and then you say, okay, this is too ugly for words. I have to go back and then so it's not linear. And some of it's been now gotten to a pretty polished state and some of it is pretty rough. All of that said, there's about -- the sequential separation logic soundness rules, all about 25,000 lines. So that's one rule for each of those, one lima for each of those hoare rules, definitions. >>: Do you know how many hoare rules? >> Aquinos Hobor: I don't know. I have to count. You know the truth is, like I said, it required very little modification and I actually have the person who had written it. He spent a week and did it. I haven't looked at them almost at all. In some sense bad me, but in the other sense it really shows the isolation is very strong. There's a bunch, though. So C minor has various kinds of nasty control flow, for example, you can break out of loops multiple levels. So let's see you have nested loops. You can break out two levels immediately. So like -- and function call and return. If loops, all kinds of stuff. We don't have, for example, exceptions. But, I don't know, maybe there's 25 rules, 30 rules. These proofs get remarkably long, even just reasoning basically fully sequentially, they get remarkably long, remarkably fast. So that's let's say 25,000 lines. And that was kind of cool. Then there's about 20 or maybe 15,000 lines that are basically borrowed from Zavy Luoux (phonetic), so this is the definition of sequential C minor. The definitions of the memory model and related kinds of library type stuff. So that gives us about 40. So I would say the concurrency stuff, the actual concurrency stuff is only about 20. That's about 5,000 lines for the actual hoare rules, of which most of them are not that bad. The lock rule might be 2500 lines, but unlock, fork, make lock and free lock are relatively short. And then the remaining piece is the definitions of all the machinery. That's probably 10,000 lines, five to ten. Because we have three machines. We have the sequential machine. We have a concurrent machine. We have an oracler machine. It's a real system. So the definitions are larger than in a toy system. I don't know, might be five. And then the remaining piece is relating the oracler execution to the concurrent execution. And maybe that's five to ten. So that's rough. But that would be my guess, which in other words the way I think about it is that the actual -- the actual fully concurrent piece is about 10 percent of it. All of the machinery required to get it up and executing sequentially is more than 50 percent. The machinery required to get it executing concurrently might be another 15 percent. And then the connection might be the rest. >>: So different share locks. >> Aquinos Hobor: Yeah, something I didn't illustrate, due to you have to hide some detail somewhere, is we actually have fractional permissions. So, in other words, speaking again loosely, we have fractional permissions for data, where we can have a situation where two threads can write, I'm sorry, can read from the same data as long as none of them are writing. And also where you really need it is for locks. So multiple threads can have pieces of lock. And, of course, you can't free a lock unless you get 100 percent of it back because you wouldn't want to free a lock that somebody still has a piece of. You only need epsilon to actually lock or unlock. You need 100 percent to free or make. >>: Is that part of the operational semantics, not operational semantics? >>: So have you also used these methods to prove some program. >> Aquinos Hobor: I have some program methods. I mentioned that a little bit at the end. >>: The proofs were down manually in Coq as well? >> Aquinos Hobor: So in the extended version of our paper which doesn't have a (inaudible) I've got to do that, I give an example program. That was done -- that was too complicated for Alexi's algorithm to actually do. For programs that Alexi's algorithm actually can do, there's sort of an automatic piece and that's been run on those sub sets of programs. That's definitely -- I mean there's a lot of work to be done there to get those tools up to where you could take -- I didn't think my example program was all that hard. But the tool was it kind of died. It ran forever, which eventually meant that it was killed. But yes, there have been actual example programs, fully machine checked. Other questions? Well, thank you again. (Applause)