A denotational semantics for PROMELA addressing arbitrary jumps

PROMELA (Process Meta Language) is a high-level specification language designed for modeling interactions in distributed systems. PROMELA is used as the input language for the model checker SPIN (Simple Promela INterpreter). The main characteristics of PROMELA are non-determinism, process communication through synchronous as well as asynchronous channels, and the possibility to dynamically create instances of processes. In this paper, we introduce a bottom-up, fixpoint semantics that aims to model the behavior of PROMELA programs. This work is the first step towards a more ambitious goal where analysis and verification techniques based on abstract interpretation would be defined on top of such semantics.

READ FULL TEXT VIEW PDF

page 1

page 2

page 3

page 4

04/24/2018

Connectors meet Choreographies

We present Cho-Reo-graphies (CR), a new language model that unites two p...
04/02/2021

Fairness and Observed Communication Semantics for Session-Typed Languages

Observed communication semantics provide an intuitive notion of equivale...
09/25/2018

Skeletal Semantics and their Interpretations

Many meta-languages have been proposed for writing rule-based operationa...
10/05/2021

Deductive Verification of Programs with Underspecified Semantics by Model Extraction

We present a novel and well automatable approach to formal verification ...
08/09/2022

The Right Kind of Non-Determinism: Using Concurrency to Verify C Programs with Underspecified Semantics

We present a novel and well automatable approach to formal verification ...
07/09/2018

A Formal Approach to Open Multiparty Interactions

We present a process algebra aimed at describing interactions that are m...
02/05/2020

A Domain Semantics for Higher-Order Recursive Processes

The polarized SILL programming language uniformly integrates functional ...

1 Introduction

Concurrent programming has posed additional difficulties to the problem of guarantying correctness of programs. In fact, it may be almost impossible to replicate and debug an error caught by testing due to the fact that the different threads or processes can interact with each other at a different pace (depending on, for instance, processors speed). Not to mention the lack of coverage that usually testing techniques suffer.

Program verification is needed to ensure the correctness of a program in the case when, first, testing is simply not reliable enough due to a large number of possible inputs and, second, the consequences of failure are too costly in terms either of economic losses or of consequences that affect our lives.

In this context, PROMELA and SPIN ([9]) provide the tools for effectively model check ([3, 14]) the concurrent behavior of systems (specified in PROMELA). SPIN

incorporates powerful techniques to handle big (finite) search spaces, some of them based on approximations or on heuristic searches. However, no complementary techniques (different from model checking) for the formal verification of

PROMELA models have been proposed in the literature. A tool to check some of the classical equivalences defined for PROMELA is presented in [6].

This work constitutes the first step of a project that aims to provide an approach to verification of properties of PROMELA programs different from model checking. More specifically, we are interested in analysis and verification techniques based on approximation via abstract-interpretation techniques. Depending on the kind of technique to be used, denotational semantics with some properties can be more convenient than others. On top of the concrete inductive, fixpoint semantics defined in this work, it will be possible to define appropriate abstract semantics which implement specific analysis, for instance related to the temporal behavior of the system. The experience with the semantics proposed for the tccp language [5] showed that good results are obtained when abstraction is based on a concrete inductive, fixpoint semantics. To the best of our knowledge, in the literature, we only find proposals for (transitional) operational semantics for PROMELA.

The semantics defined in this paper works in two phases. Intuitively, at a process level we first collect for each process declaration a set of hypothetical traces corresponding to the set of behaviors independent from the context. As one can imagine, some of these traces may become impossible to travel given a context (a set of processes run in parallel). Then, in a second phase at a system level, the traces from the processes running in parallel are combined to obtain global traces. During this phase, some traces might be discarded (for instance, when a synchronous communication is requested but there is no process able for handshaking).

Due to the PROMELA language spirit, our plan is to focus on global (concurrent) properties, namely on defining analyses on top of the sets obtained at the system level. We note that it would be possible to define analyses for local processes (on top of the sets obtained after the first phase) for analyzing the behavior of local variables. However, these would be in general of less interest since, 1) PROMELA defines concurrency only at the level of the system (in contrast to other concurrency models, there is no parallel operator among the basic constructs), and 2) the language is strongly designed to precisely capture the concurrent behavior and abstract as much as possible the local behavior of processes (as a means to reduce the number of states to explore).

