This page provides the formal operational semantics for a light version of the pSpace programming model.
A pSpace concurrent program S
is a pair M |- P
, where M
is a memory (i.e. a map of variables into values) and P
is a multiset of concurrent processes. More precisely, the syntax for pSpace programs is
S := M |- P
A memory M
is represented as list of mappings x |-> u
, where x
is a variable and u
is a value:
M := nil | x |-> u | M , M
We use the usual notational conventions:
M[x]
is the value associated tox
, if any;- Memory
M[x|->u]
is likeM
after updating the value associated tox
byu
or addingx |-> u
ifM[x]
is not defined.
Tuple spaces reside in memory just as other values. A tuple space value is denoted by TS
with the following syntax:
TS ::= nil | t | TS * TS
Concurrent processes are composed with the parallel composition operator ‖
. Such operator is associative, commutative and has the empty set 0
as identity:
P ::= 0 | P‖P | A;P | ...
For simplicity, we usually drop the final ;0
from programs. We do not specify control flow constructs and other language ingredients and focus instead on tuple space actions A
.
The actions of a process include creation of new processes, creation of new tuple spaces and assignments using tuple space operations.
A ::= new P
| space := new Space()
| space.O
| x,y := space.O
Operations O
correspond to the core API of pSpaces:
O ::= put(t)
| query(T)
| queryP(T)
| queryAll(T)
| get(T)
| getP(T)
| getAll(T)
Tuples are denoted by non-empty lists of expressions e
t ::= e | e,t
Templates are denoted as non-empty lists of expressions e
or types τ
T ::= e | τ | e,T | τ,T
The operational semantics of concurrent pSpace programs is defined by the below set of inference rules of the form
premises
============
conclusion
and, very often of the shape:
premise1 premise2 ... premiseN
================================
A => B
explaining how a program A
can perform a computation step and evolve into B
if the premises hold.
The inference rules provided below can be applied up-to the axioms of the symbols used (e.g. associativity of list concatenation ,
, associativity and commutativity of parallel process composition ‖
, ...).
Spawning a process just creates a new concurrent activity:
===================================================================
M |- new P1 ; P2 ‖ P3 => M |- P1 ‖ P2 ‖ P3
Creating a new local space is formalised by the following rule:
===================================================================
M |- space := new Space() ; P1 ‖ P2 =>
M[space |-> nil] |- P1 ‖ P2
The rule says that new spaces are created with an empty tuple list. The effect of the assignment is to create a new variable space
(or overwrite space
if it already exists).
The following rules describes the behaviour of executing an operation on a local space.
TS1.O => TS2,t,e
===================================================================
(M , space |-> TS1) |- , x,y := space.O ; P1 ‖ P2 =>
(M , space |-> TS2)[x|->t][y|->e] |- P1 ‖ P2
TS1.O => TS2,t,e
===================================================================
(M , space |-> TS1) |- space.O ; P1 ‖ P2 =>
(M , space |-> TS2) |- P1 ‖ P2
Note that the premise of the above rule requires a reaction of the space to the operation. The tuple space may not react and thus may block the process (by not allowing to fire the rule). The reactions of tuple spaces are described as operational rules below. The effect of the assignment on variables x
and y
is the usual one (i.e. update/overwrite the variables).
Recall that tuple lists TS
are to be understood up to associativity of *
, and identity of nil
. Note that the result of an operation may alter the tuple structure TS
, and that they produce a tuple and an error code (either ok
or ko
)
The behaviour of operation put
is described by the following rule:
TS.put(t) => (t*TS),t,ok
The rule essentially says that the put
operation updates the list of the tuple space with the new tuple t
.
The behaviour of query
is governed by the following rule:
t matches T and no tuple in TS' matches T
===================================================================
TS*t*TS'.query(T) => TS*t*TS',t,ok
The behaviour of queryP
is similar but ensures progress and returns error codes accordingly.
t matches T and no tuple in TS' matches T
===================================================================
TS*t*TS'.queryP(T) => TS*t*TS',t,ok
no tuple in TS matches T
===================================================================
TS.queryP(T) => TS,null,ko
The rest of the operations are defined similarly:
t matches T and no tuple in TS' matches T
===================================================================
TS*t*TS'.get(T) => TS*TS',t,ok
t matches T and no tuple in TS' matches T
===================================================================
TS*t*TS'.getP(T) => TS*TS',t,ok
no tuple in TS matches T
===================================================================
TS.getP(T) => TS,null,ko
TS' = {t in TS such that t matches T}
===================================================================
TS.queryAll(T) => TS,TS',ok
TS' = {t in TS such that t matches T}
===================================================================
TS.getAll(T) => TS\TS',TS',ok