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

04/06/2020 ∙ by Sadegh Dalvandi, et al. ∙ 0

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.



There are no comments yet.


page 1

page 2

page 3

page 4

This week in AI

Get the week's most popular data science and artificial intelligence research sent straight to your inbox every Saturday.

1 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, 35, 35, 13, 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. We111For the CAV reviewers: This work is under review. An anonymised copy of the paper has been uploaded [9]. have recently recast Doherty et al.’s semantics in an equivalent timestamp-based semantics[20, 19]. 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 Isabelle222Our entire Isabelle formalisation can be found as ancillary files here, which we apply to a number of examples. We present related work in Section 6.

2 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 , each 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.

  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)
      ("_ WHILE _ INV _ DO _ OD")
| AnnAwait ( assn) ( bexp) ( com)
      ("_ AWAIT _ THEN _ END")
  com =
Parallel ( ann_com option   assn) list
| Basic (  )
| Seq ( com)  ( com) ("_ ,, _")
| Cond ( bexp)  ( com)  ( com) ("IF _ THEN _ ELSE _ FI")
| While ( bexp)  ( assn)  ( com) ("WHILE _ INV _ DO _ OD")

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 where is an annotated (sequential) command and is a post-condition. The concrete syntax for parallel composition (not shown above) is: COBEGIN COEND.

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. 2. It comprises two shared variables: (that stores some data) and (that stores a flag), both of which are initially . Under sequential consistency, the postcondition of the program is . This is because the loop in thread 2 only terminates after has been updated to in thread 1, which in turn happens after has been set to by thread 1. Therefore, the only possible value of for thread 2 to read is . 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 abstract syntax above, and to better highlight the extensions necessary to handle C11-style weak memory. The state of the program is defined using an Isabelle record where all shared variables and local registers are modelled as its fields. For the proof outline in Fig. 2, the oghoare tactic generates 29 proof obligations, each of which is automatically discharged.

Figure 1: MP litmus test under sequential consistency
 mp_state = d::nat   f::nat   r1::nat   r2::nat : ||-   f = 0  d = 0         COBEGIN         True   d := 5 ;;         d = 5  f := 1         True         ||        ...        COEND         r2 = 5 
Figure 2: Proving MP under sequential consistency using Nipkow and Nieto’s encoding [28] of Owicki-Gries

3 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 [11, 9, 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  [19, 25]. Thus, it is only possible to establish the weaker postcondition (see Section 4 for details). To regain the expected behaviour, one must introduce additional synchronisation in the program. In particular, the write to in thread 1 must be a releasing write (denoted ) and the read of 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 [11, 9], 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 [39, 11] 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.

"_AnnWrX" :: " assn  L  T   V  Cstate   ann_com"
          ("(_ <_  :=_  _ _>)")
"_AnnWrR" :: " assn  L  T   V  Cstate   ann_com"
          ("(_ <_ :=_  _ _>)")
"_AnnRdX"::" assn  idt  T  L  Cstate   ann_com"
                ("(_ < _  _  _ _ >)")
"_AnnRdA"::" assn  idt  T  L  Cstate   ann_com"
                ("(_ < _  _  _ _ >)")
"_AnnSwap"::" assn   L  V  T  Cstate   ann_com"
          ("(_ <SWAP[_, _]_ _>)")

Antiquotations of the from are used to denote that the element is a field of the record corresponding to the state of each program. For example, for the program in Fig. 2, the variables d, f, r1 and r2 of record mp_state would be interpreted in the syntax using antiquotated variables. This allows the translations (see below) to update fields in an external record yet for the programs themselves to be written using a natural imperative syntax.

To cope with weak memory, we embed the weak memory state as a special variable in the standard encoding (see Figs. 8 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:

"r < x := v s>" 
      "AnnBasic r (_update_name s (WrX s x v t))"
"r < x := v s>" 
      "AnnBasic r (_update_name s (WrR s x v t))"
"r < x   y s>" 
      "AnnAwait r ((_update_name s (fst (RdX s y t))),,
                               (_update_name x (snd (RdX s y t))))"
"r < x   y s>" 
      "AnnAwait r ((_update_name s (fst (RdA s y t))),,
                               (_update_name x (snd (RdA s y t))))"
"r <SWAP[x, v] s>"  "AnnBasic r (fst (Upd s x v t))"

These translations rely on an operational semantics defined by functions WrX (relaxed write), WrR (releasing write), RdX (relaxed read), RdA (acquiring read) and Upd (RMW update), which we define in Section 4.2.

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.

4 A C11-Style Memory Model: RC11-RAR

Figure 3: Unsynchronised MP under RC11-RAR
Figure 4: MP with release-acquire synchronisation

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, 24, 23], 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].

4.1 Message Passing

To motivate the memory model, consider again the MP litmus test but for RC11-RAR (Figs. 4 and 4). In Fig. 4, all accesses are relaxed, and hence the program can only establish the weaker postcondition since it is possible for thread 2 to read 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 reads from the write at line 2 [6]. This in turn ensures that thread 2 sees the most recent write to at line 5.

We use the operational semantics described in [9], which models the weak memory state using timestamped writes and thread view fronts [31, 15, 19, 20]. 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 [11, 9]). This caveat is to ensure atomicity of RMW operations. In particular, a write to a variable is covered whenever there is a RMW on that reads from the write. In this instance, it would be unsound for another write to 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. 4). The execution comprises four weak memory states , , , . 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 is the initial state. Each thread’s viewfront in is consistent with the initial writes.