One of the purposes of the PROMELA language is to facilitate the modeling of concurrent state machines. This is why it includes the goto statement that is intensively used by programmers to implement jumps from source to target states. It is well known that dealing with arbitrary jumps constitutes a technical difficulty when defining a semantics by induction on syntax. In this paper, for the sake of conciseness, we build the process denotations based on the static graph provided by the process code.

This work is organized as follows. In the following section, we introduce the PROMELA language. sec:semanticsDomain is devoted to the presentation of the denotational domain. The formalization of the fixpoint denotational semantics and also some examples are shown in sec:denotational. Finally, we conclude by discussing some relevant properties of the semantics and describe our plans for future work.

2 Promela

PROMELA is a high-level specification language designed for modeling interactions in concurrent and distributed systems [9]. Its specification is given on the SPIN website111http://spinroot.com. Other important sources are [10, 2]. Given a system specified in PROMELA, SPIN can either simulate the system’s behavior or it can generate a C program that carries out the verification of the system against some specified correctness properties. These properties can be defined within the model or as temporal LTL properties ([13]). In order to achieve effectiveness, PROMELA’s core design philosophy is to limit the size of the state space of models. This is enforced by introducing some restrictions on the language and trying to create a model where the optimal model abstraction is desired (e.g., by avoiding both to underspecify and to overspecify the system). However, to consider only finite sized models is often not enough to overcome the state-explosion problem.

The emphasis of the language is not on computation details but on the modeling of process synchronization and coordination. Therefore, some common features of implementation languages such as input-output constructs or floating-point arithmetic are just not present in

PROMELA. In contrast, PROMELA is non-deterministic, allows for the dynamic creation of concurrent processes and processes can communicate via message channels that can be either synchronous or asynchronous using the CSP notation [8].

2.1 Syntax and informal operational semantics of Promela

For space reasons, we do not describe the syntax and operational semantics of PROMELA constructors exhaustively in this paper. Instead, we give an intuitive description of the main features of the PROMELA language by means of examples. As it will be clear in the following discussion, the syntax of the language is similar to C. A PROMELA program consists of a sequence of global declarations (types and variables), process declarations (proctype) and the (optional) init process used to initialize the system, when required.

1        bit f[2] = {0,0};
2        proctype P(bit id){
3          L0: if :: atomic{ f[id] = 1;
4          L1:        !f[1-id] } ;
5          L2:       skip; //critical section
6          L3:       f[id] = 0;
7          L4:       goto L0
8              fi }
9        init{ L5: atomic{ run P(0); L6: run P(1) } }
Figure 1: Example of PROMELA code

Consider as running example the PROMELA code given in fig:peterson. The program corresponds to a variant of the well-known first attempt to the Peterson’s algorithm [12] to solve the mutual exclusion problem for two processes. The algorithm contains two processes P(0) and P(1) which are trying continuously to enter in its critical section (CS). To avoid to violate the mutual exclusion property (that is, to ensure that always there is at most one process executing its critical section), each process uses a boolean flag (f[0] and f[1]). When a process wants to enter its CS, it changes its own boolean to true and waits for the other process to be outside its CS (its boolean is false). After executing the CS, each process sets its own boolean to false. Although this first attempt solves the mutual exclusion requirement, it contains many possible deadlocked executions.

As can be observed, the program starts by defining, in line:dec, a global array f with two components of the predefined type bit. Both components are initialized to false (0). Next, in Lines 1-1, the proctype P is declared. Proctypes are similar to C functions (they can contain local types, variables and executable code) except for, when invoked, their code is always executed in a new execution thread. When processes do not need initial values, PROMELA allows to directly declare and instantiate them placing the keyword active before proctype.

The init process in Line 1 acts as the main function in C. In our example it is used to create by means of statement run two instances of the proctype P, one with id=0 and the other with id=1. When processes are created, SPIN identifies them through its pid which is an integer different for each process instance.

