Formal But Lively Buffers in TLA^{+}

Peter B. Ladkin Universität Bielefeld, Technische Fakultät Postfach 10 01 31, D-33501 Bielefeld

             ladkin@techfak.uni-bielefeld.de

Version of January 6, 1996

Abstract

I perform some rigorous verifications in TLA, by using simple examples which nevertheless illustrate TLA techniques, in particular liveness proofs. Since the method of invariants for safety proofs is well understood, our example needs only the trivial invariant, which is simply omitted. We specify in TLA a buffer implemented as an array, a double buffer implemented as two arrays in series, and an abstract buffer which uses a sequence. We prove, formally and rigorously, that the two ‘implementations’ implement the abstract buffer. The non-trivial part is the proof of liveness.

1 The Problem

When she was told Calvin Coolidge had died, Dorothy Parker asked, “How could they tell?”. If your system dies, how could you tell? The chances are slim if you don’t specify that it shall live. Nonetheless, many specification methods, including most process algebraic approaches, omit a liveness requirement. Liveness can be hard to prove, which is maybe one reason why it isn’t stated. And engineers writing specifications don’t always understand its importance. We illustrate how to perform rigorous verifications in TLA, emphasising the proofs of liveness. We specify some buffers in TLA^{+}. Our examples are not mathematically very interesting, because that is not our point. We chose them small, in order better to illustrate the proof techniques.

TLA^{+} is a specification language based on Leslie Lamport’s TLA, the Temporal Logic of Actions [Lam94b]. TLA^{+} contains structuring and naming conventions (such as modules, with import and include), and some shorthand for necessary mathematics (e.g., a notation for values of functions). TLA^{+} is to TLA rather as L^{A}TEX is to TEX.

We define an abstract bounded buffer of length N , which contains a sequence of elements pushed on it, and which elements may be popped off. The sequence may contain at most N elements at one time. We don’t require anything to be pushed on the buffer, but if there is something there, we want it eventually to be popped off (that’s liveness). And if it’s full, then it must be popped before anything more may be pushed on. We then define an implementation of a buffer as an array of fixed length, which may have elements pushed in the last place, and popped off the first place, and which may shuttle them forwards to get them ready for popping. Again, we don’t require anything to be pushed. But if there are elements in the buffer, and if the shuttle doesn’t happen sometime when it can, they can’t be popped because they don’t get to the front. So, to show that the concrete buffer implements the abstract buffer requires that the shuttle action is lively. A further wrinkle is that the shuttle action is ‘invisible’ to the abstract buffer. It can’t ‘see’ a shuttle because shuttling alters only the absolute positions of elements in the array. The abstract buffer can only ‘see’ relative position.

The Fairchild F3341 chip implemented a ‘bubble-through’ FIFO buffer in exactly this way. It was 64 bits long by 4 bits wide. Two were used in parallel to give an 8-bit-wide transfer path. It was used by original equipment manufacturers to buffer disk transfers, notably Digital Equipment Corporation in their RP03 drives.

We also specify the concrete buffer as two smaller concrete buffers of half the size rigged in series. We want to prove that this is also a buffer. We provide a completely formal, syntactic proof that the buffers-in-series implement a larger concrete buffer, and then that the concrete buffer implements the abstract buffer specification. Since implementation is just logical implication in TLA, and implication is transitive, this shows that the double-buffer is indeed an implementation of a buffer. The specifications were written using the tla.sty style file of Lamport.

The hard part of the proof is the proof of liveness. Our proof is rigorously syntactically formal. That is to say, every deduction in the proof is a syntactically rigorous application of one of the TLA inference rules (with the exception of some deductions from the theory of sequences and arithmetic, and the Bridge Rule, a derivable TLA rule defined in the proof on Page 46 which is needed to tie together the TLA rules WF2 and Lattice in the liveness proof). This level of rigor is achieved by automated theorem provers, but rarely by ‘hand proofs’, which often employ looser standards. Rigorous proofs are harder, as can be seen by comparing our proof sketch in Section 6 with the formal TLA proof in Section 9. The proof was constructed using the pf.sty style file of Lamport.

Both [Mil89] and [Hoa85] treat the example of buffers, and composing larger buffers from smaller, as we do. Buffers are specified in [Mil89, pp72-73] as constructed from single-place buffers and proved correct with respect to their properties. Two buffers (or other processes with a single input and a single output channel) are constructed in [Hoa85, Section 4.4 Pipes, pp151-161]. What are the differences between those approaches and ours? Both of these approaches use process algebra in order to check mathematically that the buffer specifications/constructions are correct. However, firstly, the reasoning used for the mathematical arguments is the usual informal mathematical reasoning, and, secondly, liveness properties of the constructions may not be stated. Any liveness properties in these process algebras follow only from general liveness properties of the specification style and may not be tailored to individual problems.

When a part of the reasoning is not formal, the possibility remains that errors may be made. The purpose of purely formal reasoning is to minimise the possibility of errors in a field of computer science in which errors are both rife and notorious, by the nature of the subject. When the entire reasoning needed for the proofs in CCS and CSP is formalised, a part of it must be in formal logic. One would be in the position of having part of the reasoning in a formal logic, and part in the axiomatic theory of the process algebra.

Liveness properties are not explicit in process algebra formalisations such as these. Thus, what liveness properties there are follow from general principles of the process algebra, and cannot be tailored to the wishes of the specifier, as ours are. A specifier who requires specific liveness properties may then be constrained to use a specification and verification method in which they may be stated explicitly, such as TLA. Liveness properties may of course be stated in the temporal logic version of CCS, the Modal Mu-Calculus [Sti89].

Writing proofs is hard - rather like programming. However, the technology of programming is ahead of the technology of proving. This paper aims to contribute to the technology of proving.

2 Conclusions

In a case study, it’s appropriate to put the conclusions at the front. The general lessons learned from this exercise are:

• Writing literate proofs is as important, for similar reasons, as writing literate programs, and requires non-trivial skills;

• Writing proofs is aided by derived proof rules such as Theorem 1;

• Writing syntactically correct proofs (ultimately, the only appropriate sort of proofs) by hand can be aided by good organisation, such as suggested by Lamport, but resembles trying to write programs without use of a compiler.

• A well-designed proof checker would save work and time, playing a role rather like a compiler when developing programs. But proofformatting aids are as helpful as a proof checker. Examples would include a structured editor, and tools allowing fragments of a proof to be viewed according to the proof structure.

• The hierarchical structure recommended by Lamport for the most part works well, but there are occasions on which proofs of substeps may be used also in a different part of the proof, and there are as yet limited means within the hierarchical proof support tools to do so. Rigid hierarchical structure often entails duplication of proof fragments, which is unnecessary and undesirable for ‘literacy’.

Issues specific to TLA are:

• A certain amount of pure temporal logic (TL) manipulation is needed for the liveness proofs;

• A library of derived TL rules is needed for effective use of TLA. Deriving TLA temporal logic rules needed even for such simple proofs as this is not easy, unless one happens to be good at temporal logic. The most significant derived rule, needed for Weak Fairness proofs, is the Bridge Rule

\frac{\begin{matrix}□X ∧ Y ⇒ □Y \\ □X ⇒ □(Z ⇒ ◇Y ) \end{matrix}}{□X ∧ ◇□Z ⇒ ◇□Y}

The Bridge Rule is derived in the formal proof on Page 46 from the STL rules. It is needed to tie together the conclusion of the Lattice Rule and the fourth hypothesis of WF2 in the liveness proof. It is also used in the proof that the Lazy Caching algorithm implements the Complete Cache algorithm, in [Lad96]. For background to the Lazy Caching algorithm and its proof of correctness, see [LLOR94]. In order to state derived rules, one needs to use propositional variables, logical symbols that may be substituted by propositions. These are not TLA variables. However, it seems appropriate to declare them when used. We thus introduce an extra-TLA meta-category propositional variable to declare these variables.

• A restriction must be added to the application of the Lattice Rule in order that it shall be sound.

• An inference rule is not literally a statement of TLA. TLA proof steps are, however, supposed to be true TLA statements. Establishing the validity of a derived inference rule is a TLA meta-operation, and is not strictly TLA syntax. For convenience in writing proofs which need some temporal logic manipulation, it is desirable to allow the statement of inference rules as legitimate proof steps, even though TLA strictly defined does not allow it. The proof of such a rule, i.e., the substeps, consist in establishing its validity as a derived rule. Normally, that will be via a derivation. I’ve used such an operation to introduce and prove the Bridge Rule, as well as in other places. It obviates the need to claim a theorem outside of the proof proper, and then refer to it inside the proof. Other useful derived rules, which are used in the proof of the Bridge Rule and have frequent application in any temporal logic calculations, are STL7, STL2^{◇}, STL4^{◇} and STL6^{◇}. See the formal proof of the Bridge Rule for statements of these rules.

• Propositional logic does not follow from the TLA proof rules as given in [Lam94b].^{1} This is easy to fix and should be fixed by a user, either by including Modus Ponens and a set of Hilbert-style axioms for propositional logic, or by including a set of Gentzen-style ‘introduction’ and ‘elimination’ rules. Predicate logic is included in TLA, as shown in Rules E1 and F1 of [Lam94b, Figure 9].

1This may be seen as follows. Rules STL1 and STL4 are the only rules with nontrivial hypotheses. (The other rules with trivial hypotheses are usually called axioms in a Hilbert-style formulation of logic.) Both these rules increase the number of occurrences of the operator □ in the consequent over the number in the antecedent, as do all the STL rules (this follows trivially for the others). However, any propositional rule (or axiom)

has the same number of occurrences of □ in hypothesis and consequent, namely, none.

Thus no application of STL1 and STL4 can yield, for example, Modus Ponens, contrary

3 Why Do This?

Why is this logic of practical use? Here are a few questions asked about an earlier version of this paper, and some responses. Many of these points have been made before, but it seems they are not yet clearly understood within the community. Similar issues are discussed in [Lam89].

Is this work of any practical importance? You’re talking about proving the implementation, but still the implementation is just a formal specification. How is it related to a program?

The formal specification of the concrete buffer is a simplified specification of the design of a chip. (We have abstracted the four-bit-wide data path into a single data element. In a verification of this design, this abstraction would have to be decomposed into its component parts, but one would not expect the proof to differ significantly – just to gain a few new theorems.) A formal specification may be a precise description of an algorithm or a program or an architecture or whatever. You can’t tell from just looking at the symbols what a specification describes. It could be at a very low level of detail, describing the results of executing each individual step of an imperative program, or it could be thirty lines describing a property of a system implemented by many megalines of imperative code. A precise description of any implementation can always be considered to be a specification - a better one or a worse one. Even Fortran – but you might not be able easily to write down the exact semantics of a Fortran program, because it depends on the inner workings of the compiler (often a better approach is to describe the compiled code instead, using the semantics of the assembler [Yu93]). But if things are so tricky that you don’t know what’s going on or can’t say so succinctly, how on earth can you expect that your code does what you think it does? In safety-critical applications, for example, it’s crucial to know what the code does and how, and while proofs are merely hard, truly exhaustive testing is all but impossible.

to what Lamport says in [Lam94b, Section 5.6], that STL1 ‘incorporates all the rules of ordinary logic, such as modus ponens’. A similar extension to the argument shows that Modus Ponens does not follow from the Lattice Rule in combination with STL1–STL6.

To go about proving code correct is a task which can be structured into levels (e.g., [Par72, Par74, Wen78]). One separates the business of proving the architecture correct (i.e., that the machine implements the semantics of its machine code) from the business of proving the assembler correct (i.e., that the semantics of the assembler instructions are correct with respect to the machine code and its semantics), from the business of proving the compiler correct (i.e., that the sequence of instructions implementing each high-level statement implements its formal semantics) from the business of proving that the program is correct (one assumes that the instructions implement their formal semantics, and proves that the sequence of instructions implements the algorithm). You may ask ‘where do the formal specs stop?’ At the low end, at some level of granularity of the architecture, and at the high end, at the requirements specification. At which low level they terminate depends on the physics of the chip. But these questions (what does a proof apply to, how can you decompose proofs, what features can you show correct and how?) concern the metamathematics of a proof, which is not the topic of this paper. This paper is about the technical details of a particular proof, just one of many that would be involved in verification of code on a particular machine. In particular, proofs of liveness are important, because liveness is important, and further because engineers seem unused to specifying, or indeed thinking about, liveness.

What is important about liveness?

Liveness is important for systems with many concurrently-running processes, because, first, any system property may be regarded as a combination of safety + liveness [AS87] and, second, liveness is not guaranteed in a concurrent system. (Most specification methods understand and promote proofs mainly of safety properties.) For serial algorithms or code, one often assumes that at any stage there is nothing to prevent the next program action from taking place (PC users may smile at this point). It is generally more pressing to show that the program calculations yield the desired results. However, in concurrent systems, there are other processes in the environment whose actions may interfere with your own. These interferences may be encouraged (communication) or deplored (competition for other system resources). The trick is to design algorithms that succeed no matter what other processes do. And the property that they succeed in doing something no matter what else happens is assured by a proof of liveness.

Algorithms in TLA are described more carefully and more generally than in an imperative program. They are construed as a series of actions, defined by assertions concerning the pre- and post-values of program variables. For example, in Pascal one may write x := 1 to generate an action which sets the value of x equal to 1. In TLA, one may define an action unity =^{Δ} x′ = 1 which is a statement of logic expressing the postcondition that the value of x after the action, denoted x′, is equal to 1. In a given state, many actions may be enabled, and similarly many actions may take place at once (precisely when the preceding and succeeding states satisfy the definitions of more than one action). In an imperative program, usually one action is defined to follow another, and which one may be known at compile time (sequential composition) or only at run-time (many conditionals). TLA can specify such algorithms, but also more general ones that exist in a concurrent environment, in which the ordering of statements from different processes in an execution is much less predictable, and therefore in which ensuring liveness becomes important. Buffers, even though they may be essentially sequential objects, operate mostly in a environment with other processes around. It’s appropriate that they should be specified accordingly and liveness must be proven.

Where is the particular example you chose relevant, and are there any ‘real’ implementations around that directly relate to your specification of an implementation?

Every buffer implemented as an array in this way is a real implementation. A buffer implemented by a linked list or a ring buffer is different from this, and this proof shows nothing about such buffers. But one should be able to prove that they have the properties of a buffer also.

I prove that the properties of a buffer are true of an array with the operations I describe. That shows that a particular algorithm implements a buffer, if by buffer you mean something that satisfies the specification AbstractBuffer. The practical application is simply that if you implement a buffer by an array in this way, and want to know for sure that you have made no mathematical mistakes in your algorithm design, this proof shows that. And similarly if you join two such buffer implementations in series. But the subject matter of this paper is not really buffers. It’s proofs and how to do them, in detail.

What’s the point of doing such a detailed and intricate proof?

To distinguish between correct and incorrect proofs. Every correct proof admits such detail, and no incorrect proof does. If the detail works out, the proof is correct. Conversely, if the proof is incorrect, the detail will not work. Hence the point of the intricacy is to ensure that the proof is correct. A formal logic is defined syntactically by inference rules. Proofs of algorithm properties do not necessarily require elegant new theorems of the sort that excite mathematicians – after all, the mathematical insight went into devising the algorithm, and if you don’t already have some idea of why the algorithm works, you’re unlikely to be able to construct a formal proof of it. Rather, a formal proof requires a careful checking of details, and some organisation. If the details are not checked carefully, there may be gaps or falsehoods. It’s easy to omit proof obligations which are only discovered in the course of performing the formal proof. I know of no scientist working in verification who has not had such an experience. ‘Normal’ mathematical standards do not seem to suffice – Lamport notes that many concurrent ‘algorithms’ which were incorrect have been published along with their ‘proofs’ [LLOR94].

There is also the problem of scale. Methods of organising large proofs to ensure that all the bits fit together requires some kind of reliable decomposition. Syntactic proof methods lend themselves to decompositions of this sort – if all the bits are locally correct, there is nothing more to do to make the proof globally correct. There is less of a problem of ‘interpretation’ or ‘gaps’ than with proofs that may not be completely formal and which rely on an interpretive or implicit semantic component.

Finally, syntactic proofs are the only type of proof universally recognised by logicians as unequivocally demonstrating the relation of logical consequence between assumptions and conclusions. Of course, the proof rules have to be justified semantically, but as for a programming language, this is done once and thoroughly, and then all syntactically correct proofs using these rules are known to be valid according to the intended meaning of the formulae.

