Blech Sem Rep v2 PDF
Blech Sem Rep v2 PDF
Abstract
Traditional imperative synchronous programming languages heavily rely on a strict separation
between data memory and communication signals. Signals can be shared between computational units
but cannot be overwritten within a synchronous reaction cycle. Memory can be destructively updated
but cannot be shared between concurrent threads. This incoherence makes traditional imperative
synchronous languages cumbersome for the programmer.
The recent definition of sequentially constructive synchronous languages offers an improvement.
It removes the separation between data memory and communication signals and unifies both through
the notion of clock synchronized shared memory. However, it still depends on global causality
analyses which precludes procedural abstraction. This complicates reuse and composition of software
components.
This report shows how procedural abstraction can be accommodated inside the sequentially
constructive model of computation. We present the Sequentially Constructive Procedural Language
(SCPL) and its semantic theory of policy-constructive synchronous processes. SCPL supports proce-
dural abstractions using policy interfaces to ensure that procedure calls are memory safe, wait-free
and their scheduling is determinate and causal. At the same time, a policy interface constrains the
level of freedom for the implementation and subsequent refactoring of a procedure. As a result,
policies enable separate compilation and composition of procedures.
We present our extensions abstractly as a formal semantics for SCPL and motivate it concretely
in the context of the open-source, embedded, real-time language Blech.
An abridged version of this report has appeared in: Forum on Specification and Design Languages (FDL 2020), Kiel,
Germany, 15-17 September 2020.
1
approach for synchronizing concurrent systems are classical locking mechanisms such as
mutexes or spin-locks. However they are inefficient for frequent, fine-grained communication
and hard to debug.
Where the focus is on control-flow, imperative synchronous programming (ISP) languages
such as Esterel [1], Quartz [2], PRETC [3], ForeC [4] or Céu [5] are a good choice to develop
reactive, safety-critical embedded software. Based on precise mathematical foundations, ISP
utilises mechanisms such as logical clocks and clock-aligned signals for inter-module synchro-
nization and communication. The compiler verifies determinacy by static program analysis and
thus relieves the developer from the burden of manually solving synchronization problems.
However, traditional ISP languages come with rather specialised syntax and rigidly syn-
chronized signals as the only form of decoupling, which precludes sharing of complex data
structures and multiple updates within a clock cycle. While current ISP languages enable the
building of complex systems through white-box [6]–[8] or grey-box [9] modules they do not
properly support black-box abstraction. Notably, “.. the Esterel language has no mechanism
for separate compilation or pre-compiled component libraries..” [10, p. 34]. Procedural
abstractions for ISPs exist but they are not an integrated part of the source language and
its well-defined semantics. The modular structures generated by compilers [11]–[13] for ISP
are based on shared memory and cannot be coded in ISP itself. This makes SP programming
an archane experience for main-stream programmers familiar with procedural abstractions.
Recently, the sequentially constructive model of computation, which has given rise to ISP
languages such as SCCharts [8], has reconciled shared memory and signals. In [14] this
approach has been extended to policy-coherent shared objects that can encapsulate general
abstract data structures, but without considering modularisation. In this report we show that
using those techniques, ISP can achieve black-box procedural abstraction on general abstract
data structures. We present the core language SCPL and its structural operational semantics in a
generic memory model of structured data whose coherence is protected by policy interfaces.
An SCPL process P which is constructive with respect to a given interface C guarantees
memory safe, deterministic and deadlock-free execution in all memory stores coherent for
this interface C. Together with its interface, a pair pP, Cq then constitutes a generic unit for
procedural composition. A procedure can be mapped safely into another memory context C 1
through interface extensions f : C Ď C 1 that preserve policy-constructiveness. The scheduling
of a procedure pP, Cq in its calling context is wait-free and entirely determined by its interface
C and the map f . The procedure P is treated like a black-box and can be replaced by any
step function functionally equivalent to pP, Cq. In this way, procedures operating on shared
data can be assembled as first-class, black-box actions just like the reading or emission of a
signal in conventional ISP. SCPL reconciles the high-level syntax of ISP with intermediate and
low-level computational structures under a uniform and constructive synchronous semantics.
It provides the same safety guarantees and predictability as conventional ISP but in the shared
memory context familiar to main-stream programmers. It promotes synchronous programming
less as a language but as a “mark-up” of conventional syntax via scheduling interfaces.
The Blech language developed at Bosch is among the first ISP languages with first-class
procedures that does not depend on white-box inlining. However, there is no formal semantics
for it, yet. In this report, we fill this gap. In doing so we adapt and extend the theory of [14]
and provide the calculus SCPL. It can serve as an intermediate format not only for Blech but
also other sequentially constructive languages, like SCCharts [8].
2
II. I NTRODUCTORY E XAMPLE
Sample Ctrl rd
Accumulate MacOp
rd,wr rd,wr rd
rd,wr read (rd)
arr 0 1 :[2]float64 s s.m1 s.m2 s.acc :State write (wr)
signature extension
Fig. 1. Architecture and implementation of a multiply-accumulate process with procedural abstraction in pure synchronous
control flow.
Let us consider a simple reactive system and its implementation in Blech to illustrate the
concepts of sequentially synchronous reaction, synchronized shared memory and black-box
procedural abstraction. Figure 1 depicts a reactive system in Blech syntax. System consists
of two decoupled procedures (called activities in Blech) implementing a controller component
Ctrl, a component Sample for external data input and imports module MAC. The latter imple-
ments a multiply–accumulate operation in software by the two decoupled procedures MacOp
and Accumulate. The MAC operation in MacOp computes the product of two multipliers
m1 , m2 and adds its result to an accumulator Accumulate, i.e., acc Ð acc ` m1 ¨ m2 .
Usually, MAC operations are implemented by highly-optimised circuits and are part of DSP
and floating-point execution units [15], [16]
The procedures of System communicate via shared memory, passed in procedure calls
by reference. Module System imports MAC and shares its data structures. Procedure Sample
interacts with the external environment, providing a pair of values of type float64 fetched as
inputs from the environment via external function get. Note the multiple writes to arr in one
reaction. The controller Ctrl instantiates the shared data structures arr and s:Mac.State and
concurrently combines calls of the activities Sample and Accumulate. Note, that Sample and
Ctrl are declared as singleton, meaning that there may exist at most one instance (thread)
of such a subprogram in a concurrent context. This is usually required by threads that interact
directly with external or limited resources.
MAC declares the accumulator state by the structure State, i.e., by two sample values m1, m2
and the accumulator acc, typed as double precision floating-point numbers. Each procedure
declares its in- and output parameters. In Blech this is expressed by two separate lists for
input and output, e.g., the interface of procedure Accumulate declares arr:[2]float64 as
an input that can be read and s:State as an output that can be read and written during
execution. The procedure Accumulate concurrently combines the mapping of the array arr
3
to m1 and m2, a call of procedure MacOp(s)(s.acc) to perform the multiply-accumulate, and
a third thread await s.acc > CAP to detect that acc exceeds the threshold, which triggers
the forced termination of the first two threads. The preemption is indicated by indicated by
the keywords weak in the cobegin parallel construct.
All activities are reaction procedures which operate in synchronous lock-step. At each tick
of a global logical clock, each activity performs one iteration of its behavior until it either
pauses at an await statement waiting for another cycle, or terminates by reaching the end
of the activity. In the example, exactly two data samples are multiplied and accumulated
into the state s during each reaction cycle, called a macro-step. The physical timing which
relates the clock iteration and the data sampling determines how much computation can be
performed during each macro-step. This needs to be calibrated by timing analysis. Assuming
this is done, each reaction is considered to happen instantaneously in zero time (Synchrony
Hypothesis) in the high-level semantics.
Our example implements a stream-processing function that could also be programmed in
a data flow language like Lustre. Such languages can provide a higher level of abstraction
when control-flow is determined by static functional relationships between signals. Yet, in
many practical applications, control-flow is not static or functional and more naturally de-
composed through interactions in shared memory. For instance, the Accumulate activity in
Fig. 1 must terminate when the accumulated signal on s.acc surpasses a threshold value
CAP. This is achieved by the watchdog thread await s.acc > CAP running in parallel with
MacOp(s)(s.acc). The latter, which is labelled as a weak thread is preempted by the
watchdog when the threshold condition is detected. When MAC.Accumulate terminates, then
also the main activity Ctrl terminates and returns a final value. Preemption and terminating
stream processes are not naturally coded in pure Lustre.
Control-flow constructs, of course, can be simulated in data flow, see e.g. SCADE [17].
Yet, this introduces the problem that efficient control-flow code exploiting shared memory
and inplace destructive updates must be “rediscovered” by the compiler. Where the shared
memory procedural code such as in Fig. 1 can be provided by the programmer it is often
more efficient. The catch is that procedural imperative code does not give the same degree of
safety guarantees that come with domain-specific languages such as Lustre, Signal or Esterel.
Shared memory multi-threading introduces many tangled risks of data races and memory
incoherence that seem hard to avoid. In this report we aim to show that the sequentially
constructive model of synchronous computation can be applied to marshall procedural multi-
threaded code in much the same way that the static analyses of domain-specific ISP languages
safeguard interactions of synchronous modules.
To safegurd against memory races, Blech functions and procedures are implicitly wrapped
by policy interfaces that specify the standard data types of their parameters, the memory access
methods applied to these and their causal ordering. A policy interface acts as an assumption-
guarantee contract to enable black-box abstraction and separate compilation. For instance,
the declaration MacOp(ms)(a shares ms.acc) tells us that the parameter ms in the first list
is read-only whereas a in the second list is potentially written. This information is used to
resolve the scheduling order in Accumulate where the procedure call MacOp(s)(s.acc) is
concurrent (the cobegin...with...end construct) with the watchdog testing s.acc > CAP.
Assuming the data type float64 of the shared cell s.acc specifies a regular data flow
variable, the policy interface of the calling site Accumulate will impose the “write-before-
read” causal order s.acc.wr ą s.acc.rd to eliminate data races. As a consequence, the call
4
MacOp(s)(s.acc) will be scheduled before s.acc > CAP. As scheduling precedences go,
the parameter lists in Blech procedure headers are analogous to the separation between input
and inputoutput signals in Esterel module interfaces. The essential difference is that Blech
parameters may be destructively updated during a macro-step, which is not possible in Esterel.
Because of that the MacOp interface also contains a sharing specification a shares ms.acc.
This tells us that the procedure implements all memory accesses to a and to the substructure
ms.acc of ms in a single thread. This makes it possible to alias the two parameters in the
activity call MacOp(s)(s.acc) of Accumulate and perform the operation instantaneously in
the same memory cell. While Esterel would detect a causal cycle, in sequentially constructive
Blech this is memory safe, because the write access s.acc.wr is sequentially ordered after
the read s.acc.rd.
The formal mechanism to control the safe instantiation of activities is an extension relation
on interfaces. The compiler checks that the memory mapping applied at the procedure call
establishes a formal extension of the interface of the callee to that of the caller. This is
indicated by the blue arrows in Fig. 1. For instance, the call MacOp(s)(s.acc) implies a
mapping [s/ms, s.acc/a] of the (formal) memory paths of the callee to the (actual) paths of
the caller. This aliasing is a proper extension because (i) the data types match up and (ii) the
interface of the callee MacOp exports a sharing between a and ms.acc.
The use of policy interfaces for procedure instantiation and scheduling likewise applies to
external procedures for which the code is not available. The external function get imported
by System returns a new, different sample value each time it is called. This would induce non-
determinism when called from concurrent threads. Therefore, it is flagged by the keyword
singleton which is part of the policy interface of get and protects it from concurrent
accesses.
Policy interfaces also actively help to resolve the scheduling order of procedure calls. For
instance, in Ctrl the memory cell arr is in the output list of procedure call Sample()(arr)
and at the same time in the input list of MAC.Accumulat(arr)(s). Assuming the data
type arr:[2]float64 specifies a regular data flow variable, we apply the “write-before-
read” causal order to eliminate data races. The “write-before-read” precedence is part of
the policy interface of memory cell arr and forces Sample()(arr) to be scheduled before
MAC.Accumulate(arr)(s). In a similar way, the three threads in Accumulate are scheduled
with the help of policy interfaces.
5
M ::“ rD1 |D2 s˚ P
D1 ::“ struct o with D` 1 | var x “ e
D2 ::“ proc ppx1 , x2 , . . . , xn q “ P
P ::“ exit k
| trap P
| P;P
| P}P
| if e then P else P
| P }y P
| let x “ o.mpeq in P
| loop P
| run ppo1 , o2 , . . . , on q
Fig. 2. Abstract syntax of SCPL main program M, declarations D1 , D2 and control-flow statements P, where k P t0, 1, 2u
is a completion code, e is a pure value expression without side effects, x a value variable, o, oi memory paths, m a method
name and p a procedure name.
assume that type checking has been done and all memory structures are statically allocated
and referenced via fully qualified names, which are called memory paths.
Control-flow is completed by the statements exit k where the completion code k P t0, 1, 2u
distinguishes three forms of continuation. The instance exit 0, also written nothing, instanta-
neously completes by termination and continues with any sequentially down-stream statements
in the current thread. The instance exit 2, called exit, completes instantaneously but returns
control to an immediately enclosing trap ... end which introduces a trap scope. The third
completion level exit 1, abbreviated pause, does not terminate but completes by pausing.
It waits for the next macro-step in which it will then terminate instantaneously. These exit
statements are well known from Esterel [6].
Sequential composition P1 ; P2 first executes P1 until it terminates and then continues with
P2 . When P1 exits or pauses, then also P1 ; P2 exits or pauses, respectively. The prescriptive
program order between P1 and P2 implements a sequentially constructive semantics differing
from traditional synchronous languages such as Esterel. In Esterel, the semicolon acts like
a parallel composition with an extra control wire that permits P2 to start only when P1 has
terminated. Since the statement blocks P1 and P2 are parallel, there may be a causality cycle
between them in Esterel. In SCPL, the program P1 ; P2 can never have a causality cycle unless
this it already exists in P1 or P2 .
In the parallel composition P1 } P2 , both processes P1 and P2 are executed to completion,
concurrently in separate threads. Their statements are interleaved according to the policy
interfaces associated with method and procedure calls as described below. The completion
level of the parallel P1 } P2 is the maximum of the completion levels of both threads.
Specifically, the parallel terminates (k “ 0) when both threads P1 terminate. It exits (k “ 2)
if one of the threads exits, though letting the other thread run to completion. Otherwise, in
an instant where one of the threads pauses (k “ 1) and the other does not exit (k “ 2), the
parallel construct also pauses. Again, both threads run to completion. This behavior coincides
with the standard semantics of synchronous parallel AND from languages such as Esterel (for
general completions k P N) or SCCharts (for completions k ď 1).
6
The construct if e then P1 else P2 expresses conditional branching of control flow. First,
the condition e is evaluated and then, depending on its value, either P1 or P2 is instantaneously
executed. In particular P1 and P2 may overwrite memory that was read by e because these
writes take place sequentially after the condition test. Notice, we ensure that the conditional e
is evaluated strictly before any of the branches P1 and P2 can start. Hence, the evaluation of
e cannot depend on any statement inside P1 or P2 . This sequentially constructive semantics
makes the conditional in Blech behave like in SCCharts rather than Esterel. Furthermore, the
branches P1 and P2 are mutually exclusive, like in SCEst. In Esterel, for contrast, a conditional
if e then P1 else P2 end can be equivalently coded as a parallel composition of one-sided
conditionals if e then P1 end } if e else P2 end. This can create causality cycles between P1
and P2 in Esterel, which do not exist in SCPL or SCEst.
The operator P1 }y P2 is a parallel OR with built-in preemption sequentialised from left
to right. We first run P1 to completion, disregarding any policy constraints between P1 and
P2 . Once P1 completes, there are three possibilities depending on its completion code. If P1
terminates or exits, then P2 is instantaneously aborted and the construct terminates or exits,
respectively. This is the strong abort behavior of the construct. If however P1 pauses in state
P11 , then control passes instantaneously to P2 which is permitted to complete the macro-step.
When P2 terminates (exits), the configuration P11 }y P2 terminates (exits) as well, thereby
weakly aborting P1 . If P2 pauses in state P12 then the construct pauses in state P11 }y P12 .
This generates a cycle-wise, round-robin sequential left-to-right evaluation which cannot be
expressed in Esterel. In SCCharts [8], which is sequentially constructive, the operator }y does
not exist but can be coded. Céu [5] does implement this operator as par/or but it does not
have any equivalent of } nor a notion of causality.
The statement let x “ o.mpeq in P calls a method m in a structure identified by memory
path o. Methods are actions on the memory, defined outside SCPL and not to be confused
with procedures. The value of expression e is the (composite) input argument of the method
and the return value is bound to the value variable x. Sequentially after the atomic execution
of o.m, which may have a side-effect on memory, control is passed to P, which may depend
on the return value x. The value of x is not stored in memory. It is thread local and its
scope is P. For example, an assignment x :“ y ` 13 where x and y are memory references
would be represented by the sequence let x “ y.rdpq in let _ “ x.wrpx`13q in nothing. For
convenience, we will abbreviate let x “ o.mpeq in P as x :“ o.mpeq; P when x is used in P
and as o.mpeq; P, otherwise.
Control flow loops are expressed as loop P. The thread P is run until it terminates,
whereupon P is repeated, instantaneously, from the beginning. We can build terminating
rules using loop and trap as we will see in the next section.
A procedure call run ppoq instantiated from a static declaration proc ppxq “ P starts the
precompiled procedure body P, passing a list o of memory paths by reference. The mapping
f “ ro{xs is subject to static typing and policy constraints as described below. The callee
p may access the memory through method calls of the given arguments. The procedure is a
generic control-flow process that is statically associated with the name p as an external host
code or a precompiled SCPL process. The procedure is run atomically until it completes by
either pausing or terminating. Procedures provide a general form of behavior abstraction in
which arbitrary control flow (subject to interface restrictions) can be encapsulated. Concurrent
instantiations run ppo1 q } run ppo2 q will execute the same program code in concurrent threads.
This is permitted provided the procedures do no create data races on the memory structures
7
Assignment x = y op z let v1 “ y.rdpq in
let v2 “ z.rdpq in
let _ “ x.wrpv1 op v2 q in
nothing
Conditional statement if c then P else Q end if c then P else Q
Loop repeat P until c end trap ploop p
P ; if c then exitqq
Sequential composition P Q P ;Q
Reaction step await c repeat pause until c end
Preemption when c abort P end await c }y P
Return from procedure return e rv = e ; exit 2
Activity declaration activity A (ι)(o) P end proc Apι, o, rvq “ trap P end
Activity call run A (o1 )(o2 ) run Apo1 , o2 q
Cobegin cobegin P with Q end P }Q
Cobegin with weak cobegin weak P with Q end pP }y await tq } pQ ; t=trueq
Fig. 3. Rewrite rules that transform a Blech program into an SCPL program. The result is obtained by applying these
rules recursively. A few remarks: In SCPL expressions are purely built on values. Therefore, assignment is decomposed into
reading memory cells, combining the obtained valued and then writing the result back into memory. Analogously, we assume
every Blech condition expression c is mapped to a boolean value c in SCPL by prepending a corresponding assignment.
If the else branch is missing, nothing is used in the else-case of the SCPL program. SCPL only has an infinite loop.
Therefore it is wrapped in a trap and a guarded exit 2 is added as the last statement to the loop’s body. Conceptually, this
construction separates control flow of the loop and the evaluation of the condition inside the loop. This makes the formal
semantics simpler. In Blech a whitespace (or semicolon) token is used to separate statements whereas a semicolon must be
used in SCPL. An SCPL procedure has only one parameter list which comprises all input and input-output parameters. A
fresh variable rv is used as the designated procedure output to store the value of expression e to be returned. By means
of a scheduling policy (Sec. VI) one can ensure that input parameters ι can only be read. Cobegin with strong branches is
directly mapped onto }. Using a fresh termination flag t we can encode a weak branch in SCPL. Symmetrically, the same
construction works if the second branch is weak or both branches are weak.
shared between o1 and o2 . To this end, each procedure p has an associated causality interface
πp , called a p-policy, which specifies its synchronization with the memory. The interface πp
is instrumental not only to avoid races but also in order to guarantee memory safe, wait-free
execution of each individual call. This will be explained in detail later.
IV. M APPING B LECH TO SCPL
SCPL is designed to be a compact formalism for studying semantics. It is not a practical
programming language in any specific domain. In this section we argue that semantics of
languages used in practice may be formally explained by mapping them to SCPL. This can
be shown for Esterel, SCEst and in particular we consider Blech [18] here.
Blech1 is an imperative, synchronous language that offers high-level abstractions and safety
guarantees for reactive, real-time embedded programming. Blech compiles to C code, which
may be integrated into existing projects or simulation frameworks. What makes Blech inter-
esting for our purposes is that it is among the first ISP languages with first-class procedures
that does not depend on white-box inlining. However, there is no formal semantics for it, yet.
As indicated in Sec. II, a simple form of policy interfaces is already built into the language
by design to support common programming patterns and guarantee an easy-to-understand
behavior.
1
blech-lang.org
8
The rules in Fig. 3 map Blech syntax to SCPL. We take the liberty to also map to a mix of
SCPL with Blech when it is clear how to further transform the intermediate term to pure SCPL.
Figure 3 only shows the essential control flow statements of Blech. More constructs are found
in our example in Fig. 1: the return being the last statement in an activity can be understood
as a one-time assignment to a special (anonymous) output parameter. Furthermore, a cobegin
with more than two branches can be rewritten as a nested cobegin with only two branches
at each level by grouping together strong and weak branches. Other constructs in Blech that
are not shown in our example, too, may be rewritten inside the language itself: A while-loop
can be rewritten as a repeat-loop. The Blech statement when c reset P end is a shorthand
notation for an abort inside a loop that restarts P if it was preempted. Blech functions can
be mapped to SCPL procedures in the same way as activities. Finally note, that Blech allows
arbitrary expressions on the right hand side of assignments or as input arguments. They can
be rewritten in static single assignment form and therefore our mapping assumes only variable
names in procedure arguments and only a binary right hand side of an assignment.
The focus of this work is to explain the operational semantics of a program. Thus we do
not consider Blech constructs that are only used for structuring a program such as do-blocks,
variable declarations, namespaces and modules. We assume these have been resolved statically
by the compiler.
V. S YNCHRONOUS ACTIVITIES
The notion of activities is central to the design of Blech and the primitive notion of a
schedulable unit of synchronous computation. The behavior of an activity lies in its (destruc-
tive, updating) side-effects on memory. By executing its side-effects in lock-step with a global
clock, an activity implements a sequence of macro-step reactions that are synchronized with
the reactions of other running activites. In each reaction step input data (stimulus) is read from
memory and output data (response) is written back. An activity in Blech can have multiple
input parameters and output parameters of different types through which it can communicate
with its calling thread. The input value is taken when the activity is started and the output
value returned when it completes. Here, for simplicity, we assume each activity takes a single
start value and returns a single output value, both taken from a fixed global domain D of
values. Since it is a black-box, once an activity is started it cannot be interrupted until is
has run to completion. Different types of completion are used to pass control to different
continuation activites through which these are chained up for sequential control flow. In this
report, activites have three ways to complete: termination, pausing or return. In each case,
upon completion, also a completion value is passed back to the calling thread which forms
the output parameter of the activity.
Definition 1 (Synchronous Activity) A synchronous activity p on stores S with (local) con-
trol states Q consists of
‚ an initialisation function p.init : D Ñ Q;
‚ a step function p.step : S ˆ Q Ñ S ˆ Q;
‚ a completion predicate óp Ď Q ˆ t0, 1, 2u ˆ D written q óp pk, vq instead of pq, k, vq P óp
for q P Q, v P D, k P t0, 1, 2u;
‚ a tick operation p.tick : Q Ñ Q.
The relation óp is a partial function in the first argument, i.e., if q óp pk1 , v1 q and q óp pk2 , v2 q
then k1 “ k2 and v1 “ v2 . A state q P Q for which óp is undefined is called incomplete.
9
The initialisation function p.initpvq “ q0 P Q selects the start control state of the activity,
depending on a start value v P D. The function p.init is called in the macro step in which
the activity is executed for the first time. From then on, unless the activity is preempted, the
step function p.steppΣ, qn q “ pΣ1 , qn1 q is used to execute a single synchronous reaction of p
from a given store Σ P S and current control state qn . The result of the reaction is to produce
an updated store Σ1 and a completion state qn1 . If we have qn1 ó pk, vq, the completion state
provides the completion value v and the completion code k indicating termination (k “ 0),
pausing (k “ 1) or return (k “ 2). When k “ 1 the activity is pausing, i.e., it synchronizes
with the global clock of the environment. All concurrent activites run in lock step and thus
must synchronize with each other on the clock tick when pausing.
Note that it does not matter whether or not the sequence of initialisation init and step
operations (in the starting macro-step) and the sequence of tick and step (in each subsequent
macro-step) are executed atomically for each running activity. This is because the effects
of the init and tick operators are isolated from each other as they only change the local
control state of the activites. Synchronous activites communicate with each other and with
the environment only through the store. While an activity is pausing, the environment can
safely update the store with new external data (e.g., from the user interface) to prepare for the
next macro step. In line with the synchronous model of execution (Synchrony Hypothesis [1],
[19], [20]), new input data are set only when all concurrent activites have completed. Thus,
external input appears to arrive simultaneously for all pausing activities. When all activities
have completed, each paused activity is advanced to the control state p.tick pqn1 q “ qn`1 from
which the next macro-step is obtained. Although the tick function is only used on paused
states with qn1 ó p1, vq, it is convenient to assume that it acts as the identity on all other states,
making tick a total function on Q.
Note that the function ó is partial. It may not assign a completion code and value to every
given control state. The control states for which completion is typically not defined are the
initial states p.initpvq P Q. These states still contain unevaluated program expressions which
only stabilise by executing the step function. For most purposes, we may assume that the
initial states are the only incomplete states. Specifically, every macro step of a precompiled
black-box activity (external procedure, external method call) must complete and yield a stable
state. The structural semantics of white-box scheduling, as introduced later, will give a more
concrete motivation for incomplete states. For now it suffices to imagine the complete states,
i.e., the control states in the domain of ó, as being analogous to the normal forms in λ-calculus
and the step function p.step as being analogous to a β-reduction normalisation procedure.
With the convention that we always apply the step function on the initial state before we
extract the completion value, we may assume that all control states are complete (simply
chose a random completion code and value in intial states). In this vein, henceforth ó is
considered a total function.
The notion of a synchronous activity as per Def. 1 is meant to capture the black-box seman-
tics of arbitrary syntactic fragements of a (sequentially constructive) synchonous programming
language like Blech [18], SCCharts [8] or SCEst [21]. The term ‘activity’ has been introduced
by Blech where the idea of a step function as a procedural side-effect on a global store has
been identified more prominently than in earlier (more academic) synchronous languages. To
be precise, Blech distinguishes between activities and functions which are two slightly more
specific forms of synchronous activities, as we define them here.
10
‚ A Blech activity is a synchronous activity p with a unique initial state and that always
either terminates or pauses on completion, but cannot return. Formally, p.initpv1 q “
p.initpv2 q for all v1 , v2 P D and if q óp pk, vq then k ‰ 2. A Blech activity also
gives informative completion values only upon termination not on pausing. This can
be captured by saying that the completion value is constant on pausing states, i.e., if
q1 óp p1, v1 q and q2 óp p1, v2 q then v1 “ v2 .
‚ A Blech function executes some side-effect on the store and instantaneously returns with
a value. A Blech function thus is the special case of a Blech activity p for which the step
function always terminates, i.e., q óp p0, vq for all q P Q. Since the step function is only
executed once on the initial state which itself does not depend on the start value, we may
assume that the set of control states is the set of values, e.g., Q “ D and v óp p0, vq.
Both activities and functions in Blech communicate through side-effects in the memory. At
the same time they can pass completion values into the calling threads and thus influence their
control-flow. If we only permit pausing (k “ 1) and remove the completion values altogether
(assuming they are constant and thus redundant) we have the standard synchronous Mealy
machine model with stores S as the input and output domain. For contrast, a pure function
is a Blech function (see above) with Q “ D and for which the step function is constant, i.e.,
f.steppΣ, qq “ pΣ, qq. In this case, the function behavior lies in entirely in the initialisation
f.init : D Ñ D. Pure functions cannot communicate through memory but must pass values
explicitly and sequentially in the calling thread which calls one function and then passes the
completion value as argument to another function. In this way, the semantics of synchronous
activities includes the classical pure functional model of λ-calculus.
In our kernel language SCPL we use two other special forms of synchronous activities,
called procedures and methods. These still capture the essence of Blech activities and Blech
functions, yet permit somewhat simpler operator syntax.
‚ An SCPL procedure is a synchronous activity that can terminate or pause and does not
have start or completion values. In other words, a procedure is a Blech activity without
completion values, or equivalently a synchronous Mealy machine with termination. A
procedure declaration proc prx1 , x2 , . . . , xn s “ P defines a global activity p operating in
a generic memory context that is introduced by the formal path parameters x1 , x2 , . . . , xn .
It can be instantiated in procedure calls run pro1 , o2 , . . . , on s for different actual mem-
ory paths o1 , o2 , . . . , on . Since procedures are global, their names are absolute and not
qualified by memory paths.
‚ An SCPL method m is similar to a Blech function2 , i.e., a synchronous activity for
which the step function always terminates. We sometimes call such synchronous activities
instantaneous, because they return to the calling thread in the same macro-step (tick)
in which they are called. In contrast to a Blech function which is also instantaneous, an
SCPL method is treated as primitive while Blech functions are declared generically like
procedures. Typical examples of methods in our sense are the implicit read and write
accesses to memory variables in the expressions and assignments of the source-level
syntax. These are not user-defined but built-in by the run-time system. In the syntax of
the source level language, methods might also be declared as complex access methods
along with abstract data types. An important point to note is that method calls operate
2
We use the term ‘method’ in analogy with the methods in object-based programming. This stresses the side-effect on
the store and prevents any confusion with pure functions that are side-effect free.
11
in statically fixed memory locations. This is the reason why we are able to resolve all
memory aliasing and scheduling causality between procedure calls in our semantics.
We are now going to introduce our semantics of memory accesses bottom up. We start
with the built-in semantics for method calls in the following Sec. VI. Procedures are declared
as processes that execute individual method calls in sequential and concurrent control-flow.
Their semantics will be induced by the semantics of methods calls via the structural operation
semantics discussed in Sec. VII.
VI. T HE SCPL M EMORY M ODEL
A SCPL program is executed in a store structured into a system of memory contexts. These
constitute a name space to access and manipulate the data stored in the memory cells of the
store. A context is a set of typed memory paths O. For each o : t P O a set Mtd rts of context
methods is determined by the path type t, which is assumed to be statically fixed. Each
path has exactly one type, i.e., if o : t1 P O and o : t2 P O then t1 “ t2 . The memory
paths are the entry points into the shared data store, providing a primitive notion of locality
and separation. The set of qualified methods (qmethods) available in a memory context is
Mtd rOs “ to.m | o : t P O, m P Mtd ptqu.
Example 1 The memory paths O “ tn : int, b : bool, p : point3D, . . .u may be typed refer-
ences to scalar memory cells such as integers, booleans or composite data structures such
as point objects. The access methods on scalar memory will typically be
Mtd rints “ trd : unitÑint, wr : intÑunitu
for reading and for writing and
translate_x : floatÑunit P Mtd rpoint3Ds
for translating the point object in x-direction. Substructures of composite data can be modelled
either through memory paths or via methods. For instance, take a method call p.x.wrpvq
for writing the x coordinate of p. We may consider p.x : float32 P O as a path and
wr : float32Ñunit P Mtd rfloat32s as the method. Alternatively, we can take p : point3D P
O as the path and x.wr : float32Ñunit P Mtd rpoint3Ds as the method.
Example 2 The memory paths used by MacOp (see Fig. 1) at its declaration-site, are
OMacOp “ tms : State, a : float64u
where a is a scalar memory cell of type float64 and ms a composite data structure of type
State. The access methods for the scalar a are
Mtd rfloat64s “ trd : unitÑfloat64, wr : float64Ñunitu
for reading and for writing a double precision floating point number. The members of the
composite structure ms : State can be modelled through methods or via memory paths. In
context OMacOp a method call ms.m1.rdpq is composed of path ms : State P OMacOp and method
m1.rd : unitÑfloat64 P Mtd rStates. Alternatively, in context
1
OMacOp “ tms.m1, ms.m2, ms.acc, au
12
the path is ms.m1 and rd : unitÑfloat64 P Mtd rfloat64s is the method. In both cases,
we get the same set of qmethods, i.e., Mtd rOMacOp s “ Mtd rOMacOp
1
s “ tms.m1 .m2 | m1 P
tm1, m2, accu ^ m2 P trd, wruu. Furthermore, we may also have a special method
mac : float64Ñfloat64Ñfloat64Ñunit P Mtd rStates
for computing the MAC operation, taking as inputs the substructures of State determined
by their fixed order.
Memory paths can reference standard simple data structure like signals, variables, buffers or
composite structures like vectors or arrays. It may also encapsulate complex behaviors such
as priority queues, external input-output devices such as displays or precompiled reactive
modules.
Example 3 In the Cronos modular compiler [9] an Esterel module m : module P O is com-
piled as an ADA package where Mtd rmodules contains accessor methods x.set:unitÑunit
to set each input signal x, methods y.rd:unitÑbool to read each output signal y, partial step
evaluation functions i.eval:unitÑunit for modular scheduling and methods init, reset,
run, clear all of type unitÑunit for executing the module as a whole.
For most of this report, it suffices to consider a factorisation of memory actions through
“triple qualifications” o.mpvq consisting of memory paths o : t P O, context methods m P
Mtd rts and start values v P D. In general, a memory context will appear as a tree-like name
space with overlap, i.e., some memory paths may be aliases of other paths of the same type.
The tree structure is represented in Mtd rOs by iterated qualifications o1 .o2 . . . . .on generated
by nested struct types. To allow for potential memory aliasing, the memory context comes
equipped with a binary sharing relation # of qmethods Mtd rOs. The condition o1 .m1 # o2 .m2
states that the qmethods o1 .m1 and o2 .m2 have overlapping memory locations. This indicates
that their side effects potentially conflict with each other, and thus their relative order of
execution must be kept observable to maintain determinate program behavior. The dual is the
relation ˛ of insulation, so that o1 .m1 ˛ o2 .m2 means the two methods operate in separated
memory locations and can be executed in any (uncontrollable, unobservable) order. In our
notion of precedence policy defined below (Def. 4) we will refine these relations in terms
of a precedence relation o1 .m ą o2 .m2 . We will not consider how the static structure on
Mtd rOs is introduced through the typing of data structures and possibly aliasing arising from
memory references. As mentioned before, we assume this has been resolved by the compiler
in the upstream static program analysis. Like the procedures, the methods Mtd rOs of a store
are provided by the compiler and implemented externally as black-box synchronous activites.
The following definition of an execution structure (Def. 2) gives a compact algebraic
reformulation of the semantics of method calls as synchronous activities (Def. 1). Furthermore,
it adds the necessary structure to reflect the dependency of methods on memory path contexts.
Definition 2 (Execution Structure) An (execution) structure S “ pS, ‚, dq is a domain S of
memory configurations, called (typed) stores together with memory actions ‚ and d. Each
Σ : O P S has an associated path context O defining the methods applicable to the store Σ:
For each o.m P Mtd rOs the operation Σ ‚ o.mpvq P D yields the return value of o.m when
called with parameter value v P D and Σ d o.mpvq : O P S is the updated store in the same
context.
The operators ‚ and d encapsulate the step function of the instantaneous synchronous
activity o.m. Method calls have trivial control states Q “ D for coding the start and completion
13
values. The initialisation function introduces the method call’s start value v into the control
state, o.m.initpvq “ v. From this initial state, the step function operates as o.m.steppΣ, vq “
pΣ1 , v 1 q such that Σ1 “ Σ d o.mpvq and v 1 “ Σ ‚ o.mpvq. The new control state is terminated,
v 1 óo.m p0, v 1 q exposing the completion value v 1 to the calling thread. The tick function for
as method call is redundant and never executed. E.g., we may stipulate o.m.tick pvq “ v.
The domain of stores needs to be uniform in the sense that the semantics of a method o.m
for o : t P O only depends on the type t not on the path name o. It must be invariant under
type-preserving renamings of memory paths, called path maps.
Definition 3 (Path Map and Uniformity)
‚ A function f : O1 Ñ O2 is a path map if f po1 : t1 q “ o2 : t2 implies t1 “ t2 . We
extend path maps f to qmethods and method calls by putting f po.mq “ f poq.m and
f po.mpvqq “ f poq.mpvq.
‚ The execution structure pS, ‚, dq is (path) uniform if for every Σ2 : O2 P S there exists a
store Σ2 rf s : O1 P S with
– Σ2 rf s ‚ apvq “ Σ2 ‚ f paqpvq and
– Σ2 rf s d apvq “ pΣ2 d f paqpvqqrf s
for every a P Mtd pO1 q.
A uniform structure permits us to retract a store Σ2 over O2 along a path map f : O1 Ñ O2
to a store Σ2 rf s which emulates o.mpvq in O1 by indirection via f , executing the renamed
call f poq.mpvq in O2 . If P is a SCPL process in context O1 , i.e., all methods calls in P are
from Mtd rOs, then we will write P rf s for the process P in which all methods calls have
been renamed by f . The relocated process P rf s is then executing in stores for context O2 .
Example 4 Consider the path context O “ tx, y, zu and processes
P “ y.wrp8q;x.wrp5q;u:“y.rdpq;pA}Bq
A “ if u “ 5 then y.wrp0q
B “ z.wrp2q.
Let Σ : O be a store which implements x, y, z in disjoint memory cells. Then, the value that
P reads from y is u “ 8, whence the write to y in A is by-passed and P only writes to z in
B. Consider the relocated process
P rf s “ x.wrp8q;x.wrp5q;u:“x.rdpq;ppif u “ 5 then x.wrp0qq}Bq
with path map f “ rx{x, x{y, z{zs : O Ñ O where y is substituted by x while x and z remain
fixed. In functional notation this is f pxq “ x “ f pyq, f pzq “ z. Now the value read into
variable u is 5 and thus x is overwritten by 0. The aliasing of cells x and y through f has
changed the behavior of P . If the execution structure is uniform, then the behavior of P rf s
in a store Σ can be simulated with P in an “aliasing store” Σrf s in which all actions on
path y are redirected to take place in x.
Aliasing relocations may not only change the semantics of a process, they may also
introduce non-determinism. Observe that both processes P and P rf s are deterministic and
do not show any data races, because the concurrent threads A } B and Arf s } Brf s do
not share memory. For the path map f 1 “ rx{x, x{y, x{zs : O Ñ O, however, the relocation
P rf 1 s creates a write-write conflict, because now both thread A and B concurrently write to
14
x. In synchronous programming, processes P and P rf s would be considered constructively
schedulable, while the process P rf 1 s is rejected as being non-constructive.
15
Every store must be protected by a scheduling policy which implements a set of causal
precedences on the memory accesses. The semantics of SCPL (Sec. VII) will execute the
actions of a program in line with these precedences. Here we use a simple algebra of
causal precedence graphs which express state-less zation protocols. These eliminate the data
dependence from the notion of policies as defined in [14]. This simplification makes policy-
conformant schedulability a property of the static program syntax. Although this seriously
restricts the class of constructive programs from a theoretical point of view, it is a typical
restriction adopted for practical synchronous programming languages.
Definition 4 (P-policy) A precedence policy (p-policy) over O is a subset π Ď Mtd rOs of
admissible methods equipped with a binary precedence relation ą Ď π ˆ π. We use the
following special notation to specify p-policies:
‚ If a, b P π and a ą b, we write π , a ą b.
These precedences can be used for deterministic scheduling. For instance, in the parallel
composition x :“ 5 } y :“ x the assignment x :“ 5 with its implicit x.wr access is executed
before the assignment y :“ x which contains a read access x.rd on the same variable.
Obviously, the causal precedence var , x.wr ą x.rd eliminates any write-read data races.
Write-write races are eliminated by the (symmetric) causal precedence var , o.wr ą o.wr
which forbids concurrent threads to write to the same path. Thus, a program like x :“
5 } x :“ 7 becomes unschedulable, and thus is rejected as non-causal. Note, in line with
sequential constructiveness [8] the precedence constraint only applies to concurrent accesses.
If the accesses are already resolved in sequential program order, as in y :“ x ; x :“ 5 or
y :“ x }y x :“ 5, then the read x.rd takes place before the write x.wr. Similarly, sequentially
ordered writes as in x :“ 5 ; x :“ 7 or x :“ 5 }y x :“ 7 are ok.
Finally, note that var does not contain precedences between accesses to x and y, because
these are separated in memory and thus var , x.m1 ˛ y.m2 for all methods m P trd, wru.
16
Example 9 If composite memory structures o : t P O (e.g. Quartz [2] signals) behave like
synchronous registers, they support the four methods Mtd rts “ tpre, wr, rd, nxtu where
o. pre extracts the value from the previous instant, o. nxt writes the value for the next instant,
while o.rd and o.wr are the read and write access to the value computed in the current
macro-step. In the high-level syntax of Blech, composite memory structures are instantiated by
declarations rvar | lets o : t “ e, where is t a structure type and e an expression determining
a composite (deep) initialisation value. A memory path o tagged with var is mutable and
if tagged by let is immutable. In the latter case, the structure is protected so the write
methods o.wr and o. nxt are not permitted. Hence Mtd rlet ts “ tpre, rdu if o is immutable
and Mtd rvar ts “ tpre, rd, wr, nxtu if o is mutable. The associated p-policy reg has all
Mtd rOs admissible, implements the “write-before-read” causality on the current value of the
structure and eliminates write-write races. Formally, for all o1 , o2 P O, if o1 and o2 overlap,
reg , o1 .wr ą o2 .wr, reg , o1 .wr ą o2 .rd reg , o1 . nxt ą o2 . nxt. The reading of the
previous value is independent from any other access, i.e., reg , o1 . pre ˛ o2 .m2 and also
reg , o1 .m1 ˛ o2 .m2 for all non-overlapping paths o1 and o2 .
Example 10 For a Cronos module m as described in Ex. 3 we build a p-policy πm to express
the internal coupling of methods as side-effects on the state, in order to protect the module
from being used incoherently. Specified as a precedence graph, the p-policy looks as follows:
reset
init x.set run y.rd
clear
The initialisation init must be called before anything else. Then, clear or reset to reset
inputs and registers can be made. In the third phase, the signal emissions x.set on inputs
are scheduled, and only then the step function run can go ahead. When the step function is
done, the output signals can be read with methods y.rd.
The precedence graph constrains concurrent accesses from producing memory glitches.
E.g., note that the run method cannot be called concurrently, because it modifies the internal
state in arbitrary ways. This is prohibited by the self-loop πm , run ą run in the precedence
relation. All other methods are without self-loops and can be called several times concurrently,
because their effect is idempotent. This is specifically important for the setting of a signal
x.set which may be called by several concurrent threads, in general.
The policy in Ex. 10 is an extended form of the init-update-read protocol (iurq of sequen-
tially constructive (SC) variables introduced in [22] and used in SCCharts [8]. Specifically, an
SC variable s (of any data type) supports the method s.ini of initialisation (called “absolute”
writes), update s.upd (called “relative” writes) and reading s.rd. The associated init-update-
read p-policy iur has the precedences such that
iur , ini ą ini
iur , ini ą upd,
iur , upd ą rd
Hence, initialisations ini can only be performed by a single thread, updates upd must wait
for initialisation but can be concurrent, and finally reads rd, too, can be concurrent but must
wait for both ini and upd. The iur scheduling regime is itself a special case of a scheduling
directive [23] which in turn is a special state-less case of a general policy [14].
17
The purpose of a p-policy is to ensure that all conformant schedules generate a deterministic
response on the store in all macro-steps. For this to be true, the store must be coherent for
the p-policy. The store together with the p-policy make up a memory interface.
Definition 5 (Memory Interface & Coherence) A pair C “ pO, πq consisting of a context
O and a p-policy π is called a (memory) interface. A store Σ : O P S is C-coherent if all method
calls concurrently independent under π commute with each other. Formally, let a, b P Mtd rOs
with π , a ˛ b. Then, for all u, v P D:
1) Σ ‚ apuq “ pΣ d bpvqq ‚ apuq
2) Σ d apuq d bpvq “ Σ d bpvq d apuq.
Moreover, for all a P Mtd rOs and v P D the updated store Σ d apuq : O P S must remain
C-coherent.
Def. 5 is an extension of [14]. Condition (1) guarantees that the value returned by a method
call apuq does not depend on whether it is executed before or after a concurrently independent
call bpvq. Condition (2) expresses that final store does not depend on the order in which both
calls are conducted.
Example 11 The standard implementation of atomic read and write on a memory cell is
coherent for the p-policy var. To implement reg coherently we need three memory cells, viz.
to store the previous, current and the next value. This is expensive yet permits many causal
programs. A tighter p-policy sreg could add extra precedences sreg , o1 . pre ą o2 .wr,
sreg , o1 .wr ą o2 . nxt and sreg , o1 .rd ą o2 . nxt for overlapping o1 , o2 . This interface
can be implemented coherently with a single memory cell. However, the extended policy sreg
is less concurrent and may leave some programs non-constructive. If a program, written
under the reg memory interface, turns out to be constructive for sreg, then the registers can
be optimised and implemented under var, by identifying the method nxt with wr and pre with
rd.
Definition 6 (Interface Extension) An extension f : C1 Ď C2 of interfaces Ci “ pOi , πi q is
a path map f : O1 Ñ O2 such that if a, b P π1 then f paq, f pbq P π2 and if π2 , f paq ą f pbq
then π1 , a ą b.
If the extension is an inclusion f : O1 Ď O2 of paths, then we drop f and write C1 Ď C2 ,
or simply π1 Ď π2 . In this case π1 enforces a more restrictive scheduling on a subset of the
qmethods from π2 , without any renaming of paths.
Example 12 The discrete p-policy top contains all method calls Mtd rOs without any ą
edges. It is the maximum element, i.e., π Ď top for every π. The p-policy top permits
unconstrained concurrent access to the store. The empty p-policy bot “ H admits no actions
and represents a locked store, disallowing any memory access. It is the minimum element
satisfying bot Ď π for all π. The indiscrete p-policy seq is the fully connected ą relation
on Mtd rOs. It specifies a store that can only be accessed by a single thread. The set of
p-policies π Ď seq is isomorphic to the set of (fully connected) subsets of Mtd pCq. We have
bot Ď var Ď sreg and bot Ď seq Ď sreg Ď reg Ď top.
Procedures are abstractions of control flow to manipulate the store. They encapsulate pro-
gram modules generic in their memory context which is abstracted through context parameters.
An procedure is accessed through its name and instantiated to a concrete memory context
like a module in Esterel or a node in Lustre. In contrast to Esterel or Lustre, however, the
memory that can be shared through concurrent procedures can be of general type and is not
18
restricted to signals, buffers or data flow variables. In order to achieve modularisation, we do
not depend on the source code of an procedure to be available. For our evaluation semantics,
an procedure is a black-box that is precompiled like a host procedure in Esterel. However, in
contrast to Esterel, an procedure has an associated causality interface.
Each generic procedure p comes with a memory interface Cp “ pOp , πp q in which πp exports
the admissible qmethods through which the memory is potentially accessed and how these
on the other hand, will usually define a tree of objects which this using the shar
are causally dependent on each other. Interface extensions (Def. 6) is the key mechanism to
we would have to walk over to create a deep copy. In order to these locations are
instantiate procedures (see below). As an interface, the p-policy πp embodies an assumption-
exclude this performance pitfall, we generally have the rule: Consequently, the c
guarantee contract between the external use context and the internal behavior of the procedure.
Outside at the call site, “no
the prevs
causalontype
refs!”
implies a coherence constraint on the store forrun the add(x, sum)
procedure to be safely instantiated. Inside VIII. of the Sprocedure,
HARING DATA the causal type constrains the
is allowed now. S
scheduling of memory accesses by the encapsulated implementation code. For instance, if
So far, we must
πp , b ą c, then the implementation have guarantee
always assumed
that anythat an output
concurrent callparameter theys can be access
to c P Mtd rO p
of a subprogram
happens after any concurrent call to b P Mtd is rO
distinct from any other formal parameter, of “overlapping” a
p s. The store then does not need to be protected
against any data races i.e. the two
arising fromnames
the bnever
and cpoint
typesto of
theactions.
same memory
On thelocation.
other hand,respect
if the sharing
πp , b ˛ c, then there is This permits concurrent
no precedence information access
aboutto how
thosethe
parameters
procedurewithin the
will execute the IX. O
method calls. Because ofsubprogram
the potentialand
racealso guarantees
situation, the programmer
this amounts that writing
to the coherence assumption
an output
on the store that the order parameter
of execution of b does
and cnot alter
does notother parameters.
matter. The storeOn The Blech to C c
the protect
must
the accesses in order to calling side, this
stay coherent, assumption
in which case therestricts the callerissince
implementation free toany is able to translate t
schedule
two arguments
the accesses in any way it wants. must represent completely disjoint memory value types. Compl
locations (unless they are both inputs). Consider the follow- process. Some of th
Example 13 Every activity in Blech is a SCPL procedure. Consider the activity add as seen
ing activity which sums two numbers in every reaction and prerequisite for an
in Fig. 4. It consists of two threads. The first thread waits on variable b and terminates
concurrently checks its second parameter for some threshold. possible to call e.g
as soon as it has a value greater than 10. The second thread concurrently reads b and
When a Blech program. T
variable a and writes the sumtheof threshold is exceeded,
their values the weak
into an output branchs.isThe
variable aborted
parameters
and the whole cobegin block terminates. of Blech in an ind
activity add(a: int32, b: int32)(s: int32) X
cobegin
await b > 10 A special thank
with weak invitation to contr
repeat We also thank the
s = a + b
Andrew, Jens Bran
await true
end Simon Wegendt fo
end
end
Fig. 4. An activity add in Blech syntax. The example is taken from [18]. [1] Joaquı́n Aguado,
// ... in main activity ... and Reinhard von
var sum: int32
in the Blech interface by default
var x:are var type
int32; varmemory paths, so the available methods are synchronised
y: int32 Programming Lan
sha
Mtd rOadd s “ ta.rd, a.wr, b.rd, b.wr, s.rd, s.wru. The split parameter lists of the Blech in- Programming, ESO
terface however indicate that , b are read-only,
run aadd(x, y)(sum) while
// OK, s isall
a mutable output. Hence the ences on Theory a
distinct
p-policy is πadd “ ta.rd,runb.rd,add(x,
s.wr ą x)(sum)
s.wr, s.wr //ą sOK, Ď var which guarantees
.rdu overlapping inputs that Greece, April 14-2
in Computer Scien
the add is not writing a orrunb at
add(x, sum)(sum)
all, possibly writing//s, error
but never concurrently, and also
[2] Mustaque Ahama
writes s before any concurrent
The last reading
call usesofsum
a. A tighter p-policy, exposing the fact that add Phillip W. Hutto.
in both the input and output lists. This
is not reading s at all, and only writing it in the same thread in which it reads a is programming. Dis
is not possible in general, because our separate compilation [3] Albert Benveniste
˚
πadd “ tb.rd, a.rd ą s.wr, s.wr ą s.wr, s.wr ą a.rdu Ď πadd . Also, note that πadd and thus
will compile the activity without knowing how it will be used. cation of interface
πadd are extensions of each of the var p-policies for variables a, b, and s individually.
˚
programs. In Pro
Thus the chosen sequentialisation of the cobegin-block may and Control, CDC
be fixed arbitrarily, in particular the check b > 10 may be 7252–7258. IEEE
done before an iteration of the loop. This is not a problem as [4] Albert Benveniste,
Paul Le Guernic,
long as a, b and s are pointing to disjoint memory locations. 12 years later. Pro
If they do not, as in the last call, the threshold check is done [5] Jens Brandt and K
on sum before a new value for sum is computed, which is not programs. In H
causally correct. 19 Software and Com
France, April 23 -
Sometimes however the programs are less restrictive. For [6] Jean-Louis Colaço
formal language f
Example 14 Reconsider the activity Accumulate from Fig. 4. It consists of three threads. The
first thread adapts the inputs from arr to the factors m1 and m2 of ms. The third thread waits on
path ms.acc and terminates its value is greater than CAP . The second thread concurrently
reads b and variable a and writes the sum of their values into an output variable s. The
parameters in the Blech interface by default are var type memory paths, so the available
methods are Mtd rOadd s “ ta.rd, a.wr, b.rd, b.wr, s.rd, s.wru. The split parameter lists of
the Blech interface however indicate that a, b are read-only, while s is a mutable output.
Hence the p-policy is πadd “ ta.rd, b.rd, s.wr ą s.wr, s.wr ą s.rdu Ď var which guarantees
that the add is not writing a or b at all, possibly writing s, but never concurrently, and
also writes s before any concurrent reading of a. A tighter p-policy, exposing the fact that
add is not reading s at all, and only writing it in the same thread in which it reads a is
˚
πadd “ tb.rd, a.rd ą s.wr, s.wr ą s.wr, s.wr ą a.rdu Ď πadd . Also, note that πadd and thus
πadd are extensions of each of the var p-policies for variables a, b, and s individually.
˚
A procedure call run prf s takes a procedure p with interface Cp and an interface extension
f : Cp Ď C to evaluate its (precompiled) behavior in store Σrf s for declaration context Cp for
a store Σ of the call context C. The conditions of Def. 6 make sure that the procedure call is
memory safe, even if the renaming f aliases memory paths.
Example 15 Consider contexts OAccumulate and OMacOp from Fig. 1 such that
OAccumulate “ tarr : r2sfloat64, s : Stateu
OMacOp “ tms : State, a : float64u
with admissible methods
Mtd rr2sfloat64s “ tr0s.rd, r0s.wr, r0s.rd, r1s.wru
Mtd rStates “ tm1.rd, m1.wr, m2.rd, m2.wr, acc.rd, acc.wru
Mtd rfloat64s “ trd, wru.
The split parameter lists of Accumulateparr : r2sfloat64qps : Stateq indicate that arr is read-
only, while s is mutable. The p-policies implied by this procedure header are given by
πAccumulate “ tarr.r0s.rd, arr.r1s.rd,
s.m.wr ą s.m.wr, s.m.wr ą s.m.rd | m P tm1, m2, accuu
πMacOp “ tms.m.rd, a.wr ą a.wr, a.wr ą a.rd | m P tm1, m2, accuu.
The definition of πMacOp implements the interface for a procedure header MacOppmsqpaq and ig-
nores the shares annotation in the actual procedure declaration MacOppmsqpa shares ms.accq
of Fig. 1 for the moment. Hence πMacOp expresses the concurrent independence
πMacOp , a.wr ˛ ms.acc.rd.
This turns invalid through the instantiation run MacOppsqps.accq in Accumulate with the path
map f “ rs{ms, s.acc{as which aliases the formal procedure parameters ms and a to point
to overlapping memory cells f pmsq “ s and f paq “ s.acc. Because of this, the path map f is
not an extension from the interface CMacOp “ pOMacOp , πMacOp q under which MacOp is declared
and implemented into the interface CAccumulate “ pOAccumulate , πAccumulate q under which the call
is executed. In fact, f : CMacOp Ď CAccumulate because πAccumulate , f pa.wrq ą f pms.acc.rdq
but πMacOp . a.wr ą ms.acc.rd, considering that f pa.wrq “ s.acc.wr and f pms.acc.rdq “
20
of a subprogram is distinct from any other formal parameter, of “overlapping” a
i.e. the two names never point to the same memory location. respect the sharing
This permits concurrent access to those parameters within the
IX. O
subprogram and also guarantees the programmer that writing
an output parameter does not alter other parameters. On the The Blech to C c
calling side, this assumption restricts the caller since any is able to translate t
s.acc.rd. The situation istwo arguments
resolved musttake
if we now represent completely
into account disjoint
the sharing memoryms.acc
‘a shares value
’. types. Compl
This enforces the missinglocations
precedence (unless
πMacOpthey
, aare
.wr both
ą ms inputs).
.acc.rd Consider the follow-
in the procedure’s process. Some of th
interface.
ing activity
Thus, f becomes an interface whichatsums
extension, two that
the cost numbers
MacOpinmustevery and prerequisite for an
reaction synchronize
internally
accesses to a and ms.acc. concurrently checks its second parameter for some threshold. possible to call e.g
When the threshold is exceeded, the weak branch is aborted a Blech program. T
Example 16 The generic interface πadd of the procedure add of Ex. 13 captures the coherence
and the whole cobegin block terminates. of Blech in an ind
assumptions on the store for generic formal parameters a, b and s. We do not know how they
are related, since these will be determined
activity add(a:only at the b:
int32, call int32)(s:
site of add. In particular, the con-
int32) X
current independence πadd cobegin
, b.rd ˛ s.wr turns invalid if both parameters get aliased through
await b >we10 A special thank
the instantiation. For instance, suppose instantiate add in a call iadd “ run addpa, cqpcq
with weak
where a and b are the same memory invitation to contri
repeat cell c. The corresponding renaming f “ ra{a, c{b, We c{ss
also thank the
is not a extension f : pO, πadd qsĎ=pO, + qb from the interface under which add is declared
a var
and implemented (p-policy πadd )await into thetrue
interface under which the call is executed (p-policy Andrew, Jens Bran
var). This is because var , cend .wr ą c.rd but πadd . s.wr ą b.rd, although f ps.wrq “ Simon c.wr Wegendt for
end
and f pb.rdq “ c.rd. This has practical consequences for modular compilation: If the code
for add is pre-compiled,end rather than inlined, the method call addpa, cqpcq is unsafe and must
be rejected. The generic interface of addp
πadd main a, bqpsq does not constrain the order in [1] the Joaquı́n Aguado,
// ... in activity ...
reading of b and the writing var ofsum:s. This
int32 will not matter, provided the memory referred to and Reinhard von
synchronised shar
by b and s are separated.var However, when bvar
x: int32; andy: s are
int32 instantiated with memory that are Programming Lan
overlapping, then the ordering matters. Then the call addpa, cqpcq may produce unpredictable Programming, ESO
run add(x,
results with externally precompiled y)(sum)
scheduling. // OK,
Suppose on all hand, the concurrent ences on Theory a
distinct
the other
Greece, April 14-2
body of addpa, cqpcq is inlined and we are scheduling the code as white-box ininputs
run add(x, x)(sum) // OK, overlapping the use context. in Computer Scien
run add(x, sum)(sum) // error
Then, the (in this case unique) schedule will be chosen that satisfies the “write-before-read” [2] Mustaque Ahama
policy var on cells a andThe c. last call uses sum in both the input and output lists. This Phillip W. Hutto.
programming. Dis
There are three ways isfornotan possible in general,
unsafe procedure callbecause
run prf ours to separate compilation
become memory safe. First,
[3] Albert Benveniste
as in standard synchronouswilllanguages,
compile thesuch activity without
as Esterel , theknowing
procedurehowisitinlined
will beasused.
white-box. cation of interface
programs. In Pro
Thuschanges
Second, the calling context the chosen sequentialisation
to another store, which of the cobeginfor
is coherent -block mayrelaxed and Control, CDC
a more
be fixed arbitrarily, in particular the check
p-policy. Third, the implementation of p is forced to avoid potential memory glitches b > 10 may be with a 7252–7258. IEEE,
more restrictive p-policydone
like before
addingan iterationconstraint.
a sharing of the loop. This is not a problem as [4] Albert Benveniste,
Paul Le Guernic,
long as a, b and s are pointing to disjoint memory locations.
Example 17 For the instantiation cq in Ex. 16 to remain conformant with var 12 years later. Pro
, cqplast
If they do not, run as
addp inathe call, the threshold check is done [5] Jens Brandt and K
we would need an inter-path precedence s.wr ą b.rd for the body of add, i.e., make sure
on sum before a new value for sum is computed, which is not programs. In H
that the read of cell b is guaranteed to see the effect of writing s. The code above in Fig. 4 Software and Com
causally correct.
does not achieve that, obviously. For contrast, consider the modified code add1 in Fig. 5 France, April 23 -
Sometimes however the programs are less restrictive. For [6] Jean-Louis Colaço
which implements a similar (but not the exactly same) behavior as add in a single thread.
example, consider an activity that just adds two numbers. formal language f
paper). In Frédér
activity add1(a: int32, b: 11th International
int32) (s shares a, b: int32) neering, TASE 201
repeat pages 1–11. IEEE
s = a + b [7] Roberto Lublinerm
await true code generation fr
size. In Zhong Sha
end 36th ACM SIGPLA
end Languages, POPL
pages 78–89. ACM
Here Fig.
it does not matterBlech
5. Single-threaded whether a, b and s represent the
activity add1.
[8] Dumitru Potop-Bu
same location or not. The programmer explicitly declares piling Esterel. Spr
There are no concurrent accesses so that the procedure interface πadd1 can expose the
constraint πadd1 , s.wr ą b.rd. In the Blech code of Fig. 5 this is expressed via the sharing
constraint ‘s shares a, b’ in the interface of add1. Now the instantiated activity iadd1 “
21
run add1pa, cqpcq has πiadd1 , s.wr ą s.rd. Hence, we have conformance of πiadd1 with the
policy of cell s, despite the aliasing. And of course, the instantiation add11 is thread safe, for
trivial reasons. The activity add1 exports sharing information in its formal causality interface.
The bi-directional precedences in the generic interface expose the esssential single-threaded
nature of the implementation code. After the aliasing instantiation as a method call all read
accesses are protected by the causality order.
is an inductive relation specified by the set of structural operational rules in Fig. 6 and 7. The
relation (1), which preserves closedness and well-formedness, reduces P in the environment of
a store Σ, resulting in an updated store Σ1 and a residual process P 1 . Like in other synchronous
constructive semantics [7], [14], [24], the evaluation environment contains a potential Π “
can s pEq Ď Mtd pCq which over-approximates the set of memory accesses pending in the
concurrent environment E in which the evaluation of thread P is taking place. This context
is used to block P from accessing memory cells that might be changed by the environment.
The function can s pEq is explained in the Appendix A. It is defined along very similar lines
as in [14], [24], [25].
All the memory accesses of an sstep stem from the main thread P and its descendants in
strict program order. It runs the main thread along an arbitrary number of memory accesses.
Since this does not include any context switches we call (1) a “sequential” reduction step.
22
For communication between any concurrent child threads active in P , several ssteps must
be chained up. The definition of an sstep, as given below, is somewhat more complex in
comparison to more standard small-step or big-step semantics. It is however technically
expedient, because it nicely separates the sequential steps of a thread from the concurrency
control of the scheduler. The reduction (1) may be thought of as a multi-step evaluation of
expression P , similar to a sequence of β-reductions in the λ-calculus. Like β-reduction, our
evaluation reduction (1) is confluent and thus defines a unique outcome.
When P is a purely sequential process, i.e., it does not contain any concurrency operators
(}, }y) and it executes on its own, then Π “ H and the sstep reduction (1) will run P to
completion, i.e., until it terminates, pauses or exits. Thus, for sequential processes without
other concurrent threads competing for memory, the reduction (1) behaves like a big-step
evaluation for a single macro-step. In general, where P is concurrent or runs in a non-empty
environment Π ‰ H, the continuation P 1 in (1) either has completed or is waiting at a method
or procedure call. In the latter case, if the reduction is maximal, then each active thread in
P 1 is blocked by the potential Π. For instance, a read x.rd in P must wait for all concurrent
writes to the same cell x to have taken place, before it can go ahead. Thus, if the potential
indicates a pending write, x.wr P Π, the evaluation step (1) will block at the read.
Definition 7 (Completed Process) A process P is k-complete for k P t0, 2u if P “ exit k.
A process P is 1-complete if one of the following holds:
‚ P “ exit 1
‚ P “ Q ; R and Q is 1-complete
‚ P “ Q ‹ R with ‹ P t}y, }u and both Q and R are 1-complete.
We write P ó k if P is k-complete and P ó K, otherwise.
The completed processes are the abstract values or normal forms of our evaluation system.
The iteration of sstep (1) performs an evaluation of the surface behavior of a process, i.e.,
until it becomes completed. If the residual is 1-completed, the process has paused, and we
can apply a tick operator to activate the next macro-step, called its depth behavior.
Definition 8 (Tick Function) For 1-completed processes the (syntactic) tick function steps
the process to the next tick:
tick pexit 1q “ exit 0
tick pQ ; Rq “ tick pQq ; R
tick pQ } Rq “ tick pQq } tick pRq
tick pQ }y Rq “ tick pQq }y tick pRq.
We now discuss the SCPL reduction rules for the inductive relation (1) in two charges
as seen in Fig. 6 and Fig. 7. All the rules are defined for an implicit interface C “ pO, πq
and execution structure S “ pS, ‚, dq. The statement P ó K abbreviates the condition Dk P
K. P ó k, extending somewhat the notation of Def. 7 for conciseness of presentation.
Expressions: We assume the evaluation of expressions is side-effect free and does not
involve any memory accesses. Therefore, the evaluation of sub-expressions can be done in
any order without causing potential non-determinism. Other than that, we do not restrict the
language of expressions, merely assume the existence of an evaluation relation eval peq P D
for closed expressions defined in some external fashion.
23
Completion & Branching: The statements exit k immediately complete. The evaluation
of a conditional statement if e then P else Q is standard. By construction, the expression
e is closed, so that its value eval peq can be determined instantaneously without memory
synchronization.
Sequencing: The sequential composition P ; Q first executes the micro-steps of P until it
completes. If P pauses then control resumes in P for the next instant. If P terminates then
control passes instantaneously to Q within the current instant. If P exits with completion
k “ 2, then the sequential statement also exits at the same level. The rule Seq1 handles the
cases where we wait or pause in P , while Seq2 is used when P terminates and Seq3 takes
effect when P exits at k “ 2.
Loops: The reductions of the loop construct loop P is governed by the rules Rep1 –Rep3 .
They implement the idea that loop P should behave like an infinite repetition of the loop
body P , i.e., be equivalent to P ; loop P end. This is seen in rule Rep1 which runs P until
it blocks or pauses with P 1 ó tK, 1u where it yields as P 1 ; loop P end to pass control to
the environment. In case P terminates as P 1 “ nothing we immediately continue to repeat
loop P with rule Rep3 . This is necessary because our sstep reduction runs each thread to
completion. Analogously, when P completes as P 1 “ exit we use rule Rep2 to pass the exit
up to an enclosing trap, without interruption of the sstep.
Traps: The combination of statements exit 2 and trap P provide a simple form of one-
level break point, corresponding to the trap completion code 2 in Esterel. When a process
executes exit 2 it leaves the current program scope and directly passes control to an enclosing
trap handler, where the exit is transformed into a normal termination to continue the sequential
control flow. Similar to the loop we have a rule Trp1 which reduces the process P in a trap
trap P until is blocks or pauses, preserving the trap handler. When P terminates or exits,
the rule Trp2 makes the trap handler terminate.
Sequential Parallel OR: The operator P }y Q is a parallel with built-in abortion sequen-
tialised from left to right. We first run P to completion, disregarding any dataflow constraints
between P and Q. Once P completes there are two possibilities depending on the completion
code. If P terminates, then Q is strongly aborted and the construct terminates and passes
control sequentially to the downstream process. This is the strong abort behavior of the
construct. If however P pauses in state P 1 , then control passes instantaneously to Q which is
permitted to complete the tick. When Q terminates, the preemption construct terminates as
well, weakly aborting P . If Q pauses in state Q1 then the construct pauses in state P 1 }y Q1 .
The rule SPa1 runs P by itself as long as it does not complete, i.e., blocks with completion
K. This permits the process P in construct P }y Q to interleave and communicate with its
environment. Note that P is blocked by the potential Π which describes the environment of
P }y Q and does not include the potential accesses performed by Q. The rule SPa2 deals with
the situation where P completes without pausing. In this case, the preemption construct }y
complete likewise, while the waiting process Q, is preempted. If P pauses, by the following
rules SPa3 and SPa4 , control passes to Q: The process Q is now allowed to run and interact
with the environment, possibly blocked by potential Π.Again, this potential is only accounting
for the environment concurrent to P }y Q and does not include the potential contribution from
P . In this case, P is already paused, so it does not contribute anything. Observe that in
pause }y Q1 the evaluation of pause will stutter. So, the rule SPa3 can be repeated until Q1
finally blocks or pauses in process Q1 . Finally, if Q1 pauses as with rule SPa3 , then both
24
eval peq “ T ù Σ1 $ P 1
Σ; Π $ P ñ eval peq “ F ù Σ1 $ P 1
Σ; Π $ P ñ
Cnd1 Cnd2
ù Σ1 $ P 1
Σ; Π $ if e then P else Q ñ ù Σ1 $ P 1
Σ; Π $ if e then Q else P ñ
P “ exit k ù Σ1 $ P 1 ó tK, 1u
Σ; Π $ P ñ
Cmp Seq1
Σ; Π $ P ñ
ù Σ$P ù Σ1 $ P 1 ; Q
Σ; Π $ P ; Q ñ
ù Σ1 $ exit 0
Σ; Π $ P ñ Σ1 ; Π $ Q ñ
ù Σ2 $ Q1 ù Σ1 $ exit 2
Σ; Π $ P ñ
Seq2 Seq3
ù Σ2 $ Q1
Σ; Π $ P ; Q ñ ù Σ1 $ exit 2
Σ; Π $ P ; Q ñ
ù Σ1 $ exit 2
Σ; Π $ P ñ ù Σ1 $ P 1 ó tK, 1u
Σ; Π $ P ñ
Rep2 Rep1
ù Σ1 $ exit 2
Σ; Π $ loop P ñ ù Σ1 $ P 1 ; loop P
Σ; Π $ loop P ñ
ù Σ1 $ exit 0
Σ; Π $ P ñ Σ1 ; Π $ loop P ñ
ù Σ2 $ P 2
Rep3
ù Σ2 $ P 2
Σ; Π $ loop P ñ
ù Σ1 $ P 1 ó tK, 1u
Σ; Π $ P ñ ù Σ1 $ exit k
Σ; Π $ P ñ k‰1
Trp1 Trp2
ù Σ1 $ trap P 1
Σ; Π $ trap P ñ ù Σ1 $ exit 0
Σ; Π $ trap P ñ
Fig. 6. SCPL reduction rules for conditionals if then else, completion statements exit, sequential composition P ; Q,
iteration loop and trap exceptions.
processes pause and wait for the next tick. If Q completes without pausing (exit k with ‰ 1)
then the rule SPa4 preempts the pausing P 1 and completes as Q does.
Method Call: For closed programs, at the point where a method call let x “ o.mpeq in P
is reduced, the expression e is closed. Its value is determined as eval peq “ v. We can then
perform the method call in the memory structure, obtaining an updated store Σ d o.mpvq and
a return value Σ ‚ o.mpvq. The return value Σ ‚ o.mpvq is substituted for value variable x into
the continuation program P , and the instantiated process P rΣ ‚ o.mpvq{xs is reduced in the
new memory Σ d o.mpvq using rule Mtd2 in Fig. 7. The reduction of a method call can only
go ahead under the condition π . Π ą to.mu which expresses that the concurrent potential
Π does not contain a memory access o1 .m1 with precedence over o.m. When this condition
is not satisfied, the method call blocks and the stuttering rule Mtd1 is the only sstep possible.
Procedure Call: Every procedure p has an associated generic method p.steprf s that ab-
stracts a step function to manipulate the store. The procedure is statically defined and generic
in a list of memory access paths x. A procedure call run prf s instantiates the step function as
p.steprf spΣ, p.initq in the initial state p.init. The interface extension f captures the mapping
of formal parametersin the step function to the given arguments. The p-policy πp in the
interface of p provides causality information about the accesses that procedure is maximally
willing to conduct on the memory through the parameters x. The instantiated p-policy πp ro{xs
is used to ensure thread-safe atomic execution of the procedure. The rules to evaluate a
procedure call are Act2 and Act3 . These run the procedure as a single atomic action on the
memory until it completes. If the step function pauses, i.e., the next state satisfies s1 óp 1, as
in Act2 then the procedure call also pauses and installs itself as exit 1; runp. tick ps1 q prf s to be
resumed in the next macro-step. If the step function terminates, i.e., s1 óp 0, as in rule Act3 ,
25
ù Σ1 $ P 1 ó K
Σ; Π $ P ñ ù Σ1 $ P 1 ó 1
Σ; Π $ P ñ Σ1 ; Π $ Q ñ
ù Σ2 $ Q1 ó tK, 1u
SPa1 SPa3
ù Σ1 $ P 1 }y Q
Σ; Π $ P }y Q ñ ù Σ2 $ P 1 }y Q1
Σ; Π $ P }y Q ñ
ù Σ1 $ P 1 ó 1
Σ; Π $ P ñ Σ1 ; Π $ Q ñ
ù Σ2 $ exit k k‰1
SPa4
ù Σ2 $ exit k
Σ; Π $ P }y Q ñ
ù Σ1 $ exit k
Σ; Π $ P ñ k‰1 P “ let x “ o.mpeq in Q
SPa2 Mtd1
1
Σ; Π $ P }y Q ñ
ù Σ $ exit k Σ; Π $ P ñ
ù Σ$P
ù Σ1 $ P 1 ó tK, 1u
Σ; Π Y can s pQq $ P ñ ù Σ1 $ Q1 ó tK, 1u
Σ; Π Y can s pP q $ Q ñ
APa1 APa4
ù Σ1 $ P 1 } Q
Σ; Π $ P } Q ñ ù Σ1 $ P } Q1
Σ; Π $ P } Q ñ
ù Σ1 $ exit 0
Σ; Π Y can s pQq $ P ñ ù Σ1 $ exit 0
Σ; Π Y can s pP q $ Q ñ
APa2 APa5
ù Σ1 $ Q
Σ; Π $ P } Q ñ ù Σ1 $ P
Σ; Π $ P } Q ñ
ù Σ1 $ exit 2
Σ; Π Y can s pQq $ P ñ ù Σ1 $ exit 2
Σ; Π Y can s pP q $ Q ñ
APa3 APa6
ù Σ1 $ pQ ; exit 2q }y exit 2
Σ; Π $ P } Q ñ ù Σ1 $ pP ; exit 2q }y exit 2
Σ; Π $ P } Q ñ
Fig. 7. SCPL reduction rules for sequential parallel OR }y, concurrent parallel AND }, memory actions let and procedure
calls run p. An occurrence of run prf s is the same as runp.init prf s.
26
completes as P 1 } Q under rule APa1 . When P terminates its sstep with exit 0 then rule
APa2 removes child P from the parallel composition. Finally, if P exits with exit 2 then
with rule APa3 the sibling Q is permitted to complete the current macro-step in configuration
pQ ; exit 2q }y exit 2. The two occurrences of exit 2 force an exit at level 2 as soon as Q
completes. This is the behavior of parallel in Esterel for the completion code 2. The rules
APa4 –APa6 are symmetrical to APa1 –APa3 . They perform an sstep in the right child Q of a
parallel P } Q.
Toplevel processes are evaluated in the empty environment. We write
ù pΣ1 , P 1 q
C $ pΣ, P q ñ
Proposition 1
1) If P is completed then Σ $ P is stable for all stores Σ.
2) Let Σ $ P ñ ù Σ1 $ P 1 . If P is closed and well-formed then P 1 is closed and well-
formed, and canpP 1 q Ď canpP q.
Bigsteps are maximal evaluation sequences with thread interleaving, running the process
to stability which may be blocked or completed.
Definition 10 (Big- & Macrostep) The transitive closure C $ pΣ, P q ñ ñ pΣ1 , P 1 q of the
sstep relation ñ
ù is a bigstep if pΣ1 , P 1 q is stable and, more specifically, a macro-step if P 1
is also completed.
Coherence of a store ensures that the transitive closure of ñ
ù is confluent and thus ñ
ñ leads
to a unique final configuration.
Theorem 1 (Bigstep Determinacy) For C-coherent Σ, if C $ pΣ, P q ñ
ñ pΣi , Pi q for i P
t1, 2u, then Σ1 “ Σ2 and P1 “ P2 .
Definition 11 (Constructiveness) A process P is called C-constructive in a store Σ, if there
exists a macro-step C $ pΣ, P q ñ ñ pΣ1 , P 1 q such that if P 1 is 1-completed then tick pP 1 q
is C-constructive in Σ . A process is C-constructive, if it is C-constructive in all C-coherent
1
stores Σ.
A C-constructive process P generates an infinite stream of macro-step reactions on a global
store shared by all concurrent threads in P . The memory is protected by the p-policy in
C which synchronizes concurrent accesses in P , thus preventing data races in C-coherent
structures S.
Constructiveness and coherence trade off against each other. The restrictive p-policy seq
blocks all concurrent accesses, whence a process is seq-constructive iff it is sequential. Yet, all
stores regardless of their implementation of method calls are seq-coherent. The most liberal
policy top does not block any access, whence all processes are trivially top-constructive.
However, a store is top-coherent only if all method calls commute with each other. This is
27
very restrictive and prevents any communication between threads. Useful instances of shared
memory lie somewhere between these extremes. The policy var supports single-writer, multi-
reader data-flow variables and var-constructive P correspond to causal dataflow process as
in Lustre. With policy reg we can model data-flow with shared memory as in [26]. For single-
writer and single-reader unbounded buffers under policy buf providing asynchronous writes
b.send and blocking reads b.wait, the buf-constructive processes are progressive synchronous
Kahn dataflow networks. A memory of SC variables with methods s.ini, s.upd, s.rd and
the init-update-read policy iur obtains sequentially constructive SCCharts [8] and sequentially
constructive Esterel, dubbed SCEst [21].
How does a practical compiler check if a given process P is C-constructive? If P is non-
constructive, then for some C-coherent initial store Σ0 , the reduction of P in some macro-step
n reaches a configuration pΣ1n , Pn1 q which is either (1) divergent, i.e., there is no pΣ2n , Pn2 q
with
S : C $ pΣ1n , Pn1 q ñ
ù pΣ2 , Pn2 q
or (2) blocked, i.e., pΣ1n , Pn1 q is stable but not completed. In the crash case (1) the process
Pn1 is spinning in an infinite sequential loop. This is the only way in which our reduction
semantics in Figs. 6 and 7 can fail to generate an sstep. The search for a derivation must run
through an infinite number of applications of rule Rep3 . Since a single sstep only involves
sequential steps, this means that Pn1 must contain an instantaneous control flow cycle which
must be a cycle in the static program structure of P . The crash situation (2) in which Pn1 is
stable but not completed occurs ifthe method or procedure calls inside Pn1 are blocked because
of the side conditions in rules Mtd2 , Act2 , Act3 being false. In this case the stability of Pn1 is
obtained by rules Mtd1 and Act1 which permit Pn1 to yield back to the scheduler. The blocking
inside Pn1 must involve a set of (at least two) threads Qi waiting at a method or procedure call
with a precendence constraints of form π , Πi ą Ai , where Ai “ to.mu for a method call
o.mpvq and Ai “ πp rf s for a procedure call. The potentials Πi are obtained from sequentially
reachable method calls in the sibling threads Qj (j ‰ i) concurrent to the thread Qi trying to
execute the actions Ai . Hence each entry in Πi must be sequentially reachable from some of
the blocked threads Qj (j ‰ i). But this implies that Pn1 contains an instantaneous cycle of
memory accesses involving precedence dependencies from C. This cycle must already be a
static cycle in the original process P from which Pn1 has been unrolled. In sum, the absence
of (1) and (2) is implied by the criterion of acyclic sequential schedulability (ASC) [8] which
can be statically checked in polynomial time.
Theorem 2 Let C “ pO, πq be a interface and P be a closed process well-formed for C.
Suppose the static program graph of P does not contain an instantaneous cycle through
sequential program order or concurrent precedence dependencies. Then, P is C-constructive.
For general policies the problem of determining whether a process is constructive can
be arbitrarily complex. Since constructiveness involves loop termination at every tick it is
undecidable for infinite data types. But even if loops are clock-guarded and statically bounded,
and all memory is boolean, like in Esterel, the decision problem can be intractable (co-NP).
Even the simpler question of whether two statements can be executed in the same tick (called
the “tick alignment problem”) without considering memory at all, seems to be exponential,
see e.g. [27], yet the exact complexity is still unknown. In practice, the static cycle check
according to Thm. 2 suffices. What makes our approach important is that this (well-known)
28
efficient ASC criterion can now be applied uniformely to arbitrary shared memory structures
controlled by policies. This idea can be fruitfully integrated as a simple extension to current
compilers in languages like Lustre, Esterel or SCCharts.
Observe that Thm. 1 suggests an interesting trade off between the memory model and the
program making the simple ASC criterion of Thm. 2 even more powerful: A constructive
process P may have been instantiated with SC variables subject to the p-policy iur. Suppose
that a static analysis reveals that P remains constructive even if the policy is strengthened
to the policy var Ď iur for data flow variables. Then the store remains coherent for var and
thus P has the same behavior if it is compiled for shared memory consisting of data flow
memory rather than SC variables.
Example 18 Consider the process P “ ms.acc.wrp4.2q; pA}Bq in context context OMacOp 1
where A is as in Ex. 5 and B from Ex. 7. P initialises the acc field of ms with value 4.2
and then spawns children A and B. If either cell m1 or m2 gets aliased with acc we run into
a write-write conflict, because both A and B then write into ms.acc. This means that P is
constructive for an interface CP “ pOMacOp
1
, πP q if πP forces cells m1 and m2 to be separated
from acc, i.e., πP , o.wr ˛ ms.acc.wr for o P tms.m1, ms.m2u. This is achieved, e.g., by
the fully unrestrictive p-policy top (see Ex. 12). For πP “ top, a store Σ : OMacOp1
P SS is
CP -coherent if in Σ all write accesses o.wr to cells o P tms.m1, ms.m2u are commuting with
write access ms.acc.wr. This eliminates all stores in which these cells are aliased. Note that
in every well-formed instantiation P rf s the interface extension f : CP Ď C must preserve
the independencies expressed in πP (see Def. 6). This makes sure that P rf s remains C-
constructive. In contrast, if πP “ seq, the extension f can do any aliasing it likes. Even, if
f collapses all OMacOp
1
into a single cell x, f : pOP , seqq Ď ptxu, varq is an extension. Yet,
P rf s is obviously not ptxu, varq-constructive. For the same reason, P is not CP -constructive
because of the existence of aliasing stores Σrf s : OMacOp
1
P S.
A black-box process remains constructive if it is inline expanded along an interface exten-
sion and behaves identical.
Theorem 3 (Memory Safety) Let f : C1 Ď C2 be a interface extension. If P is a C1 -
constructive process then the inline expansion P rf s is C2 -constructive.
29
declaration proc ppxq “ P . Then, the inline expansion QtP rf s{ run prf su is C-constructive
and generates the same sequence of macro-states as Q, from all C-coherent stores.
Note, the expansion QtP rf s{ run prf su, which schedules the inline expanded process P rf s,
may be constructive while the black-box execution of P in Q is not constructive.
30
combine state-dependent scheduling of [13] with shared data structures of [14] in a black-box
setting. We leave this to future work.
The scheduling policies of [26] for data flow objects represent finite safety automata (prefix
closed, star-free regular languages) that specify the legal sequence of mode calls that make
up a memory-safe and full step reaction in the object. In this respect, they play a similar
role as our p-policies, yet can express stateful behavior which p-policies cannot. On the other
hand, [26] does not make a difference between sequential and concurrent accesses, which is
the key feature of p-policies. For instance, concurrent writes x “ 4 and x “ 2 are treated like
a choice of sequential writes if e then x “ 4 in x “ 2 else x “ 2 in x “ 4 and both rejected. In
SCPL only the former is non-constructive while the latter, being single-threaded, is accepted.
Specifically, the singleton constraint of Blech that forces memory accesses to be executed
sequentially is not expressible by [26].
Benveniste et. al [31] introduce the notion of convex acceptance interfaces (CAI) for
the composition of synchronous modules. CAI specify the synchronization behavior of a
synchronous block regarding read and write accesses to its interface signals and thus can be
used for modular causality analysis. CAI interfaces, like CAPs, are more expressive than our
p-policies since they are data-dependent. On the other hand, CAI do not consider destructive
(sequentially constructive) read and write actions and are limited to signals as shared memory
structures. More importantly, as CAI are obtained from data flow specifications (Signal), they
do not express negative trigger conditions like the precedences of our p-policies. Hence, they
cannot capture Esterel-style reaction to absence. The reason is the CAI express program-order,
while p-policies (and also CAPs) express causality between concurrent actions.
Causality analysis for optimal grey-box modularisation [9], [28], [29], CAP [14], CAI [31],
as well as for scheduling policies of [26] have worst-case exponential complexity. There
have been several other proposals for interface models in synchronous programming [32],
[33] which are computationally tractable and close to p-policies in expressing purely static
dependency relations. Cuoq and Pouzet [32] use polymorphic record (“row”) types as a static
type system for the data flow language Lucid Synchrone. It captures instantaneous dependence
of functional expressions on argument variables. Lee et al. [33] propose to model the causality
interfaces as dioid algebras. These are very general and applicable for input-output blocks
of data and control flow alike. Our p-policies can be seen as an instantaneous dependency
relation in the sense of [32] for memory accesses in the imperative setting, or as causality
interfaces in the sense of [33] with boolean weights. Then, the static type checking of first-
order recursion in [32] or the fixed point analysis of [33] corresponds to static ASC cycle
detection in the program graph.
All of the standard causality interfaces as far as we are aware, merely describe the properties
of data and computations at run-time and thus can be removed after compilation. In contrast to
this, SCPL policies actively enforce scheduling priorities and thus control program execution.
Since they prescribe a specific synchronization model, they are an integral part of the program,
like the init-update-read specification of SC-variables in SCCharts or the procedural interfaces
in Blech. SCPL policies cannot, in general, be globally synthesised as principal type schemes,
like the causality types of [32] or the “scheduling policies” of [26]. SCPL p-policies can, how-
ever, be locally optimised by shifting along the Ď relation in the spectrum between seq (purely
sequential) and top (purely concurrent) without changing program behavior. We envisage such
optimisations to be useful in compilation schemes by source-level transformation.
31
For future work, we aim to extend the theory of SCPL policies to model dynamic state
and develop a contract theory in the spirit of [34]. For this, a policy specification language
and its semantics will be developed. Further, we plan to complete SCPL by a general trap
mechanism as in Esterel and by local object declarations. For practical experimentation we
wish to integrate our interface theory in a concrete open source compiler, such as the Blech
or the SCCharts compilers.
ACKNOWLEDGMENT
The authors would like to thank Joaquín Aguado for many inspiring discussions on the use
of policies as procedural interfaces. Reinhard von Hanxleden and his Kiel research group have
helped clarify our choice of the primitive operators for SCPL, specifically highlighting the
central role of the “sequential-parallel-OR” operator P }y Q. The third author thanks Gerald
Lüttgen for valuable comments on a preliminary version of this work.
R EFERENCES
[1] G. Berry, “The Foundations of Esterel,” in Proof, Language and Interaction: Essays in Honour of Robin Milner. MIT
Press, 2000.
[2] K. Schneider, “The Synchronous Programming Language Quartz,” Department of Computer Science, University of
Kaiserslautern, Kaiserslautern, Germany, Internal Report 375, December 2009.
[3] S. Andalam, P. Roop, and A. Girault, “Predictable multithreading of embedded applications using PRET-C,” in
MEMOCODE’10, 2010.
[4] E. Yip, A. Girault, P. S. Roop, and M. Biglari-Abhari, “The ForeC Synchronous Deterministic Parallel Programming
Language for Multicores,” in MCSoC’16, 2016.
[5] G. F. Lima, R. C. M. Santos, R. Ierusalimschy, E. H. Haeusler, and F. Sant’Anna, “A memory-bounded, deterministic
and terminating semantics for the synchronous programming language Céu,” J. Syst. Archit., vol. 97, pp. 239–257,
2019.
[6] E. Technologies, “The Esterel v7 Reference Manual Version v7_30 – initial IEEE standardization proposal,” Esterel
Technologies, Tech. Rep., November 2005.
[7] E. Vecchié, J.-P. Talpin, and S. Boisgérault, “A higher-order extension for imperative synchronous languages,” in
Proc. SCOPES 2010, 06 2010.
[8] R. von Hanxleden, B. Duderstadt, C. Motika, S. Smyth, M. Mendler, J. Aguado, S. Mercer, and O. O’Brien, “SCCharts:
Sequentially Constructive Statecharts for safety-critical applications,” in PLDI’14, 2014.
[9] H. Olivier, P. Laurent, Y. L, B, and N. Eric, “Cronos: A Separate Compilation Toolset for Modular Esterel Applications.”
in World Congress on Formal Methods. Springer, Sep. 1999, pp. 1836–1853.
[10] D. Potop-Butucaru, S. Edwards, and G. Berry, Compiling Esterel, 1st ed. Springer, May 2007.
[11] E. Closse, M. Poize, J. Pulou, P. Venier, and D. Weil, “SAXO-RT: Interpreting Esterel semantic on a sequential
execution structure,” in ENTCS, vol. 65. Elsevier, 2002, pp. 80–94.
[12] D. Biernacki, J.-L. Colaço, G. Hamon, and M. Pouzet, “Clock-directed Modular Code Generation of Synchronous
Data-flow Languages,” in LCTES’08, Tucson, AZ, USA, Jun. 2008.
[13] E. Vecchie, J. Talpin, and K. Schneider, “Separate compilation and execution of imperative synchronous modules,” in
DAC’09, 2009.
[14] J. Aguado, M. Mendler, M. Pouzet, P. S. Roop, and R. von Hanxleden, “Deterministic concurrency: A clock-
synchronised shared memory approach,” in ESOP’18, 2018, pp. 86–113.
[15] F. Elguibaly, “A fast parallel multiplier-accumulator using the modified booth algorithm,” Circuits and Systems II:
Analog and Digital Signal Processing, IEEE Transactions on, vol. 47, pp. 902 – 908, 10 2000.
[16] E. Quinnell, E. E. Swartzlander, and C. Lemonds, “Floating-point fused multiply-add architectures,” in 2007 Conference
Record of the Forty-First Asilomar Conference on Signals, Systems and Computers, 2007.
[17] J.-L. Colaço, B. Pagano, and M. Pouzet, “SCADE 6: A Formal Language for Embedded Critical Software Development
(Invited Paper),” in TASE’17, Inria Sophia Antipolis, France, September 2017.
[18] F. Gretz and F.-J. Grosch, “Blech, imperative synchronous programming!” in Languages, Design Methods, and Tools
for Electronic System Design: Selected Contributions from FDL 2018, 2020, pp. 161–186.
[19] D. Potop-Butucaru, R. Simone, and J.-P. Talpin, “The synchronous hypothesis and synchronous languages,” Embedded
Systems: Handbook, 2005.
[20] G. Berry, “SCADE: Synchronous design and validation of embedded control software,” in Next Generation Design
and Verification Methodologies for Distributed Embedded Control Systems. Springer, 2007, pp. 19–33.
[21] S. Smyth, C. Motika, K. Rathlev, R. von Hanxleden, and M. Mendler, “SCEst: Sequentially Constructive Esterel,”
ACM TECS—Special Issue on MEMOCODE 2015, vol. 17, no. 2, pp. 33:1–33:26, Dec. 2017.
32
[22] R. von Hanxleden, M. Mendler, J. Aguado, B. Duderstadt, I. Fuhrmann, C. Motika, S. Mercer, O. O’Brien, and P. Roop,
“Sequentially Constructive Concurrency—A conservative extension of the synchronous model of computation,” ACM
TECS, vol. 13, no. 4s, 2014.
[23] S. Smyth, A. Schulz-Rosengarten, and R. von Hanxleden, “Practical causality handling for synchronous
languages,” in Design, Automation & Test in Europe Conference & Exhibition, DATE 2019, Florence,
Italy, March 25-29, 2019, J. Teich and F. Fummi, Eds. IEEE, 2019, pp. 1281–1284. [Online]. Available:
https://doi.org/10.23919/DATE.2019.8715081
[24] F. Boussinot, “SugarCubes implementation of causality,” INRIA, Research Report RR-3487, Sep. 1998.
[25] G. Berry, The Constructive Semantics of Pure Esterel. Draft Book, 1999.
[26] P. Caspi, J.-L. Colaço, L. Gérard, M. Pouzet, and P. Raymond, “Synchronous Objects with Scheduling Policies:
Introducing safe shared memory in Lustre,” in LCTES’09, Dublin, Ireland, 2009, pp. 11–20.
[27] M. Mendler, P. S. Roop, and B. Bodin, “A novel WCET semantics of synchronous programs,” in Formal Modeling
and Analysis of Timed Systems - 14th International Conference, FORMATS 2016, Quebec, QC, Canada, August 24-26,
2016, Proceedings, 2016, pp. 195–210.
[28] J. Zeng and S. A. Edwards, “Separate compilation of synchronous modules,” in ICESS’05, Xian, China, Dec. 2005.
[29] R. Lublinerman, C. Szegedy, and S. Tripakis, “Modular code generation from synchronous block diagrams - modularity
vs. code size,” in POPL’09, 2009.
[30] M. Pouzet and P. Raymond, “Modular static scheduling of synchronous data-flow networks - an efficient symbolic
representation,” Design Autom. for Emb. Sys., vol. 14, no. 3, pp. 165–192, 2010.
[31] A. Benveniste, B. Caillaud, and J.-B. Raclet, “Applications of interface theories to separate compilation of synchronous
programs,” in CDC’12, 2012.
[32] P. Cuoq and M. Pouzet, “Modular causality in a synchronous stream language,” in ESOP 2001, 2001, pp. 237–251.
[33] E. A. Lee, H. Zheng, and Y. Zhou, “Causality interfaces and compositional causality analysis,” in Foundations of
Interface Technologies (FIT’05), ser. ENTCS. Elsevier, 2005.
[34] A. Benveniste, B. Caillaud, D. Nickovic, R. Passerone, J.-B. Raclet, P. Reinkemeier, A. Sangiovanni-Vincentelli,
W. Damm, T. A. Henzinger, and K. G. Larsen, “Contracts for system design,” Foundations and Trends in Electronic
Design Automation, pp. 124–400, 2018.
A PPENDIX A
P OTENTIALS
Potentials synchronize a thread with its concurrent environment. Informally, a potential
is a summary of all memory accesses potentially performed by the procedures running
concurrently with the thread during the current macro-step. In its simplest form, a potential
is a subset Π Ď M of qmethods where M “ Mtd pCq. We obtain Π from the process P
that represents the concurrent environment of the given thread. To compute Π structurally by
recursion on the structure of P we need to predict also a completion code in K “ t0, 1, 2u
for P and add it to Π. Technically, we define a function canpP q “ pΠ, Kq P 2M ˆ 2K by
recursion on the structure of P . The base cases nothing, pause and exit do not perform
any memory accesses, yet have different completion potentials. Formally,
can s pnothingq “ K0
can s ppauseq “ K1
can s pexitq “ K2 .
where Kk “ pH, tkuq for k P K.
For the conditional branching we need to over-approximate using the collective seman-
tics, joining the memory capabilities of both branches as well as the completion codes. If
can s pPi q “ pΠi , Ki q then
can s pif e then P1 else P2 q “ pΠ1 Y Π2 , K1 Y K2 q.
Note that expressions e do not perform any no memory accesses, they are pure values3 at run
time.
3
We assume expressions are computed on the thread-local stack.
33
The potential of can s pP1 ; P2 q is a sequential composition of memory accesses and com-
pletion codes which depends on whether P1 can terminate. If can s pPi q “ pΠi , Ki q then
#
pΠ1 , K1 q if 0 R K1
can s pP1 ; P2 q “
pΠ1 Y Π2 , K1 zt0uq Y K2 q otherwise.
If P1 cannot terminate (0 R K1 ) then the downstream process P2 in a sequential composition
P1 ; P2 cannot contribute anything to the memory accesses of the current macro-step. As a
consequence, can s pnothing ; P q “ can s pP q, can s ppause ; P q “ K1 and can s pexit ; P q “
K2 .
The parallel composition P1 } P2 is joining the memory access capabilities of P1 and P2 ,
while the completion code models the synchronization of control-flow. A parallel P1 } P2
terminates iff both threads terminate, returns if one thread returns and pauses if one thread
pauses and the other thread does not return. Formally, if can s pPi q “ pΠi , Ki q then can s pP1 }
P2 q “ pΠ1 Y Π2 , Kq where
K “ tmaxpk1 , k2 q | k1 P K1 , k2 P K2 u.
The preemptive parallel-or P }y Q is like normal parallel as regards the memory access
but has a different synchronization behavior for control flow. Thread Q can only contribute
memory accesses to the current tick, if P may pause. Otherwise, Q gets preempted. Suppose
can s pP q “ pΠ1 , K1 q and can s pQq “ pΠ2 , K2 q. Then,
#
pΠ1 , K1 q if 1 R K1
can s pP1 }y P2 q “
pΠ1 Y Π2 , Kq otherwise
where
0 P K ô 0 P K1 _ 0 P K2
1 P K ô 1 P K1 ^ 1 P K2
2 P K ô 2 P K1 _ 2 P K2 .
The preemptive parallel-or terminates or returns as soon as one of the threads terminates or
returns, and it pauses only if both threads pause.
The purpose of a trap is to catch a return of the enclosed process and turn it into a normal
termination.4 If can s ptrap P q “ pΠ, Kq then can s ptrap P q “ pΠ, K 1 q where
0 P K 1 ô t0, 2u X K ‰ H
1 P K1 ô 1 P K
2 P K 1 ô never.
A critical construct is the statically unbounded loop loop P . A loop cannot terminate but if
P pauses or returns, then loop P also pauses or returns, respectively. Since every well-formed
loop body P must be non-instantaneous or loop-safe, which is checked by the compiler, each
execution through P must necessarily run into a pause or exit eventually. The former stops
the loop for the tick and the latter breaks out of the loop altogether. For the potential suppose
can s pP q “ pΠ, Kq. Then
can s ploop P q “ pΠ, Kzt0uq.
4
The completion potential K 1 is written KÓ in Esterel.
34
So far all constructs merely manipulate the completion potentials. The potentials for mem-
ory accesses arise from memory access statements:
can s plet x “ o.mpeq in P q “ pto.mu, t0uq.
We assume that each memory access is executed atomically and must complete by termination.
Let ppxq be an procedure with interface Sp “ pOp , Mp , πp q in which Op “ tx1 , x2 , . . . , xn u.
An procedure call5 run ppoq with o “ o1 , o2 , . . . , on is approximated by its instantiated p-
policy πp ro{xs. When the procedure call run ppoq is executed, the instantiated p-policy πp ro{xs
is tested for conformance πp ro{xs Ď πpΣq with the memory p-policy. This verifies that the
method calls inside the instantiated procedure are both admissible and race-free, i.e., preserve
coherence of the store. For potential, we only need the set of memory accesses potentially
executed by p. This is the underlying set πp ro{xs of the p-policy. Since all procedures may
pause or terminate in each tick, their completion can be 0 or 1. Overall, thus
can s prun ppoqq “ pπa ro{xs, t0, 1, 2uq. (2)
It is important to observe that the potential dompπp ro{xsq is determined from the static
interface of p without looking at its actual code. The advantage of this “black-box” approach
is that it supports pre-compilation of possibly recursive methods. The disadvantage is that
the static interface πp ro{xs will typically be a rather conservative over-approximation of
the memory accesses actually performed by ppoq. If the procedure definitions are available
we could use a “white-box” approach and obtain the potential from the procedure code.
Specifically, if the procedure p is a module defined by the declaration proc ppxq “ Q we
could safely define
can s prun ppoqq “ can s pQro{xsq.
Soundness of the method body Q for interface Sp
can s pQro{xsq Ď πp ro{xs.
Using the white-box potential (3) is more expensive at run-time but less conservative than the
static proxy (2). For statically verified constructive programs, both versions produce the same
result. However, if a program is not statically constructive, the more precise potential (3) may
resolve scheduling deadlocks produced by the statical potential (2).
5
In Blech there are only two access types let and var and the formal parameters are separated into two lists. A mutating
activity call o.ppo1 qpo2 q with let parameters o1 and var parameters o2 is treated like a procedure call ppo1 qpo2 , oq and a
non-mutating call is handled like a procedure call ppo1 , oqpo2 q. In this way, the information about separation lies entirely
in the position of the parameters. Here, we code the same information in the procedure’s interface type πp .
35