The evolution of a PROMELA program proceeds by freely interleaving the instructions of the processes in execution. To completely understand this behavior is mandatory to introduce the notion of executability in PROMELA. SPIN selects an instruction to be next executed only if the instruction is an executable instruction of a process, that is, a statement that does not suspend. If no executable statement can be selected, the system blocks. Assignments (as f[id]=1 in line:atomic) and run statements are always executable. However, boolean expressions (as !f[1-id] in line:L1) are only executable when they are true. This is the natural synchronization mechanism based in shared memory implemented in PROMELA. A similar behavior occurs for instance when processes communicate using shared channels. The send or receive channel operations are only executable if the corresponding channels are not full or not empty, respectively.

The atomic statement in line:runs is used to avoid that the init process loses the execution control during the creation of the two new processes. When a statement inside an atomic statement is non-executable (as the boolean expression of line:L1 when it evaluates to false), SPIN breaks the atomic process execution to avoid the system deadlock. Thus, the atomic statement should be read as “execute the code in an atomic manner while it is possible”. An atomic statement is executable iff its first nested statement is. For instance, atomic in line:L0 is always executable since its first nested statement f[id] = 1 is an assignment that can be always be executed.

Channels are declared through variable declarations by using the chan type. The declaration chan c = [N] of bit,mtype defines a channel c of capacity N for storing N records composed of a bit and an mtype (enumerated types that users can freely define). The send and receive operations on channels are written using the CSP notation. For instance, c?x,y and c!1,m are operations on channel c defined above, where we are assuming that m is a value of type mtype, and x and y variables of appropriate type. If the capacity N is greater than zero, a buffered channel is created. Otherwise, a rendezvous channel (synchronous channel) is created. Rendezvous channels can pass messages only through handshake communication between sender and receiver and cannot store messages.

The control statements in PROMELA are do/od and if/fi, although do/od is not really needed since it can be simulated with if/fi and goto. These statements introduce the non-determinism in the language. An if/fi statement such as the one in Lines 1-1 of fig:peterson starts with the keyword if, ends with fi and contains one or more branches that begin with symbols :: (the instruction in the example has only one branch). The execution of an if statement consists in the execution of one of its branches. To do this, SPIN selects one of the executable branches non-deterministically. A branch is executable if its first instruction (the guard) is executable. If no branch is executable, then the if/fi instruction suspends. In the case of the if/fi statement of fig:peterson, as its guard (the atomic statement of line:L1) is always executable, the selection never suspends. It is also possible to add an unique ending else branch to statements if/fi. In this case, the if/fi never suspends, since when no branch is executable the else branch is selected.

As we observe in fig:peterson, PROMELA statements can be labeled using identifiers. For instance, L3 is the label of instruction f[id]=0.

Operational engine.

The official semantics of PROMELA is defined in terms of an operational model which contains one or more processes, zero or more variables, zero or more channels, and a semantics engine to handle the non-determinism (due to processes interleaving or to selection statements) and some internal variables such as exclusive (for atomic) or handshake (for rendezvous). Other read-only predefined variables handled by the semantic engine are _pid, which is local to each process and contains the corresponding process identifier, and _nr_pr which stores the number of active processes in the system.

The local control of each process is represented by a labeled transition system (LTS). Each LTS models the local behavior of a single process. The semantics engine runs the system in a stepwise manner: selecting and executing one basic transition from some LTS at the time. In brief, the behavior of the engine is as follows. While there exist executable (enabled) statements, it selects one of them to execute and updates the global state. There are some statements that might globally influence executability and that are handled by means of other internal variables. For instance, a side effect of the execution of any statement in an atomic block (except the last one) is to set a variable exclusive to the of the executing process, thus preserving the exclusive privilege to execute. We leave the rest of non-local constructs (such as timeout or unless) as future work.

In the model, there is no explicit characterization of time. The semantics engine keeps executing transitions until no executable one remains. This happens if either the number of active processes is zero, or when a deadlock occurs. The interested reader can consult [10] for the pseudo-code of this process.

3 The semantic domain

The definition of our denotational semantics is inspired by the semantics for the tccp language of [4]. In this work, denotations are based on so-called hypothetical computations; i.e., traces of conditional steps that ideally model actual computations that would be obtained by feeding them with initial system states that validate every condition.

The semantics domain for PROMELA essentially is formed by sequences of possible evolutions of the stored information (i.e., the system memory and the channels instances). As such, we first need to formalize how the information stored at each execution point is represented (sec:FDefinitions) and then we formalize the notion of traces of conditional evolutions (sec:condTraces) that are the basic constituent of our denotations.