How can I tell the difference between an ‘important’ or ‘creative’ proof step, and one that is just routine?

That’s not so easy. There are some simple heuristics, such as that a step which has few substeps when expanded fully in logic is likely to be easier to perform than one which has many. However, it’s not so obvious how to distinguish a routine proof with many steps from a ‘creative’ proof with many steps. One can attempt to locate the ‘creativity’ in a proof by finding metalogical theorems such as Theorem 1, which factor out logically routine steps and focus on the ‘proof obligations’, the logically variable steps. The obligations are necessary and sufficient conditions for a proof of a certain form to exist. This kind of theorem seems to be the closest we can come to defining ‘creativity’ in a proof at the moment.

4 A Short Introduction to TLA

TLA is an assertional method designed for the specification and verification of concurrent algorithms. It is based on the by-now standard view of a system execution as an infinite progressive sequence of states, each state being defined by the collection of values of variables that are true in that state. As the system executes, the states change. A specification (or a program) constrains the states to change in certain specific ways. For example, executing an assignment x ← 1 causes the state to change so that in the new state the value of the variable x is 1. The state may also change in other ways. For example, suppose microcode variables are included in the state. Many of these variables will also change during the assignment, as will the register in which this assignment is carried out. But the specification may not need to comment on these changes – they can be ‘hidden’.

An action in TLA is semantically a relation between states. Let A be an action. A pair of states ⟨a, b⟩ ∈ A just in case intuitively state b is what you get by executing the action in state a. We specify action A by giving a syntactic definition of the relation. For example, an action such as x ← 1 can be defined by the logical relation x-in-the-new-state = 1. Notice that according to this definition, any action which ensures x in the new state is equal to 1 counts as a x ← 1 action. That’s appropriate and useful in concurrency – a state change may incorporate many actions defined in a description (may satisfy many action predicates). What’s important is that the relation between the new state and the old is precisely defined. If certain other variables may not change during the execution of an action, we have explicitly to state that in the action definition.

TLA refers to x-in-the-new-state syntactically by use of a special operator. In defining actions, one wants to talk about x-in-the-old-state and x-in-the-new-state, and to specify mathematical relations between these values. Values of variables in the old state are referred to by just using the variable name, say x. Values of the variable x in the new state are referred to as x′, as in the language Z [Spi89]. A logical definition of the action will be a mathematical expression involving x and x′. The assignment action could be specified by the equation x′ = 1. Notice that ‘normal’ mathematical equality is used in this definition; in fact, normal mathematical assertions and inferences can be made without tears. TLA uses normal mathematics for talking about everything. It includes Zermelo-Fraenkel set theory (just as in mathematics), so Riemann integrals and suchlike can be defined and used in a TLA specification just as you would use them in normal mathematics [Lam93b].

Variables such as x above may change value from state to state, and are called flexible variables. In contrast, there are parameters of formulas (for example, the a in the action push(a) which we use) which have a constant but unspecified value, not dependent on state. They are called rigid variables. Quantification over rigid and over flexible variables is different. If x is flexible, the quantifier ∃ ∃ ∃ ∃ ∃ ∃ x means that there is a value of x in every state such that [...]. In different states, the properties asserted of x may or may not hold (for example, the value of x may be related to the sequence of previous values of x). In contrast, quantification over rigid variables is just normal first-order quantification. The difference is not so important for the buffer example, since only rigid variables are quantified over.

TLA is a temporal logic. How much temporal logic is actually used in a TLA proof that an implementation meets a specification? In the example in this paper, quite a lot. But the amount of pure temporal logic used seems to vary much less than linearly with the size of the specification. Hence not much of a real example consists of temporal reasoning – maybe 2–4%, as may be seen from the lengthy proof in [LLOR95]. Most of the reasoning is straight mathematics and propositional and predicate logic. Theorem 1 shows that, of the five proof obligations incurred for an implementation proof, four are classical logic and just one is temporal logic - and that one is the liveness proof. TLA^{+} includes the usual module constructs, as well as useful shorthand notation for functions and finite sets, and such goodies.

An uncommon logical feature of TLA is the choose operator, Hilbert’s ϵ operator. I use it in the definition of the length of a sequence. choose binds a variable and a predicate, and the result is a term. choose x : P(x) denotes an object that satisfies P, if there is one, although it is not specified which if there is more than one, and has an unspecified value if there is no such object. It is extensional, in that it selects the same object satisfying equivalent predicates. It’s a primitive of TLA^{+}. choose satisfies axioms such as the following:

⊢ (∃ x : P(x)) ⇒ P(choose x : P(x))

provided that no instance of choose x : P(x) appears within the scope of a construct (a quantifier or another choose ) that binds a free variable y in choose x : P(x) (this condition may be fulfilled by a suitable change of bound variable at the appropriate place); and

⊢ (∀ x : (A(x) ≡ B(x))) ⇒ (choose x : A(x)) = (choose x : B(x))

Proving implementations correct in TLA is done by writing both the high-level specification and a description of the implementation in TLA, and proving that the implementation description logically implies the specification. Implementation is implication. The proof is carried out by using the assertional method [Gri81] as adapted to concurrent programs by Ashcroft [Ash75]. Section 8 gives a general TLA proof scheme for showing that one description implements another. A proof using this schema may be many pages long, for examples see [LLOR95].

A TLA specification has the form Init ∧ □[N]_{vars} ∧ F_{vars}(M). Init is a description of the initial state in which the system starts. □[N]_{vars} asserts the safety properties of the system. A safety property asserts that nothing untoward happens during execution. The standard type of safety property asserts that every action taken by the system in an execution is a legitimate action defined by the specification. □ is the always operator, saying that the following assertion is true in every state. N is the disjunction of all the action definitions of the system, i.e. it asserts that every step is a step which satisfies one or other of the action predicates of the specification. [N]_{vars} is defined to mean N ∨ vars′ = vars, i.e. that either N holds or the variables of interest do not change value during the step (in which case the step is a so-called stuttering step). (When considering liveness, we shall also need the notation ⟨N ⟩_{vars} which is defined to mean N ∧ vars′ ≠ vars.) The entire formula □[N]_{vars} thus says that every step is either a legitimate action, or the values of the variables of interest don’t change.

But one way for a system to satisfy the safety formula is for it never to progress beyond the initial state, i.e. that vars′ = vars is true at every state change, for ever. In order for the system to do any useful work, it must actually take some legitimate step at some point. The various ways of saying this are known as liveness properties. The forms of liveness property used in TLA specifications is a conjunction of so-called Weak Fairness requirements on actions and Strong Fairness requirements, specified in the formula above by the conjunct F_{vars}(M). In this paper, we only consider weak fairness requirements, denoted WF_{vars}(M). To assert weak fairness of an action A means to say that if A is enabled continuously, it must eventually happen in some state change. For an action to be enabled in a state s simply means that there is some state t such that the pair of states ⟨s, t⟩ satisfies the action predicate (it doesn’t say that t must be the next state in the execution, just that it could be). More formally, Enabled(A) is the formula obtained from A by replacing the primed variables by new variables and existentially quantifying them all. For example, Enabled(unity) =^{Δ} ∃ c : c = 1. To perform a proof, it’s not necessary to know how weak fairness is expressed in temporal logic, since there’s a TLA proof rule geared specially to the proof of Weak Fairness properties. But this proof rule ensures that liveness properties of the specification must be proved from liveness properties asserted of the implementation – they don’t come from nowhere! Sufficiently much liveness must be included in the implementation description to enable the liveness properties of the specification to be proved. (Connoisseurs who wish to consider other liveness properties than weak or strong fairness may write them out directly in temporal logic and reason with them using the PTL rules. TLA only includes specific inference rules for weak and strong fairness.)

One final TLA operator that appears in the proof rules is the ‘leadsto’ operator, ⇝. To say P ⇝ Q is to say that it is always the case that if P then eventually Q, expressed in temporal logic by □(P ⇒ ◇Q). We shall use the ⇝ operator and its definition in the proof.

This brief description of TLA leads to the specification and the proof of implementation.

5 Constructing the Specifications

We use three specifications for this example. AbstractBuffer (Figure 5) specifies the actions, safety and liveness properties which define a bounded buffer of size N , as in Figure 1. The buffer is represented by variable buffer, and has push and pop actions respectively to put elements in the buffer (provided that there aren’t N elements there already), and to take elements out of the buffer (provided there is something there). The buffer itself is a sequence. A state of the buffer is given by the values of variables, that is, by the value of buffer. The final portion of the module contains the assertion of properties of the buffer: that it starts off empty, that every state change of the buffer is caused by a push or pop action, and that always if there’s something in the buffer, eventually a pop must happen (recall that we don’t require that anything is ever put in the buffer).

The specification ConcreteBuffer (Figure 6) implements this in a very

[figure: buffer; buffer′; < b, x, y, d >; pop; < x, y, d >; < b, x, y, d >; push(a); < b, x, y, d, a >]

Figure 1: An Abstract Buffer with Operations

[figure: Buffer; Buffer′; ⊥; b; x; y; ⊥; d; ⊥; push(a); ⊥; b; x; y; ⊥; d; a; ⊥; b; x; y; ⊥; d; a; move(6); ⊥; b; x; y; d; ⊥; a; ⊥; b; x; y; d; ⊥; a; move(2); b; ⊥; x; y; d; ⊥; a; b; ⊥; x; y; d; ⊥; a; pop; ⊥; ⊥; x; y; d; ⊥; a]

Figure 2: A Concrete Buffer with Operations particular way. The Buffer is an array of fixed length N , as in Figure 2, where N = 7. Data values are inserted at the end of the array by push, provided this terminal position is void, and are removed from the first position of the array by pop, providing this position is non-void. Thus push, but not pop, is parametrised by the data value that is pushed. Since this value does not change from state to state (although the element is moved around, it doesn’t spontaneously change), it is a rigid variable so we may employ a first-order existential quantifier. ∃ a : push(a) asserts that some element has been pushed. To move elements from the rear of the Buffer to the front, there is an action move which moves an element into the position in front, if this position is void. move is itself parametrised by the starting position of the element moved. This parameter is another rigid variable. Existential quantifiers over these variables are the only quantifiers that appear in the specification. The specification is a logical formula asserting that the array starts out filled with ⊥ elements (which are not Data, by the assertion), that every state change of the Buffer is caused by a push, a pop or a move, and finally that if something can be popped, it eventually will be, and that if something can be moved, it eventually will be (recall our discussion in Section 1 why this is necessary). We need to show that the concrete buffer defined as an array implements the abstract buffer. A different way to implement the concrete buffer would be as a linked list.

AbstractBuffer and ConcreteBuffer, as well as Sequences, import the Naturals module, which is not given here. Naturals includes definitions of the set Nat of natural numbers along with the standard arithmetic operations. It also defines such constant symbols as 1, 2. Since there are many ways one could wish to write such a module, we don’t commit ourselves to one. The use we make of it is slight and any reasonable definition will suffice.

Finally, we write a double-buffer specification DoubleBuffer (Figure 7), which implements a buffer of size 2N by arranging two concrete buffers of size N in series, as in Figure 3 in which N = 3. This specification has similar actions to the concrete buffer, with identical enabling conditions, but one move action consists in shuttling an element from the front of the rear buffer to the rear of the front buffer. This is accomplished by an action which is simultaneously a pop of the rear buffer and a push of the same popped element onto the front buffer.

An important feature of the DoubleBuffer specification is that the same element that is popped from the rear buffer must appear on the front buffer. Consequently, move(N + 1) is defined as B1.pop ∧ B2.push(B1[1]).

The goal is to show that this double buffer implements an abstract buffer

[figure: Buffer1; Buffer2; Buffer1′; Buffer2′; ⊥; b; ⊥; d; ⊥; ⊥; push(a); ⊥; b; ⊥; d; ⊥; a; ⊥; b; ⊥; d; ⊥; a; move(6); ⊥; b; ⊥; d; a; ⊥; ⊥; b; ⊥; d; a; ⊥; move(4); B1.pop ∧; B2.push(B1[1]); ⊥; b; d; ⊥; a; ⊥; ⊥; b; d; ⊥; a; ⊥; move(2); b; ⊥; d; ⊥; a; ⊥; b; ⊥; d; ⊥; a; ⊥; pop; ⊥; ⊥; d; ⊥; a; ⊥]

Figure 3: A Double Buffer with Operations of size 2N . First, we prove that the double buffer actually implements a single array buffer with the same total number of places. We then prove that a concrete array buffer implements the abstract buffer specification. These two assertions are contained in Module Theorems (Figure 9).

TLA is a system for writing and verifying specifications and implementations of concurrent algorithms. Is there any explicit concurrency in this specification? Just a little, in module DoubleBuffer. The specification DoubleBuffer specifies a serial ‘composition’ of two buffers, of which the actions are not generally constrained to synchronise. The only exception to this is the move(N+1), which is defined to be simultaneously a pop of Buffer2 and a push of the same element on Buffer1. TLA specifications in general don’t enforce concurrency like this, but don’t rule it out either, except by means of whatever conditions are explicitly written in the action definitions.

6 Proving the Implementation

The proof that the system specified in DoubleBuffer implements the system specified in AbstractBuffer, with the parameter N of AbstractBuffer instantiated as 2.N , is straightforward. Implementation in TLA is implication, and we can use the transitivity of implication. We show that the double buffer implements a single concrete buffer, and then we show that a concrete buffer implements an abstract buffer. (In the Module Theorems, where this is stated, we retain the parameter N in this second assertion for convenience. Literally, the parameter should be 2N , but since the parameter is a parameter of the proof, a proof for N is isomorphic to a proof for 2N . We might as well save ink and say ‘N ’.)

Implementations of specifications, that is, implications of TLA specification formulas, are proved according to a template given in Section 8, available also as [LL94]. Call the description of the implementation the implementiens, and the description that is (asserted to be) implemented the implementiendum. The safety properties are proved by step simulation: a step of the implementiens is either an action step or a stuttering step of the implementiendum. The liveness properties of the implementiendum are proved from the liveness properties of the implementiens. In order to show the implication, it may be necessary to add history variables (state functions which record part of the history of a computation), or so-called prophecy variables (variables that predict a future action) to a specification. One may always add such variables, and the specification obtained by adding history Temporal Logic Rules

STL1. \frac{F a tautology}{□F}  STL4. \frac{F ⇒ G}{□F ⇒ □G}
STL2. ⊢ □F ⇒ F  STL5. ⊢ □(F ∧ G) ≡ (□F ) ∧ (□G)
STL3. ⊢ □□F ≡ □F  STL6. ⊢ (◇□F ) ∧ (◇□G) ≡ ◇□(F ∧ G)

LATTICE. \frac{≻ a well−founded partial order on a set S c not free in F or G F ∧ (c ∈ S) ⇒ (H_{c} ⇝ (G ∨ ∃d ∈ S : (c ≻ d) ∧ H_{d}))}{F ⇒ ((∃c ∈ S : H_{c}) ⇝ G)} Basic TLA Rules

TLA1. \frac{P ∧ (f′ = f ) ⇒ P′}{□P ≡ P ∧ □[P ⇒ P′]_{f}} TLA2. \frac{P ∧ [A]_{f} ⇒ Q ∧ [B]_{g}}{□P ∧ □[A]_{f} ⇒ □Q ∧ □[B]_{g}}

Invariance and Weak Fairness Rules

INV1. \frac{I ∧ [N]_{f} ⇒ I′}{I ∧ □[N]_{f} ⇒ □I}  INV2. ⊢ □I ⇒ (□[N]_{f} ≡ □[N ∧ I ∧ I′]_{f} )

WF1.

\frac{P ∧ [N]_{f} ⇒ (P′ ∨ Q′) P ∧ ⟨N ∧ A⟩_{f} ⇒ Q′ P ⇒ Enabled ⟨A⟩_{f}}{□[N]_{f} ∧ WF_{f} (A) ⇒ (P ⇝ Q)}

WF2.

\frac{⟨N ∧ B⟩_{f} ⇒ ⟨\overline{M}⟩\overline{g} P ∧ P′ ∧ ⟨N ∧ A⟩_{f} ∧ \overline{Enabled ⟨M⟩_{g}} ⇒ B P ∧ \overline{Enabled ⟨M⟩_{g}} ⇒ Enabled ⟨A⟩_{f} □[N ∧ ¬B]_{f} ∧ WF_{f} (A) ∧ □F ∧ ◇□\overline{Enabled ⟨M⟩_{g}} ⇒ ◇□P}{□[N]_{f} ∧ WF_{f} (A) ∧ □F ⇒ \overline{WF_{g}(M)}}

