Integrating Owicki-Gries for C11-Style Memory Models into Isabelle/HOL

Weak memory presents a new challenge for program verification and has resulted in the development of a variety of specialised logics. For C11-style memory models, our previous work has shown that it is possible to extend Hoare logic and Owicki-Gries reasoning to verify correctness of weak memory programs. The technique introduces a set of high-level assertions over C11 states together with a set of basic Hoare-style axioms over atomic weak memory statements (e.g., reads/writes), but retains all other standard proof obligations for compound statements. This paper takes this line of work further by showing Nipkow and Nieto's encoding of Owicki-Gries in the Isabelle theorem prover can be extended to handle C11-style weak memory models in a straightforward manner. We exemplify our techniques over several litmus tests from the literature and a non-trivial example: Peterson's algorithm adapted for C11. For the examples we consider, the proof outlines can be automatically discharged using the existing Isabelle tactics developed by Nipkow and Nieto. The benefit here is that programs can be written using a familiar pseudocode syntax with assertions embedded directly into the program.


Introduction
Hoare logic [17] is fundamental to understanding the intented design and semantics of sequential programs. Owicki and Gries' [29] framework extends Hoare logic to a concurrent setting by adding an interference-free check that guarantees stability of assertions in one thread against the execution of another. Although several other techniques for reasoning about concurrent programs have since been developed [33], Owicki-Gries reasoning remains fundamental to understanding concurrent systems and one of the main methods for performing deductive verification. Mechanised support for Owicki-Gries' framework has been developed for the Isabelle theorem prover [30] by Nipkow and Nieto [28] and is currently included in the standard distribution.
Our work is in the context of C11 (the 2011 C standard), which has a weak memory model that is designed to enable programmers to take advantage of weak memory hardware [6,20,23,24]. Unlike in sequentially consistent memory [26], states are graphs with several relations that are used to track dependencies between memory events (e.g., reads, writes, updates) [2,6,11,20,23,24]. This means that it is not possible to use the traditional Owicki-Gries framework to reason about concurrent programs under C11. Researchers have instead developed a set of specialised logics, e.g., [2], including those that extend Owicki-Gries framework [25] and separation logic [12,13,35,35,37] designed to cope with specific fragments of C11.
Our point of departure is the operational semantics of Doherty et al. [11] for the RC11-RAR fragment of C11 [23]. As indicated by the RAR, the memory model allows both relaxed and release-acquire accesses. Moreover, the model restricts the C11 memory model to disallow the "load-buffering" litmus test [23,24]. A key advancement in the semantics developed by Doherty et al. is a transition relation over states modelled as C11 graphs, allowing program execution to be viewed as an interleaving of program statements as in classical approaches to concurrency. They provide a primitive assertion language for expressing properties of such states, which is manually applied to the message passing litmus test and Peterson's algorithm adapted to C11. However, the assertion language itself expresses state properties at a low level of abstraction (high level of detail), and hence is difficult to mechanise. We 3 have recently recast Doherty et al.'s semantics in an equivalent timestamp-based semantics [19,20]. More importantly, we have developed a high-level set of assertions for stating properties of the C11 state [9]. These assertions have been shown to integrate well with a Hoare-style proof calculus, and, by extension, the Owicki-Gries proof method. Interestingly, the technique enables reuse of all standard Owicki-Gries proof rules for compound statements.
In this paper, we push this technique further by showing how this framework can be integrated into Isabelle via a straightforward extension of the Owicki-Gries encoding by Nipkow and Nieto [28]. Unlike [9], where program counters are used to model control flow and relations over C11 states are used to model program transitions, the approach in this paper is more direct. We show that once a correct proof outline has been encoded, the proof outlines can be validated with minimal user interaction. Our extension is parametric in the memory model, and can be adapted to reason about other C11-style operational models [24]. Contributions. Our main contributions are thus: 1. A simple and generic extension to the standard Isabelle encoding of Owicki-Gries to cope with C11-style weak memory, 2. An instantiation of the RC11-RAR operational semantics within Isabelle as an example memory model, 3. An integration with a high-level assertion language for reasoning about weak memory states, and 4. Verification of several examples in the extended theory, including Peterson's algorithm for C11.
Overview. In Section 2, we briefly present the Owicki-Gries encoding by Nipkow and Nieto [28], as well as the message passing litmus test which serves as a running example. We describe how this encoding can be generically extended to cope with weak memory in Section 3, then in Section 4, we present RC11-RAR as an example instantiation. In Section 5, we present a technique for reasoning about C11-style programs as encoded in Isabelle 4 , which we apply to a number of examples. We present related work in Section 6.