3.1 The system state

The system state of a PROMELA program contains a representation of the memory and channel instances. Due to space limitations, we do not formally introduce the different components of the system (basic types, arrays, records and their associated operators) but we informally introduce on demand the needed notation. In the following, we use for elements that can be stored in memory and for the denotations of channels.

Definition 1 (System State)

A system state of a PROMELA program is an element of the domain where are locations of a memory capable to store denotable values , that are basic values, structures, and arrays222For all set , we write for the flat domain of values of with bottom ., and is the set of channel ids; is the set of all channel instances; denotes an erroneous (inconsistent) state.

In the sequel, we write as a typical element of . Moreover, for we write as a shorthand of or when . Analogously for .

As usual, the scope is managed by environments, which are not required to be part of the state since PROMELA has static scope.

Definition 2 (Environment)

An environment is a partial function associating program identifiers with locations or channel ids .

In the sequel, we write as a typical element of the set of .

3.2 The Domain of Denotations

Let us now define the domain upon which our denotations are built. Intuitively, each component in the semantics, called conditional trace aims to represent a sequence of hypothetical (conditional) computational steps.

Definition 3 (Conditional step and conditional traces)

A conditional step is either

a conditional transition

which consists of a state transformer guarded by a condition , or

a finalizer,

written , or

a process spawn,

written , where is a non-empty set of conditional traces.

For conditional transitions and process spawns inside atomic blocks, we can add the markers and . In the sequel, we refer to the marked or non-marked version of conditional steps and process spawns indifferently, except in those cases when it is necessary to distinguish them. Given a conditional transition (or ) we use the notation and to denote and .

A conditional trace is defined as

  • a finite sequence of conditional steps (except ), possibly ending with ,

  • an infinite sequence of conditional steps (except ).

We use to denote the empty trace (of length zero). We denote by the set of all conditional traces.

We denote by the conditional trace whose conditional steps are . Moreover, given a finite trace and a trace , we denote their concatenation by . Finally, given a set of traces , we denote by the set .

Example 1 (Conditional trace)

Given an environment , a trace formed by two conditional steps is . The first step is a conditional transition whose predicate is always satisfied, and transformer binds variable to the value of variable in . The second component of the trace is the finalizer . Another example of conditional trace is .

Intuitively, a conditional transition represents all possible computations where the current process state satisfies the condition and then the process progress to state . The finalizer represents the end of the execution. In the following section we show how the process spawn is used to handle process calls.

Definition 4 (Preorder on )

We order conditional traces by their information content. Namely, for all conditional steps and , for all , , and for all , , we define , , and

Example 2

Given the conditional trace in ex:traces and the conditional trace we can observe that since the predicate at the first position of implies the one at the first position of , and the remaining components are equal. This also means that the behaviors represented by are also represented by .

Note that is not anti-symmetric; for instance, and . Therefore, in order to obtain a partial order, we use equivalence classes w.r.t. the equivalence relation induced by .

Definition 5 (Semantic domain)

Given , , we define as . We denote by the class of non-empty sets of conditional traces modulo . Formally, . We abuse notation and denote by also the partial order induced by the preorder on equivalence classes of .

is a complete lattice, where , and, for all , and

It is worthy noting that we exclude from since the absence of progression is already represented by .

In the sequel, any is implicitly considered to be a set in that is obtained by choosing an arbitrary representative of the elements of the equivalence class . Actually, all the operators that we use on are independent of the choice of the representative. Therefore, we can define any operator on in terms of its counterpart defined on sets of .

To give meaning to process calls we use the following notion of interpretation that associates to each process symbol an element of

modulo variance.

Definition 6 (Interpretations)

Let be the set of process names and , are distinct locations (or simply when clear from the context).

Two functions are variants, denoted by , if for each there exists a location remapping such that .

An interpretation is a function modulo variance333In other words, a family of elements of indexed by modulo variance..

The semantic domain (or simply when clear from the context) is the set of all interpretations ordered by the point-wise extension of (which by abuse of notation we also denote by ).

