Parallel Object-Oriented Specification Language
The Parallel Object-Oriented Specification Language (POOSL) is a very expressive language to model concurrent hardware/software systems. POOSL has a small set of powerful primitives and it has an unambiguous formal semantics in terms of mathematical axioms and rules. POOSL models can be simulated with the Rotalumis simulator. There is an Eclipse-based tool for editing and debugging POOSL models PooslIDE (and the older, no longer developed SHESim tool). An experimental POOSL unit testing plug-in is available for the Eclipse-based editor.
The formal semantics of POOSL forms the basis of Markov-chain based performance analysis techniques and run-time model checking techniques for Formal Verification of correctness properties. It furthermore serves as a basis for a software synthesis technique that preserves timing properties.
The language is structured into three layers the first layer captures passive data objects. The second layer active process objects and the third describes system structure and architecture. Below we present the syntax of Data, Process and Architecture layers of POOSL and we briefly discuss a few aspects of the meaning (semantics).
In case you want to refer to POOSL, you can use the following reference:
B.D. Theelen, O. Florescu, M.C.W. Geilen, J. Huang, P.H.A. van der Putten and J.P.M. Voeten, Software/Hardware Engineering with the Parallel Object-Oriented Specification Language. In: Proceedings of MEMOCODE’07, pp. 139-148, 2007
Data Layer
The data layer is a probabilistic version of traditional imperative
object-oriented programming languages and supports the object-oriented
principles of encapsulation, single inheritance (including method overriding)
and polymorphism.
All data in a POOSL models is represented by data
objects, which are instances of data classes. A data class has a
name, a (single) inheritance relation, instance variables and instance
methods. Every data class directly or indirectly inherits from the data
class called Object
, which is the only class that does not inherit from
any other class. The instance variables specify the attributes of a data
object. A data class inherits all instance variables of its super class and
may add variables of its own. The behaviour of data is sequential and defined
by its instance methods or (data) methods. Data classes inherit all methods
from their super classes and may redefine or add methods. Data objects may
receive a messages from data objects or from processes, which automatically
leads to the execution of a method with the same name as the message. As
usual, this may in turn lead to other data methods of the object or of other
objects being called recursively. Upon completion of the method, a result in
the form of a reference to a data object is always returned. This may be
the receiver of the message, but also any other data object as specified by
the method that was executed. In summary, data objects are very similar to
objects in traditional imperative object-oriented programming languages such
as Java or C++.
Data methods
Data methods are defined according to the following form:
m(p1: DC1, ..., pi: DCi) : DCr
|l1: DCl1, ..., lk: Dlk|
E
An example is the following method.
vectorLength(x: Real, y: Real) : Real
| dsqr: Real |
dsqr := x sqr() + y sqr();
return dsqr sqrt()
In general, m
is the method name (vectorLength
in the example) and DCr
(Real
) is a data class name that denotes the type of the object returned by the method (see below for a short note on the type system of POOSL). The lists p1: DC1, ..., pi: DCi
defines the formal arguments of the method. When the method is called, values (references to data objects) need to be supplied, which will be bound to local variables with the corresponding names (p1
, pi
) before the method starts.
DC1
, DCi
specify the types of the parameters.
l1: DCl1, ..., lk: DClk
, written between vertical bars denote similarly the other local
variables of the method. The body of a data method is a data expression E
. Note that this expression may itself be composed as a sequence of smaller expressions as in the example method.
Primitie and Native Data Classes
Some data classes are primitive. This means that they are defined by the semantics of the language. Models can add non-primitive data classes of their own. Primitive data classes have primitive methods, the behaviour of these methods is defined by the semantics, but models can add their own methods, as described above, also to primitive classes.
Specific tools may also offer native data classes with native methods. These are defined by the tool implementation, not the formal semantics. They are used, for instance, for I/O purposes.
The primitive classes are: data class Object
(the root of the inheritance hierarchy), with primitive methods like =
, !=
, ==
, deepCopy
and shallowCopy
(which can therefore be used on any data object) and the data classes Boolean
, Integer
, Real
, Char
and Nil
. Data class Boolean
has exactly two instances, called true
and false
.
Data classes Integer
and Real
have exactly one instance for each numerical value that they can represent, so there is exactly one Integer
object 1
(!). Instances of data class Char
represent strings and the primitive methods of this class reflect standard operations on strings. Data class Nil
has only a single instance, nil
.
Native data classes in the POOSL tools are String
, Array
, FileIn
, FileOut
and RandomGenerator
. A String
represents a string of (extended ASCII) characters as usual. An object of class Array
holds an array of data objects. FileIn
and FileOut
manage file I/O. Instances of RandomGenerator
represent a pseudo random number generator.
Data Expressions
E ::= c literal
| x variable
| self reference self
| new(D) data object creation
| currentTime current model time
| x := E assignment
| E1; E2 sequential composition
| E m(E1, ..., Ei) data method call
| self ^m(E1, ..., Ei) superclass data method call
| if Ec then E1 else E2 fi choice
| while Ec do E od loop
| return E annotation of an intended return value in a method
Table 1. Expressions.
The behaviour of ordinary, non-primitive data methods is specified by
expressions. Table 1 summarises the syntax for the most relevant
forms of expressions. Literal c
refers to an instance of one of the data
class Boolean
, Integer
, Real
, Char
, String
or Nil
that has an
explicit syntactic form. Examples are Boolean
true
, Real
3.8
, Char
'z'
and String
"POOSL"
. The expression x
denotes a (instance or
local) variable name and it evaluates to the object referred to by the
variable x
. The keyword self
evaluates to the data object that is
evaluating the expression. Evaluation of new(D)
results in the creation of
a new instance of data class D
and evaluates to a reference to that new
object. Note that instances of Boolean
, Integer
, Real
and Char
are
immutable and will always keep the same value. Expression currentTime
evaluates to a numerical values that denotes the current model time. It can
only be used in the context of processes. Expression x := E
denotes the assignment of the value to which E
evaluates to variable x
.
As an expression by itself, it evaluates to the same value. It is therefore
possible to write, for instance, x := y := z := 0
. Sequential composition
of expressions is denoted using a semicolon. Be aware that the semicolon is
used only between expression, not, as in many other languages, at the end
of every expression. In particular, the last expression is not followed by a
semicolon. Parenthesis can be used to group expressions.
The next expression in table 1 is the data method call E m(E1, ..., Ei)
. Evaluation results in the invocation of method m
for the object to which expression E
evaluates. The
expressions E
and E1
, …, Ei
are evaluated (from left to right) and passed as parameters to the method. If the data class of the object evaluating the expression redefines or overrides a method m
of its superclass by
defining a method with the same name m
and the same number of parameters,
then the expression self ^m(E1, ..., Ei)
invokes the inherited method m
as defined in the superclass.
Expression if Ec then E1 else E2 fi
evaluates to the result of expression E1
in case the condition Ec
evaluates to true
and E2
if it evaluates to false
.
The loop while Ec do E od
, repeatedly evaluates expression E
as long as condition Ec
evaluates to true
and ends when Ec
evaluates to false
the expression itself always evaluates to nil
. The expression return E
is fully equivalent to the expression E
in any context where it is allowed to be used (only in data methods). It can be used to emphasize the intended result to be returned after completion of the method.
Type System
POOSL includes a type system for data objects. The extent to which types are checked, statically or dynamically, is left to the specific tools. In terms of the semantics of the language, the type system is not used. The type system is simply defined as follows. With every data class DC
, a type is associated that includes all instances of class D
or any sub class of D
and every type includes the object nil
(the single instance of the data class Nil
, which is used to initialise all fresh variables).
Memory Management
In POOSL it is not possible to explicitly destroy objects. In the formal semantics, objects that are created remain in existence forever. The tools handle this by on automatic storage reclamation, Garbage Collection, for objects that can no longer be accessed by any active objects in the model.
Details
The native class Random Generator
has a primitive method random
that returns a Real
according to a uniform distribution (0, 1), while primitive method randomInt
, which has an Integer
parameter p
, returns an Integer
according to a uniform distribution [0, p
). The primitive method seed
initialises the initial value (seed) of the random number generator
used for implementing random
and randomInt
. Primitive method randomiseSeed
initialises the seed in an arbitrary way based on the real time at which randomiseSeed
is evaluated. To obtain random numbers according to various other distributions, a library of Distribution Classes has been predefined for POOSL and is available here.
Here is an example of a complete data class definition.
Process Layer
Process objects or processes, in short, distinguish themselves from data objects by the fact that they are static, active and concurrent objects in a POOSL model. The behaviour of processes is defined by process classes. A process class has a name, instantiation parameters, instance variables, ports, a message interface, instance methods, an initial method call and an (optional, single) inheritance relation.
The instantiation parameters and
instance variables are the attributes of a process object. The instantiation parameters are given values when the process is instantiated. This allows their behaviour to be parameterised.A typical example is to provide different instances of the same class with a unique identifier. Processes can communicate via their ports. The message interface lists the signatures of all possible messages that the process may use and includes for each message the name of the port, the symbol !
or ?
for send and receive messages respectively, the message name and a (possibly empty) list of types of the message parameters.
The behaviour of processes is defined by the instance methods or (process) methods of the process class. Methods may be called with formal input parameters (like data methods) and may, in contrast to data methods, return multiple results through output parameters. The startup behaviour of a process is defined by the initial method call. In case an process class inherits from another process class, it inherits the instantiation parameters, instance variables, port interface and message interface as well as all the methods of its superclass.
Process Methods
The behaviour of a process method is specified using the following syntax:
m(p1: Dp1, ..., pi: Dpi)(r1: Dr1, ..., rj: Drj)
|l1: Dl1, ..., lk: Dlk|
S
An example is the following method.
receiveInput(b: Bool)(r: Object)
| |
if b then
p1 ? input(r)
else
p2 ? input(r)
fi
m
is the method name and declarations p1: Dp1, ... pi: Dpi
, r1: Dr1, ... rj: Drj
and l1: Dl1, ..., lk: Dlk
denote the input parameters, output parameters and local variables, respectively for method m
. Similarly to data method, they are defined with names and types. The body of m
is defined with a statement S
. The syntax for statements is shown in table 2.
Process Statements
S ::= E expression
| m(E1, ..., Ei)(v1, ..., vj) process method call
| par S1 and ... and Sn rap parallel composition
| S1; S2 sequential composition
| p!m(E1, ..., Ei){E} message send
| p?m(v1, ..., vi | Ec){E} (conditional) message reception
| sel S1 or ... or Sn les non-determinis tric selection
| [Ec]S guarded statement
| interrupt S1 with S2 interrupt
| abort S1 with S2 abort
| delay E time delay
| if Ec then S1 else S2 fi choice
| while Ec do S od loop
| skip empty behaviour
Table 2. Statements.
Most data expressions of table 1 are also valid statements (exceptions are expressions using self
and return E
). It is possible to surround data expression with curly braces to enforce atomic (indivisible) execution of the expression. This may be needed, because processes execute concurrently. The statement m(E1, ..., Ei)(v1, ..., vj)
makes that method m
is called after expressions E1
, …, Ei
are evaluated from left to right and bound to the input parameters of m
. After the execution of m
, results are returned by binding the returned objects to variables v1
, …, vj
. Methods without output parameters may be called in a tail-recursive manner.
The par
statement for parallel composition denotes the concurrent, non-deterministically interleaved execution of statements S1
, …, Sn
. This allows asynchronous concurrency within processes besides concurrency among processes. Sequential composition of statements is denoted
using a semicolon. Similar as for expressions, sequentially composed statements can be grouped using the optional parentheses.
The message send statement p!m(E1, ..., Ei){E}
leads to the synchronous
sending of message m
to port p
. It only happens when a matching message
receive statement is executed by another process connected to the same
channel to which the port connects. Parameters E1
, …, Ei
are evaluated
from left to right and (recursive, ‘deep’) copies of the resulting data
objects are bound to the parameters of the corresponding receive statement.
The send statement can optionally be followed by a data expression between
curly braces. In that case the expressionb is evaluated, atomically,
immediately after the message is sent. The complementary message receive
statement p?m(v1, ..., vi | Ec){E}
synchronously receives message m
on
port p
. The parameters accompanying the received message are bound to
variables v1
, …, vi
. The synchronisation only occurs if the message
names match, the number of parameters are equal and the optional reception
condition Ec
(which may depend on the data objects that are being
received!) evaluates to true
. Also the receive statement may be followed
by an atomically evaluated expression.
The select statement sel S1 or ... or Sn les
for non-deterministic
selection executes one of its constituent statements S1
, …, Sn
that is
enables to execute an action. A time delay postpones the decision. The
guarded statemtn [Ec] S
inhibits the execution of S
as long as the
condition expression Ec
evaluates to false
.
The interrupt statement interrupt S1 with S2
executes S1
until S2
starts execution. At that time, S1
is suspended until S2
completes. Then
S1
resumes, but can be interrupted by S2
again. When both S1
can
progress and S2
can start the decision is made non-deterministically. The
statement abort S1 with S2
behaves similarly, but S1
is terminated when
S2
starts executing.
The statement delay E
delays further execution for a duration of simulation
time equal to the result of evaluating E
. It is the only statement to
express quantitative timing behaviour. It can be combined with other
statements to realise more intricate timing behaviour like time-outs or
watchdogs. The time domain of a model can be discrete (when delays are
integer or dense when delays use Real
s.
The if
an while
statements behave like their data expression
counterppoarts. The skip
statement performs an (internal) action without
any side effect. It is meaningful in combination with other statements.
Here is an example of a complete process class definition.
Architecture Layer
The architecture layer allows one to hierarchically build a structure of process objects connected with channels, using clusters. Clusters are defined by cluster classes.
A cluster class has a name, instantiation parameters, ports, a message interface and a behaviour specification. The instantiation parameters are the attributes that can be used to parameterise a cluster, similar to the instantiation parameters of process classes. The ports and message interface define how a cluster may communicate to the outside. They are defined in the same way as for process classes.
The behaviour specification defines what instances of process and clusters exist in the cluster instantiated from the class and how they are connected by channels. Channels may connect only instances inside the cluster, but channels may also connect to the outside ports of the cluster. The specification also provides expressions for all instantiation parameters of the instances inside the clusters. Those expressions can in turn refer to the instantiation parameters of the cluster class being defined. It is important to be aware that the language semantics defines that two ports *of the same instance (cluster or process) that are externally connected by a channel do not allow a message passing synchronization to occur. This is only possible between ports from different instances, or between an instance and an external port.
The final part of the architecture layer is the specification of the top-level diagram or system specification of a model. The system specification defines the processes and clusters that are instantiated in the actual model and how they are interconnected by channels. At this level, there are no external ports.
figure 1 shows the behaviour specification of a cluster class C
with parameters p1
, …, pn
, which encapsulates instances A
and B
of
process classes P1
and P2
. Their parameters are initialised by
expressions PE1
, …, PEi
and PE1
, …, PEj
respectively, which may
depend on p1
, …, pn
. Processes A
and B
may communicate via ports
a
and b
respectively over channel ch
, which is also connected to port
c
of C
.
Figure 1. Example of graphically denoting a behaviour specification.