where F , G, H_{c} are TLA formulas  P, Q, I are predicates A, B, N, M are actions  f , g are state functions

Figure 4: Axioms and proof rules used of TLA. variables is logically equivalent to the original specification when the history variables are hidden by quantification. Prophecy variables were defined in [AL91]. A prophecy variable, Descending, a so-called stuttering variable, is used in the proof that the concrete buffer implements the abstract buffer. It’s a state function defined in the scope of the proof. A stuttering variable is a variable on a well-founded order (often, as in this case, the natural numbers) which is decreased by implementation steps which are stuttering steps of the specification. When this variable reaches a minimum value, then some implementation step occurs which is a non-stuttering step of the specification. Its value at the outset predicts how many stuttering steps may occur before a non-stuttering step is enabled. In this case, how many CB.moves may occur before a CB.pop is enabled.

Firstly, we show that DoubleBuffer is a ConcreteBuffer with size 2.N – the first implication in Module Theorems. The step simulation is straightforward. There are no stuttering steps. The push action is the push of the rear buffer, the pop is the pop of the forward buffer, and the move action is the move action of whichever buffer holds the two slots – in the case it’s a move from the first slot of the rear buffer to the last slot of the forward buffer, the move action is simultaneously a pop of the rear and a push of the forward with the same element (whatever that element may be).

In order to show that the ConcreteBuffer implements an AbstractBuffer, we need to produce a state function in ConcreteBuffer that satisfies the conditions of being the AbstractBuffer.buffer. This association of a state function with each existentially quantified variable of AbstractBuffer in the theorem is called the refinement mapping. It’s easy to see what this mapping should be. In AbstractBuffer, the buffer is regarded as a sequence of whatever has been put in, up to length N . In ConcreteBuffer, there are always N slots in Buffer, some of which are filled and some not. If we ignore the slots that aren’t filled, the sequence of elements is the sequence needed for the AbstractBuffer.buffer. In Module Sequences, a standard TLA^{+} module used in other specifications [LLOR94], the function SelectSeq forms a sequence from an input sequence by selecting those elements of the input sequence which satisfy a particular predicate. (The alert may notice that Sequences imports a module Naturals, which defines certain properties of the natural numbers. We don’t bother to include the module as a figure.) We choose the predicate saying that a value is non-void, and define the state function consisting of the sequence of non-void elements of the ConcreteBuffer.Buffer. This state function implements AbstractBuffer.buffer, and is the sole statefunction/variable pair in the refinement mapping.

Step simulation is straightforward. The action ConcreteBuffer.pop simulates the action AbstractBuffer.pop, and similarly for ConcreteBuffer.push and AbstractBuffer.push. The only conditions that are not trivial are those on Len(buffer). However, it is easy to prove that ConcreteBuffer.push adds a non-void element to ConcreteBuffer.Buffer, without removing any non-void elements that are there already and thus increases the length of the state function SelectSeq(Buffer,NonVoid) simulating AbstractBuffer.buffer by 1. Similarly, it is easy to see that ConcreteBuffer.pop decreases the number of non-void elements of ConcreteBuffer.Buffer by 1, without adding any. It is straightforward to show that the action ConcreteBuffer.move is a stuttering step of AbstractBuffer. This completes the demonstration of safety properties.

Liveness properties are more complex. The AbstractBuffer satisfies the liveness property that if a pop may be done, eventually it will be done. Since the pop of the AbstractBuffer is simulated by the pop of the ConcreteBuffer, ConcreteBuffer.pop needs to satisfy the same liveness property. However, the enabling conditions for the AbstractBuffer.pop and the ConcreteBuffer.pop are not the same! AbstractBuffer.pop is enabled when there is some nonvoid element in AbstractBuffer.buffer. However, ConcreteBuffer.pop has the prima facie stronger requirement that there must be a non-void element in the first place of its Buffer. But if there is not, and there is a non-void element somewhere in the Buffer, some move is enabled. To see why, note that there must be some first k such that Buffer[k] is non-void. Thus Buffer[k −1] must be void by hypothesis (since k is at least 2 by hypothesis, Buffer[k −1] is a well-defined Buffer place) and hence move(k) is enabled. Other moves may also be enabled, but this first element is important since it’s the one which will eventually be popped off the front by a ConcreteBuffer.pop operation, which simulates the AbstractBuffer.pop that we are trying to show will occur. Any enabled concrete move remains enabled if a concrete push occurs, since only Buffer[N ] is affected by a push, only move(N ) has any precondition concerning Buffer[N ], and move(N ) is not enabled if push is enabled. Similar reasoning shows that when move(k) occurs, any enabled move(j ) for j ≠ k remains enabled (note that move(k +1) could not have been enabled if move(k) was enabled, but might be enabled afterwards if Buffer[k + 1] were non empty). Thus, for k the first number such that Buffer[k] is non-void, move(k) remains enabled through other move, or push, operations. Weak liveness guarantees it will eventually be executed, whence Buffer[k − 1] becomes the first non-void Buffer place. The same reasoning applies now to Buffer[k − 1] and move(k − 1). Intuitively, the first non-void element will eventually make it to Buffer[1], at which point ConcreteBuffer.pop becomes enabled. ConcreteBuffer.pop remains not enabled until Buffer[1] is nonvoid, at which point it becomes enabled. It remains enabled and thus must eventually be executed, according to its weak fairness requirement. This step simulates AbstractBuffer.pop. Thus is the weak-fairness requirement on AbstractBuffer.pop satisfied. Notice that one requires not only the liveness of ConcreteBuffer.pop to show that AbstractBuffer.pop is live, but also the liveness of ConcreteBuffer.move. If ConcreteBuffer.move is not required to be live, the ConcreteBuffer could sit there with elements non-void, and with ConcreteBuffer.push and ConcreteBuffer.pop both disabled, waiting for an enabled move that will never happen. The liveness of AbstractBuffer.pop could not be proven in this case, since AbstractBuffer.pop is enabled, but will not be executed. Thus, the liveness of ConcreteBuffer.move is crucial for the proof of liveness of AbstractBuffer.pop.

We have informally indicated the refinement mapping, and the step simulation, and how the step simulation and the liveness requirements are proved. This suffices to construct a formal proof in TLA of the implementation according to the template given in Section 8.

There are two minor counters to our claim that the proof we give is syntacically formal. One step of temporal logic is required which we were unable to derive strictly from STL1–STL6 and the Lattice Rule, the TLA temporal logic rules. Further, we were unable to modify our use of WF2, the Lattice Rule and WF1 in order to avoid this calculation in temporal logic. Problems of this sort may easily be avoided by adjoining rules of propositional logic, as we noted in Section 2. A simple derivation may be given using Modus Ponens and tautologies, and we include one due to Lamport.

The second counter is that we require a small bit of simple arithmetic in order to perform the calculations with Descending for the stuttering steps (Descending is a so-called stuttering variable). We have described no Naturals module and therefore we assume that the arithmetic is adequate to derive the trivial arithmetic results we need. The steps which need such arithmetic are marked in the proof, and the reader can easily see by inspection that the arithmetic is correct - it involves merely noting such facts as that (a) + is associative; (b) (i −2^{N −(k−1)} +2^{N −k}) < i for k ∈ 2..N ; and (c) 2^{N −N} = 1 and (i − 1) < i. Assertion (a) should be included in any arithmetic module, since it’s a basic axiom for addition, and (b) and (c) require only easy calculations with exponents and <. We assume the reader is sufficiently assured of the truth of these two assertions not to need a formal proof of either.

7 The Proof Style

We use the hierarchical proof style advocated by (and supported by the macros of) Lamport [Lam93a]. Each step is justified by a sequence of lowerlevel steps, such that it is a rigorous logical consequence of the lower-level steps. The inference rules which may be used to derive a step from a sequence of lower-level steps are given in Figure 4 (the use of propositional logic, predicate logic, and some elementary propositional temporal logic, is understood). Each lower-level step in turn must be justified in the same manner. The proof stops descending when propositional logic, or obvious theorems concerning data structures such as the natural numbers, or properties of sequences, or any known mathematical theorems, are reached. In practice, one halts a descent in a proof when one is certain how to complete the proof but too lazy to do so.

The point about the style is that a proof in TLA is logically rigorous, and entirely syntactic. That means that each step in the proof is derived from other, named, steps using only the permitted syntactic rules of inference. Furthermore, no new principles need to be used in order to prove a new example correct. TLA is a logic, with general rules, and has remained stable for a number of years. Every proof of correctness in TLA uses the same old TLA rules, albeit in a new way. The proof style supports this rigorous syntactic derivation.

The proof steps are numbered in the following way: step ⟨n ⟩.m has as substeps steps ⟨n + 1⟩.1 through ⟨n + 1⟩.k, with the last step being a QED step. The proof of the QED step is usually a commentary indicating how the steps ⟨n + 1⟩.1 through ⟨n + 1⟩.k yield the proof of step ⟨n ⟩.m.

Formulas are also written in a special form. Conjuncts and disjuncts may be lengthy, and so rather than being written in a line, they are written as tableaux in which each line is a separate con(dis)junct, preceded by the con(dis)junction sign. Thus nested con(dis)juncts are indented, and consequently much easier to read or to manipulate. The notation is explained in [Lam94a].

8 The Proof Scheme for Implementation

In order to introduce the proof style, and to give a taste of a TLA proof, we present a general proof scheme to be used when showing that one specification (∃ y : Init_{1} ∧ □[N_{1}]_{v} ∧ L_{1}) implements another (∃ x : Init_{2} ∧ □[N_{2}]_{w} ∧ L_{2}). This schema illustrates the hierarchical proof style recommended for use with TLA [Lam93a], and briefly explained in Section 7 above.

To Prove:

(∃ y : Init_{1} ∧ □[N_{1}]_{v} ∧ L_{1}) ⇒ (∃ x : Init_{2} ∧ □[N_{2}]_{w} ∧ L_{2})

Most of the structure of an implementation proof is just logic, that is, propositional logic and application of TLA Proof Rules, as may be seen below. The important parts to show for each individual problem instance are indicated by ‘********’. All of these are simply mathematics and not temporal logic, with the important exception of the liveness proof. Showing liveness can involve some non-trivial temporal logic, and uses rules WF1, Lattice, and WF2.

First, the definition of the refinement mapping.

Let: \overline{Init_{2}} =^{Δ} Init_{2}  with  x ← \overline{x}
\overline{w} =^{Δ} w  with  x ← \overline{x}
\overline{L_{2}} =^{Δ} L_{2}  with  x ← \overline{x}

Now the proof that the initial condition of the implementation implies the initial condition of the specification.

⟨1⟩1. Init_{1} ⇒ \overline{Init_{2}} Proof: ******This must be shown for each problem instance.*******

Next, the proof that the initial condition and safety property of the implementation implies that there is an invariant.

⟨1⟩2. Init_{1} ∧ □[N_{1}]_{v} ⇒ □Inv

⟨2⟩1. Init_{1} ⇒ Inv

Proof: ******This must be shown for each problem instance.******* ⟨2⟩2. Inv ∧ □[N_{1}]_{v} ⇒ □Inv

Proof:

⟨3⟩1. Inv ∧ [N_{1}]_{v} ⇒ Inv′ Proof: ******This must be shown for each problem instance*******.

⟨3⟩2. Q.E.D.

Proof: Immediate from ⟨3⟩1 by Rule INV1. ⟨2⟩3. Q.E.D.

Proof: Immediate from ⟨2⟩1 and ⟨2⟩2 by propositional logic.

Using the invariant and the safety property of the implementation, one derives the safety property of the specification. Some manipulation using the invariant is needed in the substeps.

⟨1⟩3. Init_{1} ∧ □[N_{1}]_{v} ⇒ □[\overline{N_{2}}]\overline{w} Proof: ⟨2⟩1. □Inv ∧ □[N_{1}]_{v} ⇒ □[\overline{N_{2}}]\overline{w}

Proof:

⟨3⟩1. □Inv ∧ □[N_{1}]_{v} ⇒ □[N_{1} ∧ Inv ∧ Inv′]_{v} Proof: Immediate from INV2 using STL5.

⟨3⟩2. □[N_{1} ∧ Inv ∧ Inv′]_{v} ⇒ □[\overline{N_{2}}]\overline{w} Proof: ⟨4⟩1. [N_{1} ∧ Inv ∧ Inv′]_{v} ⇒ [\overline{N_{2}}]\overline{w}

Proof: ******This must be shown*********. ⟨4⟩2. Q.E.D.

Proof: Immediate from ⟨4⟩1 by STL4. ⟨3⟩3. Q.E.D.

Proof: Immediate from ⟨3⟩1 and ⟨3⟩2 by propositional logic. ⟨2⟩2. Q.E.D.

Proof: Immediate from ⟨1⟩2 and ⟨2⟩1 by propositional logic.

The proof that the liveness property of the specification is implied by the implementation has no general form. The TLA Proof Rules WF1, Lattice, and WF2 are used, and it is necessary to instantiate the hypotheses of these rules by appropriate formula taken from the problem instance. This argument may involve some nontrivial temporal logic, but is the only part of a TLA implementation proof to do so.

⟨1⟩4. □Inv ∧ □[N_{1}]_{v} ∧ L_{1} ⇒ \overline{L_{2}}

Proof: *******Uses Rules WF1, WF2 and the Lattice Rule.********* ⟨1⟩5. Init_{1} ∧ □[N_{1}]_{v} ∧ L_{1} ⇒ \overline{Init_{2}} ∧ □[\overline{N_{2}}]\overline{w} ∧ \overline{L_{2}}

Proof: Immediate from ⟨1⟩1, ⟨1⟩3 and ⟨1⟩4 by propositional logic. ⟨1⟩6. Q.E.D.

Proof: Theorem follows from ⟨1⟩5 by quantifying over the free variables.

One may observe directly from the proof scheme above that to prove the theorem for any given problem instance, it suffices to show just the following five steps, only the last of which may involve temporal logic:

module AbstractBuffer

import Naturals, Sequences

parameters buffer : variable Data, N : constant

predicates Init =^{Δ} buffer = ⟨ ⟩

actions push(a) =^{Δ} ∧ a ∈ Data ∧ Len(buffer) < N ∧ buffer′ = buffer ∘ ⟨a ⟩

pop =^{Δ} ∧ Len(buffer) > 0 ∧ buffer′ = tail(buffer)

temporal Spec =^{Δ} ∧ Init ∧ □[pop ∨ ∃ b : push(b)]_{buffer} ∧ WF_{buffer}(pop)

Figure 5: Module AbstractBuffer

⟨1⟩1. Init_{1} ⇒ \overline{Init_{2}} ⟨1⟩2. Init_{1} ⇒ Inv ⟨1⟩3. Inv ∧ [N_{1}]_{v} ⇒ Inv′ ⟨1⟩4. [N_{1} ∧ Inv ∧ Inv′]_{v} ⇒ [\overline{N_{2}}]\overline{w} ⟨1⟩5. □Inv ∧ □[N_{1}]_{v} ∧ L_{1} ⇒ \overline{L_{2}}

The following theorem summarises this information.

Theorem 1 ⊢ _{TLA}(∃ y : Init_{1} ∧□[N_{1}]_{v} ∧L_{1}) ⇒ (∃ x : Init_{2} ∧□[N_{2}]_{w} ∧L_{2}) if there is a formula Inv and refinement mapping \overline{x} such that ⊢ _{PredicateLogic}(Init_{1} ⇒ \overline{Init_{2}}) ∧ (Init_{1} ⇒ Inv) ∧ (Inv ∧ [N_{1}]_{v} ⇒ Inv′) ∧ ([N_{1} ∧ Inv ∧ Inv′]_{v} ⇒ [\overline{N_{2}}]\overline{w}) and ⊢ _{TLA}(□Inv ∧ □[N_{1}]_{v} ∧ L_{1} ⇒ \overline{L_{2}})

Theorem 1 will be used to simplify syntactically the proof of liveness of the abstract buffer from the liveness assumptions of the concrete buffer.

module ConcreteBuffer

parameters Buffer : variable ⊥, Data, N : constant

import Naturals, Sequences

assumption N ∈ Nat

definition ⊥ =^{Δ} choose x : x ∉ Data