Essentially, the semantics of each process in is given over formal parameters that will be stored in a location that is actually irrelevant. It is important to note that has the same cardinality of (and is thus finite) and therefore each interpretation is a finite collection (of possibly infinite elements).

The partial order on formalizes the evolution of the process computation. is a complete lattice with bottom element , top element , and the least upper bound and greatest lower bound are the point-wise extension of and , respectively. We abuse notation and use that of for as well.

Any is implicitly considered to be a function that is obtained by choosing an arbitrary representative of the elements of generated by . Therefore, we can define any operator on in terms of its counterpart defined on functions . Moreover, we also implicitly assume that the application of an interpretation to a process call , denoted by , is the application of any representative of that is defined exactly on .

4 The inductive bottom-up fixpoint semantics

In the literature, there exist some works that introduce formal definitions of an operational semantics for PROMELA. For instance, [15] proposes a clear structural operational semantics defined in an incremental style based upon [11]. However, such semantics models an old version of the language and more recent features such as the new behavior of atomic statement is missing. In our opinion, the most relevant proposal is [7], which provides a parametric structural operational semantics defined as the basis of the -SPIN tool for abstract model checking. In order to design our denotational semantics, we have took into consideration only the official specification of [9]. When any aspect was not defined there, we made our own assumptions and then we validated them with the semantics in [15, 7] and with the latest version of SPIN.

As the reader might have noted, we have restricted ourselves to a significant fragment of PROMELA and made some assumptions that we recapitulate here for clarity. For simplicity, some constructs are not considered, for instance timeout or unless, and the ordered insertion and random removal of channel messages are not handled. Moreover, we treat the primitive expression as a statement (and therefore it cannot be used into expressions). Statements that can be translated into the included ones (od/do or the for statement) are also omitted.

The semantics is defined as the least fixpoint of a semantics operator that transforms interpretations. Informally, such fixpoint is computed as the limit where . Function is defined in terms of auxiliary semantics evaluation functions described in the following sections.

4.1 Denotation of Expressions

Due to space limitations, we assume to have two functions to evaluate expressions. The first one is used for expressions on the right hand side of an assignment and it returns the value of a given r-expression in a given state . The second function, for l-expressions, returns the modifier for the location associated to the expression. Given an environment , a state and then a value-updating function , the function builds a function to update the location identified by the l-expression with modifier .

Example 3 (Evaluation of expressions)

Given and , then (which corresponds to ).

4.2 Denotation of Basic Statements of Promela

We start by showing the denotation for basic statements. Given a basic statement , an environment and an interpretation , the function , written , builds the conditional trace corresponding to the effects of . We show below the rules corresponding to the case when the constructs are not within the scope of an atomic block. We use the variants and replacing constructs and in the given rules when the basic statement is within the scope of an atomic block. The information derived from the use of these variants will be used during the second phase of the semantics computation (when synchronization among processes is dealt with). More specifically, following the operational behavior, the first statement in an atomic block behaves as a guard to enter the atomic block itself; if such guard is satisfied all statements of the block will be run as an indivisible sequence as far as they do not suspend. There are some scenarios in which the statements inside an atomic block may suspend however. In particular, when a synchronous send statement occurs in an atomic block, the control is lost, thus the behavior is equivalent to having an independent atomic block starting at the statement right after the send statement. Hence, as has been advanced, to later reproduce this behavior, when a statement is inside an atomic block (except if it is the first one of the block or it is the first one after a synchronous send statement) the used operator is the one labeled with the arrow.

(1a)
(1b)
(1c)
(1d)
For asynchronous channels (i.e., whose capacity is greater than 0)
(1f)
(1g)
For synchronous channels (i.e., whose capacity is 0)
(1i)
(1j)
(1k)

eq:s:condition models the evaluation of an expression, whose right-evaluation is used as the condition for the continuation of the trace.

eq:s:assign models an assignment (always enabled) and might alter the state .

eq:s:basicDecInit model basic-type variable initialization. This action is always enabled and modifies the system state.

eq:s:sendAsync,eq:s:receiveAsync model asynchronous communication. The conditional transition is executable only if the channel is currently defined and not full (for sending) or not empty (for receiving). The channel valuation is updated as expected. Functions push, pop and head are part of the semantic domain for system states.

