Owicki–Gries Logic for Timestamp Semantics
Abstract
Whereas an extension with non-interference of Hoare logic for sequential programs Owicki–Gries logic ensures the correctness of concurrent programs on strict consistency, it is unsound to weak memory models adopted by modern computer architectures and specifications of programming languages. This paper proposes a novel non-interference notion and provides concurrent program logic sound to timestamp semantics corresponding to a weak memory model that allows delays in the effects of store instructions. This paper reports three theoretically interesting techniques for modifying non-interference to support delays in the effects of store instructions. The techniques contribute to a better understanding of constructing concurrent program logic.
Keywords: program logic, weak memory model, non-interference, timestamp, vector clock, auxiliary variable
1 Introduction
Modern computer architectures have multiple cores. Because using multiple cores is significant in computational performance, we cannot ignore weak memory models. On a weak memory model, delays in the effects of store instructions are allowed. For example, let us consider the example program called Store Buffering:
where and are shared variables and and are thread-local variables.
On the strongest memory model called strict consistency, does not hold finally. If holds, is executed earlier than . It implies that is executed earlier than and therefore holds, and vice versa. On a weak memory model that allows delays in the effects of store instructions, is allowed.
Because the example program has just six execution traces on strict consistency, the so-called exhaustive search enables us to confirm that does not finally hold. Even if we cannot assume strict consistency, model checking with weak memory models is still a promising verification method (e.g., [1, 10]). However, we would like to focus on program logic sound to weak memory models in this paper.
Owicki–Gries logic is well-known to be sound to the standard state semantics based on strict consistency [15]. The logic is an extension for concurrent programs of Hoare logic for sequential programs [7]. Owicki and Gries invented the notion of non-interference to repair the unsoundness of the parallel composition rule without any side condition. They also invented the notion of auxiliary variables to enhance the provability of their logic.
Because Owicki–Gries logic fits strict consistency, it is too strong to be sound to weak memory models. In this paper, we modify the non-interference and propose program logic sound to a weak memory model that allows delays in the effects of store instructions.
In this paper, we adopt timestamp semantics as a weak memory model. Timestamp semantics are obtained by extending messages on memory to messages with timestamps and letting threads have the so-called vector clock, which has one clock at each shared variable [12, 14]. It is known that it expresses delays in the effects of store instructions. For example, because the store instructions that occur in Store Buffering send the messages and with timestamp to the memory as follows:
the load instructions read the messages and with timestamp . Therefore, is allowed to finally hold. On the other hand, in a similar example program called Coherence:
is not allowed to finally hold because the memory requires a total order of messages with timestamps for the same shared variable.
In this paper, we modify the non-interference in Owicki–Gries logic and construct concurrent program logic sound to timestamp semantics. The key idea is to formalize different observations of shared variables by threads. Also, this study provides three theoretically interesting techniques clarified by the novel non-interference. The author believes that broadly sharing the techniques contributes to a better understanding of constructing logic. The techniques are explained in detail in Section 4 and clearly described in Section 6 that concludes the paper.
2 Owicki–Gries Logic
We introduce notation for describing the standard state semantics for concurrent programs and remember Owicki–Gries logic, which is sound to the semantics.
Expression and sequential program are defined as follows:
where and denote a numeral and a variable, respectively. Expressions denoting arithmetic operations are appropriately defined.
In this paper, we do not adopt the so-called fork-join paradigm for parallel executions but fix the number of threads to . A concurrent program is denoted by
We also omit conditional and loop statements for the simplicity of the presentation.
State is a function from variables to numerals in a standard manner. Configurations consist of pairs of programs and states. We define operational semantics as follows:
where means the interpretation by that is defined as follows:
and the update function is defined as follows:
To describe properties that concurrent programs enjoy, we define an assertion language as follows:
We use abbreviations , , and in the standard manner.
We interpret by as follows:
Owicki and Gries provided logic that is sound to the state semantics [15]. Owicki–Gries logic consists of the so-called Hoare triples. A triple means that an execution of the program under the pre-condition implies that the post-condition holds.
The following is a subset of their inference rules:
Formula in the assignment axiom denotes the formula obtained by replacing that occurs in by . Symbol occurring in the third inference rule denotes the standard satisfaction relation as seen in the first-order predicate logic. If there exists a derivation tree of the root , we write .
Owicki and Gries defined that does not interfere with if and hold for any in and in . In this paper, we write for , for short, and call that is stable under . We call s non-interference if does not interfere with for any .
In a standard manner, we define if and implies for any and . Similarly, we define . The logic is sound to the state semantics, as shown in Figure 1.
Let us see an example derivation. In this paper, we assume that initial values of variables are . Figure 2 shows a derivation of Coherence for ensuring the property . In this paper, we write and for shared variables and write for a thread-local variable.
Notably, the shared variable acts as an intermediary between and and ensures finally as shown in the derivation. Such variables often enable us to describe assertions.
We checked all the derivations described in this paper by the Boogie program verifier [4].
The provability of the logic still needs to be improved. Figure 3 shows the concurrent program and a derivation for ensuring that finally holds. A reason that we can construct the derivation is that the assertions have sufficient information about the execution traces of the program. Specifically, the value of tells us how far the program is executed.
On the other hand, the value of in the parallel composition does not tell us. Therefore, we cannot add appropriate assertions that do not interfere with each other to the as-is program.
Owicki and Gries invented auxiliary variables, which are write-only in programs, atomically updated with existing assignments, described in assertions, and removed from programs later. Figure 4 shows a derivation for ensuring that finally holds. The auxiliary variables and enable us to describe the assertions.
Theorem 1 ([15]).
implies .
Figure 5 shows a derivation of Store Buffering with auxiliary variables.
3 Timestamp Semantics
We formally define timestamp semantics.
In the following, we formally distinguish thread-local variables from shared variables . We also divide assignments into three kinds, thread-local, load, and store instructions as follows:
and each assignment has a thread identifier denoting the thread that executes the assignment. A sequentially compound statement contains a unique thread identifier.
On timestamp semantics, thread-local states are separated from the shared memory . Thread-local state is a function from thread-local variables to numerals. We define the interpretations of expressions by as follows:
. |
The shared memory possesses messages with timestamps , differently from physical memories that we know. In this paper, timestamps are rationals, i.e., elements of . It is known that a set of timestamps should be dense (e.g. [9]). The shared memory is a function from pairs of shared variables and timestamps to numerals. Notably, is a function; a shared memory cannot possess multiple messages for the same shared variable and the same timestamp.
Each thread has a vector clock [12, 14]. A vector clock takes a shared variable and returns a timestamp. Differences between vector clocks that threads have express observations of shared variables by threads.
Thread-local configurations consist of triples of programs, thread-local states, and vector clocks. Thread-local configurations with memory consist of pairs of thread-local configurations and memories. Global Configurations consist of pairs of thread-local configurations and memories where takes a thread identifier and returns a configuration . Operational semantics are as shown in Figure 6.
No explanation for thread-local assignments is needed. An assignment of a load instruction takes any message in the memory whose timestamp is equal to or larger than the timestamp that the vector clock indicates. Furthermore, it updates the vector clock. An assignment of a store instruction sends a message whose timestamp is strictly larger than the timestamp that the vector clock indicates, and updates the vector clock. Timestamp semantics express a modern memory model on which effects of store instructions may be delayed (e.g. [9]).
4 Observation-Based Logic
In this section, we provide concurrent program logic for timestamp semantics.
4.1 Formal system
We introduce observation variable , which denotes observed by thread , for any shared variable . Whereas the notion of observation variables is previously provided by the author [2], we provide another logic in this paper.
The motivation for the introduction of observation variables is straightforward. In the standard state semantics, all variables are synchronous; if a thread updates the value of a variable, then other threads can immediately see the update. Variables that occur in assertions are also synchronous in stability checking. However, in timestamp semantics, that is not the case. Nevertheless, variables that occur in assertions in logic should be synchronous because stability checking is designed that way.
Observation variables fix the gap. Observation variables are synchronous in assertions in logic, that is, if a thread updates the value of an observation variable, then other threads can immediately see the update. Surely, a relation between observation variables and is not a relation between distinct shared variables and . We clarify and formalize the relation between observation variables in logic.
We define a satisfaction relation that configurations enjoy assertions. For convenience, we write for the thread-local states of , that is, denotes the thread-local state of . We also write for the vector clocks of , that is, denotes the vector clock of . We define as follows:
We define if and implies for any and . Similarly, we define .
We also introduce timestamp variable for any shared variable . We describe how to use timestamp variables with the explanation of non-observation-interference described later. Timestamp variables in assertions are interpreted as thread-local variables. The following are inference rules consisting of extended judgments:
Because assignments are divided into three kinds, the assignment axiom is also divided into three. Assignments of load instructions by thread replace thread-local variables with . Careful readers may wonder if the assignment axiom for load instructions is unconditionally unsound. The unsoundness is remedied by the non-observation-interference, as explained in detail in the following subsection.
The assignment axiom for store instructions is sound because we introduced observation variables to make it so. For example,
is valid because updates the vector clock of thread .
Sequence consists of reverse lists of timestamp variables at each shared variable. An initial timestamp sequence has exactly one timestamp variable for any shared variable. A timestamp variable is freshly generated at an assignment axiom of a store instruction to and is added to the end at of the timestamp sequence.
To ensure a total order of timestamps of each shared variable, we assume for any shared variable and in . To ensure a total order of timestamps of each shared variable on memory, we assume for any shared variable and any distinct and .
If there exists a derivation tree of the root in the system, we write . Similarly, we define .
4.2 Non-observation-interference
Before providing the formal definition of non-observation-interference, we explain its intuition. For the simplicity, we often omit reverse lists of timestamp variables in judgments if they are clear from the context.
First, it appears that is unsound to timestamp semantics because the value of at a timestamp , which may not be pointed by the vector clock satisfying , is loaded. That makes us want to add, for example, a necessity modality operator meaning “always true” to in the pre-condition. Alternatively, one may want to extend the assertion language with control predicates and modify the interpretation of Hoare triples [13]. However, we do not adopt them in this paper.
If the number of threads is , the judgment is still sound because the memory has no message from another thread. Soundness of is stained by a store instruction by another thread . It is reminiscent of the non-interference.
Let in . The store instruction may not be simultaneously executed with . Therefore, for any pre-condition that is sequentially composed on or before the , we have to check that the effect of to the memory does not interfere for any , which denotes arbitrary values of that are constrained by . For example, for under , we have to consider the judgments about , , and .
How do we check whether the effect of to the memory interferes or not? A load instruction updates the vector clock of , therefore, , and . We divide it into two phases. If the updates of the vector clock and are finished, the update of is sound. The unsoundness of the assignment axiom for load instructions is derived from updates of vector clocks.
Here, we do a trick. By replacing thread identifier of the store instruction with thread identifier , we can update the vector clock of thread identifier . That is, for any that is a pre/post-condition on or after the , we check instead of any judgment consisting of . Notably, our assertion language cannot directly refer to vector clocks. We notice updates of vector clocks through updates of observation variables.
Because judgments of store instructions are as-is sound, it is sufficient to check . The following:
is not derivable because is not derivable. Thus, we reuse the notion of non-interference for soundness of judgments of load instructions.
Without non-(observation-)interference | assignment axiom | parallel composition rule | |
---|---|---|---|
load | store | ||
Owicki–Gries logic | |||
Our logic ( thread) | N/A | ||
Our logic ( threads) | |||
With non-(observation-)interference | assignment axiom | parallel composition rule | |
load | store | ||
Owicki–Gries logic | |||
Our logic ( thread) | N/A | ||
Our logic ( threads) |
The assignment axiom in Owicki–Gries logic is sound to the standard state semantics, but the parallel composition rule without any side condition is unsound. Therefore, Owicki and Gries introduced the notion of non-interference. The assignment axiom becomes unsound in our logic to timestamp semantics if another thread executes a store instruction. Non-observation-interference adopted by our logic plays a role in keeping the assignment axiom for load instructions sound to timestamp semantics. Table 1 summarizes it.
Next, we explain the extension of non-interference in our logic. If an interfering instruction is , we have to consider no issue in addition to non-interference. If an interfering instruction is , we also have to consider no issue because in the assertion possessed by thread is synchronous and stability checking is the same as that in Owicki–Gries logic, as explained in the beginning of Section 4.
If an interfering instruction is , we have to take care of that updates not only but also in and . Let be a possibly interfered judgment. We have to confirm and . Similar to the discussion about the soundness of judgments of load instructions, we have to take care of that and are not sufficient because they are not ensured not to be interfered with any store instruction by another thread.
Thus, we have constructed logic that is sound to timestamp semantics. However, the provability still needs to be improved. Non-interference utilizes the pre-condition of the interfering judgment, but it is impossible because the effect of the store instruction may be delayed on timestamp semantics. It weakens the expressive power of assertion language. Therefore, we cannot delicately deal with execution traces; specifically, we cannot show the correctness of Coherence under timestamp semantics because how to grasp the difference between Store Buffering and Coherence, a total order of timestamps at each variable on memory, has not been explained yet.
We have introduced timestamp variables. Freshness in assignment axioms of store instructions and non-logical axioms ensure total orders at shared variables on memory. The variables are not updated by assignments. We can describe assertions. For any store instruction, a fresh timestamp variable is defined and added to the end of the sequence at each variable. It enables us to describe how far the program runs. Timestamp variables are also used to restrict ranges that instructions interfere in the definition of non-observation-interference. Whereas auxiliary variables with non-interference in Owicki–Gries logic do not restrict ranges that instructions interfere with, pre-conditions that interfering instructions have actually restricted ranges of interfered conditions. In our logic, where we cannot assume pre-conditions for interfering instructions, timestamp variables directly restrict ranges of instructions that interfere with each other.
Finally, we formally define non-observation-interference. We call s () non-observation-interference if for any , , and such that and hold,
-
•
holds for any in , in , a pre-condition that is sequentially composed on or before the in , , and a pre/post-condition that is sequentially composed on or after the in ,
-
•
and hold for any in and in ,
-
•
and hold for any in and in , and
-
•
and hold for any in and in , moreover,
-
–
and hold for any that is sequentially composed on or before the in , a pre-condition that is sequentially composed on or before the in , a pre/post-condition that is sequentially composed on or after the and on or before in , and
-
–
and hold for any in , a pre-condition that is sequentially composed on or before the in , , and a pre/post-condition that is sequentially composed on or after the in
-
–
where we write for , for short.
Theorem 2.
implies .
invariant: where and are freshly generated at and in the judgments, respectively.
5 Related Work and Discussion
Some logics handling dataracy programs are sound to semantics on which the effects of store instructions may be delayed [16, 11, 2, 5].
Dalvandi et al. provided Owicki–Gries logic for a C11 fragment memory model [5]. They also adopted timestamp semantics. However, they introduced multiple relations based on observations by thread to their assertion language. Each assignment has multiple axioms since their assertion language has multiple relations based on observations by threads. In this paper, we aim to construct theoretically simple logic. We adopted variables based on observations by threads. Each assignment has exactly one axiom.
Lahav and Vafeiadis proposed Owicki–Gries style logic for semantics by execution graphs [11], more axiomatic semantics than operational timestamp semantics. Their logic has high provability. They succeeded in providing derivations to ensure the correctness of Store Buffering with memory fences and a variant of Coherence. They also modified stability checking to ensure the correctness of concurrent programs on a weak memory model, as we did in this paper. Their logic differs from ours because they are constructed without observation and timestamp variables.
An unsatisfactory point is that it is necessary to check judgments of the form in their logic. Specifically, the logic requires to find satisfiable values under the semantics when checking stability. This means that it is necessary to consider models when providing derivations. Whereas the author provided logic using observation variables [2], the logic also assumes the use of external knowledge called observation invariants.
Thus, semantics on which effects of store instructions often disturb us to construct a pure logic in which assertions are added to programs in the Floyd style [6] are syntactically operated. In our logic, it occurs as the arbitrariness of the values of store instructions in the definition of non-observation-interference as described in Section 4. This study is one of the challenges to finding a purely syntactical logic for weak memory models.
The author proposed observation variables in the previous work [2]. However, observation variables are almost regarded as shared variables in the logic. Relations between observation variables strongly depend on observation invariants, which are assumed to be externally given. In this paper, observation variables are explicitly handled in the definition of non-observation-interference. This work is positioned as a detailed investigation of shared variables observed by threads.
6 Conclusion and Future Work
In this paper, we provide Owicki–Gries style logic, which uses the notion of non-interference for timestamp semantics. It theoretically contributes to show techniques that 1)we used the notion of non-interference for ensuring soundness of assignment axiom, 2)we replaced thread identifiers for an update of a vector clock on timestamp semantics, and 3)we introduced timestamp variables in order to enhance the expressiveness of the assertion language.
This study has a lot left to do. In this paper, we do not refer to completeness to timestamp semantics. We do not confirm the minimality of assertions that occur in the derivations in this paper. Above all else, Owicki–Gries logic using non-interference is not compositional; the depths of derivations combinatorial increase the number of checking judgments. It means that Owicki–Gries logic and our logic could be more practical. Jones proposed a compositional logic using rely/guarantee reasoning [8]. Lahav and Vafeiadis tried to make their logic compositional using the rely/guarantee notion. However, because their “rely” and “guarantee” are not single assertions but finite sets of assertions [11], it is hard to say that combinatorial explosion is wholly avoided. Enriching the assertion language using the next variables (e.g. [17]) is promising. Our previous study also proposed a compositional logic using observation variables [2], so to speak, observation-based Jones logic. However, external knowledge is required, called observation invariants, as described in Section 5. Thus, it is challenging to construct compositional logic for weak memory models. We try to construct a compositional logic using observation variables by utilizing the knowledge obtained in this study.
This study considered store buffering only. It is not easy to consider load buffering. Whereas we previously proposed concurrent program logic sound and complete to semantics that support reordering of load instructions [3], the semantics support reordering of load instructions that are statically determined to be independent. For a weaker memory model, Kang et al. proposed a novel notion called promise for supporting load buffering on timestamp semantics [9]. For example, in the following program:
assertion always holds on timestamp semantics. However, is allowed on timestamp semantics with promise. We try to combine the notion of promise with our logic and construct a novel logic to ensure the correctness of concurrent programs on semantics that allow load buffering.
Acknowledgments. This work was supported by JSPS KAKENHI Grant Number JP23K11051.
References
- [1] P. A. Abdulla, M. F. Atig, B. Jonsson, and C. Leonardsson. Stateless model checking for POWER. In Proc. CAV, volume 9780 of LNCS, pages 134–156, 2016.
- [2] T. Abe and T. Maeda. Observation-based concurrent program logic for relaxed memory consistency models. In Proc. APLAS, volume 10017 of LNCS, pages 63–84, 2016.
- [3] T. Abe and T. Maeda. Concurrent program logic for relaxed memory consistency models with dependencies across loop iterations. Journal of Information Processing, 25:244–255, 2017.
- [4] M. Barnett, B.-Y. E. Chang, R. DeLine, B. Jacobs, and K. R. M. Leino. Boogie: A modular reusable verifier for object-oriented programs. In Proc. FMCO, volume 4111 of LNCS, pages 364–387, 2005.
- [5] S. Dalvandi, S. Doherty, B. Dongol, and H. Wehrheim. Owicki–Gries reasoning for C11 RAR. In Proc. ECOOP, 2020.
- [6] R. W. Floyd. Assigning meanings to programs. In Proc. Symposia in Applied Mathematics, volume 19, pages 19–32, 1967.
- [7] C. A. R. Hoare. An axiomatic basis for computer programming. CACM, 12(10):576–580, 583, 1969.
- [8] C. B. Jones. Development Methods for Computer Programs Including a Notion of Interference. PhD thesis, Oxford University, 1981.
- [9] J. Kang, C.-K. Hur, O. Lahav, V. Vafeiadis, and D. Dreyer. A promising semantics for relaxed-memory concurrency. In Proc. POPL, pages 175–189, 2017.
- [10] M. Kokologiannakis and V. Vafeiadis. GenMC: A model checker for weak memory models. In Proc. CAV, volume 12759 of LNCS, pages 427–440, 2021.
- [11] O. Lahav and V. Vafeiadis. Owicki–Gries reasoning for weak memory models. In Proc. ICALP, volume 9135 of LNCS, pages 311–323, 2015.
- [12] L. Lamport. Time, clocks, and the ordering of events in a distributed system. CACM, 21(7):558–565, 1978.
- [13] L. Lamport. Control predicates are better than dummy variables for reasoning about program control. TOPLAS, 10(2):267–281, 1988.
- [14] F. Mattern. Virtual time and global states of distributed systems. In Parallel and Distributed Algorithms, pages 215–226. North-Holland, 1989.
- [15] S. S. Owicki and D. Gries. An axiomatic proof technique for parallel programs I. Acta Informatica, 6:319–340, 1976.
- [16] T. Ridge. A rely-guarantee proof system for x86-TSO. In Proc. VSTTE, volume 6217 of LNCS, pages 55–70, 2010.
- [17] Q. Xu, W. P. de Roever, and J. He. The rely-guarantee method for verifying shared variable concurrent programs. Formal Aspects of Computing, 9(2):149–174, 1997.