In this section, we formalize the technique illustrated informally above. We start by stating our program model and formally define the kind of bounds we consider:
Program model
Definition 1
(Program) Let \( LVar \) and \( SVar \) be finite disjoint sets of typed local and shared program variables, and let \( Var = LVar \cup SVar \). Let \( Val \) be a set of values. Program states \(\varSigma : Var \rightarrow Val \) over \( Var \) map variables to values. We write \(\sigma {\downharpoonright _{ Var '}}\) where \( Var ' \subseteq Var \) for the projection of a state \(\sigma \in \varSigma \) onto the variables in \( Var '\). Let \( GC = Guards \times Commands \) denote the set of guarded commands over \( Var \) and their effect be defined by \(\llbracket \cdot \rrbracket : GC \rightarrow \varSigma \rightarrow 2^\varSigma \cup \{\bot \}\) where \(\bot \) is a special error state. A program P over \( Var \) is a directed labeled graph \(P = (L,T,\ell _0)\), where L is a finite set of locations, \(\ell _0 \in L\) is the initial location, and \(T \subseteq L \times GC \times L\) is a finite set of transitions. Let S be a predicate over \( Var \) that is evaluated over program states. We overload \(\llbracket \cdot \rrbracket \) and write \(\llbracket S \rrbracket \subseteq \varSigma \) for the set of states satisfying S. We represent executions of P as sequences of steps \(r \in \varSigma \times T\times \varSigma \) and write \(\sigma \xrightarrow {t} \sigma '\) for a step \((\sigma ,t,\sigma ')\). A run of P from S is a sequence of steps \(\rho = \sigma _0 \xrightarrow {\ell _0, gc _0,\ell _1} \sigma _1 \xrightarrow {\ell _1, gc _1,\ell _2} \dots \) such that \(\sigma _0 \in \llbracket S \rrbracket \) and for all \(i \ge 0\) we have \(\sigma _{i+1} \in \llbracket gc _i \rrbracket (\sigma _i)\).
Definition 2
(Interleaving of programs) Let \(P_i = (L_i,T_i,\ell _{0,i})\) for \(i \in \{1,2\}\) be two programs over \( Var _i = LVar _i \cup SVar \) such that \( LVar _1 \cap LVar _2 = \emptyset \). Their interleaving \(P_1 \parallel P_2\) over \( Var _1 \cup Var _2\) is defined as the program
$$\begin{aligned} P_1 \parallel P_2 = (L_1 \times L_2, T, (\ell _{0,1},\ell _{0,2})) \end{aligned}$$
where T is given by \(((\ell _1,\ell _2), gc ,(\ell _1',\ell _2')) \in T\) iff \((\ell _1, gc ,\ell _1') \in T_1\) and \(\ell _2 = \ell _2'\) or \((\ell _2, gc ,\ell _2') \in T_2\) and \(\ell _1 = \ell _1'\).
Given a program P over local and shared variables \( Var = LVar \cup SVar \), we write \(\Vert _N P = P_1 \parallel \dots \parallel P_N\) where \(N \ge 1\) for the N-times interleaving of program P with itself, where \(P_i\) over \( Var _i\) is obtained from P by suitably renaming local variables such that \( LVar _1 \cap \dots \cap LVar _N =\emptyset \). Given a predicate S over \( Var \), we write \(\bigwedge _N S\) for the conjunction \(S_1 \wedge \dots \wedge S_N\) where \(S_i\) over \( Var _i\) is obtained by the same renaming.
Definition 3
(Expression) Let \( Var \) be a set of integer program variables. We denote by \({{\,\mathrm{Expr}\,}}( Var )\) the set of arithmetic expressions over \( Var \cup {\mathbb {Z}} \cup \{\infty \}\). The semantics function \(\llbracket \cdot \rrbracket :{{\,\mathrm{Expr}\,}}( Var ) \rightarrow \varSigma \rightarrow ({\mathbb {Z}} \cup \{\infty \})\) evaluates an expression in a given program state. We assume the usual expression semantics; in addition, \(a \circ \infty = \infty \) and \(a \le \infty \) for all \(a \in {\mathbb {Z}} \cup \{\infty \}\) and \(\circ \in \{+,\times \}\).
Definition 4
(Bound) Let \(P = (L,T,\ell _0)\) be a program over variables \( Var \), let \(t \in T\) be a transition of P, and \(\rho = \sigma _0 \xrightarrow {t_1} \sigma _1 \xrightarrow {t_2} \cdots \) be a run of P. We use \(\#(t,\rho ) \in {\mathbb {N}}_0 \cup \{\infty \}\) to denote the number of times transition t appears on run \(\rho \). An expression \(b \in {{\,\mathrm{Expr}\,}}( Var _{\mathbb {Z}})\) over integer program variables \( Var _{\mathbb {Z}} \subseteq Var \) is a bound for t on \(\rho \) iff \(\#(t,\rho ) \le \llbracket b \rrbracket (\sigma _0)\), i.e., if t appears at most b times on \(\rho \).
Given a program \(P = (L,T,\ell _0)\) and predicate S over local and shared variables \( Var = LVar \cup SVar \), our goal is to compute a function \({{\,\mathrm{Bound}\,}}:T \rightarrow {{\,\mathrm{Expr}\,}}( SVar _{\mathbb {Z}} \cup \{N\})\), such that for all transitions \(t \in T\) and all system sizes \(N \ge 1\), \({{\,\mathrm{Bound}\,}}(t)\) is a bound for t of \(P_1\) on all runs of \(\Vert _N P = P_1 \parallel \dots \parallel P_N\) from \(\bigwedge _N S = S_1 \wedge \dots \wedge S_N\). That is, \({{\,\mathrm{Bound}\,}}\) gives us the thread-specific bounds for transitions of \(P_1\). In Sect. 3.5, we explain how to obtain total bounds on \(\Vert _N P\) from that.
Extending rely-guarantee reasoning for bound analysis
To analyze the infinite family of programs \(\Vert _N P = P_1 \parallel \dots \parallel P_N\), we abstract \(P_1\)’s environment \(P_2 \parallel \dots \parallel P_N\): We define effect summaries which provide an abstract, thread-modular view of transitions by abstracting away local variables and program locations.
Definition 5
(Effect summary) Let \(\varSigma _S\) be a set of program states over shared variables \( SVar \). An effect summary \(A \subseteq \varSigma _S\times \varSigma _S\) over \( SVar \) is a binary relation over shared program states. Where convenient, we treat an effect summary A as a guarded command whose effect \(\llbracket A \rrbracket \) is exactly A.
Sound effect summaries over-approximate the state transitions of the program they abstract:
Definition 6
(Soundness of effect summaries) Let \(P = (L,T,\ell _0)\) be a program over local and shared variables \( Var = LVar \cup SVar \), and let S over \( Var \) be a predicate describing P’s initial states. We denote by \({{\,\mathrm{Effects}\,}}(P,S)\) the state transitions reachable by P from program location \(\ell _0\) and all initial states \(\sigma _0 \in \llbracket S \rrbracket \) when projected onto shared variables \( SVar \).
Let \(\mathcal {A}\) over \( SVar \) be a finite set of effect summaries, and let \(\mathcal {A}^*\) denote all sequentially composed programs of effect summaries in \(\mathcal {A}\) (its Kleene iteration). \(\mathcal {A}\) is sound for P from S if \({{\,\mathrm{Effects}\,}}(P \parallel \mathcal {A}^*,S) \subseteq {{\,\mathrm{Effects}\,}}(\mathcal {A}^*,S)\).
In Sect. 4.2 we show how to compute \(\mathcal {A}\) in a preliminary analysis step such that it over-approximates P (or \(P_1 \parallel P_2\)). We extend the above notion of soundness of effect summaries to parallel composition and the parameterized case in Lemma 1 and Corollary 1 below. Intuitively, if the effects of each individual program \(P_1, P_2, \dots \) interleaved with \(\mathcal {A}^*\) are included in effects of \(\mathcal {A}^*\), then so are the effects of their parallel composition. It is thus sufficient to check soundness for a finite number of programs and still obtain sound summaries of parameterized systems.
Lemma 1
Let P be a program over local and shared variables \( Var = LVar \cup SVar \) and let S be a predicate over \( Var \) describing its initial states. Let \(P_1, P_2, \dots , P_N\) be programs over variables \( Var _1, Var _2, \dots , Var _N\) obtained by renaming local variables in P such that \(P_1, P_2, \dots , P_N\) do not share local variables, i.e., \(\bigcap _{1\le i \le N} LVar _i = \emptyset \). Further, let \(S_1, S_2,\dots , S_N\) be predicates obtained from S using the same renaming. Let \(\mathcal {A}\) be a sound set of effect summaries for P from S.
If
$$\begin{aligned} \begin{aligned} {{\,\mathrm{Effects}\,}}(P_1 \parallel \mathcal {A}^{*}, S_1)&\subseteq {{\,\mathrm{Effects}\,}}(\mathcal {A}^{*}, S) \text { and }\\ {{\,\mathrm{Effects}\,}}(P_2 \parallel \mathcal {A}^{*}, S_2)&\subseteq {{\,\mathrm{Effects}\,}}(\mathcal {A}^{*},S), \end{aligned} \end{aligned}$$
then
$$\begin{aligned} {{\,\mathrm{Effects}\,}}\left( (P_1\parallel P_2) \parallel \mathcal {A}^{*}, S_1\wedge S_2\right) \subseteq {{\,\mathrm{Effects}\,}}(\mathcal {A}^{*},S). \end{aligned}$$
Corollary 1
In particular, if
$$\begin{aligned} {{\,\mathrm{Effects}\,}}(P \parallel \mathcal {A}^{*},S) \subseteq {{\,\mathrm{Effects}\,}}(\mathcal {A}^{*},S), \end{aligned}$$
then
$$\begin{aligned} {{\,\mathrm{Effects}\,}}\left( (\Vert _N P) \parallel \mathcal {A}^{*}\right) , S_1\wedge S_2\wedge \dots \wedge S_N) \subseteq {{\,\mathrm{Effects}\,}}(\mathcal {A}^{*}, S). \end{aligned}$$
Effect summaries are capable of expressing relies and guarantees in Jones’ RG reasoning (cf. Sect. 1.4). In the following, we extend this notion to bound analysis by equipping each effect summary with a bound expression. We call these extended interference specifications environment assertions:
Definition 7
(Environment assertion) Let \(\mathcal {A}= \{A_1, \dots , A_n\}\) be a finite set of effect summaries over shared variables \( SVar \). Let N be a symbolic parameter describing the number of threads in the system. An environment assertion \(\mathcal {E}_\mathcal {A}:\mathcal {A}\rightarrow {{\,\mathrm{Expr}\,}}( SVar \cup \{N\})\) over \(\mathcal {A}\) is a function that maps effect summaries to bound expressions over \( SVar \) and N. We omit \(\mathcal {A}\) from \(\mathcal {E}_\mathcal {A}\) wherever it is clear from the context.
We use sequences a of effect summaries to describe interference: Intuitively, the bound \(\mathcal {E}_\mathcal {A}(A)\) describes how often summary \(A \in \mathcal {A}\) is permissible in such a sequence. Finally, we define rely-guarantee quintuples over environment assertions as the specifications in our compositional proofs:
Definition 8
(Rely-guarantee quintuple) We abstract environment threads of interleaved programs as rely-guarantee quintuples (RG quintuples) of either form
where P and \(P_1 \parallel P_2\) are programs, S and \(S'\) are predicates such that \(\llbracket S \rrbracket \subseteq \varSigma \) are initial program states, and \(\llbracket S' \rrbracket \subseteq \varSigma \) are final program states, and rely \(\mathcal {R}\) and guarantees \(\mathcal {G}\) and \(\mathcal {G}_1, \mathcal {G}_2\) are environment assertions over a finite set of effect summaries \(\mathcal {A}\).
In particular, \(\mathcal {R}\) abstracts P’s or \(P_1\parallel P_2\)’s environment. The guarantees \(\mathcal {G}\) and \((\mathcal {G}_1,\mathcal {G}_2)\) allow us to express both thread-specific and total bounds on interleaved programs: The guarantee \(\mathcal {G}\) of quintuple
contains total bounds for \(P_1 \parallel P_2\), while the guarantees \(\mathcal {G}_1,\mathcal {G}_2\) of
contain the respective thread-specific bounds of threads \(P_1\) and \(P_2\).
Note that the relies and guarantees of a single RG quintuple are defined over the same set of effect summaries \(\mathcal {A}\). This is not a limitation: in case we had different sets of effect summaries \(\mathcal {A}\) and \(\mathcal {A}'\), we can always use their union \(\mathcal {A}\cup \mathcal {A}'\) and set the respective bounds to zero.
Remark 2
(Notation of environment assertions) We choose to write relies and guarantees as functions over \(\mathcal {A}\) as it simplifies notation throughout the paper. The reader may prefer to think of environment assertions \(\{ A_1 \mapsto b_1, \dots \}\) as sets of pairs of an effect summary and a bound \(\{(A_1, b_1), \dots \}\), in contrast to just a set of effect summaries \(\{ A_1,\dots \}\) as in Jones’ RG reasoning.
Trace semantics of rely-guarantee quintuples
We model executions of RG quintuples as traces, which abstract runs of the concrete system. This allows us to over-approximate bounds by considering the traces induced by RG quintuples.
Definition 9
(Trace) Let \(P = (L,T,\ell _0)\) be a program of form \(P_1\) or \(P_1 \parallel P_2\) where \(P_i = (L_i,T_i,\ell _{0,i})\). Further, let S be a predicate over local and shared variables \( Var = LVar \cup SVar \) and let \(\mathcal {A}\) be a finite sound set of effect summaries for P from S. We represent executions of P interleaved with effect summaries in \(\mathcal {A}\) as sequences of trace transitions \(\delta \in (L \times \varSigma ) \times (L\times \varSigma \cup \{\bot \}) \times \{1,2,\mathsf {e}\} \times \mathcal {A}\), where the first two components define the change in program location and state, the third component defines whether the transition was taken by program \(P_1\) (1), \(P_2\) (2), or the environment (\(\mathsf {e}\)), and the last component defines which effect summary encompasses the state change. For a trace transition \(\delta = ((\ell ,\sigma ),(\ell ',\sigma '),\alpha ,A)\), we write \((\ell ,\sigma )\xrightarrow {\alpha :A}(\ell ',\sigma ')\).
A trace \(\tau = (\ell _0,\sigma _0)\xrightarrow {\alpha _1:A_1}(\ell _1,\sigma _1)\xrightarrow {\alpha _2:A_2}\dots \) of program P starts in a pair \((\ell _0, \sigma _0)\) of initial program location and state, and is a (possibly empty) sequence of trace transitions. Let \(|\tau | \in \left( {\mathbb {N}}_0 \cup \{\infty \} \right) \) denote the number of transitions of \(\tau \). We define the set of traces of program P as the set \({{\,\mathrm{traces}\,}}(S,P)\) such that for all \(\tau \in {{\,\mathrm{traces}\,}}(S,P)\), we have \(\sigma _0\in \llbracket S \rrbracket \) and for trace \(\tau \)’s ith transition (\(0 < i \le |\tau |\)) it holds that either
-
\(\alpha _i = 1\), \((\ell _{i-1}, gc ,\ell _i) \in T_1\) for some \( gc \), \(\sigma _i \in \llbracket gc \rrbracket (\sigma _{i-1})\), and \((\sigma _{i-1}{\downharpoonright _{ SVar }},\sigma _i{\downharpoonright _{ SVar }}) \in A_i\), or
-
\(\alpha _i = 2\), \((\ell _{i-1}, gc ,\ell _i) \in T_2\) for some \( gc \), \(\sigma _i \in \llbracket gc \rrbracket (\sigma _{i-1})\), and \((\sigma _{i-1}{\downharpoonright _{ SVar }},\sigma _i{\downharpoonright _{ SVar }}) \in A_i\), or
-
\(\alpha _i = \mathsf {e}\), \(\ell _{i-1} = \ell _i\), \((\sigma _{i-1}{\downharpoonright _{ SVar }},\sigma _i{\downharpoonright _{ SVar }}) \in A_i\), and \(\sigma _{i-1}{\downharpoonright _{ LVar }}=\sigma _i{\downharpoonright _{ LVar }}\).
The projection \({\tau {\downharpoonright _{C}}}\) of a trace \(\tau \in {{\,\mathrm{traces}\,}}(S,P)\) to components \(C\subseteq \{1,2,\mathsf {e}\}\) is the sequence of effect summaries defined as image of \(\tau \) under the homomorphism that maps \(((\ell ,\sigma ),(\ell ',\sigma '),\alpha ,A)\) to A if \(\alpha \in C\), and otherwise to the empty word.
We now define the meaning of RG quintuples over traces. Given an environment assertion \(\mathcal {E}_\mathcal {A}\) over effect summaries \(\mathcal {A}\), interference by an action \(A\in \mathcal {A}\) is described by \(\mathcal {E}_\mathcal {A}(A)\), giving an upper bound on how often A can interfere:
Definition 10
(Validity) Let \(\mathcal {A}\) be a finite set of effect summaries over shared variables \( SVar \), let \(A \in \mathcal {A}\) be an effect summary, and let a be a finite or infinite word over effect summaries \(\mathcal {A}\). Let \(\mathcal {E}_\mathcal {A}\) be an environment assertion over \(\mathcal {A}\). Let \(\sigma \subseteq \varSigma _S\) be a program state over \( SVar \). We overload \(\#(A,a) \in {\mathbb {N}}_0 \cup \{\infty \}\) to denote the number of times A appears on a and define
$$\begin{aligned} a \models _\sigma \mathcal {E}_\mathcal {A}\text { iff } \#(A,a) \le \llbracket \mathcal {E}_\mathcal {A}(A)\rrbracket (\sigma ) \text { for all } A \in \mathcal {A}. \end{aligned}$$
We define
iff for all traces \(\tau \in {{\,\mathrm{traces}\,}}(S,P)\) such that \(\tau \) starts in state \(\sigma _0 \in \llbracket S \rrbracket \) and \({\tau {\downharpoonright _{\{\mathsf {e}\}}} \models _{\sigma _0} \mathcal {R}}\) (\(\tau \)’s environment transitions satisfy the rely):
-
if \(\tau \) is finite and ends in \((\ell ',\sigma ')\) for some \(\ell '\), then \(\sigma ' \ne \bot \) (the program is safe) and \(\sigma ' \in \llbracket S' \rrbracket \) (the program is correct), and
-
\({\tau {\downharpoonright _{\{1\}}} \models _{\sigma _0} \mathcal {G}}\) (\(\tau \)’s P-transitions satisfy the guarantee \(\mathcal {G}\)).
Similarly,
iff for all \(\tau \in {{\,\mathrm{traces}\,}}(S, P_1 \parallel P_2)\) s.t. \(\tau \) starts in \(\sigma _0 \in \llbracket S \rrbracket \) and \({\tau {\downharpoonright _{\{\mathsf {e}\}}} \models _{\sigma _0} \mathcal {R}}\):
-
if \(\tau \) is finite and ends in \((\ell ',\sigma ')\) for some \(\ell '\), then \(\sigma ' \ne \bot \) and \(\sigma ' \in \llbracket S' \rrbracket \), and
-
\({\tau {\downharpoonright _{\{1\}}} \models _{\sigma _0} \mathcal {G}_1}\) and \({\tau {\downharpoonright _{\{2\}}} \models _{\sigma _0} \mathcal {G}_2}\).
Proof rules for rely-guarantee bound analysis
Inspired by Jones’ proof rules for safety [28, 42] (cf. Fig. 1) and the rely-guarantee rules for liveness and termination in [15], we propose inference rules to facilitate reasoning about our bounded RG quintuples. First, we define the addition and multiplication environment assertions, as well as the subset relation over them:
Definition 11
(Operations and relations on environment assertions) Let \(\mathcal {A}\) be a finite set of effect summaries over shared variables \( SVar \), let \(A \in \mathcal {A}\) be an effect summary, and let \(\mathcal {E}_\mathcal {A}\) and \(\mathcal {E}'_\mathcal {A}\) be environment assertions over \(\mathcal {A}\). Let \(\sigma \subseteq \varSigma _S\) be a program state over \( SVar \). Let \(e \in {{\,\mathrm{Expr}\,}}( SVar )\) be an expression over \( SVar \). For all effect summaries \(A\in \mathcal {A}\) we define
$$\begin{aligned} (e \times \mathcal {E}_\mathcal {A})(A)&= e \times \mathcal {E}_\mathcal {A}(A), \text {and}\\ (\mathcal {E}_\mathcal {A}+ \mathcal {E}_\mathcal {A}')(A)&= \mathcal {E}_\mathcal {A}(A) + \mathcal {E}_\mathcal {A}'(A). \end{aligned}$$
Further, let S be a predicate over \( SVar \). We define
$$\begin{aligned} \mathcal {E}_\mathcal {A}\subseteq _S \mathcal {E}_\mathcal {A}' \text { iff } \llbracket \mathcal {E}_\mathcal {A}(A)\rrbracket (\sigma ) \le \llbracket \mathcal {E}_\mathcal {A}'(A)\rrbracket (\sigma ) \end{aligned}$$
for all \(A\in \mathcal {A}\) and all \(\sigma \in \llbracket S \rrbracket \).
Proof rules. The proof rules for our extended RG quintuples, using environment assumptions to specify interference, are shown in Fig. 3:
-
Par interleaves two threads \(P_1\) and \(P_2\) and expresses their thread-specific guarantees in \((\mathcal {G}_1,\mathcal {G}_2)\).
-
Par-Merge combines thread-specific guarantees \((\mathcal {G}_1,\mathcal {G}_2)\) into a total guarantee \(\mathcal {G}_1+\mathcal {G}_2\).
-
Conseq is similar to the consequence rule of Hoare logic or RG reasoning: it allows to strengthen precondition and rely, and to weaken postcondition and guarantee(s).
Keeping rules Par and Par-Merge separate is not only useful to express thread-specific bounds, but sometimes necessary to carry out the proofs below.
Leaf rules of the proof system. Note that our proof system comes without leaf rules. We offload the computation of correct guarantees \(\mathcal {G}\) from a given program P, a precondition S, and a rely \(\mathcal {R}\) to a bound analyzer (cf. Sect. 4.3). From this, we can immediately state valid RG quintuples
for sequential programs and use the rules from Fig. 3 only to infer guarantees on the parallel composition of programs.
Relation to Jones’ original RG rules. Note that our proof rules are a natural extension of Jones’ original RG rules (Fig. 1): If we replace set union \(\cup \) with addition of environment assumptions \(+\) (Definition 11) and the standard subset relation \(\subseteq \) with our overloaded one on environment assumptions (Definition 11), Jones’ rule J-Par equals the composed application of Par and Par-Merge, and Jones’ J-Conseq equals our Conseq rule.
Postconditions of RG quintuples. Although our proof rules allow to infer both bounds (in the guarantees) and safety (through the postconditions), in this work we focus on the former. We still write postconditions because our proof rules are sound even with them, and because this notation is already familiar to many readers. As postconditions aren’t relevant for inferring bounds in this work, they default to \(\mathsf {true}\) in the examples below.
Theorem 1
(Soundness) The rules in Fig. 3 are sound.
Proof
We give an intuition here and refer the reader to Appendix A for the full proof.
Proof sketch: We build on the trace semantics of Definition 9. For each rule Par, Par-Merge, Conseq we assume validity (Definition 10) of the rule’s premises. We then consider a trace \(\tau \) of the program in the conclusion, such that it satisfies the judgement’s precondition and rely (i.e., the premises of validity), and show that the trace also satisfies the judgement’s guarantee and postcondition.
-
For rule Par, we prove satisfaction of the guarantee by induction on the length of a trace \(\tau \in {{\,\mathrm{traces}\,}}(S_1\wedge S_2, P_1 \parallel P_2)\) and by case-splitting on the labeling of the last transition. Satisfaction of the postcondition follows from the individual threads’ satisfaction of their respective postconditions.
-
For rule Par-Merge, we relabel the transitions of \(\tau \) to discern between transitions of \(P_1\) and \(P_2\). Guarantee and postcondition then follow from the premises of the individual threads’ traces.
-
For rule Conseq, the properties are shown by following the chain of implications of assertions and inclusions of environment assertions in the premise.
\(\square \)
The proof rules in Fig. 3 together with procedure SynthG defined below allow us to compute rely-guarantee bounds for the parallel composition of a fixed number of threads.
Definition 12
(Synthesis of guarantees) Let \(\textsc {SynthG}(S,P,\mathcal {R})\) be a procedure that takes a predicate S, a non-interleaved program P, and a rely \(\mathcal {R}\) and computes a guarantee \(\mathcal {G}\), such that
holds. Further, let procedure SynthG be monotonically decreasing, i.e., for all predicates S and programs P, if \(\mathcal {R}' \subseteq \mathcal {R}\) then \(\textsc {SynthG}(S, P, \mathcal {R}') \subseteq \textsc {SynthG}(S, P, \mathcal {R})\).
For now, we assume that SynthG exists. We give an implementation in Sect. 4.3.
Running example
We show how to infer bounds for two threads \(P_1 \parallel P_2\) concurrently executing Treiber’s \({\mathtt {push}}\) method. Let \({\mathbf {0}} = (0,\dots ,0)\) denote the empty environment. Our goal is to find valid premises for rule Par (Fig. 3) to conclude
That is, in an otherwise empty environment (rely \(\mathcal {R}= {\mathbf {0}}\)), when run as \(P_1 \parallel P_2\), each thread has the bounds given in \(\mathcal {G}_1\) and \(\mathcal {G}_2\). Recall from Sect. 2.4 that \( Inv \) is a data structure invariant over shared variables. We assume its existence for now and describe its computation in Sect. 4.1.
Since \(\mathcal {R}\) is empty, the premises of rule Par become
Assuming a rely \(\mathcal {G}_2\) that soundly over-approximates \(P_2\) in an environment of \(P_1\), we can compute \(\mathcal {G}_1\) as \(\mathcal {G}_1 = \textsc {SynthG}( Inv ,P_1,\mathcal {G}_2)\). As the argument above is circular, the only sound assumption we can make at this point is to let
, i.e., assume that \(P_2\) interferes up to infinitely often on \(P_1\).
As we have argued in Sect. 2.4, this is enough to show
. From this,
follows by symmetry.
Note that we have obtained a refined guarantee
. We repeat the argument from above, and obtain
. Further repeating the argument does not further refine the bounds. Thus, by symmetry we have
and applying rule Par gives us thread-specific bounds for \(P_1\) and \(P_2\) in guarantees \(\mathcal {G}_1\) and \(\mathcal {G}_2\):
Extension to parameterized systems and automation
The proof rules given in Sect. 3.4 allow us to infer bounds for systems composed of a fixed number of threads. We now turn towards deriving bounds for parameterized systems, i.e., systems with a finite but unbounded number N of concurrent threads \(\Vert _N P = P_1 \parallel \dots \parallel P_N\).
To this end, we use the proof rules from Sect. 3.4 to derive the symmetry argument stated in Theorem 2 below: It allows us to switch the roles of reference thread and environment, i.e., to infer bounds on \(P_2\parallel \dots \parallel P_N\) in an environment of \(P_1\) from already computed bounds on \(P_1\) in an environment of \(P_2\parallel \dots \parallel P_N\).
Theorem 2
(Generalization of single-thread guarantees) Let P be a program over local and shared variables \( Var = LVar \cup SVar \) and let \(\Vert _N P = P_1 \parallel \dots \parallel P_N\) be its N-times interleaving. Let S be a predicate over \( SVar \). Let \(\mathcal {A}\) over \( SVar \) be a sound set of effect summaries for P started from S, and let \(\mathcal {R}\) and \(\mathcal {G}\) be environment assertions over \(\mathcal {A}\). Let \({\mathbf {0}} = (0,\dots ,0)\) denote the empty environment.
If
then
I.e., if \((N-1) \times \mathcal {G}\) is smaller than \(\mathcal {R}\), and if
holds, then in an empty environment, \(P_1\)’s environment \(P_2\parallel \dots \parallel P_N\) executes effect summaries \(\mathcal {A}\) no more than \((N-1)\times \mathcal {G}\) times.
Proof
We give an intuition here and refer the reader to Appendix B for the full proof.
Proof sketch: We prove the property by induction for k threads up to a total of N. The main idea is to keep the effect of these k threads, \(k \times \mathcal {G}\), in the guarantee, and the effect of the remaining \(N-k\) threads, \((N-k) \times \mathcal {G}\), in the rely. For the induction base (\(k=2\)), we apply rule Conseq to the premises of Theorem 2 and obtain the interleaved guarantees of the two threads using rule Par. In the induction step, we add a \((k+1)\)th thread using rule Par and merge the guarantees using Par-Merge. Finally, for \(k=N\) we get an empty environment \({\mathbf {0}}\) in the rely, and \(N \times \mathcal {G}\) in the guarantee. \(\square \)
Algorithm 1 shows our procedure for rely-guarantee bound computation of parameterized systems. It uses Theorem 2 and procedure SynthG (Definition 12) to compute the bound of a parameterized system \(P_1 \parallel (P_2\parallel \dots \parallel P_N)\) as the greatest fixed point of environment assertions ordered by \(\subseteq \). It alternates between
-
1.
computing a guarantee \(\mathcal {G}_1\) for \(P_1\) in
(Line 2), and
-
2.
inferring a guarantee \(\mathcal {G}_2\) for \(P_2 \parallel \dots \parallel P_N\) in
(Line 3).
Intuitively, if \(\mathcal {R}\) in step 1 overapproximates the effects of \(P_2 \parallel \dots \parallel P_N\), then \(\mathcal {G}_1\) is a valid guarantee for \(P_1\) in an environment of \(P_2 \parallel \dots \parallel P_N\). In step 2, our algorithm uses Theorem 2 to generalize this guarantee \(\mathcal {G}_1\) on \(P_1\) in an environment of \(P_2 \parallel \dots \parallel P_N\) to a guarantee \(\mathcal {G}_2\) on \(P_2 \parallel \dots \parallel P_N\) in an environment of \(P_1\). Theorem 3 below formalizes this argument.
Finally, if the algorithm reaches a fixed point, it returns the results of the analysis:
-
1.
Thread-specific bounds of \(P_1\) are directly returned as \(\mathcal {G}_1\).
-
2.
For total bounds of \(P_1 \parallel \dots \parallel P_N\), apply rule Par-Merge to \(\mathcal {G}_1\) and \(\mathcal {G}_2\) to sum up the guarantees of \(P_1\) and \(P_2 \parallel \dots \parallel P_N\).
Theorem 3
(Correctness and termination) Algorithm 1 is correct and terminates.
Proof
Correctness. By Definition 12, Line 2 computes \(\mathcal {G}_1\) such that
Assume that \((N-1) \times \mathcal {G}_1 \subseteq \mathcal {R}\). Then by Theorem 2, Line 3 computes \(\mathcal {G}_2 = (N-1)\times \mathcal {G}_1\) such that \(\mathcal {G}_2\) bounds \(P_2 \parallel \dots \parallel P_N\) in an environment of \(P_1\), i.e.,
It remains to show that \((N-1) \times \mathcal {G}_1 \subseteq \mathcal {R}\) holds at Line 3 of each iteration:
-
Initially, \(\mathcal {R}= (\infty ,\dots ,\infty )\) and thus trivially \((N-1) \times \mathcal {G}_1 \subseteq \mathcal {R}\).
-
For each subsequent iteration, let \(\mathcal {G}_1', \mathcal {G}_2', \mathcal {R}'\) refer to the variables’ evaluation in the previous iteration. We have \(\mathcal {R}= \mathcal {G}_2' = (N-1)\times \mathcal {G}_1' \subsetneq \mathcal {R}'\). Since by assumption SynthG is monotonically decreasing, from \(\mathcal {R}\subsetneq \mathcal {R}'\) we have \(\mathcal {G}_1 \subseteq \mathcal {G}_1'\) and thus \((N-1)\times \mathcal {G}_1 \subseteq \mathcal {R}\).
Termination. From the above, we have that the evaluations of \(\mathcal {G}_1\) (and \(\mathcal {G}_2, \mathcal {R}\), respectively) are strictly decreasing in each iteration. The lattice of environment assertions ordered by \(\subseteq \) is finite and bounded from below by the least element \((0,\dots ,0)\). Thus no infinitely descending chains of evaluations of \(\mathcal {G}_1\) exist and Algorithm 1 terminates. \(\square \)
Running example
Let us return to the task of computing bounds for N threads \(\Vert _N P = P_1 \parallel \dots \parallel P_N\) concurrently executing Treiber’s \({\mathtt {push}}\) method. Our method starts from the RG quintuple with unknown guarantee “?”
Recall from Sect. 2.4 that \( Inv \) is a data structure invariant over shared variables. We assume its existence for now and describe its computation in Sect. 4.1.
Algorithm 1 starts by computing a correct-by-construction guarantee for the RG quintuple in (5): It summarizes \(P_1\)’s environment \(P_2 \parallel \dots \parallel P_N\) in the rely \(\mathcal {R}\). At this point, it cannot safely assume any bounds on \(P_2 \parallel \dots \parallel P_N\), and thus on \(\mathcal {R}\). Therefore, it lets
(Line 1 of Algorithm 1), which amounts to stating the query from (5) above as
Next, Line 2 of Algorithm 1 runs the RG bound analysis procedure SynthG. As we have argued in Sect. 2.4, this yields
, i.e., we have
At this point, our method cannot establish tighter bounds for \(P_1\) unless it obtains tighter bounds for its environment \(P_2 \parallel \dots \parallel P_N\) and thus \(\mathcal {R}\). In Sect. 2.4, we informally argued that if
is a guarantee for \(P_1\), then
must be a guarantee for the \(N-1\) threads in \(P_1\)’s environment \(P_2 \parallel \dots \parallel P_N\). Line 3 of Algorithm 1 applies Theorem 2 to (7) and obtains
From the above, we have that
is a bound for \(P_1\)’s environment \(P_2 \parallel \dots \parallel P_N\) when run in parallel with \(P_1\). Going back to the RG quintuple (5), our technique refines the rely \(\mathcal {R}\), which models \(P_2 \parallel \dots \parallel P_N\), by letting
since this is a tighter bound than
, i.e.
(Lines 4–6 of Algorithm 1).
This means that we can refine our query for a guarantee from above to
iterating our fixed point search. This second iteration again runs SynthG, which returns
. Thus,
and by Theorem 2 we have
Another refinement of \(\mathcal {R}\) from \(\mathcal {G}_2\) and another run of SynthG gives
This time, the guarantee has not improved any further over the one in (10), i.e., our method has reached a fixed point and stops the iteration. Applying Theorem 2 gives
of which \((\mathcal {G}_1,\mathcal {G}_2)\) are returned as the algorithm’s result.
To compute thread-specific bounds for the transitions of \(P_1\), our method may stop here; the bounds can be read off \(\mathcal {G}_1\). For example, the fourth component of \(\mathcal {G}_1\) indicates that back edge \(\ell _3\rightarrow \ell _1\) is executed at most \(N-1\) times. Note that according to Remark 1 this gives an upper bound on the asymptotic time complexity of the corresponding loop.
To compute total bounds for the transitions of the whole interleaved system \(P_1 \parallel \dots \parallel P_N\), our technique simply applies rule Par-Merge, which gives
Again, bounds can be read off \(\mathcal {G}\), for example the second component indicates that transition \(\ell _1\rightarrow \ell _2\) is executed at most \(N^2\) times by all N threads in total.