eq:s:sendSync,eq:s:receiveSync model synchronous communication. We treat synchronous channels as channels with size one and synchronization is achieved by using the predefined variable . The behavior for synchronous communication is similar to the asynchronous one, the particular point is that the send statement updates the variable with the channel identifier so that the receive statement can check if the channel it is listening to coincides with that in . Upon completion, is set to its default value (). Observe that guards in eq:s:sendSync,eq:s:receiveSync establish the mandatory conditions needed for the sender/receiver processes to communicate via the synchronous channel. At system level, functions and , given in Section 4.4, make use of these guards to guarantee that both processes can only progress synchronously.

Finally, eq:s:run models a process call of the run statement for the interpretation . All conditional traces built by this rule start with a conditional transition always enabled that allocates the needed space for the local variables of the new process; stores the values of the actual parameters into (the locations of) the formal parameters; creates the value for the variable of the new process; and increments the global variable (the number of active processes). The second conditional step is a process spawn that contains the suitable instance of the denotation of the process definition in , whose “working locations” are those settled in the first transition. The interleaving behavior is handled in a second phase of the semantics computation.

Example 4 (Semantics for assignment statement)

Given an environment and an interpretation , the semantics for an assignment x=y is

This expression can be read as a conditional transition that is always enabled and, as expected, transforms the state by copying the value (stored in the location) of variable (i.e., ) to (the location of) variable (i.e., ).

4.3 Denotation of process definitions

Since we have arbitrary goto instructions in the programs, the flow of control does not follow predefined patterns like when we have only structured constructs. To define a semantics by “straight” (one-to-one) induction on syntax one has to resort to the use of some sort of higher-order constructions (like continuations). However, the development of abstract semantics based on this type of concrete semantics certainly becomes more complicated.

In order to build “first-order” denotations we choose to define the semantics of process definitions by induction on the structure of the Control Flow Graph (CFG) of its body. Such CFG can be easily constructed from the process syntax, likewise the labeled transition systems of the operational engine. More specifically, each node of a CFG is what we call a process point, i.e., a point that is referred by the process counters of the operational engine as well as a goto statement444Note that due to the operational semantics of goto statements, nodes in the CFG might not correspond to states of the PROMELA process since, in the language, process counters never point to goto statements. However, because of (1a), the steps in traces generated by the semantics do correspond to system states. . We follow the typical convention to name process points as the statement labels (when they are present in the program). Furthermore, we interchange each label with the corresponding process point. In the sequel, we denote by the set of process points of process (the nodes of ).

Edges of the CFG are labeled by the basic statement that generates such edge. The construction of the CFG is standard, except for the case of statement if/fi. For a generic : , for all branches in we have an edge labeled with from process point to process point and then we have the path (starting at point ) corresponding to (the translation of) the statements (if the last statement of is reached, the path ends in point ). When the else branch is present, we add also a path where the first arc is a special one and then we proceed with (the translation of) the statements . In the sequel we denote by the set of edges or of .

Example 5

The CFG of the process P defined in fig:peterson is the following.

L0

L1

L2

L3

L4

f[id]=1

!f[1-id]

skip

f[id]=0

goto L0

The denotation of a process definition is built by using the following immediate consequences operator . This operator uses a family of sets of conditional traces indexed by process points (coded with a function ) that convey the behavior from each process point “to the end” and then builds (backward) the behaviour of all statements that after one execution step proceed like specified by .

where . The semantic operator is used for else branches and it adds the negation of each guard of the other branches in order to make the else branch executable only when all other branches are not. Note that conjunction is finite since we have finitely many different first conditional states in any .

Given a process definition , an environment and an interpretation , the function , written , builds the conditional traces corresponding to the executions in isolation of .

where

The semantics operator replaces the marked conditional steps with all possible executions that correspond to the prescribed semantics of the atomic regions. It is defined as where

  • if does not contain marked conditional steps.

  • Otherwise, without loss of generality, let where are conditional transitions; are a possibly empty sequences of process spawns; has no marks and are marked. Then

This operator generates traces for the different scenarios in an atomic region. It composes the conditions and effects of two atomic steps and in a single step. But the alternative trace when suspends is also generated (). This strategy is applied recursively.

Example 6 (Process denotations)

We start by showing the output of the fixpoint computation for the init process in fig:peterson. is

