In the previous section, we have defined the control flow of a statement S as a finite state machine whose states are encoded by the location variables of the statement. The control flow refers to the values of input, local, and output variables to determine the locations that are be reached at the next point of time. However, the control flow does not determine the values of output and local variables, which is done by the data flow that will be defined in this section.
We will define the data flow of a statement S in terms of guarded actions, i.e., pairs (γ, C) where C is an atomic statement that is executed if and only if the condition γ holds. Clearly, the only atomic statements C that modify the values of variables are the assignments, so that it would be sufficient to consider the assignments to define the data flow. However, to set up the proof goals for verification, we also have to compute the guards of the assume and assert statements. Hence, we have the following definition of guarded actions:
Definition 4.13 (Guarded Actions). A guarded action is a pair (γ, C), where
γis a boolean valued expression called the guard, and C is either an immediate assignment x=τ , a delayed assignment next(x)=τ , an assumption assume(σ), or an assertion assert(σ).
The intuition behind a guarded action (γ, C) is that the action C is executed if and only if the condition γ is satisfied. Guarded actions may be viewed as a single programming language like Unity [68] in that every guarded action runs as separate processes in parallel that observes the guard γ in each step, and executes C if the guard holds.
One might think that the set of guarded actions can be computed in a single pass as done for the control flow predicates of the previous section. In particular, one might wish to start with a precondition ϕ that holds when the (sub)statement is started and that is refined when the deeper nested substate- ments have to be started. However, we can not use the start condition as used in Definition 4.12 for this purpose, since the weak preemption statements make it necessary to distinguish between the start of the depth that allows the control flow to enter the statement and the start of the surface where the control flow will not enter the statement, but nevertheless some of its actions are enabled.
Hence, we might wish to start with the initial precondition st and to com- pute the guarded actions Actions (ϕ, S) of a statement S intuitively as follows (we neglect local declarations, since the definition below is already wrong for the mentioned statements):
• Actions (ϕ, x=τ ) :≡ {(ϕ, x=τ )}
• Actions (ϕ, next(x)=τ ) :≡ {(ϕ, next(x)=τ )}
• Actions (ϕ, assume(σ)) :≡ {(ϕ, assume(σ))}
• Actions (ϕ, assert(σ)) :≡ {(ϕ, assert(σ))}
• Actions (ϕ, nothing) :≡ {} • Actions (ϕ, ` : pause) :≡ {} • Actions (ϕ, if(σ) S1 else S2)
:≡ Actions (ϕ ∧ σ, S1) ∪ Actions (ϕ ∧ ¬σ, S2)
• Actions (ϕ, {S1; S2})
:≡ Actions (ϕ, S1) ∪ Actions (ϕ ∧ inst (S1) ∨ term (S1) , S2)
• Actions (ϕ, {S1k S2}) :≡ Actions (ϕ, S1) ∪ Actions (ϕ, S2)
• Actions (ϕ, do S while(σ)) :≡ Actions (ϕ ∨ σ ∧ term (S) , S)
• Actions (ϕ, during S1 do S2) :≡ Actions (in (S1) , S2) ∪ Actions (ϕ, S1)
• Actions (ϕ, abort S when(σ))
:= {(γ ∧ (in (S) → ¬σ), C) | (γ, C) ∈ Actions (ϕ, S)}
However, the above definition is already wrong for the definition of the strong abortion statement. However, before showing its deficiencies, let us advocate in favor of the definitions of the strong abortion. The idea of the above defini- tion is to compute the guarded actions Actions (ϕ, S) as if no abortion context
would exist. Then, we have to disable the actions whenever the abortion con- dition σ is false. However, a simple conjunction γ ∧ σ is not correct, since there are actions C that are executed in S before the control flow enters S. As the abortion is not an immediate one, we have to distinguish between those actions that are executed in the depth of the body S and those that are exe- cuted in the surface, i.e., when entering S from outside. For this reason, the conjunct in (S) → ¬σ is added to the guards which disables the actions when the control flow is inside and σ holds.
However, this definition is wrong which is demonstrated by the following example:
module RedoAbort(event i,&a,&b) { do abort emit a; `:pause; emit b; when(i) while(true) }
Assume that the control flow rests currently at the pause statement and that i holds. Then, b is not emitted (due to the strong abortion) and the strong abortion statement instantaneously terminates. Therefore, the loop body is re-entered, and the strong abortion statement is therefore also re-entered. While entering the abortion statement, the emission of a takes place, since the abortion is not an immediate one, and then the control stops again at the pause statement.
However, according to the above definition of Actions (st, S), we would obtain the two guarded actions ((st ∨ `) ∧ (` → ¬i), emit a) and (` ∧ (` → ¬i), emit b). Hence, if ` holds, the guarded actions are simplified to (¬i, emit a) and (¬i, emit b) which is wrong for the emission of a. The cor- rect guarded actions should be rather (st ∨ `, emit a) and (` ∧ ¬i, emit b).
So, what went wrong? The problem is that we have to distinguish for delayed preemption statements between those actions that are executed by entering their body statements and the remaining actions that are executed in the depth of the body statement. If we could present these actions in two different sets, then we can simple add the conjunct ¬σ to the guards of the actions of the depth, while the guards of the actions of the surface remain unchanged. For this reason, we have to separately compute the actions of the surface ActSurf~(ϕ, S)and the actions of the depth ActDepth (S).
Another reason for distinguishing between actions of the surface and the depth is that we have to distinguish between different scopes of locally de- clared variables. As we have explained in Section3.2, the local declaration of a variable can be left and re-entered several times within a macro step, and we have to distinguish the different reincarnations of the local variable. The
computation of the surface actions refers to one of these reincarnations, so that we have to add an incarnation level function ~ as a further argument that tells us the incarnation levels of the variables. Using ~, we can appropri- ately endow the variable occurrences with their incarnation indices, so that we can safely distinguish between the different scopes, when guarded actions of different surfaces are merged later on.
function DisableDelayed(L, ϕ, G)
G0:= {};
for every (γ, C) do case C of
next(x)=τ : if x ∈ L then G0:= G0∪ {(γ ∧ ¬ϕ, next(x)=τ )};
else G0 := G0∪ {(γ, next(x)=τ )}; end else G0 := G0∪ {C}; end end; return G0; end
Fig. 4.9. Disabling Delayed Local Assignments
Moreover, we have to make sure that no delayed action on x is exe- cuted in case that S is instantaneously executed. This is done by the func- tion DisableDelayed(L, ϕ, G) shown in Figure 4.9 that replaces each guard (γ, next(x)=τ ) ∈ G of a delayed assignment to a variable x ∈ L by (γ ∧ ¬ϕ, next(x)=τ ) ∈ G. Hence, the delayed assignments on x are disabled when- ever ϕ holds.
The definition of the guarded surface actions is then as follows, where LocVar (S)computes the set of locally declared variables in S:
Definition 4.14 (Guarded Actions of the Surface). Given a Quartz statement
S, an incarnation level function ~, and a precondition ϕ, the following recur- sively defined function computes the set of guarded surface actions:
• ActSurf~(ϕ, x=τ ) := {(ϕ, ~(x) = ~(τ ))}
• ActSurf~(ϕ, next(x)=τ ) := {(ϕ, next(x)=~(τ ))} • ActSurf~(ϕ, assume(σ)) := {(ϕ, assume(~(σ)))} • ActSurf~(ϕ, assert(σ)) := {(ϕ, assert(~(σ)))} • ActSurf~(ϕ, nothing) := {}
• ActSurf~(ϕ, `:pause) := {} • ActSurf~(ϕ, if(σ) S1 else S2)
:= ActSurf~(ϕ ∧ ~(σ), S1) ∪ ActSurf~(ϕ ∧ ¬~(σ), S2)
• ActSurf~(ϕ, {S1; S2}) := ActSurf~(ϕ, S1) ∪ ActSurf~(ϕ ∧ inst~(S1) , S2)
• ActSurf~(ϕ, do S while(σ)) := ActSurf~(ϕ, S)
• ActSurf~(ϕ, {α x; S}) := DisableDelayed({x}, inst~(S) , ActSurf~(ϕ, S)) • ActSurf~(ϕ, during S1 do S2) := ActSurf~(ϕ, S1)
• ActSurf~(ϕ, [weak] abort S when(σ)) := ActSurf~(ϕ, S)
• ActSurf~(ϕ, weak immediate abort S when(σ))
:= DisableDelayed(LocVar (S) , σ, ActSurf~(ϕ, S))
• ActSurf~(ϕ, immediate abort S when(σ)) := ActSurf~(ϕ ∧ ¬~(σ), S)
• ActSurf~(ϕ, [weak] suspend S when(σ)) := ActSurf~(ϕ, S)
• ActSurf~(ϕ, weak immediate suspend S when(σ)) := ActSurf~(ϕ, S)
• ActSurf~(ϕ, immediate suspend S when(σ)) := ActSurf~(ϕ ∧ ¬~(σ), S)
The above definition for the basic statements should be clear: we take the so-far computed precondition ϕ as the guard for the atomic action that has to be renamed according to the current scopes given by the incarnation level function ~. Note that in case of delayed assignments only the right hand side τ must be renamed, while the left hand side always refers to the depth. In general, we have to apply ~ to all expressions that are to be evaluated in the surface.
For the conditional statement, we simply add ~(σ) or its negation to the precondition to start the corresponding substatement. According to the se- mantics of a sequence, we first execute S1. If the execution of S1 is instan-
taneous, then we also execute S2 in the same macro step. Hence, the pre-
condition for the surface actions of S2 is ϕ ∧ inst~(S1). The preconditions of
the substatements of a parallel statement are simple the precondition of the parallel statement.
The do-while loop always enters its body statement, and therefore, we forward the precondition of the loop to the computation of the surface actions of the body statement. Note that the body statement is also entered when the body statement terminates, but that is another surface that will be computed with a potentially different incarnation level function.
The surface actions of a local declaration {α x;S} are simply the surface actions of its body S. Note that we do not increment the incarnation level function ~ here since the correct incarnation levels must be provided for the computation of the surface actions. This is done by the computation of the depth actions defined below. Note also that we disable all delayed assignments to the local variable x in case that S is instantaneous.
At starting time, the during statement only executes the surface of its first substatement S1. Therefore, the surface actions are only computed of S1.
As delayed preemptions are ignored at starting time of a delayed preemp- tion statement, we can ignore them for the computation of the surface actions. The same holds for the weak-immediate variants, since even if the preemp- tion takes place, the actions are still retained due to the weak preemption. However, there is the exception that we have to disable delayed actions on lo- cal variables when their scope is left by an abortion (regardless whether this
is weak or strong). Finally, the strong preemption statements are handled by adding the conjunct ¬~(σ) to the precondition ϕ.
It is easily seen by induction on the above definition that the following statements are valid:
• The guard γ of a guarded surface action (γ, C) only refers to the current values of the input, output, and local variables, where for the latter the incarnation levels as provided by ~ are chosen.
• The runtime to compute ActSurf~(ϕ, S)is O(|S|), since the recursive calls describe a single pass over the syntax tree of S. To this end, we prelimi- narily assume that the control flow predicates are already available. • Every guard γ of a guarded action (γ, C) ∈ ActSurf~(ϕ, S)implies ϕ. It remains to compute the remaining actions which are executed in the depth of the statement S. These actions are enabled by the resumption of the control flow that already rests somewhere in the statement. For this reason, we do not need a precondition as argument since the precondition is the active current control flow location itself.
For the same reason, we do not need an incarnation level function ~, since we start the resumption of the execution with the incarnation level function ~0 that maps all variables to 0. As the computation of the depth actions will
invoke further calls to surface actions, the computation of the depth actions will also compute an incarnation level function that can be forwarded to the computation of these surface actions.
Definition 4.15 (Guarded Actions of the Depth). Given a Quartz statement
S, the following recursively defined function ActDepth (S) computes a triple
(~, Ξ, A), where ~ is the incarnation level function that points to the scopes after
execution of the depth of S, Ξ holds the starting conditions of the loop surfaces contained in the depth of S, and A is the set of the guarded actions of the depth:
• ActDepth (S) := (~0, {}, {})for all atomic statements S
• ActDepth (if(σ) S1 else S2) := (Max(~1, ~2), Ξ1∪ Ξ2, A1∪ A2), where
(~i, Ξi, Ai) := ActDepth (Si)for i ∈ {1, 2}
• ActDepth ({S1; S2}) := (Max(~1, ~2), Ξ1∪ Ξ2, A1∪ A02∪ A2), where – (~1, Ξ1, A1) := ActDepth (S1)
– A0
2:= ActSurf~1(term (S1) , S2) – (~2, Ξ2, A2) := ActDepth (~2) S2
• ActDepth ({S1k S2}) := (Max(~1, ~2), Ξ1∪ Ξ2, A1∪ A2), where (~i, Ξi, Ai) := ActDepth (Si)for i ∈ {1, 2}
• ActDepth (do S while(σ)) := (~2, ξ ∪ Ξ, A1∪ A2), where – (~1, Ξ, A1) := ActDepth (S) – L := LocVar (S) – ~2(x) := ~1(x) : if x 6∈ L ~1(x) + 1 : if x ∈ L – A2:= ActSurf~2(σ ∧ term (S) , S), – ξ := {(L, ~2, σ ∧ term (S))}
• ActDepth ({α x;S}) := (~, Ξ, DisableDelayed(x, term (S) , A)),
where (~, Ξ, A) := ActDepth (S)
• ActDepth (during S1 do S2) := (~1, Ξ1, A1∪ A2), where – (~1, Ξ1, A1) := ActDepth (S1)
– A2:= ActSurf~(in (S1) , S2)
• ActDepth (weak [immediate] abort S when(σ))
:= DisableDelayed(LocVar (S) , σ, ActDepth (S))
• ActDepth ([immediate] abort S when(σ)) := (~1, Ξ1, A2), where – (~1, Ξ1, A1) := ActDepth (S)
– A2:= {(γ ∧ ¬σ, C) | (γ, C) ∈ A1}
• ActDepth (weak suspend S when(σ)) := ActDepth (S)
• ActDepth (suspend S when(σ)) := (~1, Ξ1, A2), where – (~1, Ξ1, A1) := ActDepth (S)
– A2:= {(γ ∧ ¬σ, C) | (γ, C) ∈ A1}
• ActDepth (`:weak immediate suspend S when(σ)) := (~1, Ξ1, A1∪ A2), where (~1, Ξ1, A1) := ActDepth (S)andA2:= ActSurf~0(`, S)
• ActDepth (`:immediate suspend S when(σ)) := (~1, Ξ1, A1∪ A2), where
– (~1, Ξ1, A1) := ActDepth (S)
– A2:= {(γ ∧ ¬σ, C) | (γ, C) ∈ ActSurf~0(`, S)}
Statements without control flow locations do not have depth actions. This explains the definitions of the atomic statements except for pause, which is however obvious.
As the control flow can rest in one of the branches of an if-statement, it can be resumed from any of these branches. We therefore simply take the ‘union’ of the two computations of the depth actions. For if-statements, sequences, and parallel statements, we merge again the obtained incarnation level func- tions. Note once more that this is not problematic, since the incrementations of the incarnation level function done in the separate substatements refer to the local variables of those substatements, and therefore, to different local variables.
Similarly, the control flow can rest in either one of the substatements S1or
S2of a sequence {S1;S2}, and hence, we can resume it from either S1or S2.
If the control flow is resumed from somewhere inside S1, and S1terminates,
then also the surface actions of S2are executed in the depth of the sequence.
Note that the computation of the depth of a sequence {S1;S2} leads to
the computation of the surface actions of S2. Even though this is a copy of
the surface of S2that occurs in the depth of {S1;S2} (the other copy occurs
in the surface of {S1;S2}), there is no need to increase the incarnation levels
in this copy. It is the case that this copy of the surface can be executed at the same macro step as the copy in the surface. However, then a surrounding loop is required and the incrementation of the incarnation levels is done there. We consider this discussion in more detail in Section5.1.
The parallel statement {S1k S2} can hold the control flow in each of its
substatements, and in contrast to sequences and if-statements, the control flow may even rest in both substatements at the same time. The computation of the depth actions is simply the ‘union’ of the depth actions of the two substatements.
The most interesting case is the computation of the depth actions of loops, since we have to handle the incarnation problem there: Clearly, we first com- pute the depth actions of the loop’s body statement. However, similar to se- quences, it may be the case (namely when the loop body terminates and the loop condition holds) that the body statement is re-entered. If the loop body contains a local variable declaration, then the surface of the loop body may contain a part of the local declaration while the depth contains another one. Here, we have to distinguish the two different scopes to avoid confusion with the schizophrenic local declarations. For this reason, the incarnation levels of all variables locally declared in S are incremented by one before computing the surface actions.
Note that the only case, where the incarnation level function is modified is in the computation of loops. The computation done there is moreover pes- simistic, since the worst case is considered where the scopes of all local vari- ables declared in S are suspected to be re-entered. Heuristics could be used here to reduce the number of reincarnations by limiting the incrementation of the incarnation levels to those variables whose scopes can really be re-entered by the loop.
Moreover, loops are the only statements where the additional result Ξ is modified. In this set, we store triples of the form (L, ~, ϕ) for every surface that
has been generated by a loop. This information is required for the construction of the data flow transition relation as described in the next section. To this end, it is necessary to know the preconditions ϕ of the surfaces generated by loops that are contained in the depth together with the reincarnated variables (that can be derived from L and ~).
The depth actions of a local declaration are simply the depth actions of the body statement. We do not have to worry about incarnation levels here, since we start in the depth of the local declaration. However, we have to disable all delayed actions that would otherwise be executed at termina- tion time of the local declaration. This is again done by the function call DisableDelayed(x, term (S) , A)) for the function defined in Figure4.9.
The second substatement of the during S1 do S2statement must always
be instantaneous. Therefore, the depth actions are only obtained by resuming the control flow from all possible locations inside S1. However, before resum-
ing the execution in S1, the semantics of the during statement is to execute
the instantaneous statement S2.
Weak preemption statements can be ignored for the computation of the depth actions, since even if the abortion takes place, all actions remain en- abled due to the weak preemption. However, similar to the surface actions, we have to disable all delayed actions on local variables since their scopes are left due to the abortion (regardless whether it is weak or strong).
For a strong abortion, we add a conjunct ¬σ to the guards of all actions to disable them in case σ holds. Note that we do not have to care about incarnation levels in σ since σ is here evaluated in the depth: Even if the body statement S enters some scopes of local declarations, these variables can not occur in σ, and local variables that are declared around the preemption statement can not be reincarnated by the resumption of the abortion’s body statement. The same explanations hold for the strong suspension statement.
The immediate versions of the suspension are a bit more difficult since the control flow could have been caught either inside the body statement or on the additional location of the immediate suspension. A weak immediate suspension can therefore execute both the surface and depth actions of its body statement. The strong variant must furthermore disable the guards when the suspension condition σ holds.
Similar to the computation of the surface actions, we can easily observe the following:
• The guard γ of a guarded depth action (γ, C) only refers to the current values of the input, output, and local variables, where for the latter the right incarnation levels are chosen.
• The runtime to compute ActDepth (S) is O(|S|2), since the recursive calls describe a single pass over the syntax tree of S, but in each call, it is pos- sible to invoke a computation of the surface which requires time O(|S|). Again, we preliminarily assume that the control flow predicates are al- ready available.