Owicki-Gries in Isabelle/HOL
Nipkow and Nieto [28] present a formalisation of Owicki-Gries method in Isabelle/HOL. Their formalisation defines syntax, its semantics and Owicki-Gries proof rules in higher-order logic. Correctness of the proof rules with respect to the semantics is proved and their formalisation is part of the standard Isabelle/HOL libraries. To provide some context for our extensions, we provide an overview of this encoding here; an interested reader may wish to consult the original paper [28] for further details.
The defined programming language is a C-like WHILE language augmented with shared-variable parallelism (||) and synchronisation (AWAIT). Parallelism must not be nested, i.e. within c 1 || c 2 || ... || c n , each c i must be a sequential program. The programming language allows program constructs to be annotated with assertions in order to record proof outlines that can later be checked. The language also allows unannotated commands that may be placed within the body of AWAIT statements. As in the original treatment [29], AWAIT is an atomic command that executes under the precondition of the AWAIT block.
datatype α ann_com = AnnBasic (α assn ) (α ⇒ α) | AnnSeq (α ann_com ) (α ann_com ) (" _ ;; _ ") | AnnCond (α assn ) (α bexp ) (α ann_com ) (α ann_com ) (" _ IF _ THEN _ ELSE _ FI ") | AnnWhile (α assn ) (α bexp ) (α assn ) (α ann_com ) In the datatype above, the concrete syntax is defined within (" ... "). α assn and α bexp represent assertions and Boolean expressions, respectively. AnnBasic represents a basic (atomic) state transformation (e.g, an assignment). AnnSeq is sequential composition, AnnCond is conditional, AnnWhile is a loop annotated with an invariant, and AnnWait is a synchronisation construct. The command Parallel is a list of pairs (c, q) where c is an annotated (sequential) command and q is a post-condition. The concrete syntax for parallel composition (not shown above) is: COBEGIN The semantics of programs are defined by transition rules between configurations, which are pairs comprising a program fragment and a state. The proof rules of the Owicki-Gries formalisation are syntax directed. A proof obligation generator has been implemented in the form of an Isabelle tactic called oghoare. Application of this tactic results in generation of all standard Owicki-Gries proof obligations, each of which can be discharged either automatically or via an interactive proof. We omit the full details of standard semantics and verification condition generator [28].
Example 1 (Message passing (MP)). To motivate our extensions to a C11-style weak memory model, we consider the message passing litmus test given in Fig. 1. It comprises two shared variables: d (that stores some data) and f (that stores a flag), both of which are initially 0. Under sequential consistency, the postcondition of the program is r2 = 5. This is because the loop in thread 2 only terminates after f has been updated to 1 in thread 1, which in turn happens after d has been set to 5 by thread 1. Therefore, the only possible value of d for thread 2 to read is 5. The proof of this property straightforward, and can be easily handled by Nipkow and Nieto's encoding [28]. We provide a partial encoding in Fig. 2 to provide an example instantiation of the  Fig. 2, the oghoare tactic generates 29 proof obligations, each of which is automatically discharged.