After executing line 1, the program transitions to , which introduces a new write (with value ) to and updates the viewfront of thread 1 to the timestamp of this write. At this stage, thread 2’s viewfront for is still at the write with value . Thus, if thread 2 were to read from , it would be permitted to return either or . Moreover, if thread 2 were to write to , it would be permitted to insert the write after or .

After executing line 2, the program transitions to , which installs a (relaxed) write of with value . Now, consider the execution of line . There are two possible poststates since there are two possible values of that thread 2 could read. State depicts the case where thread 2 reads from the new write . 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 remains unchanged. This means that when thread 2 later reads from in line 4, it may return either or . We contrast this with the execution of the synchronised MP described in Example 3.

T1 view

T2 view

T1 view

T2 view

T1 view

T2 view

T1 view

T2 view

Figure 5: An execution of the unsynchronised message passing
Example 3 (Synchronised MP)

Consider Fig. 6, which depicts an execution of the program in Fig. 4. State is a result of executing line 5 and is identical to . However, after execution of line 6, we obtain state , which installs a releasing write to (denoted by ). As in Example 2, the acquiring read in line 7 could read from either of the writes to . State depicts the case in which thread 2 reads from the releasing write . Now, unlike Example 3, this read establishes a release-acquire synchronisation, which means that the viewfront of thread 2 for both and are updated. Thus, if the execution continues so that thread 2 reads from (line 8), the only possible value it may return is .

T1 view

T2 view

T1 view

T2 view

T1 view

T2 view

T1 view

T2 view

Figure 6: An execution of the synchronised message passing

4.2 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.

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.

 Cstate =   writes::(L  TS) set   covered::(L  TS) set   mods::(L  TS)  write_record   tview::T  L  (L  TS)   mview::(L  TS)  L  (L  TS)  write_record =   val :: V   rel :: bool

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 that thread may read from in state . For a write , we assume a pair of functions and that return the variable and timestamp of w, respectively. Thus, we obtain:

 "visible_writes  t x 
   {w  writes  . var w = x  tst(tview  t x)  tst w}"

We use a function getVW to select some visible write from which to read:

 "getVW  t x 
      (SOME w . w  visible_writes  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 to , where 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:

 value  w  val (mods  w) 
 RdX :: "L   T   Cstate  (Cstate  V)" where
       "RdX x t  =  (let w = getVW  t x; v = value  w in
                                  (read_transX 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, is updated to (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, is updated to , where

Overall, we have:

 RdA :: "L   T   Cstate  (Cstate  V)" where
       "RdA x t  =  (let w = getVW  t x; v = value  w in
                                  (read_transA 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

  "getVWNC  t x 
   SOME w . w  visible_writes  t x   w   covered"

where NC stands for “not covered”. The write operation must also determine a new timestamp, for the new write. Given that the new write is to be inserted immediately after the write operation w, the timestamp must be greater than but smaller than the timestamp of other writes on after . Thus, we obtain a new timestamp using:

 "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 to , to and both and to , where:

Thus, adds the new write w’ to the set of writes of . The new sets the value for w’ to v and the rel field to b (which is iff the new write w’ is releasing). Finally, updates tview of t for variable (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

 WrX :: "L  V  T  Cstate  Cstate" where
  "WrX x v t  =
      (let w = getVWNC  t x ; ts' = getTS  w in
                  write_trans t False w v  ts')"
 WrR :: "L  V  T  Cstate  Cstate" where
  "WrR x v t  =
      (let w = getVWNC  t x ; ts' = getTS  w in
                  write_trans 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 to , to , and to , and both and to , where

Thus, adds the new write w’ corresponding to the update to the set of writes of and adds the write w that w’ reads from to the covered writes set of . The new sets the value for w’ to v and sets the rel field to True. Finally, 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:

  Upd :: "L  V  T  Cstate  (Cstate  V)" where
"Upd x v t  =  (let w = getVWNC  t x ; ts = getTS  w ;
                                       v = value  w in
                                              (update_trans 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.

  "writes_on  x  {w. var w = x  w  writes }" 
  "lastWr  x  (x, Max (tst`(writes_on  x)))"
                  ( t x. tview  t x  writes_on  x) 
                  ( w x. mview  w x  writes_on  x) 
                  ( x. finite(writes_on  x)) 
                  ( w. w  writes   mview  w (var w) = w) 
                  ( w x. w = lastWr  x  w  covered )"

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.

5 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.

5.1 Load Buffering

1 2  x :: L   y :: L 3 4 lb_state = 5  r1 :: V 6  r2 :: V 7   :: Cstate 8 9||- 10 r1 = 0 r2 = 0 11   [x = 0] [x = 0] 12   [y = 0] [y = 0] 13COBEGIN 14   [y = 0] r2 = 0 15  < r1  x  > ;; 16   [y = 0] r2 = 0 17  < y := 1  > 18   r1 = 0 r2 = 0 19|| 20   [x = 0] r1 = 0 21  < r2  y  > ;; 22   [x = 0] r1 = 0 23  < x := 1  > 24   r1 = 0 r2 = 0 25COEND 26 r1 = 0 r2 = 0
Figure 7: Isabelle encoding of the load-buffering litmus test.
1 2 d :: L     f :: L 3 4 mp_state = 5 r1 :: V    r2 :: V    ::Cstate 6 7||- 8 [f = 0] [f = 0] 9   [d = 0] [d = 0] 10COBEGIN 11     [f 1] [d = 0] 12  <d := 5 > ;; 13     [f 1] [d = 5] 14  <f := 1 > 15     True 16|| 17  DO  [f = 1]d =