predicates Init =^{Δ} ∧ ∀ n ∈ 1 . . N : Buffer[n] = ⊥

actions push(a) =^{Δ} ∧ a ∈ Data ∧ Buffer[N ] = ⊥ ∧ Buffer′[N ] = a ∧ ∀ i ∈ 1 . .(N − 1) : unchanged Buffer[i]

pop =^{Δ} ∧ Buffer[1] ≠ ⊥ ∧ Buffer′[1] = ⊥ ∧ ∀ i ∈ 2 . . N : unchanged Buffer[i]

move(k) =^{Δ} ∧ k ∈ 2 . . N ∧ Buffer[k] ≠ ⊥ ∧ Buffer[k − 1] = ⊥ ∧ Buffer′[k] = ⊥ ∧ Buffer′[k − 1] = Buffer[k] ∧ ∀ i ∈ 1 . .(k − 2) : unchanged Buffer[i] ∧ ∀ i ∈ (k + 1) . . N : unchanged Buffer[i]

temporal Spec =^{Δ} ∧ Init ∧ □[pop ∨ ∃ b : push(b) ∨ ∃ k : move(k)]_{Buffer} ∧ WF_{Buffer}(pop) ∧ WF_{Buffer}(∃ k : move(k))

Figure 6: Module ConcreteBuffer

module DoubleBuffer

parameters Buffer1, Buffer2 : variable Data, N : constant

import Naturals, Sequences include ConcreteBuffer as B1 with Buffer ← Buffer1, N ← N include ConcreteBuffer as B2 with Buffer ← Buffer2, N ← N

predicates Init =^{Δ} ∧ ∀ n ∈ 1 . . N : Buffer1[n] = ⊥ ∧ ∀ n ∈ 1 . . N : Buffer2[n] = ⊥

actions push(a) =^{Δ} B2.push(a) ∧ unchanged Buffer1 pop =^{Δ} B1.pop ∧ unchanged Buffer2 move(k) =^{Δ} ∧ k ∈ 2..2N ∧ k ∈ 2 . . N ⇒ ∧ B1.move(k) ∧ ∀ i ∈ (2 . . N ) \{k, k − 1} : unchanged Buffer1[i] ∧ unchanged Buffer2 ∧ k ∈ N + 2 . . 2N ⇒ ∧ B2.move(k − N ) ∧ ∀ i ∈ (2 . . N ) \{k − N , k − N − 1} : unchanged Buffer2[i] ∧ unchanged Buffer1 ∧ k = N + 1 ⇒ B2.pop ∧ B1.push(Buffer2[1])

temporal Spec =^{Δ} ∧ Init ∧ □[pop ∨ ∃ b : push(b) ∨ ∃ k : move(k)]_{Buffer1,Buffer2} ∧ WF_{Buffer1,Buffer2}(pop) ∧ WF_{Buffer1,Buffer2}(∃ k : move(k))

Figure 7: Module DoubleBuffer

module Sequences

import Naturals

m . . n =^{Δ} {i ∈ Nat : (m ≤ i) ∧ (i ≤ n)} Len(s) =^{Δ} choose n : (n ∈ Nat) ∧ ((Domain s) = (1 . . n))

Head(s) =^{Δ} s[1]

Tail(s) =^{Δ} [i ∈ 1 . .(Len(s) − 1) ↦ s[i + 1]]

s ∘ t  =^{Δ} [i ∈ 1 . .(Len(s) + Len(t)) ↦ if i ≤ Len(s) then s[i] else t[i − Len(s)]]

Seq(S) =^{Δ} union {[(1 . . n) → S] : n ∈ Nat} SelectSeq(s, test( )) =^{Δ} let F [ t : Seq({s[i] : i ∈ (1 . . Len(s))}) ] =^{Δ} if t = ⟨ ⟩ then ⟨ ⟩ else if test(Head(t)) then ⟨Head(t)⟩ ∘ F [Tail(t)] else F [Tail(t)] in F [s]

SubSeq(s, m, n) =^{Δ} [i ∈ (1 . .(1 + n − m)) ↦ s[i + m − 1]]

Figure 8: Module Sequences

module Theorems

include Doublebuffer as DB(N) include ConcreteBuffer as CB(N) include AbstractBuffer as AB(N)

theorems ∃ Buffer1, Buffer2 : DB(N ).Spec ⇒ ∃ Buffer : CB(2N ).Spec ∃ Buffer : CB(N ).Spec ⇒ ∃ buffer : AB(N ).Spec

Figure 9: Module Theorems

9 The Proof

We must assume that TLA contains either Modus Ponens plus a set of Hilbert-style axioms, or a set of Gentzen-style ‘introduction’ and ‘elimination’ rules, for propositional logic, as suggested in Section 2. We assume propositional and predicate logic as given - neither the purpose of TLA nor of this paper is to explain to the reader how to use them in a proof.

I use a conventional wording for indicating the completion of a proof as follows:

• the word follows indicates that some propositional or predicate logic manipulation is required;

• follows immediately indicates that trivial propositional or predicate logic is required, such as forward-chaining of conditionals, rearrangement of hypotheses, or substution and quantification of a variable;

• immediate indicates that a direct textual substitution is required.

These ‘rules’ are not necessarily applied rigorously.

The first theorem proves that the DoubleBuffer implements a Buffer of twice the size. This is a straightforward proof following the schema in Section 8. It is also rigorously logically correct in TLA without recourse to metatheorems concerning the logic. Each step of the DoubleBuffer is a step of the Buffer, so there are no stuttering steps. Furthermore, since liveness of all the move operations of the DoubleBuffer is specified, including the move that is a pop of the rear buffer and simultaneously a push on the from buffer, this guarantees that elements will be shuttled from the front of the rear buffer to the rear of the front buffer, even though liveness is not specified for push for any individual buffer.

⟨1⟩1. ∃ Buffer1, Buffer2 : DB(N ).Spec ⇒ ∃ Buffer : CB(2N ).Spec

Let: \overline{Buffer} =^{Δ} Buffer1 ∘ Buffer2

The first step is showing that the initial conditions of the specification are satisfied by the initial conditions of the implementation.

⟨2⟩1. DB.Init ⇒ \overline{CB(2N ).Init} Proof: DB.Init =^{Δ} ∧ ∀ n ∈ 1 . . N : Buffer1[n] = ⊥ ∧ ∀ n ∈ 1 . . N : Buffer2[n] = ⊥

\overline{CB.Init} =^{Δ} ∀ n ∈ 1 . . 2N : \overline{Buffer}[n] = ⊥

The proof follows immediately from the definitions of Buffer1 ∘ Buffer2 and \overline{Buffer}.

The invariant is that every element in all of the buffer arrays is either an element of the set Data or is ⊥. We now prove the invariant. It’s messy to write but trivial to prove. I believe it’s marginally easier to survey this proof when the invariant is written explicitly, and the formulae are not so complicated as to force explicit definition at this stage.

Let: k : constant ⟨2⟩2. DB.Init ∧□[∃ a : DB.push(a)∨DB.pop ∨∃ k : DB.move(k)]_{Buffer1,Buffer2}