The atomic block ensures that the two processes are created before any of them starts executing its body. Since it is not possible that those two run statements suspend, the function compacts the two steps and prevents the interleaving with the two newly created processes. In fact is

(2)

The fixpoint computation for the P process type results in a single infinite trace in which the four conditional steps actually shown below are infinitely repeated:

The second step in the loop is marked as atomic whereas the third one corresponds to the execution of the skip statement representing the critical section. The atomic region in this process prevents any interleaving between the first and second step if the condition of the second step is satisfied. However, if that condition does not hold, then interleaving is possible. This is why the function generates an infinite set of infinite traces. The following set only shows the two possible beginnings for traces in . Each trace will consist of an arbitrary combination of these fragments.

(3)

4.4 Denotation of a Promela Program

We are ready to provide the semantic function that computes the traces associated with a PROMELA program. In order to simplify the definitions but without loss of generality, we assume that programs accepted by this function are normalized. Essentially, for all processes declared as active we add a suitable run statement in the init process, encapsulated into an atomic block. Also, we add an initialization to the proper default value to all local variable declarations without an initialization. We move all global variable declarations to the top and remove printf statements.

In order to define the semantic function for programs, let us start by defining the denotation for process declarations. Given a list of process declarations, an environment, and an interpretation, the function applies the effect of one step of the computation to the input interpretation.

(4)

is the set of traces representing the final step of each process, i.e., is responsible for decreasing the counter of process instances and then stop.

We can now introduce where is the domain of sets of sequences of states in . Note that the definition uses two auxiliary functions and that are formalized later. Given a normalized program with init process , global variable declarations and a list of process declarations , we define

(5)

where is the location reserved for the variable of and is the environment populated with associations, of both the language predefined variables and the global variables declared in , to suitable (different fresh) locations.

first computes the least fixpoint of the function ; the result is used to compute the interleaving of spawned processes, and finally the initial state is propagated to the conditional traces of . is well defined since is continuous.

Given a set of conditional traces, replaces the process spawns of each conditional trace with all possible interleavings of with the rest of the trace. To define , first observe that if is not then it can be partitioned depending on the initial conditional step of all its conditional traces . Then

The second part of the definition uses predicate to detect if conditional state wants to perform a synchronous send over a channel, and wants to perform a synchronous receive over that channel. Moreover is the conditional step that passes the message content and combines the effects on the state of and then . Both actual combinations and are possible.

Given a set of conditional traces not containing process spawns, semantic function propagates the initial state through each conditional trace in , giving as a result sequences of states. If is not then it can be partitioned depending on the initial conditional step of all its conditional traces. By construction we can possibly have just one trace that starts with . Thus all traces of must begin with a finite number of conditional transitions. Thus we can formally define as

At each step, the next conditional transition of each trace is evaluated, obtaining a new state in the collected sequence. When a trace has no conditional transition it is not considered as input for the following iteration. Note that the second equation characterizes traces whose conditions become true after propagation. Since the function generates non-real traces, we cannot distinguish blocking behaviors from wrong behaviors and we discard any of them.

Let us illustrate the semantics computation by means of an example.

Example 7 (Computation of the program semantics)

Let the program in fig:peterson and the body of the init process. Let be the process declarations, i.e., and , their associated CFGs. Then, the semantics of the whole program is

where the global environment binds f[0] to , f[1] to , id to , and to . Moreover is the location reserved for the variable of . Let and . By eq:Dsemantics,

We now illustrate how the unrolling process of the trace generated by the init process works. For conciseness, let be the starting conditional step . Then,

where and are the sets of traces for P(0,\_,1) and P(1,\_,2) resulting from eq:SemProcessP, and represents the rest of the unrolling of the traces in . Below we show the set . is similar to , substituting every f[0] by f[1] and every f[1] by f[0].

The function takes each trace in these sets and computes all possible interleavings between each pair of traces. We show the beginning of two computed traces, one in which the first process executes once the loop followed by the execution of the loop by the second process, and another in which after the first two steps of the first process, the second one executes its first condition and then assumes the second step is not executable.

(6)

After the interleaving process, the propagate operator accumulates the effects of the executed steps. For conciseness, let be the state . The semantics for our program example includes all traces in which each process runs the complete loop before passing the control (or not) to the other process:

Note that when propagating the second trace in eq:interleaving, we have that we reach a point in which the condition of the following step is false, thus the behavior trace is discarded. This particular trace, included in the set of traces generated by the function, corresponds with a deadlocking trace in PROMELA, namely, . The (propagated) state falsifies the conditional trace .

5 Conclusion and future work

This work presents a first proposal of a inductive bottom-up fixpoint semantics for PROMELA, which is a widely used modeling language for concurrent, reactive systems. It does not cover the full language, but the considered fragment is very significant. In particular, it handles arbitrary gotos, which are extensively used by the PROMELA community and both synchronous and asynchronous communications. The presented semantics enjoys the good properties to be the basis for the implementation of verification and analysis techniques. First, it is goal-independent, which allows us to collect all possible behaviors from most general calls, avoiding the need of computing the semantics individually for each possible goal. Second, it is bottom-up which improves both convergence and precision in the abstract-interpretation setting for analysis and verification. This is because the join operator of the abstract domain is used less times than in a top-down approach.

As noted, the denotational semantics in this work does not capture deadlocking traces. As a future work we plan to overcome this limitation by defining a new semantic operator that propagates the conditions of previous conditional states within the conditions of successive ones, in order to discard impossible sequentializations and retain only the finite traces that stop either on successful termination or deadlock.

References

  • [1]
  • [2] M. Ben-Ari (2008): Principles of the Spin Model Checker, 1 edition. Springer-Verlag, London, doi:10.1007/978-1-84628-770-1.
  • [3] E. M. Clarke & E. A. Emerson (1982): Design and synthesis of synchronization skeletons using branching time temporal logic. In D. Kozen, editor: Logics of Programs 1981, Lecture Notes in Computer Science 131, Springer, Berlin, Heidelberg.
  • [4] M. Comini, L. Titolo & A. Villanueva (2013): A Condensed Goal-Independent Bottom-Up Fixpoint Modeling the Behavior of tccp. Available at http://www.dimi.uniud.it/comini/Papers/. Submitted for publication.
  • [5] M. Comini, L. Titolo & A. Villanueva (2014): Abstract Diagnosis for tccp using a Linear Temporal Logic.

    Theory and Practice of Logic Programming

    14(4-5), pp. 787–801, doi:10.1017/S1471068414000349.
  • [6] H. Erdogmus (1996): Verifying Semantic Relations in SPIN. In: Proceedings of the First SPIN Workshop.
  • [7] M.M. Gallardo, P. Merino & E. Pimentel (2004): A generalized semantics of PROMELA for abstract model checking. Formal Asp. Comput. 16(3), pp. 166–193, doi:10.1007/s00165-004-0040-y.
  • [8] C. A. R. Hoare (1985): Communicating Sequential Processes. Prentice-Hall.
  • [9] G. J. Holzmann (2004): The Spin Model Checker: Primer and Reference Manual. Addison-Wesley. Available at https://books.google.it/books?id=NpBdZKmOHO8C.
  • [10] G. J. Holzmann (2012): Spin Manual. Available at http://spinroot.com/spin/Man/promela.html.
  • [11] V. Natarajan & G. J. Holzmann (1996): Outline for an Operational Semantics of PROMELA. In: The SPIN Verification System. Proceedings of the Second SPIN Workshop, DIMACS 32, AMS.
  • [12] G. L. Peterson (1981): Myths about the mutual exclusion problem. Information Processing Letters 12(3), pp. 115–116, doi:10.1016/0020-0190(81)90106-X.
  • [13] A Pnueli (1971): The temporal logic of programs. In: Proceedings of the 18th Annual Symposium on Foundations of Computer Science (Providence, RI.), IEEE, New York, pp. 46–57.
  • [14] J.-P. Queille & J. Sifakis (1982): Specification and Verification of Concurrent Systems in CESAR. In: Proceedings of the 5th Colloquium on International Symposium on Programming, Springer-Verlag, London, UK, pp. 337–351. Available at http://dl.acm.org/citation.cfm?id=647325.721668.
  • [15] C. Weise (1997): An incremental formal semantics for PROMELA. In: Proceedings of the Third SPIN Workshop, SPIN97.