Extending Owicki-Gries to C11-Style Memory Models
We defer a precise description of a C11-style operational semantics to Section 4 in order to highlight the fact our Isabelle framework is essentially parametric in the memory model used. The fundamental requirement is that the memory model be an operational model featuring C-style, annotated memory operations. All that is needed to understand the rest of this section is some basic familiarity with weak memory models [9,11,19,31]. The functions encoding the weak memory operational semantics WrX, WrR, RdX, . . . will be instantiated Section 4, and for the time being can be considered to be transition functions that construct a new weak memory state for a given weak memory prestate. However, a reader may wish to first read Section 4 for an example C11 memory model prior to continuing with the rest of this section. To motivate our language extension, we reconsider MP (Example 1) in a C11-style weak memory model. In particular, if all reads and writes are relaxed, C11 admits an execution in which thread 2 reads a "stale" value of d [19,25]. Thus, it is only possible to establish the weaker postcondition r2 = 0 ∨ r2 = 5 (see Section 4 for details). To regain the expected behaviour, one must introduce additional synchronisation in the program. In particular, the write to f in thread 1 must be a releasing write (denoted f := R 1) and the read of f in thread 2 must be an acquiring read (denoted A weak memory state can be encoded as a special variable in the standard semantics. Moreover, for the semantics that we employ [9,11], within each weak memory state, for each low-level weak memory event (e.g., read or write) we must keep track of the thread identifier (of type T), the shared variable (or location) that is accessed (of type L) and the value in that variable (of type V).
The syntactic extensions necessary for encoding C11-style statements in Isabelle are straightforward. For example, to capture the so-called RAR fragment, we require five new programming constructs: relaxed reads and writes, releasing writes, and acquiring reads. Moreover, we wish to support a SWAP[x, v] command [11,39] that acquiringly reads x and releasingly writes v to x in a single atomic step. This command is used in Peterson's algorithm (see Fig. 10) and is implemented in our model using a read-modify-write update.
All of the new extensions are defined using a shallow embedding and their concrete syntax is enclosed in brackets < ... > to avoid ambiguities in the Isabelle/HOL encoding. The annotated versions of these statements are given below. For completeness, we also require syntax for unannotated versions of each command, but their details are elided.
To cope with weak memory, we embed the weak memory state as a special variable in the standard encoding (see Figs. 7 and 8). Each operation induces an update to this embedded weak memory state variable that can be observed by subsequent operations on the weak memory state.
_AnnWrX defines a relaxed write. Its first argument is an assertion (the precondition) of the command, the second is the variable being modified, the third is the thread performing the operation, the fourth is the value being written, and the fifth is the weak memory prestate. Similarly, _AnnWrA is a releasing write. _AnnRdX defines a relaxed read, which loads a value of the given location (of type L) from the given weak memory prestate into the second argument (of type idt). An acquiring read, defined by _AnnRdA is similar. Finally, _AnnSwap defines a swap operation that writes the given value (third argument) to the given location (second argument) using an update operation.
The semantics of this extended syntax is given by a translation, which updates the program variables, including the weak memory state. For the commands above, after omitting some low-level Isabelle details, we have: translations " r < x := t v´s >" ⇀ " AnnBasic r ( _ u p d a t e _nam e s ( WrX´s x v t ))" " r < x := R t v´s >" ⇀ " AnnBasic r ( _ u p d a t e _nam e s ( WrR´s x v t ))" " r <´x ← t y´s >" ⇀ " AnnAwait r (( _ u p d a t e _name s ( fst ( RdX´s y t ))) , , ( _ u p d a t e _name x ( snd ( RdX´s y t ))))" " r <´x ← A t y´s >" ⇀ " AnnAwait r (( _ u p d a t e _name s ( fst ( RdA´s y t ))) , , ( _ u p d a t e _name x ( snd ( RdA´s y t ))))" " r < SWAP [x , v ] tś >" ⇀ " AnnBasic r ( fst ( Upd´s x v t ))"  Relaxed and acquiring writes update the embedded weak memory state to the state returned by WrX and WrA, respectively. A read event must return a post state (which is used to update the value of the embedded weak memory state) and the value read (which is used to update the value of the local variable storing this value). In order to preserve atomicity of the read, we wrap both updates within an annotated AWAIT statement. The translation of a SWAP is similar.
Note that a relaxed (acquiring) read comprises two calls to RdX (RdA), which one could mistakenly believe to cause two different effects on the weak memory state. However, as we shall see, these operations are implemented using Hilbert choice (SOME), hence, although there may be multiple values that a read could return, the two applications of RdX (RdA) are identical for the same value for the same parameters.

A C11-Style Memory Model: RC11-RAR
In this section, we describe a particular instance of a C11-style memory model: the RC11-RAR fragment. This fragment disallows the load buffering litmus test [6,23,24], and all accesses are either relaxed, releasing or acquiring. It is straightforward to extend the model to incorporate more sophisticated notions such as release sequences and non-atomic accesses, but these are not considered as the complications they induce detract from the main contribution of our work. It is worth noting that RC11-RAR is still a non-trivial fragment [11].

Message Passing
To motivate the memory model, consider again the MP litmus test but for RC11-RAR (Figs. 3 and 4). In Fig. 3, all accesses are relaxed, and hence the program can only establish the weaker postcondition r2 = 0 ∨ r2 = 5 since it is possible for thread 2 to read 0 for d at line 4. In Fig. 4, the release annotation (line 2) and the acquire annotation (line 3) induces a happens-before relation if the read of f reads from the write at line 2 [6]. This in turn ensures that thread 2 sees the most recent write to d at line 5.
We use the operational semantics described in [9], which models the weak memory state using timestamped writes and thread view fronts [15,19,20,31]. A timestamp is a rational number that totally orders the writes to each variable. A viewfront records the timestamp that a thread has encountered for each variable -the idea is that a thread may read from any write whose timestamp is no smaller than the thread's current viewfront. Similarly, a write may be introduced at any timestamp greater than the current viewfront. The only caveat when introducing a write is that it may not be introduced directly after a covered write (see [9,11]). This caveat is to ensure atomicity of RMW operations. In particular, a write to a variable x is covered whenever there is a RMW on x that reads from the write. In this instance, it would be unsound for another write to x to be introduced between the write that is read and the RMW (see [11] for further details).
Example 2 (Unsynchronised MP). Consider Fig. 5, depicting a possible execution of the unsynchronised MP example (Fig. 3). The execution comprises four weak memory states σ 0 , σ 1 , σ 2 , σ 3 . In each state, the timestamps themselves are omitted, but are assumed to be increasing in the direction of the arrows. The numbers depict the value of each variable at each timestamp. State σ 0 is the initial state. Each thread's viewfront in σ 0 is consistent with the initial writes.
After executing line 1, the program transitions to σ 1 , which introduces a new write (with value 5) to d and updates the viewfront of thread 1 to the timestamp of this write. At this stage, thread 2's viewfront for d is still at the write with value 0. Thus, if thread 2 were to read from d, it would be permitted to return either 0 or 5. Moreover, if thread 2 were to write to d, it would be permitted to insert the write after 0 or 5.
After executing line 2, the program transitions to σ 2 , which installs a (relaxed) write of f with value 1. Now, consider the execution of line 3. There are two possible poststates since there are two possible values of f that thread 2 could read. State σ 3 depicts the case where thread 2 reads from the new write f = 1. In this case, the view front of thread 2 is updated, but crucially, since there is no release-acquire synchronisation, the viewfront of thread 2 for d remains unchanged. This means that when thread 2 later reads from d in line 4, it may return either 0 or 5. We contrast this with the execution of the synchronised MP described in Example 3. Fig. 6, which depicts an execution of the program in Fig. 4. State τ 1 is a result of executing line 5 and is identical to σ 1 . However, after execution of line 6, we obtain state τ 1 , which installs a releasing write to f (denoted by 1 R ). As in Example 2, the acquiring read in line 7 could read from either of the writes to f . State τ 3 depicts the case in which thread 2 reads from the releasing write 1 R . Now, unlike Example 3, this read establishes a release-acquire synchronisation, which means that the viewfront of thread 2 for both f and d are updated. Thus, if the execution continues so that thread 2 reads from d (line 8), the only possible value it may return is 5.

Operational Semantics of C11 RAR in Isabelle/HOL
We now present details of the memory model from Section 4.1 as encoded in Isabelle/HOL. Recall that the main purpose of this section is to instantiate the functions WrX, WrR, RdX, RdA and Upd from Section 3. Fig. 6. An execution of the synchronised message passing Recall that type L represents shared variables (or locations), T represents threads, and V represents values. We use type TS (which is synonymous with rational numbers) to represent timestamps. Each write can be uniquely identified by a variable-timestamp pair. The type Cstate is a nested record with fields writes, which is the set of all writes, covered, which is the set of covered writes (recalling that covered writes are used to preserve atomicity of read-modify-write updates), mods, which is a function mapping each write to a write record (see below), tview, which is the viewfront (type L ⇒ (L × TS)) of each thread, and mview, which is the viewfront of each write.
A write record contains fields val, which is the value written and rel, which is a Boolean that is True if, and only if, the corresponding write is releasing. Next, we describe how the operations modify the weak memory state.
Read transitions. Both relaxed and acquiring reads leave all state components unchanged except for tview. To define their behaviours, we first define a function visible_writes σ t x that returns the set of writes to x that thread t may read from in state σ. For a write w = (x, q), we assume a pair of functions var w = x and tst w = q that return the variable and timestamp of w, respectively. Thus, we obtain: We use a function getVW to select some visible write from which to read: definition " getVW σ t x ≡ ( SOME w . w ∈ v i s i b l e _ wr ite s σ t x )" Finally, we require functions read_transX t w σ and read_transA t w σ that update the tview component of σ for thread t reading write w. Function read_transX t w σ updates tview σ t to (tview σ t)[var w := w], where f [x := v] denotes functional override. That is, the viewfront of thread t for variable var w is updated to the write w that t reads. The viewfronts of the other threads as well as the viewfront of t on variables different from var w are unchanged. Thus, the function RdX required by the translation of a relaxed read command in Section 3 is thus defined by: " RdX x t σ = ( let w = getVW σ t x; v = value σ w in ( r e a d _ t r ansX t w σ , v ))" We use value σ w to obtain the value of the write w in state σ. The update defined by function read_transA t w σ for an acquiring read is conditional on whether w is a relaxed write. If w is relaxed, tview σ t is updated to (tview σ t)[var w := w] (i.e., behaves like a relaxed read). Otherwise, the viewfront of t must be updated to "catch up" with the viewfront of w. In particular, Overall, we have: where " RdA x t σ = ( let w = getVW σ t x; v = value σ w in ( r e a d _ t r ansA t w σ , v ))" Write transition. Writes update all state components except covered. First, following Doherty et al. [11], we must identify an existing write w in the current state; the new write is to be inserted immediately after w. Moreoever, w must be visible to the thread performing the write and covered by an RMW update. We define the following function to definition " getVWNC σ t x ≡ SOME w . w ∈ v i s i b l e _ w rite s σ t x ∧ w / ∈ σ covered " where NC stands for "not covered". The write operation must also determine a new timestamp, ts for the new write. Given that the new write is to be inserted immediately after the write operation w, the timestamp ts must be greater than tst w but smaller than the timestamp of other writes on var w after w. Thus, we obtain a new timestamp using: definition " getTS σ w ≡ SOME ts . tst w < ts ∧ (∀ w ∈ writes σ. var w = var w ∧ tst w < tst w −→ ts < tst w ) Finding such a timestamp is always possible since timestamps are rational numbers (i.e., are dense).
As with reads, we require a function write_trans t b w v σ ts that updates the state σ so that a new write w' = ((var w), ts) for thread t is introduced with write value v. The Boolean b is used to distinguish relaxed and releasing writes. The write w is the write after which the new write w' is to be introduced. The effect of write_trans is to update writes σ to writes ′ , mods σ to mods ′ and both tview σ t and mview σ w ′ to tview ′ , where: Thus, writes ′ adds the new write w' to the set of writes of σ. The new mods ′ sets the value for w' to v and the rel field to b (which is True iff the new write w' is releasing). Finally, tview ′ updates tview of t for variable var w (the variable that both w and w' update) to w'.
Finally, the functions WrX and WrR required by the translations in Section 3 are given as follows fun WrX :: " L ⇒ V ⇒ T ⇒ Cstate ⇒ Cstate " where " WrX x v t σ = ( let w = getVWNC σ t x ; ts = getTS σ w in w r i t e _ t ra ns t False w v σ ts )" fun WrR :: " WrR x v t σ = ( let w = getVWNC σ t x ; ts = getTS σ w in w r i t e _ t ra ns t True w v σ ts )" Update transition. Following Doherty et al. [11], we assume that an update performs both an acquiring read and a releasing write in a single step. It is possible to define variations that do not synchronise the read or a write, but we omit such details for simplicity. We first define a function update_trans t w v σ ts that modifies σ so that a releasing write w' = ((var w), ts) by thread t is introduced with write value v immediately after an existing write w. The effect of update_trans is to update writes σ to writes ′ , covered σ to covered ′ , and mods σ to mods ′ , and both tview σ t and mview σ w' to tview ′ , where Thus, writes ′ adds the new write w' corresponding to the update to the set of writes of σ and covered ′ adds the write w that w' reads from to the covered writes set of σ. The new mods ′ sets the value for w' to v and sets the rel field to True. Finally, tview ′ updates tview of t in the same way as a read operation, except that the first case is taken provided the write w being read is releasing. The function Upd required by the translation in Section 3 is given as follows: fun Upd :: " L ⇒ V ⇒ T ⇒ Cstate ⇒ ( Cstate × V )" where " Upd x v t σ = ( let w = getVWNC σ t x ; ts = getTS σ w ; v = value σ w in ( u p d a t e _ t rans t w v σ ts , v ))" Well formedness. Section 5 presents an assertion language for verifying C11 programs. The lemmas introduced there require states to be well formed, which we characterise by predicate wfs defined below.
definition " writes_on σ x ≡ { w . var w = x ∧ w ∈ writes σ }" definition " lastWr σ x ≡ (x , Max ( tst ( writes_on σ x )))" Function writes_on σ x returns the set of writes in σ to variable x. Function lastWr σ x returns the write on x whose timestamp is greater than the timestamp of all other writes on x in state σ.
In the definition of wfs σ, the first two conjuncts ensure that all writes recorded in tview and mview are consistent with writes σ. The third ensures the set of writes in σ is finite and the fourth ensures that for each write in σ, the write's modification view of the variable it writes is the write itself. The final conjuct ensures that the last write to each variable (i.e., the write with the largest timestamp) is not covered. We have shown that wfs is stable under each of the transitions WrX, WrR, . . . . Thus the well-formedness assumption made by the lemmas in Section 5 is trivially guaranteed.

An Assertion Language for Verifying C11 Programs
In the previous sections, we discussed how the existing Owicki-Gries theories in Isabelle can be extended with a weak memory C11 operational semantics in order to reason about C11-style programs using standard proof rules. We mentioned that how a novel set of assertions introduced in [9] can be used in our extension to annotate programs w.r.t. C11 state and reason about them. In this section we introduce the assertion language and present their encodings in Isabelle through a number of examples and litmus tests. We also provide some of the rules (lemmas) that Isabelle uses to discharge proof obligations and validate the proof outlines. We show how C11 state is incorporated into the programs and shared variables are defined. We also present a fully verified encoding of the Peterson's mutual exclusion algorithm to further validate our approach.

Load Buffering
Our first example is the load-buffering litmus test given in Fig. 7. As discussed earlier, we use restricted C11 memory model described by Lahav et al. [23], and hence we prevent the program from terminating by reading 1 for both x in thread 1 and y in thread 2.
As mentioned earlier, the C11 state is represented as a field of the record corresponding to the state of the program (i.e. as a field of lb_state record for the load-buffering example). Updates to σ are via the underlying definition of the operations in accordance with the RC11-RAR operational semantics as described in Section 4.2. In our encoding, shared variables are represented as constants representing locations in the C11 state (σ). Now consider the proof outline. The first assertion (lines 10-12) specifies the initial state of the program. The first two conjuncts are assertions on the value of local registers. The other four conjuncts are definite observation assertions. A definite observation assertion denoted [x = t n] σ states that thread t's viewfront is consistent with the last write to x in σ and that this write has value n. Thus, if t reads from x, it is guaranteed to return n. σ. Formally, All weak-memory assertions in the proof outline of Fig. 7 are definite value assertions, and this is sufficient to prove the postcondition. However, to discharge the generated proof obligations, we require the following two proof rules over C11 assertions, which are defined as Isabelle lemmas:  lemma d_obs_RdX_pres : assumes " wfs σ" and "[ x =t u] σ" shows "[ x =t u ] ( fst ( RdX y t σ ))"

|| -
lemma d_obs_WrX_diff _v ar _ pr es : assumes " wfs σ" and "[ x =t u] σ" and " y = x " shows "[ x =t u ] ( WrX y v t σ )" Lemmas d_obs_RdX_pres and d_obs_WrX_diff_var_pres give conditions for preserving definite value assertions for relaxed read and write transitions, respectively for an arbitrary variable y and thread t'. Note d_obs_WrX_diff_var_pres requires that the variable y that is written to is different from the variable x that appears in the definite value assertion. Both lemmas are proved sound with respect to the operational semantics in Section 4.2. Of course d_obs_RdX_pres also holds for an acquiring read transition and d_obs_WrX_diff_var_pres for a releasing write transition 5 . The assertions on lines 14 and 20 are locally correct because of the initial state. The assertions on lines 16 and 22 are locally correct using d_obs_RdX_pres. Local correctness of the assertions on lines 18 and 24 is trivial follows by the definite value assertion. Interference freedom of the assertions in lines 14, 16, 20 and 22 is also established using the two lemmas.

Message-Passing
We now consider the assertions used in the message-passing algorithm (Fig. 8). The first new assertion used in the proof outline is the possible observation assertion. This assertion (denoted [x ≈ t n] σ) states that thread t may read value n if it reads from variable x. The formal definition in Isabelle is as follows: The next assertion we introduce is the conditional observation assertion (denoted [x = n] y = t m σ) which states that if thread t reads a value n using an acquiring read for x, it synchronises with the corresponding write and obtains the definite value m for y. Note that this requires that any write to x with value n that t can read is a releasing write. The formal definition in Isabelle is as follows: Here we only introduce two of the interesting rules used in the proof, and refer the interested reader to the Isabelle theories for the remaining lemmas: lemma c_obs_intro : assumes " wfs σ" and "[ y =t m ] σ" and "¬[ x ≈ t ′ n ] σ" and " x = y " and " t = t " shows "[ x = u] y = t ′ v ( WrR x u t σ )" lemma c_obs_transfer : assumes " wfs σ" and "[ x = u ] y =t v σ" and " snd ( RdA σ x t ) = u " shows "[ y =t v ] ( fst ( RdA x t σ ))" Consider the conditional observation assertion in line 17. Local correctness holds trivially by initialisation. Interference freedom under line 12 is straightforward. For interference freedom under line 14, we use c_obs_intro. In particular, the assertion at line 13 (i.e., the precondition of line 14) satisfies the critical premises of c_obs_intro. We use the conditional observation assertion (line 17 of thread 2) in combination with rule c_obs_transfer to establish a definite observation on a new variable in thread 2. We note that the variable read by the transition in rule c_obs_transfer is x, whereas the definite value assertion in the consequent is on variable y. Full details of this proof may be found in [9]; in this paper, we focus on automation, which we discuss in Section 5.5.

Read-Read Coherence
We have verified three versions of the read-read coherence (RRC) litmus test using our extended theories. We have provided the more interesting of the three in Fig. 9. The other two versions are provided Appendix A. In order to prove this example, a richer set of assertions is required. In particular, in addition to the assertions regarding observability of writes, we need assertions about the order of writes and the limits on the occurrence of values. The first assertion used for this example that we discuss here is the possible value order assertion (denoted [m ≺ x n] σ), which states that there exists a write to variable x with value n ordered after (i.e., with a greater timestamp) a write to x with value m. Similarly, a definite value order assertion (denoted [m ≺ ≺ x n] σ) states that all writes to x with value n are ordered after all writes to x with value m. These are formally defined in Isabelle as follows.
The other two assertions appear in this proof outline fall into the value occurrence category: [0 x n] i means that there has not been a write with value n to variable x (where i is the initial value of x) and [1 x n] means that there has been at most one write with value n to x. These two assertions are defined in terms of ordering assertions introduced earlier as follows. The predicate init σ x n holds iff the initial value of x in σ is n.
The last new assertion used in this proof outline is encountered value (denoted as [x enc = t n]) means that thread t has had the opportunity to observe a write with value n of x. This assertion is formally defined in Isabelle as follows 6 : The five assertions above, together with other assertions introduced earlier, are sufficient to specify the behaviour of the three-threaded version of RRC. The conditional observation assertion on line 18, is used to capture the possible synchronisation between threads 1 and 2. The ordering assertions in thread 2 and 3 specify that if the writes have happened in a specific order, the read order must remain coherent with respect to the order of writes. Namely, if thread 2 synchronises with thread 1 (i.e., r1 is set to 1), then it must have observed the write of x at line 12. Thus, the write to x with value 2 at line 23 must have happened after. Therefore, it must be impossible for the third thread to read value 2 for x at line 27, then subsequently read 1 for x at line 29. This reasoning is captured by the postcondition of the program.

Peterson's Algorithm for C11
The final non-trivial example in this paper is Peterson's mutual exclusion algorithm (Fig. 10) as described in [39]. The proof outline for this algorithm has the new assertion covered (denoted cvd[x, n] σ). The assertion cvd[x, n] σ means that every write to x in state σ except the last is covered and the value written by that last write is n. This assertion is formally defined in Isabelle as: Similar to the previous examples, in order to prove the Peterson's algorithm we will need to introduce new proof rules to deal with assertions involving cvd.
The proof also uses auxiliary variables after1 and after2. We set after1 to True when turn is set in thread 1 and to False when thread 1 exits its critical section (i.e., when the flag is reset). This is a standard technique used in Owicki-Gries proofs of Peterson's algorithm in the conventional, sequentially consistent, setting [5,28]. Note that introduction of auxiliary variables must follow the same rules as the classical setting [29] and must not be a shared constant that appears within the weak memory state σ. This avoids the notions of unsoundness of auxiliary variables described in earlier work [25].

Verification Strategy
For each of the algorithms described above, we employ a generic verification strategy and outline the the proof effort. After encoding the algorithm and the assertions, the main steps in each validating the proof outlines are as follows.
1. First, we use the built-in oghoare tactic to reduce an Owicki-Gries proof outline into a set of basic Hoare logic proof obligations over weak memory pre-post state assertions. This tactic is exactly as developed by Nipkow and Nieto [28], and is used without change. 2. We pipe this output (which is a set of proof obligations on atomic commands) to the Isabelle simplifier, transforming the set-based representation of assertions by Nipkow and Nieto into a predicate-based representation. This allows the lemmas for weak memory that we haved adapted from [9] to be automatically matched with the proof obligations.
For the simple litmus tests, the first two steps either discharge all the proof obligations, or leave a few (maximum 6) proof obligations unproved. These proof obligations require slightly more sophisticated application of the lemmas over weak memory state than can be discharged by the simplifier alone. However, they can be automatically discharged using Isabelle's built-in sledgehammer tool [7]. This verification strategy works equally well for Peterson's algorithm although it is a larger example that generates a significantly higher number of proof obligations. The oghoare tactic generates 258 subgoals, but over half of these are discharged by step 2 above, leaving 91 subgoals.  Although automatic, repeated applications of sledgehammer to discharge so many proof obligations is rather tedious. However, one can quickly discover common patterns in the proof steps allowing these proof obligations to be discharged via a few simple applications of apply-style proofs.

Related Work
As has been mentioned, the current paper builds on ideas found in [11]. That paper did not develop a program logic based on Hoare triples, and was limited to invariance style proofs. Both [11] and the current paper use the same definite value assertion, but the current paper employs a much richer and more powerful assertion language. In particular, the conditional value assertion is critical for enabling an Owicki-Gries based program logic. Finally, [11] does not consider mechanisation or automation.
Of course, a great deal of work has been put into the development of separation logics for C11-style weak memory models [12,14,19,37,38]. One of the mosty recent and perhaps most fundamental of these is the Iris framework [19]. This framework has been formalised in the Coq proof assistant, and instantiations of it support a large fragemt of C11. This fragment contains C11's nonatomic accesses but not relaxed accesses and is therefore incomparable to our own. In particular, nonatomic access cannot legally race, whereas relaxed accesses are designed to enable racy code. More generally, separation logics can become complicated when applied to weak-memory, and we are partly motivated by the desire to build verification frameworks atop simple and natural relational models (other authors [25] have made similar observations).
There have been a number of recent attempts to develop mechanised deductive verification support for weak memory. Summers and Müller [34] present an approach to automating deductive verification for weak memory programs by encoding Relaxed Separation Logic [38] and Fenced Separation Logic [12,14] into Viper [27]. Their work builds on separation logic, whereas ours builds on a relational framework. Apart from this fundamental difference, Summers and Müller encode the concurrent logics into the Viper sequential specification framework, which provides a high level of automation. On the other hand, and as the authors themselves note, encoding the logic in a foundational verification tool such as Isabelle provides a higher level of assurance about correctness. In particular, the entire development of our framework is verified in Isabelle, down to the operational semantics.
Another technique based on Owicki-Gries is that of [25], which defines a proof system for the release-acquire fragment of C11, a smaller fragment than the release-acquire-relaxed fragment that we treat in this paper. It is unclear how difficult it would be to extend [25] to deal with relaxed accesses. In any case, [25] does not deal with mechanisation or automation.
Alglave and Cousot have developed another extension to the Owicki-Gries method for weak memory models [2]. Because their method, like ours, is an extension of the Owicki-Gries method, their verification method first requires a proof outline. One novelty of their approach is that their method requires a communication specification (or CS), which involves specifying for each read operation in the program, which writes the variable can read from (which may be in another thread). Verifying that the proof outline and CS are together valid, and therefore that assertions of the proof outline do in fact hold for the program, involves two proof obligations. The first is to show that the proof outline is valid in our standard sense (so that it is locally correct and noninterfering), under the assumption that the CS is satisfied. The second obligation is that a given memory model satisfies the CS. This second obligation constitutes an additional proof effort, not required in our method. The advantage they gain is that once a proof outline is known to be locally correct and noninterfering under a given CS, then the algorithm is known to be correct under any memory model that satisfies the CS.
The operational semantics in the current paper is inspired by the semantics described in [19,31]. The current paper is based on semantics and assertions found in [9], which also presents case-study verifications mechanised in Isabelle. The mechanisation in that paper is rudimentary. Programs are not represented in a while-style language as in the current paper. Instead, they use a program-counter based representation, where control flow must be explicitly modelled. As a consequence, proof obligations are not decomposed in the conventional Owicki-Gries style. Rather, the verifier must prove stability of a large global invariant mapping program counter locations to the assertions that hold at that location. Furthermore, there is little real automation, either in generating or discharging proof obligations. The current paper presents a highly-structured and mechanised Owicki-Gries framework supporting a high level of automation.
Dan et al. [10] introduce an abstraction for the store buffers of the weak memory model which reduces the workload on program analysers. They provide a source-to-source transformation that realises the abstraction producing a program that can be analysed with verifiers for sequential consistency. The approach is integrated with ConcurInterproc [18] and uses the Z3 theorem prover. Model checking has also been targeted for weak memory, e.g., by explicitly encoding architectural structures leading to weak behaviour, like store buffers [3,36]. Ponce de León et al. [16,32] have developed a bounded model checker for weak memory models, taking the axiomatic description of a memory model as input. (Bounded) model checkers for specific weak memory models are furthermore the tools CBMC [4] (for TSO), Nidhugg [1] (for TSO and PSO), RCMC [21] (for C11) and GenMC [22]. Others [8] present an approach for modelling and verifying C11 programs using Event-B and ProB model checker.

Conclusion
In this paper, we have introduced an extension to a twenty-year old formalisation of Owicki-Gries proof calculus in Isabelle [28] in order to tackle the verification problem of C11 programs under weak memory. We start by developing the necessary language support for defining C11 programs and have shown that existing operational semantics for the RC11-RAR fragment [11] can be encoded in a straightforward manner, which provides an example instantiation. We have provided a set of proof rules to facilitate verification of C11 programs, and exemplified our approach by verifying a number of example programs.
Our entire development has been carried out in the Isabelle theorem prover and is modular with respect to the underlying memory model. For the RC11-RAR fragment we consider, we have shown that the proofs are highly automated. As described in Section 5.5, a simple pattern of applying an Owicki-Gries specific proof method, and then invoking SMT solvers via Isabelle's sledgehammer tool was sufficient for verifying every proof outline. Moreover, the use of Isabelle means that we have flexibility in the specific operational semantics that we use.   12 presents the standard four-threaded version in which the two writes to x occur in two different threads. There are two reader threads both of which read from x twice. One must prove that the order of writes read by both threads are consistent. In particular, if a is set to 1 and b to 2, then thread 3 must have seen the writes to x in that order. It should therefore not be possible for thread 3 to read 1 for x if it has already seen the value 2 in the first read at line 29.