⇒ □(k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

Proof:

⟨3⟩1. DB.Init ⇒ (k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

Proof:

⟨4⟩1. DB.Init ⇒ (k ∈ 1 . . N ⇒ (Buffer1[k] = ⊥ ∧ Buffer2[k] = ⊥)) Proof: Follows immediately from the definition of DB.Init.

⟨4⟩2. Q.E.D.

Proof: Follows immediately by propositional logic from ⟨4⟩1. ⟨3⟩2.

∧ (k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

∧ □[∃ a : DB.push(a) ∨ DB.pop ∨ ∃ k : DB.move(k)]_{Buffer1,Buffer2} ⇒

□(k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

Proof: ⟨4⟩1.

∧ (k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

∧ [∃ a : DB.push(a) ∨ DB.pop ∨ ∃ k : DB.move(k)]_{Buffer1,Buffer2}

⇒ (k ∈ 1 . . N ⇒ ∧ (Buffer1′[k] ∈ Data ∨ Buffer1′[k] = ⊥) ∧ (Buffer2′[k] ∈ Data ∨ Buffer2′[k] = ⊥))

Proof: ⟨5⟩1.

∧ (k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

∧ ∃ a : DB.push(a)

⇒ (k ∈ 1 . . N ⇒ ∧ (Buffer1′[k] ∈ Data ∨ Buffer1′[k] = ⊥) ∧ (Buffer2′[k] ∈ Data ∨ Buffer2′[k] = ⊥))

Proof:

Let: a : constant ⟨6⟩1.

∧ (k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

∧ DB.push(a) ⇒

(k ∈ 1 . . N ⇒ ∧ (Buffer1′[k] ∈ Data ∨ Buffer1′[k] = ⊥) ∧ (Buffer2′[k] ∈ Data ∨ Buffer2′[k] = ⊥))

Proof: ⟨7⟩1. k ∈ 1 . .(N − 1) ∧ DB.push(a) ⇒

Buffer1′[k] = Buffer1[k] ∧ Buffer2′[k] = Buffer2[k] Proof: Follows immediately from the definition of DB.push(a).

⟨7⟩2. DB.push(a) ⇒ Buffer1′[N ] = Buffer1[N ]

Proof: Follows immediately from the definition of DB.push(a). ⟨7⟩3. DB.push(a) ⇒ Buffer2′[N ] ∈ Data

Proof: Follows immediately from the definition of DB.push(a). ⟨7⟩4. Q.E.D.

Proof: Follows by cases and elementary equational reasoning from ⟨7⟩1, ⟨7⟩2 and ⟨7⟩3.

⟨6⟩2. Q.E.D.

Proof: Follows immediately by existential quantification over a from ⟨6⟩1.

⟨5⟩2.

∧ (k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

∧ DB.pop ⇒

(k ∈ 1 . . N ⇒ ∧ (Buffer1′[k] ∈ Data ∨ Buffer1′[k] = ⊥) ∧ (Buffer2′[k] ∈ Data ∨ Buffer2′[k] = ⊥))

Proof: Follows from the definition of DB.pop using similar reasoning to that used for DB.push(a).

⟨5⟩3.

∧ (k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

∧ ∃ j : DB.move(j )

⇒ (k ∈ 1 . . N ⇒ ∧ (Buffer1′[k] ∈ Data ∨ Buffer1′[k] = ⊥) ∧ (Buffer2′[k] ∈ Data ∨ Buffer2′[k] = ⊥))

Let: j : constant ⟨6⟩1.

∧ (k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

∧ DB.move(j ) ⇒

(k ∈ 1 . . N ⇒ ∧ (Buffer1′[k] ∈ Data ∨ Buffer1′[k] = ⊥) ∧ (Buffer2′[k] ∈ Data ∨ Buffer2′[k] = ⊥))

Proof: Follows similarly to the case for DB.push(a) by division into cases j = k, j = (k − 1), j ∉ {k, k − 1} and using propositional logic on the definition of DB.move.

⟨6⟩2. Q.E.D.

Proof: Follows immediately from ⟨6⟩1 by existential quantification over j in DB.move(j ).

⟨5⟩4.

∧ (k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

∧ Buffer1′ = Buffer1 ∧ Buffer2′ = Buffer2

⇒ (k ∈ 1 . . N ⇒ ∧ (Buffer1′[k] ∈ Data ∨ Buffer1′[k] = ⊥) ∧ (Buffer2′[k] ∈ Data ∨ Buffer2′[k] = ⊥))

Proof: Follows immediately by substitution. ⟨5⟩5. Q.E.D.

Proof: Follows immediately by propositional logic from ⟨5⟩1, ⟨5⟩2, ⟨5⟩3 and ⟨5⟩4.

⟨4⟩2. Q.E.D.

Proof: Immediate from ⟨4⟩1 by Rule INV1. ⟨3⟩3. Q.E.D.

Proof: Follows immediately from ⟨3⟩1 and ⟨3⟩2 by propositional logic.

The safety property of the specification follows from the safety property of the implementation using the invariant. In this proof, we explicitly define Inv to be the invariant, which makes the layout of the proof more manageable. Safety follows by step simulation. There are no stuttering steps in this case – every step of the double buffer is a corresponding step of the double-size single buffer.

⟨2⟩3. ∧ DB.Init

∧ □[\begin{matrix}∨ ∃ a : DB.push(a) \\ ∨ DB.pop \\ ∨ ∃ k : DB.move(k)]_{Buffer1,Buffer2} \end{matrix}

⇒
□[\begin{matrix}∨ \overline{∃ a : CB(2N ).push(a)} \\ ∨ \overline{CB(2N ).pop} \\ ∨ \overline{∃ k : CB(2N ).move(k)}]\overline{Buffer} \end{matrix}

Proof:

Let: k : constant Inv =^{Δ} (k ∈ 1 . . N ⇒ ∧ (Buffer1[k] ∈ Data ∨ Buffer1[k] = ⊥) ∧ (Buffer2[k] ∈ Data ∨ Buffer2[k] = ⊥))

⟨3⟩1. ∧ □Inv

∧ □[\begin{matrix}∨ ∃ a : DB.push(a) \\ ∨ DB.pop \\ ∨ ∃ k : DB.move(k)]_{Buffer1,Buffer2} \end{matrix}

⇒
□[\begin{matrix}∨ \overline{∃ a : CB(2N ).push(a)} \\ ∨ \overline{CB(2N ).pop} \\ ∨ \overline{∃ k : CB(2N ).move(k)}]\overline{Buffer} \end{matrix}

Proof:

⟨4⟩1. ∧ □Inv

∧ □[\begin{matrix}∨ ∃ a : DB.push(a) \\ ∨ DB.pop \\ ∨ ∃ k : DB.move(k)]_{Buffer1,Buffer2} \end{matrix}

⇒ □[\begin{matrix}∧ ∨ ∃ a : DB.push(a) ∨ DB.pop ∨ ∃ k : DB.move(k) \\ ∧ Inv \\ ∧ Inv′]_{Buffer1,Buffer2} \end{matrix}

Proof: Immediate from INV2 using propositional logic. ⟨4⟩2. □[\begin{matrix}∧ ∨ ∃ a : DB.push(a) ∨ DB.pop ∨ ∃ k : DB.move(k) \\ ∧ Inv \\ ∧ Inv′]_{Buffer1,Buffer2} \end{matrix}

⇒ □[\begin{matrix}∨ \overline{∃ a : CB(2N ).push(a)} \\ ∨ \overline{CB(2N ).pop} \\ ∨ \overline{∃ k : CB(2N ).move(k)}]\overline{Buffer} \end{matrix}

Proof: ⟨5⟩1. [\begin{matrix}∧ ∨ ∃ a : DB.push(a) ∨ DB.pop ∨ ∃ k : DB.move(k)) \\ ∧ Inv \\ ∧ Inv′]_{Buffer1,Buffer2} \end{matrix}

⇒

[\begin{matrix}∨ \overline{∃ a : CB(2N ).push(a)} \\ ∨ \overline{CB(2N ).pop} \\ ∨ \overline{∃ k : CB(2N ).move(k)}]\overline{Buffer} \end{matrix}

Proof: ⟨6⟩1. ∃ a : DB.push(a) ∧ Inv ∧ Inv′

⇒

∃ a : CB(2N ).push(a) Proof: Let: a : constant ⟨7⟩1. DB.push(a)

⇒

CB(2N ).push(a) Proof: Follows from substitution \overline{Buffer}[2N ] = Buffer2[N ].

⟨7⟩2. Q.E.D.

Proof: Follows immediately by existentially quantifying over a and adjoining Inv and Inv′ to the hypothesis.

⟨6⟩2. DB.pop ∧ Inv ∧ Inv′

⇒ / (\overline{CB(2N ).pop})

Proof: Follows similarly to ⟨6⟩1. ⟨6⟩3. ∃ k : DB.move(k) ∧ Inv ∧ Inv′

⇒

∃ k : CB(2N ).move(k) Proof:

The variable k is already declared. We want the free k in Inv to be instantiated to the variable in move when the quantifier ∃ k : is removed. The reader may convince herself that this is legitimate predicate logic. We note it here to avoid momentary confusion.

⟨7⟩1. DB.move(k) ∧ Inv ∧ Inv′ ⇒ CB(2N ).move(k)

Proof: The proof is broken into components, corresponding to the antecedents of the conjuncts of DB.move. A full proof would contain a little propositional reasoning but is otherwise similar to ⟨6⟩1. Some care is needed in checking move(N + 1), where the invariant from ⟨2⟩2 is used.

⟨8⟩1. ∧ \begin{matrix}∨ k ∈ 2..N \\ ∨ k ∈ (N + 2)..2N \end{matrix}

∧ DB.move(k) ∧ Inv ∧ Inv′ ⇒ CB(2N ).move(k) Proof: Follows similarly to ⟨6⟩1.

⟨8⟩2. DB.move(N + 1) ∧ Inv ∧ Inv′ ⇒ CB(2N ).move(N + 1)

Proof: Inv is needed here to ensure that the popped value is actually in Data so that the hypothesis and therefore the result of the push follows. ⟨9⟩1.

∧ DB.B1.push(Buffer2[1])

∧ DB.B2.pop ≡ ∧ Buffer2[1] ∈ Data ∧ \overline{Buffer}[N ] = ⊥ ∧ \overline{Buffer}′[N ] = Buffer2[1] ∧ \overline{Buffer}[N + 1] = Buffer2[1] ∧ \overline{Buffer}′[N + 1] = ⊥ ∧ ∀ i ∈ 1 . .(N − 1) : unchanged \overline{Buffer[i]} ∧ ∀ i ∈ (N + 2) . . 2N : unchanged \overline{Buffer[i]}

Proof: Follows immediately from the definitions of \overline{Buffer},

Buffer2, DB.B1.push and DB.B2.pop. ⟨9⟩2. Q.E.D.

Proof: Follows immediately using the right-hand-side of the equivalence in ⟨9⟩1, Inv and Inv′ from the definition of DB.move(N + 1) by substitution.

⟨8⟩3. Q.E.D.

Proof: Follows immediately from cases ⟨8⟩1 and ⟨8⟩2 by propositional logic.

⟨7⟩2. Q.E.D.

Proof: Follows immediately from ⟨7⟩1 by propositional logic and by predicate logic quantification over the parameter of move.

⟨6⟩4. Buffer1′ = Buffer1 ∧ Buffer2′ = Buffer2 ⇒

\overline{Buffer}′ = \overline{Buffer}

Proof: Immediate from the definition of Buffer. ⟨6⟩5. Q.E.D.

Proof: Follows immediately from ⟨6⟩1, ⟨6⟩2,⟨6⟩3 and ⟨6⟩4 by propositional logic.

⟨5⟩2. Q.E.D.

Proof: Immediate from ⟨5⟩1 by STL4. ⟨4⟩3. Q.E.D.

Proof: Follows immediately by propositional logic from ⟨4⟩1 and ⟨4⟩2. ⟨3⟩2. Q.E.D.

Proof: Follows immediately by propositional logic from ⟨2⟩2 and ⟨3⟩1.

Liveness for this implementation is straightforward. The liveness for the actions of CB follow directly from the liveness of the corresponding actions of DB. Thus, a simplified version of proof rule WF2 may be used.

⟨2⟩4. ∧ DB.Init

∧ □[\begin{matrix}∨ ∃ a : DB.push(a) \\ ∨ DB.pop \\ ∨ ∃ k : DB.move(k)]_{Buffer1,Buffer2} \end{matrix}

∧ L_{1} ⇒ / (\overline{L_{2}})

Proof:

For direct step-simulation, the liveness proof is considerably simplified. The next step is the simplified version of the TLA proof rule WF2 for the case of direct step simulation.

⟨3⟩1. □Inv ∧ L_{1} ⇒ \overline{L_{2}} Proof:

Let: B : propositional variable M : propositional variable A : propositional variable

⟨4⟩1. \frac{⟨B⟩_{f} ⇒ ⟨\overline{M}⟩\overline{g} Inv ∧ \overline{Enabled ⟨M⟩_{g}} ⇒ Enabled ⟨B⟩_{f}}{WF_{f} (A) ∧ □Inv ⇒ \overline{WF_{g}(M)}}

Proof: This is an instantiation of proof rule WF2 for direct step simulation. The instantiation is:

P =^{Δ} Inv,

B =^{Δ} A,

N =^{Δ} true,

F =^{Δ} Inv.

The second premise follows by propositional logic since B =^{Δ} A, the antecedent of the third premise is simplified, and the consequent of the fourth premise becomes ◇□Inv, thus the fourth premise follows from the contrapositive ⊢ F ⇒ ◇F of STL 2 and may be omitted.

⟨4⟩2.

∧ WF_{Buffer1,Buffer2}(∃ a : DB.push(a))

∧ □Inv ⇒

WF_{Buffer}(∃ a : CB.push(a)) Proof: This step is an instantiation of the conclusion of ⟨4⟩1. Thus the substeps are instantiations of the premises of ⟨4⟩1. ⟨5⟩1. ⟨∃ a : DB.push(a)⟩_{Buffer1,Buffer2}

⇒

⟨\overline{∃ a : CB.push(a)}⟩\overline{Buffer}

Proof: This has been shown in the safety step ⟨2⟩3, modulo trivial propositional logic.

⟨5⟩2. ∧ Inv

∧ \overline{Enabled⟨∃ a : CB.push(a)⟩_{Buffer}}

⇒
Enabled⟨∃ a : DB.push(a)⟩_{Buffer1,Buffer2}

Proof:

⟨6⟩1.

Enabled⟨∃ a : CB.push(a)⟩_{Buffer}

⇒
Enabled⟨∃ a : DB.push(a)⟩_{Buffer1,Buffer2}

Proof:

⟨7⟩1.

∧ \overline{Enabled⟨∃ a : CB.push(a)⟩_{Buffer}} ≡ ∃ c, a : ∧ a ∈ Data ∧ \overline{Buffer}[2N ] = ⊥ ∧ c[2N ] = a ∧ ∀ i ∈ 1 . .(2N − 1) : c[i] = \overline{Buffer}[i] ∧ c ≠ \overline{Buffer} ≡ ∃ a : ∧ a ∈ Data ∧ Buffer2[N ] = ⊥

∧ Enabled⟨∃ a : DB.push(a)⟩_{Buffer1,Buffer2} ≡ ∃ c, d, a : ∧ a ∈ Data ∧ Buffer2[N ] = ⊥ ∧ c[N ] = a ∧ ∀ i ∈ 1 . .(N − 1) : c[i] = Buffer2[i] ∧ ∀ i ∈ 1 . . N : d[i] = Buffer1[i] ∧ c ≠ Buffer1 ∧ d ≠ Buffer2 ≡ ∃ a : ∧ a ∈ Data ∧ Buffer2[N ] = ⊥

Proof: The first equivalences are the definitions of the Enabled formulas, which are the definitions of the actions plus the added condition defined by the ⟨ ⟩_{f} construct, with the primed variables quantified out. The second equivalences are the formulas with the quantifiers passed down through the conjuncts by predicate logic, and then eliminating truths concerning the TLA primitives, namely the data structures, which follow from elementary set theory.

⟨7⟩2. Q.E.D.

Proof: Follows immediately from the second equivalences in ⟨7⟩1. ⟨6⟩2. Q.E.D.

Proof: Follows immediately by adjoining Inv to the hypothesis. ⟨5⟩3. Q.E.D.

Proof: Immediate from ⟨5⟩1 and ⟨5⟩2 using the proof rule in ⟨4⟩1. ⟨4⟩3.

∧ WF_{Buffer1,Buffer2}(DB.move(k))

∧ □Inv ⇒

WF\overline{Buffer}(\overline{CB.move(k)}) Proof: Follows similarly to ⟨4⟩2, but requires some propositional logic with the definition of DB.move.

⟨4⟩4.

∧ WF_{Buffer1,Buffer2}(DB.pop) ∧ □Inv

⇒
WF\overline{Buffer}(\overline{CB.pop})

Proof: Similar to ⟨4⟩2. ⟨4⟩5. Q.E.D.

Proof: Follows immediately by quantification and propositional logic from ⟨4⟩2, ⟨4⟩3 and ⟨4⟩4.

⟨3⟩2. Q.E.D.

Proof: Follows immediately by propositional logic from ⟨2⟩2 and ⟨3⟩1. ⟨2⟩5. Init_{1} ∧ □[N_{1}]_{v} ∧ L_{1}

⇒

\overline{Init_{2}} ∧ □[\overline{N_{2}}]\overline{w} ∧ \overline{L_{2}}

Proof: Follows immediately from ⟨2⟩1, ⟨2⟩3 and ⟨2⟩4 by propositional logic. ⟨2⟩6. Q.E.D.

Proof: Theorem follows immediately from ⟨2⟩5 by quantifying over the free variables.

The second proof shows that a concrete Buffer implements an abstract buffer. The refinement mapping is not as trivial as it was in the first proof. The abstract buffer is the selection of non-⊥ elements from the concrete buffer. The safety step is straightforward. CB.move is shown to be a stuttering step of AbstractBuffer. The main part of the proof concerns the proof of liveness, because there are stuttering variables – the one we use is a state function called Descending. That means that the Lattice Rule has to be used to obtain the postcondition that holds after the stuttering variable has counted down all the way.

⟨1⟩2. ∃ Buffer : CB(N ).Spec ⇒ ∃ buffer : AB(N ).Spec

Let: NonVoid(k) =^{Δ} k ≠ ⊥ \overline{buffer} =^{Δ} SelectSeq(Buffer, NonVoid) FirstFull =^{Δ} Buffer[1] ≠ ⊥ NotEmpty =^{Δ} ∃ i ∈ 1 . . N : Buffer[i] ≠ ⊥ NotStuffed =^{Δ} ∃ i ∈ 1 . .(N − 1) : Buffer[i] = ⊥ Descending(Buffer) =^{Δ} ∑ _{i=1}^{N}(Buffer[i] = ⊥).2^{N −i} MaxDescending =^{Δ} ∑ _{i=1}^{N}2^{N −i}

The refinement mapping is given above. We use Theorem 1 to reduce the proof, and we choose the invariant True. Two of the clauses of Theorem 1, namely Init ⇒ Inv and Inv ∧ [N_{1}]_{v} ⇒ Inv′ are then trivial. We prove ⟨1⟩2 by proving the other three clauses of Theorem 1.

In the Safety part of the proof below, we make use of a temporal-logical truth, that □∃ i ∈ 0 . . Maxdescending : Descending(Buffer) = i. One option is to use this as an invariant. We choose not to do so, both because we prefer the stronger form, because it allows us to omit two proof steps (as above), and it allows the proof to maintain hierarchical structure without duplication or cross-referencing of proof steps. We explain later in more detail.

Note that FirstFull ≡ NonVoid(Buffer[1]) and NotEmpty ≡ ∃ i ∈ 1 . . N : NonVoid(Buffer[i]). These may figure in proof justifications, but are not given as individual proof steps.

Firstly, the concrete initial state implies the abstract initial state.

⟨2⟩1. CB.Init ⇒ \overline{AB.Init} Proof: ⟨3⟩1. SelectSeq([i ∈ 1 . . N ↦ {⊥}], NonVoid) = ⟨ ⟩

Proof: Immediate from the definitions. ⟨3⟩2. Q.E.D.

Proof: Follows immediately from ⟨3⟩1 and the definitions.

Secondly, the concrete safety property implies the abstract safety property. A CB.move operation is a stuttering step for the abstract buffer.

⟨2⟩2. [\begin{matrix}∨ ∃ a : CB.push(a) \\ ∨ CB.pop \\ ∨ ∃ k : CB.move(k)]_{Buffer} \end{matrix}

⇒
[\begin{matrix}∨ \overline{∃ a : AB.push(a)} \\ ∨ \overline{AB.pop}]\overline{buffer} \end{matrix}

Proof:

First, a concrete push is an abstract push.

⟨3⟩1. ∃ a : CB.push(a)

⇒

∃ a : AB.push(a) Proof: Let: a : constant ⟨4⟩1. CB.push(a)

⇒
\overline{AB.push}(a)

Proof:

⟨5⟩1. CB.push(a) ⇒ a ∈ Data

Proof: Immediate from the definition of CB.push. ⟨5⟩2. CB.push(a) ⇒ Len(\overline{buffer}) < N Proof: ⟨6⟩1. CB.push(a) ⇒ Buffer[N ] = ⊥

Proof: Immediate from the definition of CB.push(a). ⟨6⟩2. Buffer[N ] = ⊥ ⇒ Len(SelectSeq(Buffer, NonVoid)) < N

Proof: Follows from the definition of SelectSeq and Len, along with a certain amount of data structure manipulation, which is omitted.

⟨6⟩3. Q.E.D.

Proof: Follows from ⟨6⟩1, ⟨6⟩2 and the definition of \overline{buffer} by propositional logic.

⟨5⟩3. CB.push(a) ⇒ \overline{buffer}′ = \overline{buffer} ∘ ⟨a ⟩ Proof: ⟨6⟩1. CB.push(a)

⇒

SelectSeq(Buffer, NonVoid) =

SelectSeq([i ∈ 1 . .(N − 1) ↦ Buffer[i]], NonVoid) Proof: ⟨7⟩1. CB.push(a) ⇒ Buffer[N ] = ⊥

Proof: Immediate from the definition of CB.push(a). ⟨7⟩2. Buffer[N ] = ⊥

⇒

SelectSeq(Buffer, NonVoid) =

SelectSeq([i ∈ 1 . .(N − 1) ↦ Buffer[i]], NonVoid) Proof: Follows immediately from the definition of SelectSeq using manipulations of the data structure.

⟨7⟩3. Q.E.D.

Proof: Follows immediately from ⟨7⟩1 and ⟨7⟩2 by propositional logic.

⟨6⟩2. CB.push(a)

⇒

Buffer′ = [i ∈ 1 . .(N − 1) ↦ Buffer[i]] ∘ ⟨a ⟩

Proof: Follows from the definition of CB.push and the sequence operations.

⟨6⟩3. SelectSeq([i ∈ 1 . .(N − 1) ↦ Buffer[i]] ∘ ⟨a ⟩, NonVoid) =

SelectSeq([i ∈ 1 . .(N − 1) ↦ Buffer[i]], NonVoid) ∘ ⟨a ⟩ Proof: Follows from the definition of SelectSeq and NonVoid.

⟨6⟩4. Q.E.D.

Proof: Follows immediately from ⟨6⟩1, ⟨6⟩2, ⟨6⟩3 by propositional logic, substitution, and the definition of \overline{buffer}.

⟨5⟩4. Q.E.D.

Proof: Follows immediately from ⟨5⟩1, ⟨5⟩2 and ⟨5⟩3 using propositional logic.

⟨4⟩2. Q.E.D. Proof: Follows immediately by existentially quantifying over a.

Second, a concrete pop is an abstract pop.

⟨3⟩2. CB.pop

⇒ / (\overline{AB.pop})

Proof: Follows similarly to ⟨3⟩1.

Third, a concrete move is an abstract stuttering step – it doesn’t change any of the values of the abstract variables.

⟨3⟩3. ∃ k : CB.move(k)

⇒

(\overline{buffer}′ = \overline{buffer})

Proof: Let: k : constant ⟨4⟩1. CB.move(k)

⇒

(\overline{buffer}′ = \overline{buffer})

Proof:

In the following, we want to write SelectSeq([i ∈ 1 . . N ↦ Buffer[i]], NonVoid) as the composition of three sequences, consisting respectively of the first k − 2 elements, the k − 1’th and k’th, and the tail N − k elements, and similarly with SelectSeq([idd : N ↦ Buffer′[i]], NonVoid), to show that they’re the same, by showing for the three individual components of the sequence that they’re the same. Since sequences always have as domain an initial segment of the natural numbers (namely 1 . . Len(S) for a sequence S), some rewriting on the indices has to be performed to make this turn out correctly.

⟨5⟩1. CB.move(k)

⇒

∧ SelectSeq([i ∈ 1 . .(k − 2) ↦ Buffer′[i]], NonVoid) = SelectSeq([i ∈ 1 . .(k − 2) ↦ Buffer[i]], NonVoid)

∧ SelectSeq([i ∈ 1 . .(N − k) ↦ Buffer′[i + k]], NonVoid) = SelectSeq([i ∈ 1 . .(N − k) ↦ Buffer[i + k]], NonVoid)

Proof:

⟨6⟩1. CB.move(k) ⇒ ∀ i ∈ 1 . .(k − 2) : Buffer′[i] = Buffer[i]

Proof: Follows immediately from the definition of CB.move(k). ⟨6⟩2. CB.move(k)

⇒

∀ i ∈ (k + 1) . . N : Buffer′[i] = Buffer[i] Proof: Follows immediately from the definition of CB.move(k). Note that if k = N then (k + 1) . . N = ∅.

⟨6⟩3. Q.E.D.

Proof: Follows by substitution of Buffer′ for Buffer from ⟨6⟩1 and ⟨6⟩2, by simple transformation of indices, and by data structure rules which say that two terms representing finite functions with equal values for all their arguments are equal.

⟨5⟩2. CB.move(k)

⇒

SelectSeq(⟨Buffer′[k − 1], Buffer′[k]⟩, NonVoid) = SelectSeq(⟨Buffer[k − 1], Buffer[k]⟩, NonVoid) = ⟨a ⟩

Proof: Follows from the definition of SelectSeq and CB.move(k). ⟨5⟩3. k ∈ 2 . . N

⇒

∧ SelectSeq(Buffer, NonVoid) = SelectSeq([i ∈ 1 . .(k − 2) ↦ Buffer[i]], NonVoid)∘ SelectSeq(⟨Buffer[k − 1], Buffer[k]⟩, NonVoid)∘ SelectSeq([i ∈ 1 . . N − k ↦ Buffer[i + k]], NonVoid)

∧ SelectSeq(Buffer′, NonVoid) = SelectSeq([i ∈ 1 . .(k − 2) ↦ Buffer′[i]], NonVoid)∘ SelectSeq(⟨Buffer′[k − 1], Buffer′[k]⟩, NonVoid)∘ SelectSeq([i ∈ 1 . . N − k ↦ Buffer′[i + k]], NonVoid)

Proof: Follows from the definitions of ∘ and SelectSeq. Note that if k = N then (k + 1) . . N = ∅, SelectSeq(∅, P) = ⟨ ⟩, and α ∘ ⟨ ⟩ = α for any sequence α, all by definition.

⟨5⟩4. Q.E.D.

Proof: Follows from ⟨5⟩1, ⟨5⟩2 and ⟨5⟩3 and the definition of \overline{buffer} by propositional logic and simple equational reasoning.

⟨4⟩2. Q.E.D.

Proof: Follows immediately from ⟨4⟩1, by quantification over k. ⟨3⟩4. Buffer′ = Buffer

⇒ \overline{buffer}′ = \overline{buffer}

Proof: Immediate from the definition of \overline{buffer}. ⟨3⟩5. Q.E.D.

Proof: Follows immediately from ⟨3⟩1, ⟨3⟩2, ⟨3⟩3 and ⟨3⟩4 by propositional logic.

Thirdly, the concrete liveness property implies the abstract liveness property. Liveness of the abstract pop operation needs to be shown. The abstract pop is enabled via the refinement mapping under conditions under which the corresponding concrete operation is not enabled (namely that there’s something in the buffer somewhere, even though it may not be in Buffer[1]). However, under these conditions, one of the the concrete move operations is enabled, as noted in Section 6. The enabling conditions for moves, along with weak fairness of each move, entail that move actions must occur. These actions lead eventually to the satisfaction of the enabling conditions for concrete pop, (i.e., there’s something in Buffer[1]) which simulates abstract pop. So weak fairness of concrete pop ensures eventual execution and thus, along with the invariant, entails weak fairness of abstract pop.

The proof method is: one picks a state function which acts as a stuttering variable, i.e. it decreases during a stuttering step, and its minimal values correspond to the condition one wants to reach (in this case, the enabling condition for CB.pop). One uses WF1 to show that the state function satisfies these requirements. Then, the Lattice Rule allows one to conclude that if the state function has some, any, value, this leads eventually to the goal condition being reached. Some temporal logic is used to convert this assertion into the fourth premise of an application of WF2 (it is straightforward to show the other three premises of WF2).

The structure of the proof is: the high level is an application of WF2; the next lower level consists of its four premises. The fourth of these is the tricky one, justified by a conclusion of the Lattice Rule plus some temporal logic. The conclusion of the Lattice Rule is justified by the premise. The premise is justified by an application of WF1. The three premises of WF1 are shown directly.

In this proof, the premises of TLA rules WF1, WF2 and Lattice are given their exact syntactic form as needed for the rule, and substepping is used to perform simple logical manipulations, rather than combining them into one step. This makes the proof easier to survey.

⟨2⟩3.

∧ □[∃ a : CB.push(a) ∨ CB.pop ∨ ∃ k : CB.move(k)]_{Buffer} ∧ WF_{Buffer}(CB.pop) ∧ WF_{Buffer}(∃ k : CB.move(k))

⇒
WF_{buffer}(AB.pop)

Proof:

This step is the conclusion of an application of WF2, which is justified by giving the four premises. The fourth premise is the most difficult to prove. The others are straightforward.

⟨3⟩1. ⟨(∃ a : CB.push(a) ∨ CB.pop ∨ ∃ k : CB.move(k)) ∧ CB.pop ⟩_{Buffer} ⇒

⟨\overline{AB.pop} ⟩\overline{buffer}

Proof: ⟨4⟩1.

∧ ∨ ∃ a : CB.push(a) ∨ CB.pop ∨ ∃ k : CB.move(k))

∧ CB.pop ≡ CB.pop

Proof: Follows immediately by propositional logic from the definitions of CB.push, CB.pop and CB.move.

⟨4⟩2. ⟨CB.pop ⟩_{Buffer} ⇒

⟨\overline{AB.pop} ⟩\overline{buffer}

Proof: Recall that the statement means CB.pop ∧ Buffer′ ≠ Buffer ⇒ \overline{AB.pop} ∧ \overline{buffer}′ ≠ \overline{buffer}. Thus it follows, modulo a little propositional logic, from the analysis needed for step-simulation in ⟨2⟩2 (which we didn’t give explicitly, but noted it is similar to the push simulation).

⟨4⟩3. Q.E.D.

Proof: Follows immediately by proposition logic from ⟨4⟩1 and ⟨4⟩2.

⟨3⟩2.

∧ FirstFull ∧ FirstFull′ ∧ ⟨CB.pop ⟩_{Buffer} ∧ \overline{Enabled⟨AB.pop ⟩_{buffer}}

⇒ CB.pop

Proof: ⟨4⟩1. ⟨CB.pop ⟩_{Buffer}

⇒

CB.pop

Proof: Follows immediately by propositional logic. ⟨4⟩2. Q.E.D.

Proof: Follows immediately from ⟨4⟩1 by propositional logic.

⟨3⟩3.

∧ FirstFull ∧ \overline{Enabled⟨AB.pop ⟩_{buffer}}

⇒

Enabled⟨CB.pop ⟩_{Buffer} Proof: ⟨4⟩1. FirstFull

⇒

Enabled⟨CB.pop ⟩_{Buffer}

Proof: ⟨5⟩1. Enabled⟨CB.pop ⟩_{buffer} ≡ ∃ d, c : ∧ d ≠ BufferBuffer[1] ≠ ⊥ ∧ c[1] = ⊥ ∧ ∀ i ∈ 2 . . N : c[i] = Buffer[i]

≡

Buffer[1] ≠ ⊥ Proof: The first equivalence is the definition of Enabled. The second follows by moving the quantifiers inside the conjunct by a standard rule of predicate logic, and by observing that the first, third and fourth conjuncts follow by predicate logic from the primitives of TLA.

⟨5⟩2. Q.E.D.

Proof: Follows immediately from the definition of FirstFull and the second equivalence of ⟨5⟩1.

⟨4⟩2. Q.E.D.

Proof: Follows immediately by propositional logic from ⟨4⟩1.

⟨3⟩4.

∧ □[\begin{matrix}∧ ∨ ∃ a : CB.push(a) ∨ CB.pop ∨ ∃ k : CB.move(k) \\ ∧ ¬CB.pop]_{Buffer} \end{matrix}

∧ WF_{Buffer}(CB.pop) ∧ WF_{Buffer}(∃ k : CB.move(k)) ∧ ◇□\overline{Enabled⟨AB.pop ⟩_{buffer}}

⇒ ◇□FirstFull

Proof:

The Lattice Rule is used to prove this fourth hypothesis of WF2. First, some simplifications are needed.

⟨4⟩1.

∧ ∨ ∃ a : CB.push(a) ∨ CB.pop ∨ ∃ k : CB.move(k)

∧ ¬CB.pop ≡ ∨ ∃ a : CB.push(a) ∨ ∃ k : CB.move(k)

Proof: Follows immediately by propositional logic.

⟨4⟩2. NotEmpty ≡ \overline{Enabled⟨AB.pop ⟩_{buffer}} Proof: ⟨5⟩1.

Enabled⟨AB.pop ⟩_{buffer} ≡ ∃ d, c : ∧ d ≠ SelectSeq(Buffer, NonVoid) ∧ Len(SelectSeq(Buffer, NonVoid) > 0 ∧ c = tail(Buffer)

≡

∧ Len(SelectSeq(Buffer, NonVoid) > 0 ∧ ∃ c : c = tail(Buffer)

Proof: The first equivalence is the definition of Enabled. The second follows by moving the quantifiers inside the conjunct by standard rules of predicate logic, and eliminating the first conjunct, which follows by predicate logic from the TLA requirement that any function symbol with any argument denotes, and predicate logic.

⟨5⟩2. Q.E.D.

Proof: Follows from the equivalence in ⟨5⟩1 using definitions of SelectSeq, Len, and NonVoid, by quantifier rules of predicate logic, and by observing that the second conjunct ∃ c : c = tail(Buffer) is a truth of predicate logic.

The next step is the simplified version of ⟨3⟩4, to be proved using the Lattice Rule, along with some manipulations in temporal logic.

⟨4⟩3.

∧ □[∃ a : CB.push(a) ∨ ∃ k : CB.move(k)]_{Buffer} ∧ WF_{Buffer}(CB.pop) ∧ WF_{Buffer}(∃ k : CB.move(k)) ∧ ◇□NotEmpty

⇒

◇□FirstFull Proof:

The substeps are a temporal logic proof rule, which I’ll call the Bridge Rule, and instances of its two hypotheses. The Bridge Rule enables us to prove the fourth hypothesis of WF2 using the Lattice Rule. The first hypothesis of the instantiated Bridge Rule is a (counterfactual) invariant statement, that the condition intepreting P in WF2 is invariant under the restricted safety condition □[N ∧ ¬B]_{f} . The second hypothesis is (a modified form of) the conclusion of the Lattice Rule. The instantiated conclusion of the Bridge rule is step ⟨4⟩3 above.

We state the Bridge Rule using propositional variable X , which will be interpreted so that □X is equivalent to

∧ □[∃ a : CB.push(a) ∨ ∃ k : CB.move(k)]_{Buffer}

∧ WF_{Buffer}(CB.pop)
∧ WF_{Buffer}(∃ k : CB.move(k))

(Weak Fairness conditions are equivalent to a statement of the form □Y by the complementary form of STL6, and conjunctions of statements of the form □Y are equivalent to a single statement of that form by STL5.) We will also use propositional variables NE, which will be interpreted as NotEmpty, and FF , which will be interpreted as FirstFull. However, the rule is valid for any interpretation of the propositional variables X , NE and FF .

In the TL calculation below, we use the TL rules

STL7 ⊢ □◇□F ≡ ◇□F

STL2^{◇} ⊢ F ⇒ ◇F

STL4^{◇} \frac{F ⇒ G}{◇F ⇒ ◇G}

STL6^{◇} ⊢ (□◇F ) ∨ (□◇G) ≡ □◇(F ∨ G)

STL7 was included in an earlier version of TLA, but Lamport has omitted it since Bryan Olivier noted it followed from STL1-STL6. The rules STL2^{◇} , STL4^{◇} and STL6^{◇} are the complements of STL2, STL4 and STL6 for ◇, and are easily derivable from those rules.

Additionally, there are some propositional logic rules that we use: namely, replacing a subformula by a provably logically equivalent subformula (called substituting equivalent subformulas below); and strengthening an antecedent of a conditional (called strehngthening a hypothesis).

Let: X , Y , FF , NE : propositional variable

⟨5⟩1. \frac{□X ∧ FF ⇒ □FF □X ⇒ □(NE ⇒ ◇FF )}{□X ∧ ◇□NE ⇒ ◇□FF}

Proof:

⟨6⟩1. \frac{Z ∧ FF ⇒ □FF}{□Z ⇒ □(FF ⇒ □FF )}

Proof: Follows immediately by trivial propositional logic and then STL4.

The following rule follows from STL2, STL4 and propositional logic. TL rules such as this may not be that easy to spot or to derive. Lamport suggests that any correct calculation in propositional temporal logic may be used where necessary in a formal TLA proof, which suggests a semantic short-cut using the completeness theorem: one may conclude that the rule is valid by observing that it’s valid in all linearly ordered Kripke frames, that is to say, models in which states form a linear sequence. Then one may conclude by completeness that it’s derivable, and say in the proof step, as we did for an earlier temporal logic step, that it’s ‘derivable from PTL’. The straightforward proof we give was noted by Lamport. No derivation of this proof rule has been found using only STL1–STL6 + Lattice Rule alone.

⟨6⟩2. \frac{□X ⇒ □(NE ⇒ ◇FF ) □X ⇒ □(FF ⇒ □FF )}{□X ⇒ □(NE ⇒ □◇FF )}

Proof:

⟨7⟩1. ∧ □(NE ⇒ ◇FF )

∧ □(FF ⇒ □FF ) ⇒

□(NE ⇒ □◇FF ) Proof:

⟨8⟩1. ∧ □(NE ⇒ ◇FF )

∧ □(FF ⇒ □FF ) ⇒

∧ □(NE ⇒ ◇FF ) ∧ □(FF ⇒ ◇□FF )

Proof sketch: Uses STL4, STL2^{◇} and propositional logic.

⟨8⟩2. ∧ □(NE ⇒ ◇FF )

∧ □(FF ⇒ ◇□FF ) ≡ ∧ (NE ⇝ FF ) ∧ (FF ⇝ □FF )

Proof: Follows immediately from the definition of ⇝.

⟨8⟩3. ∧ (NE ⇝ FF )

∧ (FF ⇝ □FF ) ⇒

NE ⇝ □FF Proof sketch: Follows by transitivity of ⇝.

⟨8⟩4. NE ⇝ □FF ≡ □(NE ⇒ ◇□FF )

Proof: Follows immediately by the definition of ⇝. ⟨8⟩5. □(NE ⇒ ◇□FF ) ⇒ □(NE ⇒ □◇□FF )

Proof sketch: Follows by STL4 and STL7. ⟨8⟩6. □(NE ⇒ □◇□FF ) ⇒ □(NE ⇒ □◇FF )

Proof sketch: Follows by STL4 and STL2, using substitution of equivalent subformulas.

⟨8⟩7. Q.E.D.

Proof: Immediate from ⟨8⟩1, ⟨8⟩2, ⟨8⟩3, ⟨8⟩4, ⟨8⟩5, ⟨8⟩6 by ‘forward chaining’.

⟨7⟩2. Q.E.D. Proof: The conclusion of the rule follows from the premises using ⟨7⟩1, Modus Ponens, and propositional logic.

⟨6⟩3. \frac{□X ⇒ □(NE ⇒ □Y )}{□X ∧ □NE ⇒ □Y}

Proof: This rule follows from PTL.

⟨6⟩4. \frac{□X ∧ □NE ⇒ □Y}{□(X ∧ NE) ⇒ □Y}

Proof: Immediate from STL5 and substitution of equivalent subformulas.

⟨6⟩5. \frac{□(X ∧ NE) ⇒ □Y )}{◇□(X ∧ NE) ⇒ ◇□Y}

Proof: Immediate from STL4^{◇} .

⟨6⟩6. \frac{◇□(X ∧ NE) ⇒ ◇□Y )}{◇□X ∧ ◇□NE ⇒ ◇□Y}

Proof: STL6 and substituting equivalent subformulas.

⟨6⟩7. \frac{◇□X ∧ ◇□NE ⇒ ◇□Y}{□X ∧ ◇□NE ⇒ ◇□Y}

Proof: Strengthening a hypothesis, using STL2^{◇} .

⟨6⟩8. \frac{□X ∧ ◇□NE ⇒ ◇□◇FF}{□X ∧ ◇□NE ⇒ ◇□FF}

Proof: STL7, and substituting equivalent subformulas. ⟨6⟩9. Q.E.D.

Proof: The conclusion of rule ⟨5⟩1 follows from its hypotheses by forward-chaining the rules ⟨6⟩1, ⟨6⟩2, ⟨6⟩3, ⟨6⟩4, ⟨6⟩5, ⟨6⟩6, ⟨6⟩7, ⟨6⟩8, since inference is transitive, using the substitution Y =^{Δ} ◇FF where needed.

We now prove the instantiated first hypothesis of the Bridge rule.

⟨5⟩2.

∧ □[\begin{matrix}∨ ∃ a : CB.push(a) \\ ∨ ∃ k : CB.move(k)]_{Buffer} \end{matrix}

∧ WF_{Buffer}(CB.pop) ∧ WF_{Buffer}(∃ k : CB.move(k)) ∧ FirstFull

⇒

□FirstFull Proof: ⟨6⟩1.

∧ □[\begin{matrix}∨ ∃ a : CB.push(a) \\ ∨ ∃ k : CB.move(k)]_{Buffer} \end{matrix}

∧ FirstFull ⇒ □FirstFull

Proof:

⟨7⟩1.

∧ [\begin{matrix}∨ ∃ a : CB.push(a) \\ ∨ ∃ k : CB.move(k)]_{Buffer} \end{matrix}

∧ FirstFull ⇒ FirstFull′

Proof: Follows from the definitions of push and move. ⟨7⟩2. Q.E.D.

Proof: Immediate by TLA Proof Rule INV1 from ⟨7⟩1, with the antecedent conjuncts in the other order.

⟨6⟩2. Q.E.D.

Proof: Immediate from ⟨6⟩1 by adding a hypothesis.

We now prove the instantiated second hypothesis of the Bridge Rule.

⟨5⟩3.

∧ □[∃ a : CB.push(a) ∨ ∃ k : CB.move(k)]_{Buffer} ∧ WF_{Buffer}(CB.pop) ∧ WF_{Buffer}(∃ k : CB.move(k))

⇒ □(NotEmpty ⇒ ◇FirstFull)

Proof:

First we show a temporal logic equivalence, used to change the consequent, then we prove the step with the altered consequent.

⟨6⟩1. (NotEmpty ∧ NotStuffed ⇒ ◇FirstFull)

≡

(NotEmpty ⇒ ◇FirstFull) Proof: ⟨7⟩1. ¬NotStuffed ⇒ ◇FirstFull

Proof: ⟨8⟩1. ¬NotStuffed ⇒ FirstFull

Proof: Follows immediately from the definitions. ⟨8⟩2. FirstFull ⇒ ◇FirstFull

Proof: Immediate from STL2. ⟨8⟩3. Q.E.D.

Proof: Follows immediately from ⟨8⟩1 and ⟨8⟩2 by transitivity of ⇒.

⟨7⟩2. Q.E.D.

Proof: Follows by propositional logic from ⟨7⟩1.

⟨6⟩2.

∧ □[∃ a : CB.push(a) ∨ ∃ k : CB.move(k)]_{Buffer} ∧ WF_{Buffer}(CB.pop) ∧ WF_{Buffer}(∃ k : CB.move(k))

⇒

□(NotEmpty ∧ NotStuffed ⇒ ◇FirstFull) Proof:

Some axiomatic arithmetic is required in the next stage. No Naturals module has been given, but we assume that such a module would contain enough arithmetic to allow the simple reasoning that follows. This reasoning, from a reasonable set of axioms, is not very hard. We also don’t believe that it would be particularly enlightening for our purposes in this paper to develop it here. However, it is important to note that the proof is correct only insofar as the arithmetic reasoning is correct. Arithmetic reasoning will be used where noted also in further steps below.

There is a question whether the following proof step should actually be formulated rather as an invariant. It is true that it is an invariant, but it is equally true that it is a temporal-logical necessity provable in TLA. The key issue is that Descending is defined as a parametrised sum of products, of which products one of the components is a Boolean value of a state predicate. It follows directly from propositional logic that these state predicates have one Boolean value or the other, hence the value of Descending is always defined. This is a stronger statement than claiming this assertion as an invariant of the implementation proof (which is simply to claim that it follows from the Init and Safety parts of CB.Spec).

There are two strong reasons to include the stronger version of the assertion: (a): proof of a stronger version of a statement, when true, would in principle enable one to prove consequences more easily, and thereby simplify later parts of a proof; (b): if this were part of the invariant, then the invariant would no longer be true, and it would be necessary to include proofs of the obligations Init → Inv and Inv ∧ [N_{1}]_{v} → Inv′ from Theorem 1. The second of these is very similar to, but slightly weaker than, the claim needed for the hypothesis of the Lattice Rule, below, that [N_{1}]_{v} implies that the value of Descending decreases. Thus, the proofs would be similar, and one would either refer to this later step to provide a proof of the former, or, forgoing such a crossstructure reference, duplicate the proofs (with minor modifications) to maintain the hierarchical proof structure. Either solution seems inelegant. The current approach maintains the hierarchical structure while avoiding duplication of proof steps.

⟨7⟩1. □∃ i ∈ 0 . . MaxDescending : Descending(Buffer) = i Proof: ⟨8⟩1. ∃ i ∈ 0 . . MaxDescending : Descending(Buffer) = i

Proof: ⟨9⟩1. Descending(Buffer) ∈ 0 . . MaxDescending

Proof: ⟨10⟩1.

∧ ∑ _{k=1}^{N}(Buffer[k] = ⊥).2^{N −k} ≤ ∑ _{k=1}^{N}2^{N −k} ∧ 0 ≤ ∑ _{k=1}^{N}(Buffer[k] = ⊥).2^{N −k}

Proof: Let: k : constant ⟨11⟩1. k ∈ 1 . . N

⇒

∧ (Buffer[k] = ⊥).2^{N −k} ≤ 2^{N −k}
∧ 0 ≤ (Buffer[k] = ⊥).2^{N −k}

Proof: ⟨12⟩1. k ∈ 1 . . N ⇒

∨ (Buffer[k] = ⊥).2^{N −k} = 0

∨ (Buffer[k] = ⊥).2^{N −k} = 2^{N −k}

Proof:

⟨13⟩1. k ∈ 1 . . N ⇒ ∨ (Buffer[k] = ⊥) = 0

∨ (Buffer[k] = ⊥) = 1

Proof: By definition of Boolean values.

⟨13⟩2. \begin{matrix}∧ 0.2^{N −k} = 0 \\ ∧ 1.2^{N −k} = 2^{N −k} \end{matrix}

Proof: By standard axioms of arithmetic. ⟨13⟩3. Q.E.D.

Proof: Follows by substitution and propositional logic from ⟨13⟩1 and ⟨13⟩2.

⟨12⟩2. \begin{matrix}∧ 0 ≤ 2^{N −k} \\ ∧ 2^{N −k} ≤ 2^{N −k} \end{matrix}

Proof: By standard axioms of arithmetic. ⟨12⟩3. Q.E.D.

Proof: Follows immediately from ⟨12⟩1 and ⟨12⟩2 using substitution and propositional logic.

⟨11⟩2. ∀ k ∈ 1 . . N : a(k) ≤ b(k) ⇒

∑ _{k=1}^{N}a(k) ≤ ∑ _{k=1}^{N}b(k)

Proof: This must follow from properties of ∑ defined in the Naturals module.

⟨11⟩3. Q.E.D.

Proof: Follows immediately by substitution from ⟨11⟩1 and ⟨11⟩2.

⟨10⟩2. Q.E.D.

Proof: Follows immediately from ⟨10⟩1 by definitions of ‘. .’, Descending, and MaxDescending.

⟨9⟩2. Q.E.D.

Proof: Follows immediately from ⟨9⟩1 by existential generalisation over Descending(Buffer) using the variable i.

⟨8⟩2. Q.E.D.

Proof: Immediate from ⟨8⟩1 by STL1.

The next step is a conclusion of the Lattice Rule, with P ⇝ Q written as □(P ⇒ ◇Q). Its justification is thus the premise of the Lattice Rule.

⟨7⟩2.

∧ □[∃ a : CB.push(a) ∨ ∃ k : CB.move(k)]_{Buffer} ∧ WF_{Buffer}(CB.pop) ∧ WF_{Buffer}(∃ k : CB.move(k))

⇒

□(∧ ∃ i ∈ 0 . . MaxDescending : Descending(Buffer) = i ∧ NotEmpty ∧ NotStuffed ⇒ ◇FirstFull)

Proof:

The substep is the premise of the Lattice Rule, with P ⇝ Q written as □(P ⇒ ◇Q).

Let: i : constant ⟨8⟩1.

∧ □[\begin{matrix}∨ ∃ a : CB.push(a) \\ ∨ ∃ k : CB.move(k)]_{Buffer} \end{matrix}

∧ WF_{Buffer}(CB.pop)

∧ WF_{Buffer}(∃ k : CB.move(k))

∧ i ∈ 0 . . MaxDescending ⇒ □(∧ NotEmpty ∧ NotStuffed ∧ Descending(Buffer) = i) ⇒ ◇(\begin{matrix}∨ FirstFull \\ ∨ ∃ j ∈ 0 . . MaxDescending : ∧ j < i ∧ NotEmpty ∧ NotStuffed ∧ Descending(Buffer) = j ) \end{matrix}

Proof: This step is justified by an application of WF1 (and simple propositional logic). The substeps consist of the premises of WF1, with the instantiations N =^{Δ} ∃ a : CB.push(a) ∨ ∃ k : CB.move(k), A =^{Δ} ∃ k : CB.move(k), P =^{Δ} ∧ NotEmpty ∧ NotStuffed ∧ Descending(Buffer) = i

and Q =^{Δ} ∨ FirstFull ∨ ∃ j ∈ 0 . . MaxDescending : ∧ j < i ∧ NotEmpty ∧ NotStuffed ∧ Descending(Buffer) = j ).

First, some trivial simplification of the consequent, from

∃ j ∈ 0 . . MaxDescending : j < i ∧ Θ

to ∃ j < i ∧ Θ, for appropriate Θ.

⟨9⟩1.

∧ ∃ j < i : Descending(Buffer) = j ≡ ∃ j ∈ 0 . . MaxDescending : ∧ j < i ∧ Descending(Buffer) = j

∧ ∃ j < i : Descending(Buffer′) = j ≡ ∃ j ∈ 0 . . MaxDescending : ∧ j < i ∧ Descending(Buffer′) = j

Proof: Follows from ⟨7⟩1 and its substeps by predicate logic.

The next step is the first premise of WF1.

⟨9⟩2.

∧ NotEmpty ∧ NotStuffed ∧ Descending(Buffer) = i ∧ ∨ ∃ a : CB.push(a) ∨ ∃ k : CB.move(k) ∨ Buffer′ = Buffer

⇒ ∨ ∧ NotEmpty′ ∧ NotStuffed′ ∧ Descending(Buffer′) = i

∨ FirstFull′ ∨ ∃ j ∈ 0 . . MaxDescending : ∧ j < i ∧ NotEmpty′ ∧ NotStuffed′ ∧ Descending(Buffer′) = j

Proof: ⟨10⟩1.

∧ NotEmpty ∧ NotStuffed ∧ Descending(Buffer) = i ∧ Buffer′ = Buffer

⇒

∧ NotEmpty′

∧ NotStuffed′

∧ Descending(Buffer′) = i Proof: Immediate by substitution of Buffer′ for Buffer.

The following step is also used later.

⟨10⟩2.

∧ NotEmpty ∧ NotStuffed ∧ Descending(Buffer) = i ∧ ∃ k : CB.move(k)

⇒

∨ FirstFull′

∨ ∃ j < i : ∧ NotEmpty′ ∧ NotStuffed′ ∧ Descending(Buffer′) = j

Proof: ⟨11⟩1.

∧ Descending(Buffer) = i ∧ ∃ k : CB.move(k)

⇒

∃ j < i : Descending(Buffer′) = j Proof:

The following two steps use some more easy arithmetic. Properties of ∑ are used which depend for their proofs on the exact definition of arithmetic and ∑. Different ways of specifying the properties might lead to different proofs. The proof below is intended as an example of how, and how long, such a proof would be. Any given proof depends on what is defined, and how, in the Naturals module, which is not included. One possibility for defined ∑ is to use a recursive definition, since ∑ is parameterised by N .

Let: k : constant ⟨12⟩1.

∧ Descending(Buffer) = i

∧ CB.move(k) ⇒

Descending(Buffer′) = i +(2^{N −k} −2^{N −(k−1)}) Proof: ⟨13⟩1. CB.move(k)

⇒ Descending(Buffer′) = Descending(Buffer) + (2^{N −k} − 2^{N −(k−1)})

Proof: ⟨14⟩1. CB.move(k)

⇒

∑ _{m=1}^{N}(Buffer′[m] = ⊥).2^{N −m} =

∑ _{m=1}^{N}(Buffer[m] = ⊥).2^{N −m} + (2^{N −k} − 2^{N −(k−1)}) Proof: ⟨15⟩1. CB.move(k)

⇒

∧ ∑ _{m=1}^{k−2}(Buffer′[m] = ⊥).2^{N −m} = ∑ _{m=1}^{k−2}(Buffer[m] = ⊥).2^{N −m}

∧ ∑ _{m=(k+1)}^{N}(Buffer′[m] = ⊥).2^{N −m} = ∑ _{m=(k+1)}^{N}(Buffer[m] = ⊥).2^{N −m}

Proof: Let: m : constant ⟨16⟩1. CB.move(k)

⇒

\begin{matrix}∨ m ∈ 1 . .(k − 2) \\ ∨ m ∈ (k + 1) . . N \end{matrix} ⇒

Buffer′[m] = Buffer[m]

Proof: Follows immediately from the definition of CB.move(k).

⟨16⟩2. Q.E.D.

Proof: Follows from ⟨16⟩1, given that ∑ _{k=1}^{N} is a term, and therefore admits replacement of subterms by provably equal subterms by Leibniz’s Law of predicate logic.

⟨15⟩2. CB.move(k) ⇒

∧ (Buffer′[k] = ⊥).2^{N −k} = 2^{N −k}

∧ (Buffer′[k − 1] = ⊥).2^{N −(k−1)} = 0

∧ (Buffer[k] = ⊥).2^{N −k} = 0

∧ (Buffer[k − 1] = ⊥).2^{N −(k−1)} = 2^{N −(k−1)} Proof: ⟨16⟩1. CB.move(k)

⇒

∧ (Buffer′[k] = ⊥) = 1
∧ (Buffer′[k − 1] = ⊥) = 0
∧ (Buffer[k] = ⊥) = 0
∧ (Buffer[k − 1] = ⊥) = 1

Proof: Immediate from the definition of CB.move(k).

⟨16⟩2. ∧ 1.2^{N −k} = 2^{N −k}

∧ 0.2^{N −(k−1)} = 0
∧ 0.2^{N −k} = 0
∧ 1.2^{N −(k−1)} = 2^{N −(k−1)}

Proof: Instantiations of standard axioms for 1 and 0 in arithmetic.

⟨16⟩3. Q.E.D.

Proof: Follows immediately by substitution and propositional logic using ⟨16⟩1 and ⟨16⟩2.

The following step follows from various properties of ∑, including those which follow from a recursive definition, and properties of ‘+’ and ‘−’. We state the step and simply assert that it follows. It should be clear that it is semantically correct, although syntactically an atomic step.

⟨15⟩3.

∑ _{m=1}^{N}(Buffer′[m] = ⊥).2^{N −m} = ∑ _{m=1}^{k−2}(Buffer′[m] = ⊥).2^{N −m}+ (Buffer′[k − 1] = ⊥).2^{N −(k−1)}+ (Buffer′[k] = ⊥).2^{N −k}+ ∑ _{m=(k+1)}^{N}(Buffer′[m] = ⊥).2^{N −m} = ∑ _{m=1}^{k−2}(Buffer[m] = ⊥).2^{N −m}+ 0 + 2^{N −k}+ ∑ _{m=(k+1)}^{N}(Buffer[m] = ⊥).2^{N −m} = ∑ _{m=1}^{k−2}(Buffer[m] = ⊥).2^{N −m}+ +2^{N −(k−1)} + 0+ ∑ _{m=(k+1)}^{N}(Buffer[m] = ⊥).2^{N −m}+ (2^{N −k} − 2^{N −(k−1)}) = ∑ _{m=1}^{N}(Buffer[m] = ⊥).2^{N −m}+ (2^{N −k} − 2^{N −(k−1)})

Proof: Follows from the definition of ∑, and the associativity and commutativity of ∑ and ‘+’, and other standard arithmetic axioms, including those for ‘−’, using ⟨15⟩1 and ⟨15⟩2.

⟨15⟩4. Q.E.D.

Proof: Follows immediately from ⟨15⟩3 using transitivity of equality.

⟨14⟩2. Q.E.D.

Proof: Immediate from ⟨14⟩1 by substitution of Descending(Buffer′) and Descending(Buffer) for the ∑’s.

⟨13⟩2. Q.E.D.

Proof: Immediate from ⟨13⟩1 using the definition i =^{Δ} Descending(Buffer).

⟨12⟩2. i + (2^{N −k} − 2^{N −(k−1)}) < i Proof: ⟨13⟩1. i + (2^{N −k} − 2^{N −(k−1)}) =

i + (2^{N −k} − 2^{N −k+1}) = i + (1.2^{N −k} − 2.2^{N −k}) = i + 2^{N −k}(1 − 2) = i − 2^{N −k} < i

Proof: Follows by axioms of arithmetic for ‘-’, for ‘1’, for exponent, the distributive law of multiplication over ‘-’, the equation 1 − 2 = −1, and the law x + (−1).y = x − y.

⟨13⟩2. Q.E.D.

Proof: Follows immediately from ⟨13⟩1 by transitivity of equality.

⟨12⟩3. Q.E.D.

Proof: Follows from ⟨12⟩1 and ⟨12⟩2 by setting j =^{Δ} i + (2^{N −k} − 2^{N −(k−1)}), using Descending(Buffer′) = j ⇒ j ∈ 0 . . MaxDescending from an earlier proof step, then existential quantification over j , followed by existential quantification over k.

⟨11⟩2.

∧ ∃ k : CB.move(k)

∧ ¬(NotEmpty′ ∧ NotStuffed′) ⇒ FirstFull′

Proof: ⟨12⟩1. ∃ k : CB.move(k)

⇒

NotEmpty′ Proof: Follows immediately by predicate logic from the definitions of CB.move and NotEmpty.

⟨12⟩2.

∧ ∃ k : CB.move(k)

∧ ¬(NotEmpty′ ∧ NotStuffed′) ⇒ ¬NotStuffed′

Proof: Follows immediately by propositional logic from ⟨12⟩1.

⟨12⟩3. ¬NotStuffed′ ⇒ FirstFull′

Proof: Follows immediately from the definitions. ⟨12⟩4. Q.E.D.

Proof: Follows immediately by propositional logic from ⟨12⟩2 and ⟨12⟩3.

⟨11⟩3. Q.E.D. Proof: Follows immediately from ⟨11⟩1 and ⟨11⟩2 by propositional logic and by moving the quantifier ∃ j .

⟨10⟩3.

∧ NotEmpty ∧ NotStuffed ∧ Descending(Buffer) = i ∧ ∃ a : CB.push(a)

⇒

∃ j < i : ∧ NotEmpty′ ∧ NotStuffed′ ∧ Descending(Buffer′) = j Proof: ⟨11⟩1.

∧ NotEmpty

∧ NotStuffed

∧ Descending(Buffer) = i ∧ ∃ a : CB.push(a)

⇒

∧ NotEmpty′ ∧ NotStuffed′ ∧ ∃ j < i : Descending(Buffer′) = j

Proof:

The following step also uses some simple arithmetic.

⟨12⟩1.

∧ Descending(Buffer) = i ∧ ∃ a : CB.push(a)

⇒

∃ j < i : Descending(Buffer′) = j Proof: ⟨13⟩1.

∧ Descending(Buffer) = i ∧ ∃ a : CB.push(a)

⇒

Descending(Buffer′) = i − 1 Proof: Follows from definition of Descending and CB.move using 2^{N −N} = 1 and other arithmetic axioms.

⟨13⟩2. Q.E.D.

Proof: Follows immediately from ⟨13⟩1 by substituting j =^{Δ} i − 1 and existentially quantifying over j .

⟨12⟩2.

∧ NotStuffed ∧ NotEmpty ∧ ∃ a : CB.push(a) ⇒ ∧ NotStuffed′ ; ∧ NotEmpty′

Proof: Follows from definition of NotStuffed and NotEmpty by using predicate logic rules for quantifiers.

⟨12⟩3. Q.E.D.

Proof: Follows immediately by propositional logic from ⟨12⟩1 and ⟨12⟩2.

⟨11⟩2. Q.E.D. Proof: Follows immediately by moving the quantifier ∃ j < i outside the conjunction in the consequent.

⟨10⟩4. Q.E.D. Proof: Follows immediately by propositional logic from ⟨10⟩1, ⟨10⟩2 and ⟨10⟩3, using ⟨9⟩1 to substitute the quantifier ∃ j ∈ 0 . . Maxdescending : j < i ∧ . . for the quantifier ∃ j < i : . ..

End of proof of first premise of WF1 application. The next step is the statement and proof of the second premise of WF1.

⟨9⟩3.

∧ NotStuffed ∧ NotEmpty ∧ Descending(Buffer) = i ∧ ⟨\begin{matrix}∧ ∨ ∃ a : CB.push(a) ∨ ∃ k : CB.move(k) \\ ∧ ∃ k : CB.move(k)⟩_{Buffer} \end{matrix}

⇒

∨ FirstFull′

∨ ∃ j ∈ 0 . . MaxDescending : ∧ j < i ∧ NotStuffed′ ∧ NotEmpty′ ∧ Descending(Buffer′) = j

Proof: ⟨10⟩1.

∧ ∨ ∃ a : CB.push(a) ∨ ∃ k : CB.move(k)

∧ ∃ k : CB.move(k)

≡

∃ k : CB.move(k) Proof: Follows by propositional logic from the definitions of CB.pop and CB.move.

⟨10⟩2.

∧ NotStuffed ∧ NotEmpty ∧ Descending(Buffer) = i ∧ ⟨∃ k : CB.move(k)⟩_{Buffer}

⇒

∨ FirstFull′

∨ ∃ j ∈ 0 . . MaxDescending : ∧ j < i ∧ NotStuffed′ ∧ NotEmpty′ ∧ Descending(Buffer′) = j

Proof: Follows by propositional logic, by strengthening the consequent, from the second substep of ⟨9⟩2 using ⟨9⟩1.

⟨10⟩3. Q.E.D. Proof: Follows by propositional logic from ⟨10⟩1 and ⟨10⟩2.

The next step is the statement and proof of the third premise of WF1.

⟨9⟩4.

∧ NotStuffed

∧ NotEmpty

∧ Descending(Buffer) = i ⇒ Enabled⟨∃ k : CB.move(k)⟩_{Buffer}

Proof: Follows by quantifier rules of predicate logic from definition of NotStuffed and NotEmpty.

⟨9⟩5. Q.E.D.

Proof: Immediate from TLA Rule WF1 with hypotheses ⟨9⟩2, ⟨9⟩3 and ⟨9⟩4 under the substitutions P =^{Δ} ∧ NotEmpty ∧ NotStuffed ∧ Descending(Buffer) = i

N =^{Δ} ∨ ∃ a : CB.push(a) ∨ ∃ k : CB.move(k),

f =^{Δ} Buffer, Q =^{Δ} ∨ FirstFull ∨ ∃ j ∈ 0 . . MaxDescending : ∧ j < i ∧ NotEmpty ∧ NotStuffed ∧ Descending(Buffer) = j ,

A =^{Δ} ∃ k : CB.move(k), and the definition (P ⇝ Q) ≡ □(P ⇒ ◇Q). Finally, the statement i ∈ 0 . . MaxDescending is conjoined to the hypotheses.

⟨8⟩2. Q.E.D. Proof: Immediate from ⟨8⟩1 by the Lattice Rule of TLA with substitutions c =^{Δ} i, S =^{Δ} 1 . . N , ≻ =^{Δ} >, H_{c} =^{Δ} ∧ NotEmpty ∧ NotStuffed ∧ Descending(Buffer) = i,

G =^{Δ} FirstFull, and F =^{Δ} ∧ □[∃ a : CB.push(a) ∨ ∃ k : CB.move(k)]_{Buffer} ∧ WF_{Buffer}(CB.pop) ∧ WF_{Buffer}(∃ k : CB.move(k)).

⟨7⟩3.

∧ □[∃ a : CB.push(a) ∨ ∃ k : CB.move(k)]_{Buffer} ∧ WF_{Buffer}(CB.pop) ∧ WF_{Buffer}(∃ k : CB.move(k))

⇒

□(∃ i ∈ 0 . . MaxDescending : Descending(Buffer) = i ⇒ (NotEmpty ∧ NotStuffed)

⇒

◇FirstFull))

Proof: Follows from ⟨7⟩2 by temporal logic, replacing a formula of the form A ∧ B ⇒ C with one of the form A ⇒ (B ⇒ C ) in the consequent, in which A =^{Δ} ∃ i ∈ 0 . . MaxDescending : Descending(Buffer) = i, B =^{Δ} (NotEmpty ∧ NotStuffed), and C =^{Δ} ◇FirstFull.

⟨7⟩4.

∧ □[∃ a : CB.push(a) ∨ ∃ k : CB.move(k)]_{Buffer} ∧ WF_{Buffer}(CB.pop) ∧ WF_{Buffer}(∃ k : CB.move(k)) ∧ □(∃ i ∈ 0 . . MaxDescending : Descending(Buffer) = i)

⇒

□((NotEmpty ∧ NotStuffed)

⇒

◇FirstFull) Proof: Follows from ⟨7⟩3 by temporal logic, using the following temporal logic rule:

Let: X , Y , Z : propositional variable

⟨8⟩1. \frac{□X ⇒ □(Z ⇒ □Y )}{□X ∧ □Z ⇒ □Y}

Proof: This calculation in temporal logic follows from PTL. ⟨8⟩2. Q.E.D.

Proof: Follows from ⟨7⟩3 using ⟨8⟩1 with

Z =^{Δ} (∃ i ∈ 0 . . MaxDescending : Descending(Buffer) = i), Y =^{Δ} (NotEmpty ∧ NotStuffed) ⇒ ◇FirstFull.

⟨7⟩5. Q.E.D.

Proof: Follows by propositional logic from ⟨7⟩4 using ⟨7⟩1 to eliminate a conjunct in the antecedent.

⟨6⟩3. Q.E.D. Proof: Follows immediately by replacing the consequent of ⟨6⟩2 by an equivalent formula from ⟨6⟩1.

This completes the proof of the second hypothesis of the Bridge rule ⟨5⟩1. We now use the Bridge rule to infer its (instantiated) conclusion, which is the desired simplified version of the fourth premiss of WF2, step ⟨4⟩3.

⟨5⟩4. Q.E.D.

Proof: Follows from the Bridge rule ⟨5⟩1 under the instantiation □X =^{Δ} ∧ □[\begin{matrix}∨ ∃ a : CB.push(a) \\ ∨ ∃ k : CB.move(k)]_{Buffer} \end{matrix} ∧ WF_{Buffer}(CB.pop) ∧ WF_{Buffer}(∃ k : CB.move(k)),

NE =^{Δ} NotEmpty,

FF =^{Δ} FirstFull, with first hypothesis ⟨5⟩2 and second hypothesis ⟨5⟩3.

We now conclude the validity of the fourth premiss of WF2 from its simplified version ⟨4⟩3.

⟨4⟩4. Q.E.D.

Proof: Follows immediately from ⟨4⟩3 using ⟨4⟩1 and ⟨4⟩2.

We now conclude the validity of the conclusion of WF2, which gives the desired liveness property of AB.pop from the specification of CB.

⟨3⟩5. Q.E.D.

Proof: Follows from ⟨3⟩1, ⟨3⟩2, ⟨3⟩3 and ⟨3⟩4 using TLA Proof Rule WF2 with N =^{Δ} (∃ a : CB.push(a)∨CB.pop ∨∃ k : CB.move(k)), f = Buffer, A =^{Δ} B =^{Δ} CB.pop, M =^{Δ} AB.pop, g = buffer, P =^{Δ} FirstFull, □F =^{Δ} WF_{Buffer}(∃ k : CB.move(k)).

Finally, we conclude the main theorem from the steps demonstrating the proof obligations for the initial condition, the safety property, and the liveness property.

⟨2⟩4. Q.E.D.

Proof: Follows from ⟨2⟩1, ⟨2⟩2 and ⟨2⟩3 by Theorem 1, using Inv = True.

Acknowledgements

Paul Gibson and Abdellilah Mokkedem suggested this example. I started this work while visiting INRIA Lorraine, where I was hosted by Dominique Méry and Patrick Rambert. Bob Johnston told me about the F3314 chip in which this concrete buffer design was implemented. And Leslie Lamport developed TLA, the support tool pf.sty for hierarchical proofs, the macros tla.sty for writing TLA specifications and of course L^{A}TEX , without which we’d all be barbarians. Hearty thanks to all.

References

[AL91] M. Abadi and L. Lamport. The existence of refinement mappings. Theoretical Computer Science, 82(2):253–284, May 1991.

[AS87] B. Alpern and F. B. Schneider. Recognizing safety and liveness. Distributed Computing, 2:117–126, 1987.

[Ash75] E. A. Ashcroft. Proving assertions about parallel programs. Journal of Computer and System Sciences, 10:110–135, February 1975.

[GNRR93] R. Grossman, A. Nerode, A. P. Ravn, and H. Rischel, editors. Hybrid Systems, volume 736 of Lecture Notes in Computer Science. Springer-Verlag, 1993.

[Gri81] D. Gries. The Science of Programming. Springer-Verlag, 1981. ISBN 0-387-96480-0/3-540-96480-0/0-387-90641-X/3-540-90641-X.

[Hoa85] C. A. R. Hoare. Communicating Sequential Processes. Prentice-Hall International, 1985.

[Lad96] Peter B. Ladkin. Lazy cache implements complete cache.

In http://www.techfak.uni-bielefeld.de/~ladkin/ , January

1996.

[Lam89] Leslie Lamport. A simple approach to specifying concurrent systems. Communications of the ACM, 32(1):32–45, January 1989.

[Lam93a] Leslie Lamport. How to write a proof, January 1993. In

http://www.research.digital.com/SRC/tla/.

[Lam93b] Leslie Lamport. Hybrid systems in TLA^{+}. In [GNRR93], pages 77–102, 1993.

[Lam94a] Leslie Lamport. How to write a long formula. Formal Aspects of Computing, 6:580–584, 1994.

[Lam94b] Leslie Lamport. The temporal logic of actions. ACM Transactions on Programming Languages and Systems, 16(3):872–923, May 1994.

[LL94] Peter Ladkin and Leslie Lamport. TLA proof schema for implementation, June 1994. Written by Ladkin from notes of Lamport.

[LLOR94] Peter Ladkin, Leslie Lamport, Bryan Olivier, and Denis Roegel. Lazy caching: An assertional view, 1994. In

http://www.research.digital.com/SRC/tla/. To appear, Dis-

tributed Computing.

[LLOR95] Peter Ladkin, Leslie Lamport, Bryan Olivier, and Denis Roegel. A formal correctness proof of the lazy caching algorithm, 1995. To appear.

[Mil89] R. Milner. Communication and Concurrency. Prentice Hall International, 1989.

[Par72] D.L. Parnas. On the criteria to be used in decomposing systems. Communications of the ACM, 15(12), December 1972.

[Par74] D.L. Parnas. On a ‘buzzword’: Hierarchical structure. In Information Processing 1974 (Proc. IFIP Congress), pages 336–339. 1974.

[Spi89] J.M. Spivey. The Z Notation: A Reference Manual. Prentice-Hall International, 1989. ISBN 0-13-983768-X.

[Sti89] C. Stirling. Temporal logics for CCS. In J.W. de Bakker, W.-P. de Roever, and G. Rozenberg, editors, Linear Time, Branching Time and Partial Order in Logics and Models of Concurrency, volume 354 of Lecture Notes in Computer Science, pages 660–672. Springer-Verlag, 1989.

[Wen78] J. Wensley et al. SIFT: Design and analysis of a fault-tolerant computer for aircraft control. Proceedings of the IEEE, 66(10):1240– 1255, October 1978.

[Yu93] Y. Yu. Automated proofs of object code for a widely used microprocessor. Technical Report 114, Digital Equipment Corporation Systems Research Center, Palo Alto, California, October 1993. Revised version of the author’s Ph.D. thesis at the University of Texas at Austin.