Program Algebra and Thread Algebra

2 downloads 0 Views 675KB Size Report
Feb 16, 2006 - so useful, what explains the remarkable number of program .... the Turing machine tape (TMT) in the form of a state machine ETMT. .... Because one can always take the disjoint union of two recursive specifica- ..... repetition and instantiating the jump counters k and l with the appropriate values then yields a ...
Program Algebra and Thread Algebra Jan A. Bergstra, Inge Bethke, and Alban Ponse University of Amsterdam, Programming Research Group,

Kruislaan 403, 1098 SJ Amsterdam, The Netherlands http://www.science.uva.nl/research/prog/ February 16, 2006

ii

J.A. Bergstra, I. Bethke, and A. Ponse

Preface These lecture notes comprise slightly abbreviated versions of a number of papers on program algebra and thread algebra. Program algebra in the form of PGA, an algebra of imperative programs dates back to a paper (Program algebra for component code) by Jan Bergstra and Marijke Loots in 2000. Its semantics used so-called polarized process algebra which was renamed into thread algebra in ’Thread algebra for strategic interleaving’ by Jan Bergstra and Kees Middelburg. Subsequently these themes have been developed in a number of directions some of which are covered in this reader. Writing a new theory of imperative programming at the lowest level is in no way following todays trend or fashion. Neither is the development of a variant of process algebra which is in many respects more simplistic and less elegant than formalisms like ACP, CCS or CSP. In order to obtain results that cannot or less easily be obtained by other means many years of work will be needed, and there is no guarantee of success. We try to avoid any explicit influence from practical considerations and not to import any convention or rule which has grown out of experience in the hope to be able to model failed designs and methods as adequately as successful designs and methods, because failure asks for an explanation just as much as success. The motivation of this work is of a different nature than many other efforts in the field seem or claim to be. Rather than to enhance the power of technology in any of its many useful directions the focus of this work is on an attempt to develop a conceptual grasp of a part of technology (in this case software technology) and if possible understand why this technology develops as it does. One might view the science of computer programming as an effort to understand this social phenomenon rather than as a means to guide it. This is a perspective comparable to that of political science. The basis for this work is a hard headed attempt to provide conceptual definitions for a number of key ’ideas’ in the field that I started around 1995 with Bas van Vlijmen. What is a program? What is a program notation? What is programming and what is a programmer? Is there a technology independent definition of programming? What is the behavior of a program? If programs are mathematical objects, like numbers, what nature might these have? What is the undecidability of the halting problem as a result about programs rather than about machines? Why are high level program notations so useful, what explains the remarkable number of program notations and their steady evolution? Why is machine code classified as ’program’ ? What makes an executable executable? Many questions can be asked so much is clear. Our answers are not always satisfactory but slowly some matters have become clear, which may of course be equally clear to other people via other considerations, or clear but in quite a different way. I mention some conclusions in random order, with only few of them explicitly elaborated in the reader. It is this kind of issues that I am after but by nature few admit mathematical proof. The following viewpoints have either emerged from or been somehow additionally validated during our work on program algebra. (1) The serious problems concerning software patents find an important source in an insufficient specification of the concept of a program in legal documents which refute any notion of semantics beforehand, (2) there is no reasonable definition of ’executable

EWSCS’06 - Program Algebra and Thread Algebra

iii

code’, (3) the halting problem is like the liar paradox, but Cohen’s impossibility result for virus detection is not, (4) the concept of a program is independent of that of a computer or machine, (5) goto’s add expressive power to structured program notations (and so they survive), (6) a focus on ASCII based syntax for programs may impose unforeseen constraints and dedicated mathematics and semantics, entirely absent in seemingly marginally more abstract versions of a program notation cast in more mathematical language, which underly a language design, (7) the intuition that all transformations of a finite memory are computable is somehow futile and devoid of explanatory value regarding the development of software and hardware technology, (8) the Turing machine is not a plausible model of programmable equipment, because of its deficient representation of the essence of programs; stated differently: one needs to know what a program is beforehand in order to accept that a chunk of tape content constitutes a program, (9) a difference can be made between programs and control code: tables with machine learning data useful for robot control constitute control code but not program code, (10) programming is essentially a human task; years ago a computer was a human being, preferably that should still be the case; there is no progress in an identification of the ’human’ computer who computes because s/he intends to compute, with his/her tool. Lacking the nice term ’computer’ we now talk about ’users’, ’operators’, ’clients’, ’computer specialists’ none having the right connotation, (11) if a new compiler for a program notation makes the same ’program texts’ run differently the program notation has changed itself, rather than the compiler, unless extremely careful and normative semantic work has been done in a way that will not conceivably be compromised by compiler writer complaints; this is rather uncommon, however, (12) the theoretical literature contains no workable definition of an operating system, (13) I could not even find a convincing definition of an array; for those who doubt this: to what extent is the household notion of an array connected with the existence in program notations of finite domains of so-called integers. Now these matters are no worry for many, and frankly neither for me. But if our TV teletext reports about the next damaging computer virus spreading around we all know that not naming the families of specific computers and systems that may be infected by the alleged virus is wrong and simply protects certain business interests. A more systematic focus on meaning would reveal this kind of assertion as equally empty as the assertion that ’some’ biological virus is spreading around, which gives one no clue whatsoever. If people (nowadays no exception) assert that programming requires no academic qualifications and will be outsourced from Western Europe anyhow, so that we must now focus on creative design based on our hopefully superior taste, this kind of opinion must be based on some qualified definition of the concept of programming. Failing that conceptual analysis I consider such viewpoints potentially harmful for our local economic prospects. Amsterdam, February 2006 Jan Bergstra

iv

J.A. Bergstra, I. Bethke, and A. Ponse

Origin of the Chapters Chapter 1 (page 1) —Execution Architectures for Program Algebra by Bergstra and Ponse— is accepted for publication in the Journal of Applied Logic (see also [22]). Chapter 2 (page 31) —Polarized Process Algebra and Program Equivalence by Bergstra and Bethke— is published in the ICALP 2003 Proceedings [7]. Chapter 3 (page 51) —Decision Problems for Pushdown Threads by Bergstra, Bethke and Ponse— is submitted for publication [10]. Chapter 4 (page 69) —Virtual Machine-based Projection Semantics by Bergstra and Bethke— is a draft. Chapter 5 (page 97) —A Bypass of Cohen’s Impossibility Result by Bergstra and Ponse— is published in the EGC Proceedings 2005 [23]. The references are collected in a joint bibliography (page 107).

Chapter 1

Execution Architectures for Program Algebra Jan A. Bergstra and Alban Ponse

We investigate the notion of an execution architecture in the setting of the program algebra PGA, and distinguish two sorts of these: analytic architectures, designed for the purpose of explanation and provided with a process-algebraic, compositional semantics, and synthetic architectures, focusing on how a program may be a physical part of an execution architecture. Then we discuss in detail the Turing machine, a well-known example of an analytic architecture. The logical core of the halting problem — the inability to forecast termination behavior of programs — leads us to a few approaches and examples on related issues: forecasters and rational agents. In particular, we consider architectures suitable to run a Newcomb Paradox system and the Prisoner’s Dilemma.

1.1

Introduction

The program algebra PGA introduced in [15] aims at the clarification of the concept of a program at the simplest possible level. In this paper we will use PGA as a vehicle to study fundamentals of the execution of programs. Having available a rigid definition of what a program is, the subject of how programs may be used raises compelling questions. This paper focuses on the notion of an execution architecture. This notion is more general than that of a machine and admits many different forms of interaction between a program and its context. First, we consider programs modelled independently of any execution environment by means of threads. In this view, it is unavoidable to contemplate computable threads as a general semantic category. It turns out that computable instruction streams can describe all computable threads. Using finite programs only (regular instruction streams), an execution architecture with the well-known Turing machine tape as a state machine, i.e., an interacting data structure, is sufficiently powerful to denote all computable threads as well. 1

2

J.A. Bergstra, I. Bethke, and A. Ponse

Then, an attempt is made to cover the most important phenomena regarding program execution in a context. Two kinds of execution architectures are defined: analytic architectures (AnArchs) and synthetic architectures (SynArchs). An AnArch serves to model how a program may be executed in a context by making explicit its interaction with other system components, in particular the so-called reactors. A reactor is a generalization of a state machine: it may also have interaction with other parties than the control. A SynArch focuses on how a program may be a physical part of a context. The AnArch is useful for explanation, while a SynArch may play a role during construction. It is shown that all SynArchs admit a specification in a process algebra with abstraction and recursion operators. In the remainder of the paper some special analytic execution architectures are discussed. First, we consider the Turing machine and introduce an enhanced version of the Turing machine tape (TMT) in the form of a state machine ETMT. Finite control is phrased in terms of programs executed in an analytical architecture providing only the ETMT. In this setting the halting problem takes the form of the nonexistence of certain programs, which is demonstrated in full detail. Ignoring the (E)TMT, the halting problem reduces to its logical core: the inability to forecast termination behavior of programs that may use the results of forecasting. It is shown how an analytic architecture can be used to give a sound definition of a forecasting reactor, and it is demonstrated that a correct forecaster needs to escape from the two classical truth values. This brings the halting problem close to some logical paradoxes, in particular the Liar Paradox. A rational agent is a reactor that has the objective to achieve certain goals by giving appropriate replies for specific requests. It is shown that in some cases also a rational agent needs to use more truth values than true and false. Combining rational agents and forecasting reactors provides a remarkable setting, in which the famous Newcomb Paradox [40] can be modelled. This paradox seems to prove that the very concept of a forecaster reliably forecasting a rational agent is utterly problematic. Nevertheless this is done all the time in stock market transactions, gaming technology and so on. Using the analytic architectures and some, perhaps exotic process algebra involving the constant 0 (see [4]), a formalization of one reactor forecasting another reactor is given. The Newcomb Paradox now shows up as follows: given a fixed execution architecture (viewed as a geometric structure with several components), its process semantics determines what a rational agent reactor should best reply in order to achieve a specific objective. The normal process semantics predicts one reply as being rational, whereas the semantics specifically tailored to forecasting predicts a different reply. But the normal semantics is so robust that it seems to take into account the possibility that one reactor predicts the behavior of another reactor just as well. The novelty of our approach is a precise formalization of the conditions required to run both executions of the Newcomb Paradox system. As a last and related example, we discuss the wellknown Prisoner’s Dilemma [52]. In the setting of program algebra one can consider execution architectures that take security matters into account, and establish an analysis in similar style. Based on the undecidability of virus detection as described by Cohen [32], a first result is recorded in [23].

EWSCS’06 - Program Algebra and Thread Algebra

3

The further content of this paper is divided into five parts: in Section 1.2 we formally introduce threads and state machines. In Section 1.3 we introduce the program algebra PGA. Then, in Section 1.4 we introduce execution architectures. In Section 1.5, we study the Turing machine as an example of an analytic architecture. Finally, in Section 1.6, we focus on forecasting reactors and rational agents in the setting of (analytic) execution architectures.

1.2

Threads and State Machines

The behavior of programs under execution is modelled by threads. In this section we introduce thread algebra. Then we introduce state machines, devices that can be used by a thread in order to increase expressiveness.

1.2.1

Thread Algebra

Basic thread algebra, or BTA for short, is intended for the description of sequential program behavior (see [17]; in [15] BTA is introduced as basic polarized process algebra). Based on a finite set A of actions it has the following constants and operators: • the termination constant S, • the deadlock or inaction constant D, • for each a ∈ A, a binary postconditional composition operator  a  . We use action prefixing a ◦ P as an abbreviation for P  a  P and take ◦ to bind strongest. Furthermore, for n ∈ N we define an ◦ P by a0 ◦ P = P and an+1 ◦ P = a ◦ (an ◦ P ). The operational intuition behind thread algebra is that each action represents a command which is to be processed by the execution environment of a thread. More specifically, an action is taken as a command for a service offered by the environment. The processing of a command may involve a change of state of this environment. At completion of the processing of the command, the service concerned produces a reply value true or false to the thread under execution. The thread P aQ will then proceed as P if the processing of a yielded the reply true indicating successful processing, and it will proceed as Q if the processing of a yielded the reply false. BTA can be equipped with a partial order and an approximation operator. 1. v is the partial ordering on BTA generated by the clauses (a) for all P ∈ BTA, D v P , and (b) for all P1 , P2 , Q1 , Q2 ∈ BTA, a ∈ A, P1 v Q1 & P2 v Q2 ⇒ P1  a  P2 v Q1  a  Q2 . 2. π : N × BTA → BTA is the approximation operator determined by the equations

4

J.A. Bergstra, I. Bethke, and A. Ponse (a) for all P ∈ BTA, π(0, P ) = D, (b) for all n ∈ N, π(n + 1, S) = S, π(n + 1, D) = D, and (c) for all P, Q ∈ BTA, n ∈ N, π(n + 1, P  a  Q) = π(n, P )  a  π(n, Q). We further write πn (P ) instead of π(n, P ).

The operator π finitely approximates every thread in BTA. That is, for all P ∈ BTA, ∃n ∈ N π0 (P ) v π1 (P ) v · · · v πn (P ) = πn+1 (P ) = · · · = P. Threads can be finite or infinite. Following the metric theory of [3] as the basis of processes in [12], BTA has a completion BTA∞ which comprises also infinite threads. Standard properties of the completion technique yield that we may take BTA∞ as the cpo consisting of all so-called projective sequences. That is, BTA∞ = {(Pn )n∈N | ∀n ∈ N (Pn ∈ BTA & πn (Pn+1 ) = Pn )} with (Pn )n∈N v (Qn )n∈N ⇔ ∀n ∈ N Pn v Qn and (Pn )n∈N = (Qn )n∈N ⇔ ∀n ∈ N Pn = Qn . (For a detailed account of this construction see [7].) Let I = {1, ..., n} for some n > 0. A finite linear recursive specification over BTA is a set of equations Xi = ti (X) for i ∈ I with X = X1 , ..., Xn and all ti (X) of the form S, D, or Xil  ai  Xir for il , ir ∈ I and ai ∈ A. In BTA∞ , finite linear recursive specifications represent continuous operators having as unique fixed points regular threads, i.e., threads which can only reach finitely many states. Example 1.2.1 Let n > 0. The regular thread an ◦ D is the fixed point for X1 in the specification {Xi = a ◦ Xi+1 | i = 1, ..., n} ∪ {Xn+1 = D}. The regular thread an ◦ S is the fixed point for X1 in {Xi = a ◦ Xi+1 | i = 1, ..., n} ∪ {Xn+1 = S}. Both these threads are finite. The infinite regular thread a∞ is the fixed point for X1 in the specification {X = a ◦ X} and corresponds to the projective sequence (Pn )n∈N with P0 = D and Pn+1 = a ◦ Pn . Observe that e.g. an ◦ D v an ◦ S, an ◦ D v a∞ but an ◦ S 6v a∞ .

EWSCS’06 - Program Algebra and Thread Algebra

5

For the sake of simplicity, we shall often define regular threads by providing only one or more equations. For example, we say that P = a ◦ P defines a regular thread with name P (so P = a∞ in this case). We end this section with the observation that for regular threads P and Q, P v Q is decidable. Because one can always take the disjoint union of two recursive specifications, it suffices to argue that Pi v Pj in P1 = t1 (P ), ..., Pn = tn (P ) is decidable. This follows from the assertion ∀i, j ≤ n πn (Pi ) v πn (Pj ) ⇔ Pi v Pj ,

(1.1)

where πl (Pk ) is defined by πl (tk (P )), because v is decidable for finite threads. Without loss of generality, assume n > 1. To prove (1.1), observe that ⇐ follows by definition of regular threads. For the reverse, choose i, j and assume that πn (Pi ) v πn (Pj ). Suppose Pi 6v Pj , then for some k > n, πk (Pi ) 6v πk (Pj ) while πk−1 (Pi ) v πk−1 (Pj ). So there exists a trace of length k from Pi of the form a

b

−→ Pi0 −−false −−→ ... Pi −−true that is not a trace of Pj , while by the assumption the first n actions are a trace of Pj . These n actions are connected by n+1 states, and because there are only n different states P1 , ...,Pn , a repetition occurs in this sequence of states. So the trace witnessing πk (Pi ) 6v πk (Pj ) can be made shorter, contradicting k’s minimality and hence the supposition. Thus Pi v Pj . As a consequence, also P = Q (i.e., P v Q and Q v P ) is decidable for regular threads P and Q.

1.2.2

State Machines

A state machine is a pair hΣ, F i consisting of a set Σ of so-called co-actions and a reply function F . This reply function is a mapping that gives for each finite sequence of co-actions from Σ a reply value true or false. State machines were introduced in [21]. Example 1.2.2 A counter is a state machine C = hΣ, F i with Σ = {inc, dec} consisting of the increase and decrease co-actions and the reply function F which replies true to inc, and false to dec if and only if the counter is zero. The initial value of C is zero, and the counter has a value in each state. Below, we return to this example. In order to provide a specific description of the interaction between a thread and a state machine, we will use for actions the general notation c.a where c is the so-called channel or focus, and a is the co-action. For example, c.inc is the action which increases a counter via channel c. This interaction is is defined with help of the use operator / . For a state machine S = hΣ, F i, a finite thread P and a channel c, the defining rules

6

J.A. Bergstra, I. Bethke, and A. Ponse

for P/c S (the thread P using the state machine S via channel c) are: S/c S D/c S (P  c 0 .a  Q)/c S (P  c.a  Q)/c S (P  c.a  Q)/c S (P  c.a  Q)/c S

= = = = = =

S, D, (P/c S)  c 0 .a  (Q/c S) if c0 6= c, P/c S 0 if a ∈ Σ and F (a) = true, Q/c S 0 if a ∈ Σ and F (a) = false, D if a 6∈ Σ.

where S 0 = hΣ, F 0 i with F 0 (σ) = F (aσ) for all co-action sequences σ ∈ Σ+ . The use operator is expanded to infinite threads P by stipulating P/c S = (πn (P )/c S)n∈N . As a consequence, P/c S = D if for any n, πn (P )/c S = D. Finally, repeated applications of the use operator bind to the left, thus P/c0 S0 /c1 S1 = (P/c0 S0 )/c1 S1 . We end this section with an example on the use of a state machine. Example 1.2.3 Let {a, b, c.inc, c.dec} ⊆ A, where the last two actions refer to the counter C defined in Example 1.2.2. We write C(n) for a counter with value n ∈ N, so C = C(0). By the defining equations for the use operator it follows that for any thread P, (c.inc ◦ P )/c C(0) = P/c C(1), and ∀n ∈ N, (c.inc ◦ P )/c C(n) = P/c C(n+1). Furthermore, it easily follows that ( S if n = 0, (P  c.dec  S)/c C(n) = P/c C(n−1) otherwise. Now consider the regular thread Q defined by1 Q = (c.inc ◦ Q)  a  R, R = b ◦ R  c.dec  S. Then Q/c C(0) = ((c.inc ◦ Q)  a  R)/c C(0) = (Q/c C(1))  a  (R/c C(0), and for all n ∈ N, Q/c C(n) = (Q/c C(n+1))  a  (R/c C(n). It is not hard to see that Q/c C(0) is an infinite thread with the property that for all n, a trace of n+1 aactions produced by n positive and one negative reply on a is followed by bn ◦ S. This yields an irregular thread: if Q/c C(0) were regular, it would be a fixed point of some finite linear recursive specification, say with k equations. But specifying a trace bk ◦ S already requires k+1 linear equations X1 = b ◦ X2 , ..., Xk = b ◦ Xk+1 , Xk+1 = S, which contradicts the assumption. So Q/c C(0) is not regular. 1

Note that a linear recursive specification of Q requires (at least) five equations.

EWSCS’06 - Program Algebra and Thread Algebra

1.3

7

Programs and Program Algebra

In this section we introduce the program algebra PGA (see [15]) and discuss its relation with thread algebra. Furthermore, we show that each computable thread can be expressed by either a computable sequence of PGA-instructions, or by a regular sequence of PGA-instructions that uses a Turing machine tape as a state machine.

1.3.1

PGA, Basics of Program Algebra

Given a thread algebra with actions in A, we now consider the actions as so-called basic instructions. The syntax of PGA has the following primitive instructions as constants: Basic instruction a ∈ A. It is assumed that upon the execution of a basic instruction, the (executing) environment provides an answer true or false. However, in the case of a basic instruction, this answer is not used for program control. After execution of a basic instruction, the next instruction (if any) will be executed; if there is no next instruction, inaction will occur. Positive/negative test instruction ±a for a ∈ A. A positive test instruction +a executes like the basic instruction a. Upon false, the program skips its next instruction and continues with the instruction thereafter; upon true the program executes its next instruction. For a negative test instruction −a, this is reversed: upon true, the program skips its next instruction and continues with the instruction thereafter; upon false the program executes its next instruction. If there is no subsequent instruction to be executed, inaction occurs. Termination instruction !. This instruction prescribes successful termination. Jump instruction #k (k ∈ N). This instruction prescribes execution of the program to jump k instructions forward; if there is no such instruction, inaction occurs. In the special case that k = 0, this prescribes a jump to the instruction itself and inaction occurs, in the case that k = 1 this jump acts as a skip and the next instruction is executed. In the case that the prescribed instruction is not available, inaction occurs. PGA-terms are composed by means of concatenation, notation ; , and repetition, notation ( )ω . Instruction sequence congruence for PGA-terms is axiomatized by the axioms PGA1-4 in Table 1.1. Here PGA2 is an axiom-scheme: for each n > 0, (X n )ω = X ω , where X 1 = X and X k+1 = X; X k . A closed PGA-term is often called a PGAprogram. Table 1.1: Axioms for PGA’s instruction sequence congruence (X; Y ); Z = X; (Y ; Z) (PGA1) n ω ω (X ) = X for n > 0 (PGA2)

Xω; Y = Xω (X; Y )ω = X; (Y ; X)ω

From the axioms PGA1-4 one easily derives unfolding, i.e., X ω = X; X ω .

(PGA3) (PGA4)

8

J.A. Bergstra, I. Bethke, and A. Ponse

Furthermore, each PGA-program can be rewritten into an instruction equivalent canonical form, i.e., a closed term of the form X or X; Y ω with X and Y not containing repetition. This also follows from the axioms in Table 1.1. We use the abbreviation SPI for Sequence of Primitive Instructions. A SPI is also called a program object, or sometimes shortly, a program. PGA-programs represent a certain class of SPI’s, called the regular SPI’s. In particular, each regular SPI can be represented in PGA as a canonical form. We will often use basic instructions in so-called focus.method notation, i.e., basic instructions of the form f.m where f is a focus (channel name) and m a method name. The m here is sometimes called a service-instruction because it refers to the use of some state machine, and is related with a co-action as defined in Section 1.2.2. Two examples of instructions in focus.method notation are c.inc and c.dec, related with the actions controlling a counter discussed in Example 1.2.3. In the next section we will relate all basic and test instructions to the actions of a thread; this is called thread extraction.

1.3.2

Thread Extraction: from PGA to Thread Algebra

The thread extraction operator |X| assigns a thread to program object X. Thread extraction is defined by the thirteen equations in Table 1.2, where a ∈ A and u is a primitive instruction. Table 1.2: Equations for thread extraction on PGA |!| |a| |+a| |−a|

= = = =

|!; X| |a; X| |+a; X| |−a; X|

S a◦D a◦D a◦D

= = = =

S a ◦ |X| |X|  a  |#2; X| |#2; X|  a  |X|

|#k| |#0; X| |#1; X| |#k + 2; u| |#k + 2; u; X|

= = = = =

D D |X| D |#k + 1; X|

Some examples: |(#0)ω | = |#0; (#0)ω | = D, |−a; b; c| = |#2; b; c|  a  |b; c| = |#1; c|  a  b ◦ |c| = |c|  a  b ◦ c ◦ D = c ◦ D  a  b ◦ c ◦ D.

In some cases, these equations can be applied from left to right without ever generating any behavior, e.g., |(#2; a)ω | = |#2; a; (#2; a)ω | = |#1; (#2; a)ω | = |(#2; a)ω | = . . .

EWSCS’06 - Program Algebra and Thread Algebra

9

In such cases, the extracted thread is defined as D. It is also possible that thread extraction yields an infinite recursion, e.g., |aω | = |a; aω | = a ◦ |aω | (in the previous section we denoted this thread by a∞ ). If the behavior of X is infinite, it is regular and can be represented by a (linear) recursive specification, e.g., |(a; +b; #3; −b; #4)ω | = P in P = a ◦ (P  b  Q), Q = P  b  Q. It follows easily that any PGA-program defines a regular thread, and conversely, each regular thread can be defined in PGA: linear equations of the form X = S or X = D can be defined by instructions ! and #0, respectively, and a linear equation X =Y aZ can be associated with a triple +a; #k; #l. Connecting these program fragments in a repetition and instantiating the jump counters k and l with the appropriate values then yields a PGA-program that defines a solution for the first equation. A typical example: P1 = P2  a  P2 , P2 = P3  b  P1 , P3 = D.

1.3.3

7→

(+a; #2; #1; +b; #2; #2; #0)ω .

Programming Computable Threads

A thread is computable if it can be represented by an identifier E0 and two computable functions g, f in the following way (k ∈ N):   if g(k) = 0, D Ek = S if g(k) = 1,   Ehk+f (k),1i  ag(k)  Ehk+f (k),2i if g(k) > 1. Here we use the bijective pairing function h , i defined by hn, mi = 12 ((n+m)2 +3m+n). So n < hn+i, 1i < hn+i, 2i for all n, i ∈ N. Theorem 1.3.1 PGA’s sequences of primitive instructions (SPI’s) are universal: for each computable thread α there is a SPI with α as its behavior. Proof. Let E0 be a computable thread as defined above. Then we define  #0; #0; #0 if g(k) = 0,    !; !; ! if g(k) = 1, ˜k = E  +ag(k) ; #3(hk+f (k), 1i − k − 1) + 2;    #3(hk+f (k), 2i − k − 1) + 1 if g(k) > 1. ˜0 ; E ˜1 ; ...| (and that Ek = |E ˜k ; E ˜k+1 ; ...|). a It is easily seen that E0 = |E

10

J.A. Bergstra, I. Bethke, and A. Ponse

Furthermore, PGA’s repeating sequences of instructions—the regular SPI’s—are universal with the aid of a state machine TMT (Turing machine tape) if we restrict to a finite number of actions: Theorem 1.3.2 For each computable thread α there is a closed PGA-term X such that |X|/tmt TMT = α. This is a standard result in the setting of Turing machines (see, e.g., [63, 54]), given the fact that finite control can be modelled in PGA.

1.4

Execution Architectures

In this section we focus on programs in an execution architecture. We will use ACPbased process algebra to model so-called ‘analytical architectures’. Finally, we try to clarify the role of programs as binaries in machines.

1.4.1

Analytic versus Synthetic Architectures

We consider the following types of execution architectures: Analytic Architecture (AnArch): provides a hypothetical decomposition of a system into parts. An AnArch can serve as an explanation of a setting in a black box context (the system is seen as a blackbox, with the AnArch describing its internals for the sake of explanation). An AnArch will not be on the pathway to construction. In Section 1.4.2 we discuss this type of architecture in detail. Synthetic Architecture (SynArch): an architecture (description of how a whole is composed from parts) providing information on the true (or proposed, intended) internal structure of a system. Often a compositional semantic technique is absent. Typically, a program in compiled form (a binary) can be a part of a SynArch. In Section 1.4.3 we discuss these architectures. The proposed execution architecture for PGA is an AnArch consisting of several interacting components. First there is a component containing an instruction sequence. This component is called the SPI container ; it is able to process one instruction at a time and issue the appropriate action. Next there are reactors. A reactor is a generalized state machine: it can not only process actions generated by the SPI container and issue replies, but may also engage in external communications. No attention is paid to the way in which a SPI may in fact be stored or generated. We visualize an AnArch in the following way: α

SPI container

f0

f1

f2

fn

channels

?

?

?

?

reactors

···

EWSCS’06 - Program Algebra and Thread Algebra

11

Here α represents a SPI of which the basic instructions are in focus.method notation for foci/channels f0 , ..., fn , thus of the form fi .a (see Section 1.3.1). These channels play a reserved role and are supposed not to be composed with other parts of the AnArch or any extension of it. As in the case of state machines (see Section 1.2.2), a reactor is unaware of the name of the SPI-channel that addresses it. We will not be very specific about the definition of a reactor, and impose only a few requirements. A first semantics for reactors comprises that of state machines: a reply function is defined that takes a history of requests as input (a sequence of co-actions) and upon the request via a focused action produces a reply true or false. In Section 1.4.2 we introduce a process based semantics for AnArchs. A process is a mathematical entity in a space of processes (like a number being an element of a field). The design of the process space depends on the underlying theory of processes used. We will use ACP-based process algebra ([12], but many other process theories can be used instead. The purpose of the use of processes is specification. Here, ‘specification’ is used in a fairly limited way: it must be compared with ‘quantification’ (stating numerical sizes) and ‘qualification’ (expressing objectives, goals, methods and reasons). Furthermore, specification stands for the specification of behavior. Specification need not be perfect (i.e., it may provide an approximation of a system rather than a perfect view, be it at some level of abstraction). Specification has no a priori place in some artifact construction life cycle, just as quantification or qualification. A process expression, e.g. r1 (a)(s2 (true) · r1 (b) + s3 (false) · δ) provides a text that represents a process (that is, a specification of behavior). In a similar way, a program expression f1 .a; (+f2 .b; #4, −f3 .c)ω represents a SPI. However, there is a crucial difference: suppose process expression P denotes a specification of a system Sys, say P = Sys, or at least, P is a reasonable approximation of Sys. Now it is not plausible to expect that P (in any form) constitutes a part in any SynArch for Sys. On the other hand, if Sys is a system executing some program p, then it is plausible that a SynArch of Sys contains, perhaps in a transformed (compiled) form, p as a part. Process expressions occur as parts of systems that analyze or simulate other systems. The following AnArch is perfectly acceptable: Sys P Sys contains process expression P and behaves as P , thus Sys is a P -simulator. As a SynArch this makes little sense. Moreover, simulation is only one of many objectives supported by processes. Calculation and verification is another and probably more important one.

1.4.2

Compositional Process Specification for Analytic Architectures

We introduce an ACP-based process semantics for AnArchs over action alphabet A = {f.a | f ∈ F, a ∈ B}

12

J.A. Bergstra, I. Bethke, and A. Ponse

where F is a set of channels and B a set of co-actions. For this process semantics we will employ communication between send actions sf (a) and receive actions rf (a): rf (a)|sf (a) = cf (a) for a ∈ B ∪ {true, false}. Furthermore, we use an action t to model the difference between thread termination and deadlock. So we assume that our set of process algebra actions includes {t, rf (a), sf (a), cf (a) | f ∈ F, a ∈ B ∪ {true, false}}. We use the notation [[P ]] for process semantics of a thread P extracted from a SPI container: [[S]] = t, [[D]] = t∗ δ, [[P  f.a  Q]] = sf (a)(rf (true)[[P ]] + rf (false)[[Q]]). Here x∗ y is defined by x∗ y = x(x∗ y) + y (see [9]). Taking δ for y and using the ACP-axiom x + δ = x, it follows that t∗ δ behaves as t∞ , i.e., an infinite sequence of t-actions. We write [[R]] for the process algebraic semantics of a reactor R. We require that [[R]] uses the following actions for communication with a SPI container: rserv (a), sserv (true), sserv (false). Furthermore, we require that any reactor R has the property that rserv (a) and sserv (b) (b ∈ {true, false}) occur in alternating order in each trace of [[R]]. When connected to a SPI container via channel f , the renaming operator ρserv 7→f (which renames the channel name serv to f ) will be applied to [[R]]. Definition 1.4.1 Given a PGA-program X and reactors R0 , ..., Rn , a concrete analytical architecture, notation cpgaEA, is defined by cpgaEA[ X f0 :R0 , f1 :R1 , ..., fn :Rn ] = ∂H ([[|X|]] k ρserv 7→f0 ([[R0 ]]) k ρserv 7→f1 ([[R1 ]]) k ... k ρserv 7→fn ([[Rn ]])) with encapsulation set H = {ri (a), si (a) | a ∈ B ∪ {true, false}, i = f0 , f1 , ..., fn }. An (abstract) analytical architecture pgaEA is defined by pgaEA[ X f0 :R0 , f1 :R1 , ..., fn :Rn ] = τI (cpgaEA[ X f0 :R0 , f1 :R1 , ..., fn :Rn ]) with abstraction set I = {t, ci (a) | a ∈ B ∪ {true, false}, i = f0 , f1 , ..., fn }. If the value of n is known or immaterial, we often write (c)pgaEA[ X fi :Ri ]. Notice that in common process semantics, τ ∗ δ = τ δ (cf. [20]). We observe that cpgaEA[ X f0 :R0 , f1 :R1 , ..., fn :Rn ]

EWSCS’06 - Program Algebra and Thread Algebra

13

and its pgaEA-variant are computable if |X| and all Rj are. A reactor R is a state machine if [[R]] has only actions rserv (a), sserv (true) and sserv (false), i.e., no external events, only update of its memory state and computation of Boolean output. The results in [21] imply the following theorem. Theorem 1.4.2 Let R = R0 , R1 , ..., Rk , Rk+1 , ..., Rn with Rk+1 , ..., Rn state machines. Furthermore, let α = |X|/fk+1 Rk+1 .../fn Rn . Then for H = {ri (a), si (a) | a ∈ B ∪ {true, false}, i = f0 , f1 , ..., fk } and I = {t, ci (a) | a ∈ B ∪ {true, false}, i = f0 , f1 , ..., fk }, pgaEA[ X fi :Ri ] = τI (∂H ([[α]] k ρserv 7→f0 ([[R0 ]]) k ρserv 7→f1 ([[R1 ]]) k ... k ρserv 7→fk ([[Rk ]]))).

1.4.3

Synthetic Architectures for Binaries

In this section we try to clarify the role of programs in machines. A binary is just a finite {0, 1}-sequence (i.e., a binary file) with an end-of-file marker eof. Consider the following SynArch:

Mac

loader

bf

This SynArch displays a machine Mac containing a binary file bf. It has a special port named loader used to enter bf in Mac bitwise. Assuming that Mac is a classical piece of computing machinery, the process [[Mac(bf)]] specifying the behavior of bf in Mac will be a computable process. With  denoting the empty bit-sequence, [[Mac]] can be specified in process algebra as follows: [[Mac]] = Mloading (), Mloading (w) = rloader (0) · Mloading (w0) + rloader (1) · Mloading (σ1) + rloader (eof) · [[Mac(bf)]]. It is reasonable to expect that [[Mac(bf)]] depends uniformly on bf (in the sense of computability theory, see e.g., [63]). Then, also [[Mac]] itself is a computable process, implying the following result. Theorem 1.4.3 The process [[Mac]] can be denoted modulo branching bisimulation equivalence in ACP extended with abstraction, binary Kleene star and pushdown operator, and this can be done using finitely many actions.

14

J.A. Bergstra, I. Bethke, and A. Ponse

This follows from a general expressiveness result, proven in detail in [19, Thm. 4.2.5]. Note 1.4.4 Let bf = bf0 bf1 ...bfn eof. For appropriate encapsulation set H and abstraction set I, we find τ · [[Mac(bf)]] = τI ◦ ∂H (L(bf) k [[Mac]]) where L(bf) = sloader (bf0 ) · ... · sloader (bfn ) · sloader (eof).

Let pga2bin4m be a mapping from PGA to bit sequences (pga to bin4m, where bin4m abbreviates “binaries for machine”). Then pga2bin4m is a code generator mapping if the following holds for all X ∈ PGA: pgaEA[ X fi :Ri ] = [[Mac(pga2bin4m(X))]]. That is: the analytic architecture pgaEA with its set of reactors explains (i.e., corresponds to) the synthetic architecture Mac. In practice one is happy if this works for all X with pga2bin4m(X) having a size of less than k Mb (for some k). In this setting, the following jargon is useful: 1. Middle code or intermediate code: a PGA-program as above. 2. A machine (program) producing pga2bin4m(X) from X is a code generator or compiler back end for Mac. 3. The concept of a machine code can not be defined here: clearly, some bf are more useful than other bf’s. But there is no obvious criterion regarding [[Mac(bf)]] to select the binaries for Mac from arbitrary bit sequences. 4. A higher program notation, say PGLZ, can be understood if a behavior preserving mapping pglz2pga to PGA is known (such a mapping is called a projection) and a pgaEA such that pgaEA[ pglz2pga(X) fi :Ri ] corresponds to the intended meaning of program X. A compiler is a system (or a program for a system) that allows to compute pglz2pga (or an optimized version of it that produces semantically equivalent behavior). For a PGLZ-program X we then find pgaEA[ pglz2pga(X) fi :Ri ] = [[Mac(pga2bin4m(pglz2pga(X)))]], and it is common practice to call pga2bin4m(pglz2pga(X)) a program. This is one of the possible justifications for the qualification of a binary that is part of a SynArch as a program. We fix the nature of this qualification as follows: Code generator mapping criterion: a binary bf is a program if it is in the range of a code generator mapping (in a setting that explains the behavior of Mac(bf) via an AnArch).

EWSCS’06 - Program Algebra and Thread Algebra

15

The qualification of bf as a program by this criterion seems to be at odds with the basis of PGA because PGA starts from the assumption that a program is a sequence of instructions (see [15]). However, if pga2bin4m is computable, it has a semi-computable inverse, say bin4m2pga and bf qualifies as a program because bin4m2pga(bf) does. Of course, it is immaterial that pga2bin4m is taken to be an inverse pga2bin4m. What matters is: for all bf (or as many as one cares), [[Mac(bf)]] = pgaEA[ bin4m2pga(bf) fi :Ri ]  = [[Mac(pga2bin4m(bin4m2pga(bf)))]] . Thus, the code generator mapping criterion is consistent with the PGA-criterion for being a program. Note 1.4.5 1. Having a far more detailed SynArch at hand with bf as a part, one may find other justifications for qualifying bf as a program. However, we failed to develop such a story with any form of acceptable generality. 2. The projection bin4m2pga may be called a disassembler-projection (ignoring the complexity of loading). Then, if the qualification of bf as a program in Mac(bf) is justified by means of the code generator mapping criterion, a disassembler-projection semantics of bf is (implicitly) known/given. 3. The justification of the qualification of bf as a part of the SynArch Mac(bf) is an argument of a certain form: qualification on the basis of a most plausible history. If we see an object when it is a corpse, of course we see it if it was a living individual of some species that subsequently died. How else could the object have come into existence? If we see bf in Mac where bf = pga2bin4m(X), that must be related to bf’s history. How else would it have originated? I.e., bf is just another form or phase of X, like a corpse being another phase of a living body. 4. The middle code exists at the instruction sequence level in PGA. It is at the same time target code for projection semantics. Given a SynArch Mac(...), its binaries are also called object code.

1.5

An Analytic Architecture for Turing Machines

In this section we consider an enhanced version of the Turing machine tape and a PGAbased language for programming it. We prove the unsolvability of the halting problem, and show that this problem becomes decidable if we restrict our language sufficiently.

1.5.1

The Turing Machine

The original reference to the Turing machine is [72]. A Turing machine M consists of a finite control and a tape, often visualized in the following style: 5

b b b ...

16

J.A. Bergstra, I. Bethke, and A. Ponse

where b stands for “blank” (i.e., a blank square), and a head that can be used for reading and writing on that tape, depicted as 5. Usually, the tape has a left end, and extends indefinitely to the right. The head can never fall off the tape (at the left side). The control is such that it distinguishes a halting state, after which control is terminated and the tape can be inspected for possible output. In a non-halting state, control prescribes some action to be undertaken and the next control state. Actions are either read or write a symbol in the square (where write means: replace the symbol that was already there, and write a blank means: erase), or move the head one tape square to the left (if possible) or right. The Church-Turing thesis is the following principle (formulation taken from [54, page 246]): The Turing machine that halts on all inputs is a precise formal notion that corresponds to the naive notion of an “algorithm”. Finally, the halting problem HP is the question whether or not a Turing machine M halts on input string w.

1.5.2

Enhanced Turing Machine Tape

A Turing machine tape is seen as a state machine TMT. We consider an enhanced type of Turing machine tape over alphabet {0, 1, ; } called ETMT to allow for more powerful programming. A state of the ETMT is for example 5

b ; ; 0 1 0 1 1 0 ; 1 0 1 ; b where the b stands for blank, the semi-colon serves as a separator, and the 5 is the head. The left b represents an indefinite number of blanks to the left2 , and the rightmost b signifies that the tape indefinitely extends to the right. The empty tape (containing only blanks), thus 5

b b

ˆ b ) or simply by ETMT. The configuration of ETMT that is denoted by ETMT( b contains sequence σ = σ1 ...σn with the head at the left blank, i.e., 5

b σ1 ... σn b

ˆ σ b ). In this ETMT-notation, the two occurring b ’s will be is denoted by ETMT( b referred to as the left blank and the right blank, respectively. We consider the following service-instructions for controlling the ETMT, where a bit sequence on the tape is a sequence of 0 or 1 occurrences of maximal length (so at both ends neighboring either a semicolon or a blank). test:0 test:1 test:semicolon test: b 2

write:0 mv:left mv:begin write:1 mv:right dup write:semicolon write: b

This does not increase the computational power of a Turing Machine (see e.g., [54]).

EWSCS’06 - Program Algebra and Thread Algebra

17

These instructions have the following effect: test:0 (or 1, semicolon, b ) checks whether the head points to a 0 (or the other symbol indicated) and returns true if this is the case, and false otherwise. write:0 (or 1, semicolon) writes the mentioned symbol at head position and returns true. write: b writes a blank at head positon if to the left or the right of the head there is a blank already and returns true, otherwise nothing changes and false is returned. mv:left fails if the head points at a blank and to the left there is a blank as well, in this case it returns false and nothing happens; otherwise the head moves one square to the left and returns true. mv:right works similar. mv:begin places the head at the left blank if it is not already there, and returns true. dup duplicates the leftmost bit sequence if any exists, and puts the result next to it, separated by a semicolon. Furthermore, the head moves to the left blank. Returns true if actual duplication has taken place, and false otherwise. Some examples: dup ˆ ) −− ˆ;; b) ETMT( b ; ; b −→ ETMT( b dup ˆ ) −− ˆ ; ; 0; 0 b ) ETMT( b ; ; 0 b −→ ETMT( b

(returns false), (returns true),

dup

ˆ 01; 01; 101; b ) (returns true). ETMT( b 01; 1ˆ01; b ) −−−→ ETMT( b

1.5.3

Programming the Turing Machine

For programming the Turing machine we shall use the programming language PGLC [15], a language based on PGA. The only construct (operator) in PGLC is concatenation. Instead of the repetition operator ( )ω of PGA, PGLC contains backwards jumps \#k for any k ∈ N. For example, +a; #0; \#2 is a PGLC-program that behaves as (+a; #0)ω . Furthermore, termination is modelled implicitly in PGLC: a program terminates after a jump outside the program, or after the last instruction has been executed and that instruction was no jump into the program; the termination instruction ! is not present in PGLC. Let p be some PGLC-program. We write |p|pglc for the thread defined by p. Thread extraction is defined using a projection function pglc2pga from PGLC-programs to PGA-terms: pglc2pga(u1 ; ...; uk ) = (ψ1 (u1 ); ...; ψk (uk ); !; !)ω ,

18

J.A. Bergstra, I. Bethke, and A. Ponse

with the auxiliary operators ψj defined by ( #l ψj (#l) = !

ifj + l ≤ k otherwise,

  #k+2−l if 0 < l < j ψj (\#l) = #0 if l = 0   ! otherwise, ψj (u) = u otherwise, and |p|pglc = |pglc2pga(p)|. For example, |+a; #2; \#2; +b|pglc = |(+a; #2; #4; +b; !; !)ω | = P with P = b ◦ S  a  P. As basic instructions for PGLC we use the service-instructions defined for the ETMT in the previous section prefixed with the focus etmt. An example of a basic instruction is etmt.test:0. We consider execution of Turing machine programs in an AnArch using ETMT as a state machine. To enhance readability, we use an AnArch that takes as its first argument a PGLC-program (instead of a sequence of primitive PGA-instructions), notation pglcEA[ p etmt:ETMT ]. This AnArch is defined with help of the projection function pglc2pga: pglcEA[ p etmt:ETMT ] = pgaEA[ pglc2pga(p) etmt:ETMT ]. An example: pglcEA[ etmt.dup; etmt.mv:right etmt:ETMT( b 01; ˆ101; b ) ] ↓τ ˆ 01; 01; 101; b ) ] pglcEA[ etmt.mv:right etmt:ETMT( b ↓τ √

with ETMT’s configuration: ETMT( b ˆ01; 01; 101; b ).

τ Here each of the τ -steps (−→) comes from two (abstracted) communications triggered √ by the current program-fragment and the ETMT, and the symbol represents termination.

EWSCS’06 - Program Algebra and Thread Algebra

1.5.4

19

The Halting Problem

The Halting Problem (HP) can in this setting be modelled as follows: a PGLC-program ˆ w b (w a bit sequence), notation p halts on the ETMT with initial configuration b (p, w) ∈ HP, if ˆ w b ) ] = τ, pglcEA[ p etmt:ETMT( b as opposed to τ ∗ δ (= τ δ). After halting the tape can be inspected to obtain an output. A program in PGLC is an ASCII character sequence (see, e.g., [1]), and therefore a sequence of bits. As an example, the character a has 97 as its decimal code, which is as a byte (sequence of 8 bits) 01100001, and the character “;” has 59 as its decimal code, which is as a byte 00111011. Given a PGLC-program p, we write p for its representation as a bit sequence. Definition 1.5.1 Program q ∈ PGLC solves the question whether (p, w) ∈ HP if: ˆ p; w b ) ] pglcEA[ q etmt:ETMT( b always halts, and after halting, the tape configuration is of the form ˆ w b ) ] halts, thus (p, w) ∈ HP, ETMT( ˆb 0σ b ) if pglcEA[ p etmt:ETMT( b ˆ w b ) ] halts not, i.e. (p, w) 6∈ HP, ETMT( ˆb 1ρ b ) if pglcEA[ p etmt:ETMT( b for some string σ or ρ. Theorem 1.5.2 The halting problem is unsolvable by means of any program in PGLC. Proof. Suppose the contrary, i.e., a program q ∈ PGLC exists that solves HP. Consider the following program: s = etmt.dup; q; r with r the program that tests whether a 0 or a 1 is at the leftmost non-blank square, after which respectively deadlock or termination follows: r = etmt.mv:right; −etmt.test:1; #0; etmt.mv:begin. Assume without loss of generality that the program q satisfies the following properties: • it does not end with a test instruction (note that |p|pglc = |p; #1|pglc ), • each forward jump in q does not exceed the number of subsequent instructions with more than 1, • each backward jump in q does not exceed the number of preceding instructions.

20

J.A. Bergstra, I. Bethke, and A. Ponse

As a consequence, execution of the program q; r continues after q has been executed with the first instruction of r. We show that both assumptions (s, s) ∈ HP and (s, s) 6∈ HP lead to a contradiction. Hence, s cannot exist, and thus q cannot exist. Assume that (s, s) ∈ HP. Then ˆsb)] pglcEA[ s etmt:ETMT( b ↓τ

(etmt.dup)

ˆ s; s b ) ] pglcEA[ q; r etmt:ETMT( b ↓τ

(by program q)

ˆ 0σ b ) ] pglcEA[ r etmt:ETMT( b for some string σ. The remaining behavior is displayed in Fig. 1.1, and results in pglcEA[ #0; etmt.mv:begin etmt:ETMT( b ˆ0σ b ) ]. This last AnArch clearly represents deadlock because of the first instruction #0, and therefore (s, s) 6∈ HP. Contradiction.

ˆ 0σ b ) ] pglcEA[ r etmt:ETMT( b = ˆ 0σ b ) ] pglcEA[ etmt.mv:right; −etmt.test:1; #0; etmt.mv:begin etmt:ETMT( b ↓τ

(etmt.mv:right)

pglcEA[ −etmt.test:1; #0; etmt.mv:begin etmt:ETMT( b ˆ0σ b ) ] ↓τ

(−etmt.test:1)

pglcEA[ #0; etmt.mv:begin etmt:ETMT( b ˆ0σ b ) ]. Figure 1.1: Last part of the behavior in the case that (s, s) ∈ HP in the proof of Thm. 1.5.2 ˆ s b ) ] is displayed in Assume that (s, s) 6∈ HP. The behavior of pglcEA[ s etmt:ETMT( b Fig. 1.2. This behavior ends with termination, thus (s, s) ∈ HP. Contradiction. sO OUr supposition was definitely wrong and there is no program q ∈ PGLC that solves the halting problem. a

It is an easy but boring task to program mv:begin and dup in terms of the other instructions, thus obtaining a stronger proof. As a theorem, the above one (Theorem 1.5.2) suffices. From that point of view there is nothing special about the (E)TMT or any of its versions. What we see is that: 1. For a close relative of the TMT an impossibility result is obtained. 2. Increasing the instruction set of the ETMT to a ‘super’ ETMT does not help. The proof goes exactly the same. Computability of these instructions is immaterial. What matters is that the halting problem (HP) is posed about all programs over the instruction set that may be used to program its solution.

EWSCS’06 - Program Algebra and Thread Algebra

21

ˆsb)] pglcEA[ s etmt:ETMT( b ↓τ

(etmt.dup)

ˆ s; s b ) ] pglcEA[ q; r etmt:ETMT( b ↓τ

(by program q)

ˆ 1ρ b ) ] pglcEA[ r etmt:ETMT( b = ˆ 1ρ b ) ] pglcEA[ etmt.mv:right; −etmt.test:1; #0; etmt.mv:begin etmt:ETMT( b ↓τ

(etmt.mv:right)

pglcEA[ −etmt.test:1; #0; etmt.mv:begin etmt:ETMT( b ˆ1ρ b ) ] ↓τ

(−etmt.test:1)

pglcEA[ etmt.mv:begin etmt:ETMT( b ˆ1ρ b ) ] ↓τ √

(etmt.mv:begin) ˆ 1ρ b ). with ETMT’s configuration: ETMT( b Figure 1.2: The case that (s, s) 6∈ HP in the proof of Thm. 1.5.2

3. The Church-Turing thesis is not used because the result is phrased about PGLC programs, and not about ‘algorithms’ or ‘computable methods’. Nevertheless, if it is considered convincing that an effective method can be performed by a certain Turing machine, then it is also obvious that it can be programmed in PGLC: • finite control can be modelled in the program; • additional instructions only strengthen the expressive power. This situation changes if we restrict the set of basic instructions. In the proof above we used etmt.dup, etmt.test:1, etmt.mv:right and etmt.mv:begin. Let the language PGLC− contain these as the only basic instructions. Note that etmt.dup is the only PGLC− -instruction that writes on the tape. Theorem 1.5.3 For the language PGLC− , the halting problem is decidable. Proof. We show that halting can be decided for any program and initial tape configuration (the latter not necessarily a sequence of bits and the head pointing at the left blank). ˆ σ b ) ] be given with p ∈ PGLC− and σ some string. If So, let pglcEA[ p etmt:ETMT( b the tape contains no sequence of bits, each occurrence of etmt.dup in p can be replaced by etmt.mv:begin and the tape remains a fixed and finite structure (in the case that etmt.dup is used as a test instruction, the sign of etmt.mv:begin should be reversed). Execution now either yields a cyclic pattern, or stops at #0, or after the last instruction or a terminating jump. As there are finitely many combinations of current instruction and head position, halting is decidable. In the other case the tape contains a sequence of bits. We write ETMT(σ, k) if the head points to the k’th position of the sequence σ, and to the nearest blank if k is out of range.

22

J.A. Bergstra, I. Bethke, and A. Ponse

Transform p to a canonical form in PGA using the projection pglc2pga and the axioms in Table 1.1. If this canonical form contains no repetition we are done, otherwise we obtained a PGA-program U ; V ω with U and V containing no repetition. Halting on U is decidable: either one of the decisive instructions ! or #0 is to be executed. In the other case, execution enters the repeating part V ω . So, we further consider pgaEA[ W ω etmt:ETMT(ρ, k) ] for some cyclic permutation W of V and tape configuration ρ with the head at position k. Now, either etmt.dup occurs at a reachable position in W ω (i.e., occurs in |W ω |), or not. This is decidable. In the last case, the tape remains a fixed and finite structure, and iterating W yields a regular behavior, so halting is decidable. In the other case, the number of etmt.mv:right-instructions in W , say n, limits the number of positions that the head can shift to the right. Consider pgaEA[ W n ; W ω etmt:ETMT(ρ0 , k 0 ) ]. Either halting can be decided on W n , or the repeating part is entered, say X ω (X a cyclic permutation of W ). We may replace all etmt.dup-instructions in X ω by etmt.mv:begin because these duplications concern an unreachable part at the right end of the tape. So, this case is reduced to the previous one, and halting is again decidable. a

Our objective is to position Turing’s impossibility result regarding the assessment of halting properties of program execution as a result about programs rather than machines. The mere requirement that programs of a certain form can decide the halting behavior of all programs of that form leads to a contradiction. This contradiction can be found in the case of programs for a Turing machine tape (TMT). The argument is significantly simplified if an extended command set for a Turing machine tape is used (ETMT). But then the program notation may be reduced to those features (instructions) actually playing a role in the argument and the impossibility result remains but now in a setting where the underlying halting problem is in fact decidable. Note 1.5.4 In the case of the language PGLC− , one should take care in what way a program q solves the halting problem (cf. Definition 1.5.1). A leftmost 0 or 1 on the tape upon q’s termination is not appropriate because apart from etmt.dup, the language contains no writeinstructions. For PGLC− , a possible criterion is the head position after termination of q: ˆ p; w b ) ] pglcEA[ q etmt:ETMT( b always terminates, and the head is at the left blank if (p, w) ∈ HP and at the rightmost blank otherwise. This is consistent because in PGLC− there is no means to remove bits from the tape and the initial configuration contains two non-empty bit sequences, so left and right blank can always be distinguished. As in the proof of Theorem 1.5.2, this yields the impossibility result for the question whether (s, s) ∈ HP for s = etmt.dup; q; +etmt.mv:right; #0.

We conclude that as a methodological fact about computer programming, the undecidability of the halting problem is an impossibility result which is quite independent of the computational power of the machine models to which it can be applied.

1.6

Forecasting Reactors and Rational Agents

The halting problem can be investigated without the use of TMTs as a problem regarding the potential capabilities of a reactor serving as a computation forecasting device. In this section we show that restricting to true and false is problematic and introduce a third truth-value. Furthermore, we combine forecasters with ‘rational agents’, and provide a modeling of the Newcomb Paradox. Finally, we model the Prisoner’s Dilemma as an analytic architecture.

EWSCS’06 - Program Algebra and Thread Algebra

1.6.1

23

Forecasting Reactors

Forecasting is not an obvious concept, the idea that it is to be done by means of a machine even less. We will provide a ‘clean’ interpretation of forecasting and investigate its fate in the context of execution architectures for PGA. The use of an AnArch is justified because this story is meant at a conceptual level and is not part of any technical design. In the previous section it was shown that restricting to true and false is problematic. Therefore we now consider the case that the evaluation of test instructions may yield not only true or false, but also the value M (meaningless):   a ◦ |#1; X| if a’s execution returns true, |+a; X| = a ◦ |#2; X| if a’s execution returns false,   a ◦ |#3; X| if a’s execution returns M, and   a ◦ |#2; X| if a’s execution returns true, |−a; X| = a ◦ |#1; X| if a’s execution returns false,   a ◦ |#3; X| if a’s execution returns M. More information on many-valued logics using true, false and M can be found in [11, 18]. We will use fcu as the focus pointing to a forecasting unit FCU in the following way: fcu.Q will ask the forecaster to reply about its opinion regarding the question Q. At this point the precise phrasing of the requirement on the FCU is essential. In pgaEA[ fcu.Q; X fcu:FCU, fi :Ri ] one expects FCU to reply true if Q is valid as an assertion on pgaEA[ X fcu:FCU, fi :Ri ], notation pgaEA[ X fcu:FCU, fi :Ri ] sat Q. More precisely, in pgaEA[ +fcu.Q; u; X fcu:FCU, fi :Ri ] we expect that • true is returned if pgaEA[ u; X fcu:FCU, fi :Ri ] sat Q, • false is returned if pgaEA[ X fcu:FCU, fi :Ri ] 6sat Q, • M is returned otherwise. Moreover, in case that both true and false could be returned, preference is given to returning true. Consider Q = halting : when pgaEA[ X fcu:FCU, fi :Ri ] sat halting it will hold along all execution traces (irrespective of any context) that X halts. Then, if a reactor can engage in external communications, the possibility that it will must be taken into

24

J.A. Bergstra, I. Bethke, and A. Ponse

account. Moreover, we cannot exclude the possibility that a reactor falls into inaction as a result of such an external communication. Therefore we assume the absence of reactors apart from FCU, and investigate to what extent FCU can be made to provide a useful forecast regarding the halting-question. Theorem 1.6.1 A forecasting reactor FCU needs the third truth value M. Proof. Consider pgaEA[ +fcu.halting; #0; !; ! fcu:FCU ]. If true is replied then pgaEA[ #0; !; ! fcu:FCU ] sat halting which is not true; if false is replied then pgaEA[ !; ! fcu:FCU ] 6sat halting which is also not true. Thus M should be replied, and τ

pgaEA[ +fcu.halting; #0; !; ! fcu:FCU ] −−→ pgaEA[ ! fcu:FCU ] (which will halt by the way). a

We notice that the reactor FCU may use whatever inspection of other parts of an AnArch. However, it cannot correctly forecast the question with either true or false. Nevertheless, a lot is possible, e.g.: pgaEA[ +fcu.halting; !; #0 fcu:FCU ] generates reply true, and pgaEA[ +fcu.halting; #0; #0 fcu:FCU ] generates reply false. Theorem 1.6.2 A best possible FCU for halting can be given for PGA with fcu.halting as its only basic instruction. Proof. Let X true be obtained from X by replacing each occurrence of fcu.halting by fcu.true, the test that always yields true, and let X false be defined similarly (replacement by fcu.false). Consider pgaEA[ +fcu.halting; u; X fcu:FCU ]. 1. In the case that pgaEA[ utrue ; X true fcu:FCU ] sat halting, we may define that +fcu.halting returns true, and as a consequence pgaEA[ +fcu.halting; u; X fcu:FCU ] sat halting. 2. In the case that pgaEA[ X false fcu:FCU ] 6sat halting, we may define that +fcu.halting returns false, and as a consequence pgaEA[ +fcu.halting; u; X fcu:FCU ] 6sat halting. 3. If none of the cases above applies, +fcu.halting generates reply M.

EWSCS’06 - Program Algebra and Thread Algebra

25

E.g., pgaEA[ +fcu.halting; #0; !; ! fcu:FCU ] will return M although it is going to be halting: by returning M it moves to an instruction from where halting is guaranteed indeed, while replying false would not produce a consistent answer. It is easy to see that if this definition of replies given by FCU returns M, it cannot be replaced by either true or false. Hence, FCU is optimal. a

So, a halting forecaster can be built, but it cannot always provide a useful reply. On PGA one can decide whether a useful reply can be given. Given the fact that all practical computing takes place on finite state machines for which PGA is of course sufficient, we conclude this: 1. All practical instances of halting are decidable, given a pgaEA[ X fi :Ri ] with all Ri finite state. 2. Nevertheless, a halting forecaster cannot work in a flawless fashion, although it can be ‘optimal’ (i.e., minimizing output M). If all Ri ’s are finite state machines in pgaEA[ X fi :Ri ] sat halting (which is always the case ‘in practice’), we find that for a particular AnArch fixing the Ri ’s the halting problem will be decidable, especially if the AnArch is tailored to fit the constraints of some realistic SynArch. Of course, one can investigate forecasting reactors. Then the question is: what impossibility will one encounter? The obvious undecidability result critically depends on one of the reactors being infinite state or engaging in external communications. We return to forecasting reactors in Section 1.6.3.

1.6.2

Reactors Formalizing Rational Agents

We consider a ‘rational agent’ RA with focus ra. The rational agent intends to achieve an objective and acts accordingly. Here is a simple example: X ra .

& out

RA

Out

where Out has five states 0, 1, 2, 3, 4 and initially is in state 0. There are four instructions s1 , ..., s4 which all succeed in each state of Out, with si leaving Out in state i for i ∈ {1, 2, 3, 4}. The PGA-program X is as follows: X = +ra.get; #3; out.s 2 ; !; out.s 1 ; !. The thread |X| can be visualized as follows: hra.geti true .

[out.s 1 ] ↓

S

& false

[out.s 2 ] ↓

S

26

J.A. Bergstra, I. Bethke, and A. Ponse

The task of RA is to make the system terminate with a maximal content of Out. RA is aware of the contents of program X. In this case, it is clear that the reply false is optimal. For a second example we add a decision agent Dec such that RA cannot know which decision Dec takes. The focus for Dec is dec. The instruction dec.set asks Dec to take a decision, which it will keep forever, and allows inspection via dec.get. An inspection not preceded by a dec.set returns M. X dec .

Dec

↓ ra & out

RA

Out

The model for Dec in concrete process algebra is: [[Dec]] = r(set)(t · [[Dectrue ]] + t · [[Decfalse ]]), [[Dectrue ]] = (s(true) · r(get))∗ δ, [[Decfalse ]] = (s(false) · r(get))∗ δ. We consider the following PGA-program X 0 : X 0 = dec.set; +dec.get; #2; #7; + ra.get; #3; out.s 2 ; !; out.s 1 ; !; + ra.get; #3; out.s 3 ; !; out.s 4 ; ! or as a thread: [dec.set] ↓

hdec.geti true .

hra.geti true .

& false

hra.geti

& false true .

& false

[out.s 1 ] [out.s 2 ] [out.s 4 ] [out.s 3 ] ↓

S



S



S



S

Now both replies true, false of RA are not optimal. If RA replies true, this leads to 1 after a positive decision of Dec (and false would have given 2), while false is not optimal after a negative decision of Dec (giving 3 rather than 4). Therefore it is plausible to return M, but that yields no maximum either (it leaves Out in state 0).

1.6.3

A Newcomb Paradox System

In this section we consider the following program, a small modification of the last program in the previous section (all out.s i -instructions switched places): X = dec.set; +dec.get; #2; #7; + ra.get; #3; out.s 4 ; !; out.s 3 ; !; + ra.get; #3; out.s 2 ; !; out.s 1 ; !

EWSCS’06 - Program Algebra and Thread Algebra

27

with behavior [dec.set] ↓

hdec.geti true .

hra.geti true .

& false

hra.geti

& false true .

& false

[out.s 3 ] [out.s 4 ] [out.s 1 ] [out.s 2 ] ↓

S



S



S



S

Now, quite independently of Dec’s action, it is plausible that RA replies false as its best reply. This answer is very robust and covers all possible/conceivable ways for which RA might work. For the next example we introduce the property of pgaEA’s that a reactor may be a forecaster of another one: pgaEAforecast:f >g is as pgaEA but with the additional constraint that the reactor focused by f forecasts the reactor focused by g. I.e., if f.get returns true (false) then the next g.get will reply true (false) as well. Consider pgaEAforecast:dec>ra [ X dec:Dec, ra:RA, out:Out ]. If RA chooses to reply true, Dec must have replied true, yielding Out in state 3, and if RA replies false, Dec must have replied false and the yield is 2. This is a version of Newcomb’s Paradox, the original form of which has been made famous by Gardner [40, Chapters 13, 14].3 The additional assumption of forecasting reverses the rational answer becoming true instead of false.4 But the argument for false was completely compelling, making use of case-based reasoning regarding uncertainty about past events. The Newcomb Paradox then arises from the apparently illegal identification of the two following execution architectures: pgaEA[ X dec:Dec, ra:RA, out:Out ]

and

pgaEAforecast:dec>ra [ X dec:Dec, ra:RA, out:Out ]. In particular, the mistaken view that the second architecture somehow refines the first one by merely providing additional information leads to a contradiction. Thus: adding more information about the component Dec, the plausibility of RA giving the reply false in order to maximize the contents of Out at program termination is lost. 3

A short description based on this source: Given two boxes, B1 which contains $1000 and B2 which contains either nothing or a million dollars, you may pick either B2 or both. However, at some time before the choice is made, an omniscient Being has predicted what your decision will be and filled B2 with a million dollars if he expects you to take it, or with nothing if he expects you to take both. 4 I.e. (in terms of the previous footnote), pick only box B2 instead of both.

28

J.A. Bergstra, I. Bethke, and A. Ponse

To formalize forecasting between reactors in process algebra, we use the constant 0 (see [4]): x + 0 = x, x·0 = 0

provided x 6= δ,

0 · x = 0.

We write Fa>b>c (X) for the following modification of process X: from the first a onwards, replace all b’s by 0 until the first occurrence of c. The operator Fa>b>c is axiomatized in Table 1.3. The auxiliary operator F 0 a>b>c models the situation in which the first action a is passed. Table 1.3: Axioms for Fa>b>c , where a, b, c, e are actions Fa>b>c (e) = e, ( 0 F 0 a>b>c (e) = e

if e = b, otherwise,

Fa>b>c (δ) = δ, F 0 a>b>c (δ) = δ,

Fa>b>c (x + y) = Fa>b>c (x) + Fa>b>c (y), 0 0 F 0 a>b>c (x + y) = F ( a>b>c (x) + F a>b>c (y), e · F 0 a>b>c (x) if e = a, Fa>b>c (e · x) = e · Fa>b>c (x) otherwise,   0 if e = b,   if e = c, F 0 a>b>c (e · x) = e · x  0  e · F a>b>c (x) otherwise

Forecasting can be formalized as follows: cpgaEAforecast:dec>ra [ X dec:Dec, ra:RA, out:Out ] = Fcdec (true)>cra (false)>cdec (true) ( Fcdec (false)>cra (true)>cdec (false) (cpgaEA[ X dec:Dec, ra:RA, out:Out ])) pgaEAforecast:dec>ra [ X dec:Dec, ra:RA, out:Out ] = τI (cpgaEAforecast:dec>ra [ X dec:Dec, ra:RA, out:Out ]) for appropriate I. Some computation shows that indeed pgaEAforecast:dec>ra [ X dec:Dec, ra:RA, out:Out ] = τ (τ · out.s 3 + τ · out.s 2 ), while pgaEA[ X dec:Dec, ra:RA, out:Out ] = τ (τ · out.s 4 + τ · out.s 2 ) if RA chooses false as its best reply.

1.6.4

Prisoner’s Dilemma

A close relative of the above examples is the so-called Prisoner’s Dilemma [52], which has become very well-known in game theory, psychology and economics. Consider the following situation: X ↓ ra1

↓ ra 2

RA1

RA2

↓ out 1

Out1

↓ out 2

Out2

EWSCS’06 - Program Algebra and Thread Algebra

29

Each rational agent has its own “out” and intends to maximize its own yield, irrespective of the other yield. We define the program X by X = +ra 1 .get; #2; #9; + ra 2 .get; #4; out1 .s 1 ; out2 .s 2 ; !; out1 .s 3 ; out2 .s 3 ; !; + ra 2 .get; #4; out1 .s 2 ; out2 .s 2 ; !; out1 .s 2 ; out2 .s 1 ; !

so |X| can be depicted by hra 1 .geti true .

hra 2 .geti true .

& false

& false

hra 2 .geti true . & false

[out1 .s 3 ] [out1 .s 1 ] [out1 .s 2 ] [out1 .s 2 ] ↓







[out2 .s 3 ] [out2 .s 2 ] [out2 .s 1 ] [out2 .s 2 ] ↓

S



S



S



S

This yields the following scenario’s: • if RA1 and RA2 both reply true, both yield the value 3, • if RA1 and RA2 both reply false, each gets the value 2, • if one replies true and the other false, the reply false gets 2 and the reply true yields 1. As a consequence, in order to exclude the risk of yielding only 1 a sure strategy is to choose false. But in order to get 3, both RA’s must trust one another and choose true, at the same time taking the risk to get only 1. Unable to communicate, the RA’s may both go for certainty and reply false. Note 1.6.3 The idea of forecasting does not apply to this example. Because of the particular way X is programmed, RA1 forecasting RA2, thus pgaEAforecast:ra1>ra2 [ X ra 1 :RA1, ra 2 :RA2, out 1 :Out1, out 2 :Out2 ], excludes the last scenario, and hence denies the dilemma. Applying the reverse forecasting does not have any effect: pgaEAforecast:ra2>ra1 [ X ra 1 :RA1, ra 2 :RA2, out 1 :Out1, out 2 :Out2 ] = pgaEA[ X ra 1 :RA1, ra 2 :RA2, out 1 :Out1, out 2 :Out2 ].

Note 1.6.4 A common application of the Prisoner’s Dilemma is to assume that the reply true complies with the law and false opposes the law. If both comply with the law, both have significant advantage. Complying with the law while others don’t is counterproductive, however.

30

J.A. Bergstra, I. Bethke, and A. Ponse

Chapter 2

Polarized Process Algebra and Program Equivalence Jan A. Bergstra and Inge Bethke

The basic polarized process algebra is completed yielding as a projective limit a cpo which also comprises infinite processes. It is shown that this model serves in a natural way as a semantics for several program algebras. In particular, the fully abstract model of the program algebra axioms of [13] is considered which results by working modulo behavioral congruence. This algebra is extended with a new basic instruction, named ‘entry instruction’ and denoted with ‘@’. Addition of @ allows many more equations and conditional equations to be stated. It becomes possible to find an axiomatization of program inequality. Technically this axiomatization is an infinite final algebra specification using conditional equations and auxiliary objects.

2.1

Introduction

Program algebra as introduced in [13] and [15] is a tool for the conceptualization of programs and programming. It is assumed that a program is executed in a context composed of components complementary to the program. While a program’s actions constitute requests to be processed by an environment, the complementary system components in an environment view actions as request issued by another party (the program being run). After each request the environment may undergo a state change whereupon it replies with a boolean value. The boolean return value is used to decide how the execution of the program will continue. For theoretical work on program algebra a semantic model is important. It is assumed that the meaning of a program is a process. A particular kind of processes termed polarized processes is well-suited to serve as the semantic interpretation of a program. In this paper the semantic world of polarized processes is introduced following the presentation of [15]. Polarized process algebra can stand on its own feet though significant results allowing to maintain it as an independent subject are currently missing. Then program algebra is introduced as a formalism for denoting objects (programs) that can be mapped into the set of polarized processes in a natural fash31

32

J.A. Bergstra, I. Bethke, and A. Ponse

ion. Several program algebras are defined. One of these structures may be classified as fully abstract. The focus of the paper is on an analysis of aspects of that model. This eventually leads to a final algebra specification of the fully abstract model. It seems to be the case that the fully abstract program algebra resists straightforward methods of algebraic specification. No negative results have been obtained, however. Several problems are left open.

2.2

Basic polarized process algebra

Most process algebras (e.g. ACP from [12] and TCSP from [30]) are non-polarized. This means that in a parallel composition of process P and Q, both processes and their actions have a symmetric status. In a polarized setting each action has a definite asymmetric status. Either it is a request or it is (part of) the processing of a request. When a request action is processed a boolean value is returned to the process issuing the request. When this boolean value is returned the processing of the request is completed. Non-polarized process algebra may be (but need not) considered the simplified case in which always true is returned. Polarized process algebra is less elegant than nonpolarized process algebra. Its advantage lies in the more direct modeling of sequential deterministic systems. Polarized process algebra need not dive into the depths of choice and non-determinism when deterministic systems are discussed. BPPA is based on a collection Σ of basic actions1 . Each action is supposed to be polarized and to produce a boolean value when executed. In addition its execution may have some side-effect in an environment. One imagines the boolean value mentioned above to be generated while this side-effect on the environment is being produced. BPPA has two constants which are meant to model termination and inaction and two composition mechanisms, the second one of these being defined in terms of the first one. Definition 2.2.1 For a collection Σ of atomic actions, BPPAΣ denotes the family of processes inductively defined by termination: S ∈ BPPAΣ With S (stop) terminating behavior is denoted; it does no more than terminate. Termination actions will not have any side effect on a state. inaction: D ∈ BPPAΣ By D (sometimes just ‘loop’) an inactive behavior is indicated. It is a behavior that represents the impossibility of making real progress, for instance an internal cycle of activity without any external effect whatsoever2 . 1

The phrase ‘basic action’ is used in polarized process algebra in contrast with ‘atomic action’ as used in process algebra. Indeed from the point of view of ordinary process algebra the basic actions are not considered atomic. In program algebra the phrase ‘basic instruction’ is used. Basic instructions are mapped on basic actions if the semantics of program algebra is described in terms of a polarized process algebra. Program algebra also features so-called primitive instructions. These are the basic instructions without test (void uses) and with positive or negative test, the termination instruction as well as a jump instruction #n for each n ∈ N. 2 Inaction typically occurs in case an infinite number of consecutive jumps is performed; for instance (#1)∞ .

EWSCS’06 - Program Algebra and Thread Algebra

33

postconditional composition: For action a ∈ Σ and processes P and Q in BPPAΣ P  a  Q ∈ BPPAΣ This composition mechanism denotes the behavior that first performs a and then either proceeds with P if true was produced or with Q otherwise. For a ∈ Σ and process P ∈ BPPAΣ , we abbreviate the postconditional composition P  a  P by a◦P and call this composition mechanism action prefix. Thus all processes in BPPAΣ are made from S and D by means of a finite number of applications of postconditional composition. This suggests the existence of a partial ordering and an operator which finitely approximates every basic process. Definition 2.2.2 clauses

1. Let v be the partial ordering on BPPAΣ generated by the

(a) for all P ∈ BPPAΣ , D v P , and (b) for all P, Q, X, Y ∈ BPPAΣ , a ∈ Σ, P v X & Q v Y ⇒ P  a  Q v X  a  Y. 2. Let π : N × BPPAΣ → BPPAΣ be the approximation operator determined by the equations (a) for all P ∈ BPPAΣ , π(0, P ) = D, (b) for all n ∈ N, π(n + 1, S) = S, π(n + 1, D) = D, and (c) for all P, Q ∈ BPPAΣ , n ∈ N, π(n + 1, P  a  Q) = π(n, P )  a  π(n, Q). We shall write πn (P ) instead of π(n, P ). π finitely approximates every process in BPPAΣ . That is, Proposition 2.2.3 For all P ∈ BPPAΣ , ∃n ∈ N π0 (P ) v π1 (P ) v · · · v πn (P ) = πn+1 (P ) = · · · = P. Proof. We employ structural induction. If P = D or P = S then n can be taken 0 or 1, respectively. If P = P1  a  P2 let n, m ∈ N be such that π0 (P1 ) v π1 (P1 ) v · · · v πn (P1 ) = πn+1 (P1 ) = · · · = P1 and π0 (P2 ) v π1 (P2 ) v · · · v πm (P2 ) = πm+1 (P2 ) = · · · = P2 . Thus for k = max{n, m} we have π0 (P1 )  a  π0 (P2 ) v π1 (P1 )  a  π1 (P2 ) .. . v πk (P1 )  a  πk (P2 ) = πk+1 (P1 )  a  πk+1 (P2 ) .. . = P1  a  P2 . Hence π0 (P ) v π1 (P ) v · · · v πk+1 (P ) = πk+2 (P ) = · · · = P . a

34

J.A. Bergstra, I. Bethke, and A. Ponse

Polarized processes can be finite or infinite. Following the metric process theory of [3] in the form developed as the basis of the introduction of processes in [12], BPPAΣ has a completion BPPA∞ Σ which comprises also the infinite processes. Standard properties of the completion technique yield that we may take BPPA∞ Σ as consisting of all so-called projective sequences. Recall that a directed set is a non-empty, partially ordered set which contains for any pair of its elements an upper bound. A complete partial order (cpo) is a partially ordered set with a least element such that every directed subset has a supremum. Let C0 , C1 , . . . be a countable sequence of cpo’s and let fi : Ci+1 → Ci be continuous for every i ∈ N. The sequence (Ci , fi ) is called a projective (or inverse) system of cpo’s. The projective (or inverse) limit of the system (Ci , fi ) is the poset (C ∞ , v) with C ∞ = {(xi )i∈N | ∀i ∈ N xi ∈ Ci & fi (xi+1 ) = xi } and (xi )i∈N v (yi )i∈N ⇔ ∀i ∈ N xi v yi . A fundamental theorem of domain theory states that C ∞ is a cpo with G G X=( xi )i∈N x∈X

for directed X ⊆ C ∞ . If in addition there are continuous mappings gi : Ci → Ci+1 such that for every i ∈ N fi (gi (x)) = x and gi (fi (x)) v x then, up to isomorphism, Ci ⊆ C ∞ . The isomorphism hi : Ci → C ∞ can be given by hi (x) = hf0 (f1 · · · , fi−1 (x) · · · ), · · · fi−1 (x), x, gi (x), gi+1 (gi (x)), · · · i. S Hence, up to isomorphism, i∈N Ci ⊆ C ∞ . For a detailed account of this construction consult e.g. [71]. Definition 2.2.4 1. For all n ∈ N, BPPAnΣ = {πn (P ) | P ∈ BPPAΣ } n 2. BPPA∞ Σ = {(Pn )n∈N | ∀n ∈ N(Pn ∈ BPPAΣ & πn (Pn+1 ) = Pn )}

Lemma 2.2.5 Let (C, v) be a finite directed set. Then C has a maximal element. Proof. Say C = {c0 , c1 , . . . , cn }. If n = 0, c0 is maximal. Otherwise pick x0 ∈ C such that c0 , c1 v x0 and for 1 ≤ i ≤ n − 1 pick xi ∈ C such that xi−1 , ci+1 v xi . x0 , x1 , . . . , xn−1 exist since C is directed. Now notice that xn−1 is the maximal element. a

Proposition 2.2.6 For all n ∈ N, 1. BPPAnΣ is a cpo,

EWSCS’06 - Program Algebra and Thread Algebra

35

2. πn is continuous, 3. for all P ∈ BP P AΣ , (a) πn (P ) v P , (b) πn (πn (P )) = πn (P ), and (c) πn+1 (πn (P )) = πn (P ). Proof. 1. We prove by induction on n that every directed set X ⊆ BPPAnΣ is finite. It then follows from the previous lemma that suprema exist: they are the maximal elements. The base case is trivial since BPPA0Σ = {D}. Now consider any directed X ⊆ BPPAn+1 Σ . We distinguish two cases. (a) S ∈ X: Then X ⊆ {D, S}. Thus X is finite. (b) S 6∈ X: Since X is directed there exists a unique a ∈ Σ such that X ⊆ {D, πn (P )  a  πn (Q) | P, Q ∈ BPPAΣ }. Now let X1 = {D, πn (P ) | ∃Q ∈ BPPAΣ πn (P )  a  πn (Q) ∈ X} and X2 = {D, πn (Q) | ∃P ∈ BPPAΣ πn (P )  a  πn (Q) ∈ X}. Since X is directed it follows that both X1 and X2 are directed and hence finite by the induction hypothesis. Thus X is finite. 2. Since directed subsets are finite it suffices to show that πn is monotone. Let P v Q ∈ BPPAΣ . We employ again induction on n. π0 is constant and thus monotone. For n + 1 we distinguish three cases. (a) P = D: Then πn+1 (P ) = D v πn+1 (Q). (b) P = S: Then also Q = S. Hence πn+1 (P ) = πn+1 (Q). (c) P = P1  a  P2 : Then Q = Q1  a  Q2 with Pi v Qi for i ∈ {1, 2}. From the monotonicity of πn it now follows that πn (Pi ) v πn (Qi ) for i ∈ {1, 2}. Thus πn+1 (P ) v πn+1 (Q). 3. Let P ∈ BP P AΣ . (a) follows from Proposition 2.2.3. We prove (b) and (c) simultaneously by induction on n. For n = 0 we have π0 (π0 (P )) = D = π0 (P ) and π1 (π0 (P )) = D = π0 (P ). Now consider n + 1. We distinguish two cases. (a) P ∈ {D, S}: Then πn+1 (πn+1 (P )) = P = πn+1 (P ) and πn+2 (πn+1 (P )) = P = πn+1 (P ). (b) P = P1  a  P2 : Then it follows from the induction hypothesis that πn+1 (πn+1 (P )) = πn (πn (P1 ))  a  πn (πn (P2 )) = πn (P1 )  a  π(P2 ) = πn+1 (P ) and πn+2 (πn+1 (P )) = πn+1 (πn (P1 ))  a  πn+1 (πn (P2 )) = πn (P1 )  a  π(P2 ) = πn+1 (P ). a ∞ Theorem 2.2.7 BPPA∞ Σ is a cpo and, up to isomorphism, BPPAΣ ⊆ BPPAΣ .

36

J.A. Bergstra, I. Bethke, and A. Ponse

Proof. 1. and 2. of the previous proposition show that (BPPAnΣ , πn ) is a projective system n n+1 of cpo’s. Thus BPPA∞ Σ is a cpo. Note that it follows from 3(c) that BPPAΣ ⊆ BPPAΣ n for all n. Thus if we define for all P and n, idn (P ) = P then idn : BPPAΣ → BPPAn+1 Σ for all n. idn is clearly continuous. Moreover, 3(a) yields πn (idn (P )) v P for all n and P ∈ BPPAnΣ . Likewise, 3(b) yields idn (πn (P )) = P for all n and P ∈ BPPAn+1 Σ . Thus, up S n ∞ ∞ to isomorphism, BPPA ⊆ BPPA . Thus also BPPA ⊆ BPPA since BPPAΣ = Σ Σ Σ Σ n∈N S n BPPA by Proposition 2.2.3. a Σ n

The set of polarized processes can serve in a natural fashion as a semantics for programs. As an example we shall consider PGAΣ .

2.3

Program algebra

Given a collection Σ of atomic instructions the syntax of program expressions (or programs) in PGAΣ is generated from five kinds of constants and two composition mechanisms. The constants are made from Σ together with a termination instruction, two test instructions and a forward jump instruction. As in the case of BPPA, the atomic instructions may be viewed as requests to an environment to provide some service. It is assumed that upon every termination of the delivery of that service some boolean value is returned that may be used for subsequent program control. The two composition mechanisms are concatenation and infinite repetition. Definition 2.3.1 For a collection Σ of atomic instructions, PGAΣ denotes the collection of program expressions inductively defined by termination: ! ∈ PGAΣ The instruction ! indicates termination of the program and will not return any value. forward jump instruction: #n ∈ PGAΣ for every n ∈ N n counts how many subsequent instructions must be skipped, including the jump instruction itself. void basic instruction: a ∈ PGAΣ for every a ∈ Σ positive test instruction: +a ∈ PGAΣ for every a ∈ Σ The execution of +a begins with executing a. Thereafter, if true is replied, program execution continues with the execution of the next instruction following the positive test instruction in the program. Otherwise, if false is replied, the instruction immediately following the (positive) test instruction is skipped and program execution continues with the instruction thereafter. negative test instruction: −a ∈ PGAΣ for every a ∈ Σ The negative test instruction (−a) reacts the other way around on the boolean values it receives as a feedback from its operating context. At a positive (true) reply it skips the next action, and at a negative reply it simply continues. concatenation: For programs X, Y ∈ PGAΣ , X; Y ∈ PGAΣ

EWSCS’06 - Program Algebra and Thread Algebra

37

repetition: For a program X ∈ PGAΣ , X ω ∈ PGAΣ Here are some program examples: +a; !; +b; #3; c; !; d; ! a; !; −b; #3; c; #0; d; ! −a; !; (−b; #3; c; #0; +d; !)ω . The simplest model of the signature of program algebra interprets each term as a sequence of primitive instructions. This is the instruction sequence model. Equality within this model will be referred to as instruction sequence congruence (=isc ). Two programs X and Y are instruction sequence congruent if both denote the same sequence of instructions after unfolding the repetition operator, that is, if they can be shown to be equal by means of the program object equations in Table 2.1. Table 2.1: Program object equations (X; Y ); Z (X n )ω Xω; Y (X; Y )ω

= = = =

X; (Y ; Z) Xω Xω X; (Y ; X)ω

(PGA1) (PGA2) (PGA3) (PGA4)

Here X 1 = X and X n+1 = X; X n . The associativity of concatenation implies as usual that far fewer brackets have to be used. We will use associativity whenever confusion cannot emerge. The program object equations allow some useful transformations, in particular the transformation into first canonical form. Definition 2.3.2 Let X ∈ PGAΣ . Then X is in first canonical form iff 1. X does not contain any repetition, or 2. X = Y ; Z ω with Y and Z not containing any repetition. The existence of first canonical forms follows straightforwardly by structural induction. The key case is this: (U ; X ω )ω =isc =isc =isc =isc

(U ; X ω ; U ; X ω )ω (U ; X ω ); (U ; X ω )ω U ; (X ω ; (U ; X ω )ω ) U ; Xω

by by by by

PGA2 PGA4 PGA1 PGA3

First canonical forms need not be unique. For example, a; a; aω and a; a; a; aω are both canonical forms of a; aω which is already in canonical form itself. In the sequel we shall mean by the first canonical form the shortest one. Definition 2.3.3 Let X ∈ PGAΣ be in first canonical form. The length of X, l(X), is defined by

38

J.A. Bergstra, I. Bethke, and A. Ponse 1. if X does not contain any repetition then l(X) = (n, 0) where n is the number of instructions in X, and 2. if X = Y ; Z ω with both Y and Z not containing any repetition then l(X) = (n, m) where n and m are the number of instructions in Y and Z, respectively.

Observe that N × N is a well-founded partial order by stipulating (n0 , n1 ) ≤ (m0 , m1 ) ⇔ n0 ≤ m0 or (n0 = m0 and n1 ≤ m1 ). Definition 2.3.4 Let X ∈ PGAΣ . The first canonical form of X, cf (X), is a first canonical form X 0 with X =isc X 0 and minimal length, i.e. for all first canonical forms X 00 with X =isc X 00 , l(X 0 ) ≤ l(X 00 ). We call X finite if l(cf (X)) = (n, 0) and infinite if l(cf (X)) = (n, m + 1) for some n, m ∈ N. Clearly cf (X) is well-defined, that is, there exists a unique shortest first canonical form of X. A second model of program algebra is BPPA∞ Σ . As a prerequisite we define a mapping | | from finite programs, i.e. programs without repetition, to finite polarized processes. Prior to a formal definition some examples are of use: |a; b; !| = a ◦ (b ◦ S) |a; +b; !; #0| = a ◦ (S  b  D) | + a; !| = S  a  D. The intuition behind the mapping to processes is as follows: view a program as an instruction sequence and turn that into a process from left to right. The mapping into processes removes all control aspects (tests, jumps) in favor of an unfolding of all possible behaviors. A forward jump instruction with counter zero jumps to itself, thereby creating a loop or divergence (D). Only via ! the proper termination (S) will take place. If the program is exited in another way this also counts as a divergence (D). In the sequel we let u, u1 , u2 , . . . range over {!, #k, a, +a, −a|a ∈ Σ, k ∈ N }. Definition 2.3.5 Let X ∈ PGAΣ be finite. Then |X| is defined by induction on its length l(X). 1. l(X) = (1, 0): (a) If X =! then |X| = S, (b) if X = #k then |X| = D, and (c) if X ∈ {a, +a, −a} then |X| = a ◦ D. 2. l(X) = (n + 2, 0): (a) if X =!; Y then |X| = S, (b) if X = #0; Y then |X| = D,

EWSCS’06 - Program Algebra and Thread Algebra

39

(c) if X = #1; Y then |X| = |Y |, (d) if X = #k + 2; u; Y then |X| = |#k + 1; Y |, (e) if X = a; Y then |X| = a ◦ |Y |; (f ) if X = +a; Y then |X| = |Y |  a  |#2; Y |, and (g) if X = −a; Y then |X| = |#2; Y |  a  |Y |. Observe that | | is monotone in continuations. That is, Proposition 2.3.6 Let X = u1 ; · · · ; un and Y = u1 ; · · · ; un ; · · · ; un+k . Then |X| v |Y |. Proof. Straightforward by induction on n and case ramification. E.g. if n = 1 and X ∈ {a, +a, −a} then |X| = a ◦ D and |Y | = |Z|  a  |Z 0 | for some Z, Z 0 ∈ PGAΣ . Thus |X| v |Y |. If n > 1 consider e.g. the case where X = #k +2; u2 ; · · · ; un . Then |X| = |#k +1; u3 ; · · · ; un | v |#k + 1; u3 ; · · · ; un ; · · · ; un+k | = |Y | by the induction hypothesis. Etc. a 1 2 v |Y ; Z 3 | v · · · It follows that for repetition-free Y and Z, F |Y ; Z| =n|Y ; Z | v |Y ; Z | ∞ is an ω-chain and hence directed. Thus n∈N |Y ; Z | exists in BPPAΣ . We can now extend Definition 2.3.5 to infinite processes.

Definition 2.3.7 Let Y ; Z ω ∈ PGAΣ be in first canonical form. Then |Y ; Z ω | = F n n∈N |Y ; Z |. Moreover, for arbitrary programs we define Definition 2.3.8 Let X ∈ PGAΣ . Then [[X]] = |cf (X)|. As an example consider: F [[+a; #3; !; (b; c)ω ]] = Fn∈N | + a; #3; !; (b; c)n | F n = Fn∈N |#3; !; (b; c)n |  a  F n∈N |#2; #3; !;n(b; c) | n = Fn∈N |#2; (b; c) |  a  Fn∈N |#1; !; (b; c) | a  n∈N |!; (b; c)n | = Fn∈N |#1; (c; b)n |  F = n∈N |(c; b)n |  a  n∈N |!; (b; c)n | = c ◦ b ◦ c ◦ b ◦ ···  a  S Since instruction sequence congruent programs have identical cf -canonical forms we have Theorem 2.3.9 For all X, Y ∈ PGAΣ , X =isc Y ⇒ [[X]] = [[Y ]]. The converse does not hold: e.g. #1; ! 6=isc ! but [[#1; !]] = S = [[!]]. Further models for program algebra will be found by imposing congruences on the instruction sequence model. Two congruences will be used: behavioral congruence and structural congruence.

40

J.A. Bergstra, I. Bethke, and A. Ponse

2.4

Behavioral and structural congruence

X and Y are behaviorally equivalent if [[X]] = [[Y ]]. Behavioral equivalence is not a congruence. For instance [[!; !]] = S = [[!; #0]] but [[#2; !; !]] = S 6= D = [[#2; !; #0]]. This motivates the following definition. Definition 2.4.1 1. The set of PGA-contexts is C ::= | Z; C | C; Z | C ω . 2. Let X, Y ∈ PGAΣ . X and Y are behaviorally congruent (X =bc Y ) if for all PGAΣ -contexts C[ ], [[C[X]]] = [[C[Y ]]]. As a matter of fact it suffices to consider only one kind of context. Theorem 2.4.2 Let X, Y ∈ PGAΣ . Then X =bc Y ⇔ ∀Z, Z 0 ∈ PGAΣ [[Z; X; Z 0 ]] = [[Z; Y ; Z 0 ]]. Proof. Left to right follows from the definition of behavioral congruence. In order to prove right to left observe first that—because of PGA3—we do not need to consider any contexts of the form C[ ]ω ; Z 0 or Z; C[ ]ω ; Z 0 . The context we do have to consider are therefore the ones given in the table. 1.a 1.b 1.c 1.d

− Z; − −; Z 0 Z; −; Z 0

2.a 2.b 2.c 2.d

−ω (Z; −)ω (−; Z 0 )ω (Z; −; Z 0 )ω

3.a 3.b 3.c 3.d

Z 00 ; −ω Z 00 ; (Z; −)ω Z 00 ; (−; Z 0 )ω Z 00 ; (Z; −; Z 0 )ω

Assuming the right-hand side, we first show that for every context C[ ] in the first column we have [[C[X]]] = [[C[Y ]]]. 1.d is obvious. 1.c follows by taking Z = #1 in 1.d. Now observe that for every U , [[U ; #0]] = [[U ]]: for finite U this is shown easily with induction to the number of instructions, and for U involving repetition [[U ; #0]] = [[U ]] follows from PGA3. This yields 1.a and 1.b by taking Z 0 = #0 in 1.c. and 1.d, respectively. This covers all contexts in the first column. We now turn to the third column. We shall first show that for all n > 0 and all Z 00 , [[Z ; X n ]] = [[Z 00 ; Y n ]]. The case n = 1 has just been established (1.b). Now consider n + 1: by taking Z = Z 00 and Z 0 = X n in 1.d, [[Z 00 ; X; X n ]] = [[Z 00 ; Y ; X n ]]. Moreover, from the induction hypothesis it follows that [[Z 00 ; Y ; X n ]] = [[Z 00 ; Y ; Y n ]]. Thus [[Z 00 ; X n+1 ]] = [[Z 00 ; Y n+1 ]]. ¿From the limit characterization of repetition it now follows that [[Z 00 ; X ω ]] = [[Z 00 ; Y ω ]] (3.a). 3.b is dealt with using the same argument with only a small notational overhead. For 3.c and 3.d observe that 00

[[Z 00 ; (X; Z 0 )ω ]] = = = =

[[Z 00 ; X; (Z 0 ; X)ω ]] [[Z 00 ; X; (Z 0 ; Y )ω ]] [[Z 00 ; Y ; (Z 0 ; Y )ω ]] [[Z 00 ; (Y ; Z 0 )ω ]]

follows from PGA4, 3.b and 1.d, and [[Z 00 ; (Z; X; Z 0 )ω ]] = [[Z 00 ; Z; (X; Z 0 ; Z)ω ]] = [[Z 00 ; Z; (Y ; Z 0 ; Z)ω ]] = [[Z 00 ; (Z; Y ; Z 0 )ω ]]

EWSCS’06 - Program Algebra and Thread Algebra

41

follows from PGA4 and 3.c. This covers all context in the third column. Finally we consider the second column. Here every context can be dealt with by taking in the corresponding context in the third column Z 00 = #1. a

Structural congruence is characterized by the four equation schemes in Table 2.2. The schemes take care of the simplification of chained jumps. The schemes are termed PGA5-8, respectively. PGA8 can be written as an equation by expanding X, but takes a more compact and readable form as a conditional equation. Program texts are considered structurally congruent if they can be proven equal by means of PGA1-8. Structural congruence of X and Y is indicated with X =sc Y , omitting the subscript if no confusion arises. Some consequences of these axioms are

a; #2; b; #0; c = a; #0; b; #0; c a; #2; b; #1; c = a; #3; b; #1; c a; (#3; b; c)ω = a; (#0; b; c)ω The purpose of structural congruence is to allow successive (and repeating) jumps to be taken together. Table 2.2: Equation schemes for structural congruence #n + 1; u1 ; . . . ; un ; #0 = #0; u1 ; . . . ; un ; #0 #n + 1; u1 ; . . . ; un ; #m = #n + m + 1; u1 ; . . . ; un ; #m (#n + k + 1; u1 ; . . . ; un )ω = (#k; u1 ; . . . ; un )ω

(PGA5) (PGA6) (PGA7)

X = u1 ; . . . ; un ; (v1 ; . . . ; vm+1 )ω → #n + m + k + 2; X = #n + k + 1; X

(PGA8)

Structurally congruent programs are behaviorally congruent as well. This is proven by demonstrating the validity of each closed instance of the structural congruence equations modulo behavioral congruence.

2.5

The entry instruction

As it turns out behavioral congruence on PGAΣ is not easy to axiomatize by means of equations or conditional equations. It remains an open problem how that can be done. Here the matter will be approached from another angle. First an additional primitive instruction is introduced: @, the entry instruction. The instruction @ in front of a program disallows any jumps into the program otherwise than jumps into the first instruction of the program. Longer jumps are discontinued, and the jump will be carried out as a jump to the control point following @. The entry instruction is new, in the sense that it coincides with no PGAΣ program or primitive instruction. Its use lies in the fact that it allows an unexpected number of additional (conditional) equations for programs. As a consequence it becomes possible to find a concise final algebra

42

J.A. Bergstra, I. Bethke, and A. Ponse

specification of behavioral inequality of programs. This is plausible to some extent: it is much easier to see that programs differ, by finding input leading to different outputs, than to see that they don’t differ and hence coincide in the behavioral congruence model of program algebra. The program notation extending PGAΣ with ‘@’ is denoted PGAΣ,@ . In order to provide a mapping from PGAΣ,@ into BPPA∞ Σ we add to the clauses in Definition 2.3.5 the clauses 1.-4. of the following definition Definition 2.5.1 1. |@| = D, 2. |@; X| = |X|, 3. |#n + 1; @| = D, 4. |#n + 1; @; X| = |X|, and change the clause 2d in Definition 2.3.5 into (u 6= @) ⇒ |#k + 2; u; X| = |#k + 1; X|. Using these additional rules [[ ]] can be defined straightforwardly for programs involving the entry instruction. Behavioral congruence has then exactly the same definition in the presence of the entry instruction and Theorem 2.4.2 extends trivially to PGAΣ,@ . Because programs with different behavior may be considered observationally different it is reasonable to call PGAΣ,@ /=bc a fully abstract model. It imposes a maximal congruence under the constraint that observationally different programs will not be identified. A characterization of behavioral congruence in terms of behavioral equivalence will be given in Theorem 2.5.2. The intuition behind this characterization is that behavior extraction abstracts from two aspects that can be recovered by taking into account the influence of a context: the instruction that serves as initial instruction (which for [[u1 ; · · · ; un ; · · · ]] is always u1 ) and the difference between divergence and exiting a program with some jump. To make these differences visible at the level of program behaviors only very simple contexts are needed: here are three examples (where a 6= b): #2 6=bc #1 because [[#2; !; #0ω ]] = D 6= S = [[#1; !; #0ω ]], #2; a 6=bc #2; b because [[#2; #2; a]] = a ◦ D 6= b ◦ D = [[#2; #2; b]]. !; #1 6=bc !; #2 because [[#2; !; #1; !; #0ω ]] = S 6= D = [[#2; !; #2; !; #0ω ]]. Theorem 2.5.2 Let X, Y ∈ PGAΣ,@ . Then 1. X =bc Y ⇔ ∀n ∈ N ∀Z 0 ∈ PGAΣ,@ [[#n + 1; X; Z 0 ]] = [[#n + 1; Y ; Z 0 ]] 2. X =bc Y ⇔ ∀n, m ∈ N [[#n + 1; X; !m ; #0ω ]] = [[#n + 1; Y ; !m ; #0ω ]] Proof. Left to right follows for 1. and 2. from the definition of behavioral congruence.

EWSCS’06 - Program Algebra and Thread Algebra

43

1. Assume the right-hand side. We employ Theorem 2.4.2. Suppose that for some Z, Z 0 , [[Z; X; Z 0 ]] 6= [[Z; Y ; Z 0 ]]. Then Z cannot contain an infinite repetition. Therefore it is finite. With induction on the length of Z one then proves the existence of a natural number k such that [[#k + 1; X; Z 0 ]] 6= [[#k + 1; Y ; Z 0 ]]. For l(Z) = (1, 0) we distinguish 6 cases: (a) Z =!: Then [[Z; X; Z 0 ]] = S = [[Z; Y ; Z 0 ]]. Contradiction. (b) Z = @: Then [[X; Z 0 ]] 6= [[Y ; Z 0 ]]. Thus also [[#1; X; Z 0 ]] 6= [[#1; Y ; Z 0 ]]. (c) Z = #n: As n cannot be 0 we are done. (d) Z = a: Then a ◦ [[X; Z 0 ]] 6= a ◦ [[Y ; Z 0 ]]. [[#1; X; Z 0 ]] 6= [[#1; Y ; Z 0 ]].

Thus [[X; Z 0 ]] 6= [[Y ; Z 0 ]] and hence

(e) Z ∈ {+a, −a}: If Z = +a then [[X; Z 0 ]]  a  [[#2; X; Z 0 ]] 6= [[Y ; Z 0 ]]  a  [[#2; Y ; Z 0 ]]. Then [[X; Z 0 ]] 6= [[Y ; Z 0 ]] or [[#2; X; Z 0 ]] 6= [[#2; Y ; Z 0 ]]. In the latter case we are done and in the first case we can take k = 0. −a is dealt with similarly. Now consider l(Z) = (m + 2, 0). We have to distinguish 10 cases. Seven cases correspond to the repetition-free clauses in 2 of Definition 2.3.5. They follow from a straightforward appeal to the induction hypothesis. The remaining three cases correspond to 2.–4. of Definition 2.5.1. (a) Z = @; Z 00 : Then [[Z 00 ; X; Z 0 ]] 6= [[Z 00 ; Y ; Z 0 ]]. Hence [[#k+1; X; Z 0 ]] 6= [[#k+1; Y ; Z 0 ]] for some k by the induction hypothesis. (b) Z = #n + 1; @: Then [[X; Z 0 ]] 6= [[Y ; Z 0 ]]. Hence [[#1; X; Z 0 ]] 6= [[#1; Y ; Z]]. (c) Z = #n + 1; @; Z 00 : Then [[Z 00 ; X; Z 0 ]] 6= [[Z 00 ; Y ; Z 0 ]] and we can again apply the induction hypothesis. 2. Assume the right-hand side. We make an appeal to 1. Suppose there are k and Z 0 such that [[#k + 1; X; Z 0 ]] 6= [[#k + 1; Y ; Z 0 ]]. If both X and Y are infinite then [[#k + 1; X]] 6= [[#k + 1; Y ]] and hence also [[#k + 1; X; #0ω ]] 6= [[#k + 1; Y ; #0ω ]]. Suppose only one of the two, say Y , has a repetition, then writing X = u1 ; . . . ; un , it follows that: [[#k + 1; u1 ; . . . ; un ; Z 0 ]] 6= [[#k + 1; Y ]]. At this point an induction on n can be used to establish the existence of an m with [[#k + 1; u1 ; . . . ; un ; !m ; #0ω ]] 6= [[#k + 1; Y ]] and hence [[#k + 1; u1 ; . . . ; un ; !m ; #0ω ]] 6= [[#k + 1; Y ; !m ; #0ω ]]. If both X and Y are finite instruction sequences, an induction on their maximum length suffices to obtain the required fact (again involving a significant case ramification). a

Example 2.5.3 1. @; ! =bc !ω since for all n, Z, [[#n + 1; @; !; Z]] = [[!; Z]] = S = [[#n + 1; !ω ; Z]], and 2. @; #0 =bc #0ω since for all n, Z, [[#n + 1; @; #0; Z]] = [[#0; Z]] = D = [[#n + 1; #0ω ; Z]]. The characterization above suggests that behavioral congruence may be undecidable. This of course is not the case: the quantifier over m can be bounded because m need not exceed the maximum of the counters of jump instructions in X and Y plus 1. An upper

44

J.A. Bergstra, I. Bethke, and A. Ponse

bound for n is as follows: if l(X) = (k, m) and l(Y ) = (k 0 , m0 ) then (k + m) × (k 0 + m0 ) is an upper bound of the n’s that must be checked. Programs starting with the entry instruction can be distinguished by means of simpler contexts: Corollary 2.5.4 Let X, Y ∈ PGAΣ,@ . Then 1. @; X =bc @; Y ⇔ ∀n ∈ N[[X; !n ; #0ω ]] = [[Y ; !n ; #0ω ]] 2. @; X =bc @; Y ⇔ ∀Z[[X; Z]] = [[Y ; Z]] Proof. 1. and 2. follow from that fact that for every n, k ∈ N and every X, [[#k + 1; @; X; !n ; #0ω ]] = [[X; !n ; #0ω ]] and [[#k + 1; @; X; Z]] = [[X; Z]]. a

Since [[X]] = [[X; #0ω ; Z]] for all program expressions X and Z, it follows from Corollary 2.5.4.2 that behavioral equivalence can be recovered from behavioral congruence in the following way: Corollary 2.5.5 Let X, Y ∈ PGAΣ,@ . Then X =be Y ⇔ @; X; #0ω =bc @; Y ; #0ω . Programs ending with an entry instruction allow a simpler characterisation as well: Corollary 2.5.6 Let X, Y ∈ PGAΣ,@ . Then X; @ =bc Y ; @ iff for all n ∈ N, [[#n + 1; X; !ω ]] = [[#n + 1; Y ; !ω ]] & [[#n + 1; X; #0ω ]] = [[#n + 1; Y ; #0ω ]] Proof. ‘⇒’: Suppose that X; @ =bc Y ; @, then for all n and m, (?)

[[#n + 1; X; @; !m ; #0ω ]] = [[#n + 1; Y ; @; !m ; #0ω ]].

Then [[#n + 1; X; !ω ]] = = = = =

[[#n + 1; X; !ω ; #0ω ]] [[#n + 1; X; @; !; #0ω ]] since @; ! =bc !ω (Example 2.5.3) [[#n + 1; Y ; @; !; #0ω ]] take in (?) m = 1 [[#n + 1; Y ; !ω ; #0ω ]] [[#n + 1; Y ; !ω ]]

Similarly [[#n + 1; X; #0ω ]] = = = = = = =

[[#n + 1; X; #0ω ; #0ω ]] [[#n + 1; X; @; #0; #0ω ]] since @; #0 =bc #0ω (Example 2.5.3) [[#n + 1; X; @; #0ω ]] [[#n + 1; Y ; @; #0ω ]] take in (?) m = 0 [[#n + 1; Y ; @; #0; #0ω ]] [[#n + 1; Y ; #0ω ; #0ω ]] [[#n + 1; Y ; #0ω ]]

EWSCS’06 - Program Algebra and Thread Algebra

45

‘⇐’: for m = 0, the above argument runs in the other direction [[#n + 1; X; @; !0 ; #0ω ]] = = = = = = =

[[#n + 1; X; @; #0ω ]] [[#n + 1; X; @; #0; #0ω ]] [[#n + 1; X; #0ω ; #0ω ]] [[#n + 1; Y ; #0ω ; #0ω ]] [[#n + 1; Y ; @; #0; #0ω ]] [[#n + 1; Y ; @; #0ω ]] [[#n + 1; Y ; @; !0 ; #0ω ]]

The case m > 0 is similar. a

2.6

Axiomatization of the fully abstract model

With CEQ@ the collection of 20 equations and inequations in Table 2.3 will be denoted (CEQ for, ‘conditional and unconditional equations’). They can be viewed as axioms from which other facts may be derived using conditional equational logic. Inequations can be understood as a shorthand for conditional equation: e.g. @; ! = @; #j ⇒ X = Y represents @; ! 6= @; #j. No attempt has been made to minimize or optimize this collection. We shall first show that CEQ@ is valid in PGAΣ,@ / =bc . Table 2.3: CEQ@ (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16) (17) (18) (19) (20)

@; ! =!ω @; #0 = #0ω @; @ = @ #n + 1; @ = @ +a; @ = a; @ −a; @ = a; @ #n + l + 1; u1 ; . . . ; un ; @ = #n + 1; u1 ; . . . ; un ; @ @; u1 ; . . . ; un ; @ = @; u1 ; . . . ; un ; #1 (∀1 ≤ j ≤ n uj = #k ⇒ k + j ≤ n + 1) @; u1 ; . . . ; un ; @ = @; u1 ; . . . ; un ; #1 ⇒ @; (u1 ; . . . ; un ; @)ω = @; (u1 ; . . . ; un ; #1)ω @; #1 = @ @; #n + 2; u = @; #n + 1 (if u 6= @) @; a; @ = @; a @; a = @; +a; #1 @; −a = @; +a; #2 @; X = @; Y & @; #2; X = @; #2; Y ⇔ @; +a; X = @; +a; Y @; u; X = @; v; X ⇒ u; X = v; X @; ! 6= @; #j @; ! 6= @; +a; X @; #0 6= @; +a; X @; +a; X 6= @; +b; Y (a 6= b ∈ Σ)

Proposition 2.6.1 PGAΣ,@ / =bc |= CEQ@

46

J.A. Bergstra, I. Bethke, and A. Ponse

Proof. 1. See Example 2.5.3.1. 2. See Example 2.5.3.2. 3. Since [[@; @; Z]] = [[@; Z]] for all Z, we can apply Corollary 2.5.4.2. 4. If k = 0, [[#k + 1; #n + 1; @; Z]] = [[#1; #n + 1; @; Z]] = [[#n + 1; @; Z]] = [[@; Z]] = [[#k +1; @; Z]] and if k > 0 [[#k +1; #n+1; @; Z]] = [[#k; @; Z]] = [[@; Z]] = [[#k +1; @; Z]]. Now apply Theorem 2.5.2.1. 5. We apply again Theorem 2.5.2.1. For k > 0 the process extraction equations match both sides. For k = 0 we obtain: [[#1; +a; @; Z]] = [[+a; @; Z]] = [[@; Z]]  a  [[#2; @; Z]] = [[@; Z]]  a  [[@; Z]] = a ◦ [[@; Z]] = [[a; @; Z]] = [[#1; a; @; Z]]. For k > 0 we have [[#k + 1; +a; @; Z]] = [[#k; @; Z]] = [[#k + 1; a; @; Z]]. 6. Similar to 5. 7. For n = 1, [[#k+2; u1 ; @]] = [[#k+1; @]] = [[#1; @]] = [[#2; u1 ; @]] if u1 6= @, and otherwise [[#k + 2; @; @]] = [[@]] = [[#2; @; @]]. For n > 1 we apply the induction hypothesis. 8. This follows from the fact that the entry instruction simply behaves as a skip if it does not affect preceding jumps; that is, if the jumps are small enough to be not affected by discontinuation. 9. Let ~u = u1 ; . . . ; un and suppose @; ~u; @ =bc @; ~u; #1. We shall show by induction on l that @; (~u; @)l =bc @; (~u; #1)l for all l > 0. The base case follows from the assumption. For l + 2 we have [[(~u; @)l+2 ; Z]] = = = = =

[[(~u; @)l ; ~u; @; ~u; @; Z]] [[(~u; @)l ; ~u; @; ~u; #1; Z]] by the assumption [[(~u; @)l+1 ; ~u; #1; Z]] [[(~u; #1)l+1 ; ~u; #1; Z]] by the induction hypothesis [[(~u; #1)l+2 ; Z]]

Thus also @; (~u; @)l+2 =bc @; (~u; #1)l+2 by Corollary 2.5.4.2 and hence [[(~u; @)l ]] = [[@; (~u; @)l ]] = [[@; (~u; #1)l ]] = [[(~u; #1)l ]] for all l > 0. It follows that [[(~u; @)ω ]] = [[(~u; #1)ω ]]. Therefore we have [[(~u; @)ω ; Z]] = [[(~u; #1)ω ; Z]] for all Z. Thus @; (~u; @)ω =bc @; (~u; #1)ω by Corollary 2.5.4.2. 10. Since [[#1; @; Z]] = [[@; Z]] = [[Z]] for all Z, we can apply Corollary 2.5.4.2. 11. By Corollary 2.5.4.2 since for all Z, [[#n + 2; u; Z]] = [[#n + 1; Z]] if u 6= @. 12. Again by Corollary 2.5.4.2 since for all Z, [[a; @; Z]] = a ◦ [[Z]] = [[a; Z]]. 13. Similar to (12). 14. Similar to (13). 15. This follows straightforwardly from Corollary 2.5.4.2 and the fact that ∀Z[[X; Z]] = [[Y ; Z]] & [[#2; X; Z]] = [[#2; Y ; Z]] iff ∀Z[[X; Z]]  a  [[#2; X; Z]] = [[Y ; Z]]  a  [[#2; Y ; Z]].

EWSCS’06 - Program Algebra and Thread Algebra

47

16. Apply Theorem 2.5.2.1. 17. Since [[@; !]] = S 6= D = [[@; #j]]. 18. Since [[@; !]] = S 6= [[X]]  a  [[#2; X]] = [[@; +a; X]]. 19. Since [[@; #0]] = D 6= [[X]]  a  [[#2; X]] = [[@; +a; X]]. 20. Since [[@; +a; X]] = [[X]]  a  [[#2; X]] 6= [[Y ]]  b  [[#2; Y ]] = [[@; +b; Y ]]. a

The axiom system PGA1-8 + CEQ@ is obtained by combining the equations for instruction sequence congruence, the axioms for structural equivalence and the axioms of CEQ@ . ¿From the previous proposition it follows that this system is sound, i.e. applying its axioms and the rules of conditional equational logic always yields equations that are valid in PGAΣ,@ / =bc . The converse, i.e. provable equality of behavioral congruence, can be shown in the repetition-free case. Completeness for infinite programs remains an open problem. Theorem 2.6.2 PGA1-8 + CEQ@ is complete for finite programs, i.e. for repetitionfree X, Y ∈ PGAΣ,@ , X =bc Y ⇔ PGA1-8 + CEQ@ ` X = Y Proof. Right to left follows from the previous proposition. To prove the other direction, first notice that in the absence of entry instructions lengths must be equal, or else a separating context can be easily manufactured. Then, still without @, the fact is demonstrated with induction to program lengths, using (16) as a main tool, in addition to a substantial case distinction. In the presence of entry instructions, (7) and (8) are used to transform both programs to instruction sequences involving at most a single entry instruction. If only one of the programs contains an entry instruction a separating context is found using a jump that can jump over the program without entry instruction entirely while halting at the other program’s entry instruction. At this point it can be assumed that X = X1 ; @; X2 and Y = Y1 ; @; Y2 . Let k be the maximum of the lengths of X1 and Y1 , then [[#k + 1; X1 ; @; X2 ]] = [[@; X2 ]] and [[#k + 1; Y1 ; @; Y2 ]] = [[@; Y2 ]]. Now @; X2 and @; Y2 can be proven equal, and this is shown by means of an induction on the sum of the lengths of both. Finally the argument is concluded by an induction on the sum of the lengths of X1 and Y1 . a

2.7

A final algebra specification for behavioral congruence

In this section we shall show that PGA1-8 + CEQ@ constitutes a final algebra specification of the fully abstract program algebra with entry instruction. Lemma 2.7.1 Let X ∈ PGAΣ,@ . Then 1. [[X]] = S ⇒ PGA1-8 + CEQ@ ` @; X = @; ! 2. [[X]] = D ⇒ PGA1-8 + CEQ@ ` @; X; #0ω = @; #0 3. [[X]] = P  a  Q ⇒ PGA1-8 + CEQ@ ` @; X = @; +a; Y for some Y ∈ PGAΣ,@

48

J.A. Bergstra, I. Bethke, and A. Ponse

Proof. We shall write ` instead of PGA1-8 + CEQ@ ` and consider the definition of |X| as a collection of rewrite rules, working modulo instruction sequence equivalence (for which PGA1-4 are complete). 1. The assumption implies that after finitely many rewrites the result S is obtained. We use induction on the length of this rewrite sequence. If one step is needed (the theoretical minimum), there are two cases: X =!, or X =!; Y for some Y . The first case is immediate; the second case follows by ` @; X = @; !; Y =!ω ; Y =!ω = @; ! employing (1). If k + 1 steps are needed the last step must be either a rewrite of a jump or the removal of an entry instruction. We only consider the first case. Thus X = #n; Y for some Y . If n = 1 then |Y | = S and hence ` @; Y = @; ! by the induction hypothesis. Thus ` @; X = @; #1; Y = @; Y = @; ! by (10). If X = #n + 2; u; Y there are two cases: u is the entry instruction, or not. Assume that it is not. Then |#n + 1; Y | = S. Using the induction hypothesis and (11) it follows that ` @; X = @; #n + 2; u; Y = @; #n + 1; Y = @; !. If u is the entry instruction we have ` @; X = @; #n + 2; @; Y = @; @; Y = @; Y = @; ! by (3), (4) and the induction hypothesis. 2. A proof of this fact uses a case distinction: either in finitely many steps the rewriting process of the process extraction leads to #0; Z for some Z, or an infinite sequence of rewrites results which must be of a cyclic nature. In the fist case induction on the number of rewrite steps involved provides the required result without difficulty. The structural congruence equations will not be needed in this case. In the case of an infinite rewrite it follows that the rewriting contains a circularity. By means of the chaining of successive jumps the expression can be rewritten into an expression in which a single jump, contained in the repeating part traverses the whole repeating part and then chains with itself. PGA7 can be used to introduce an instruction #0, thereby reducing the case to the previous one. This is best illustrated by means of an example. = = = = = = = = = = =

@; #5; !; #0; (#4; +a; #2; !; #1)ω @; #5; !; #0; (#5; +a; #2; !; #1)ω @; #5; !; #0; (#0; +a; #2; !; #1)ω @; #5; !; #0; #0; +a; #2; !; #1; (#0; +a; #2; !; #1)ω @; #5; !; #1; #0; +a; #2; !; #1; (#0; +a; #2; !; #1)ω @; #2; !; #1; (#0; +a; #2; !; #1)ω @; #1; (#0; +a; #2; !; #1)ω @; (#0; +a; #2; !; #1)ω @; #0; +a; #2; !; #1; (#0; +a; #2; !; #1)ω #0ω ; +a; #2; !; #1; (#0; +a; #2; !; #1)ω #0ω @; #0

PGA6 PGA7 PGA4 PGA5 PGA4 (11) (10) PGA4 (2) PGA3 (2).

3. This fact follows by means of an induction on the number of rewrite steps needed for the program extraction operator to arrive at an expression of the form P  a  Q. a

The results can be taken together in the following theorem which can be read as follows: ‘PGA1−8 +CEQ@ constitutes a final algebra specification of the fully abstract program algebra with entry instruction’.

Proposition 2.7.2 [[X]] 6= [[Y ]] ⇒ PGA1−8 + CEQ@ ` @; X 6= @; Y.

EWSCS’06 - Program Algebra and Thread Algebra

49

Proof. With induction on n it will beshown that πn ([[X]]) 6= πn ([[Y ]]) implies the provability of @; X 6= @; Y . The basis is immediate because zero‘th projections are D in both cases, and a diference cannot exist. Then suppose that πn+1 ([[X]]) 6= πn+1 ([[Y ]]) A case distinction has to be analysed. Suppose [[X]] = S and [[Y ]] = D. Then PGA1−8 + CEQ@ , ` @; X = @; ! and PGA1−8 + CEQ@ , ` @; X = @; #0 by the previous lemma. Thus PGA1−8 + CEQ@ ` @; X 6= @; Y using (17). All other cases are similar except one: [[X]] = P  a  Q and [[Y ]] = P 0  a  Q0 . Then there must be X 0 and Y 0 such that PGA1−8 + CEQ@ , ` @; X = @; +a; X 0 and PGA1−8 + CEQ@ , ` @; Y = @; +a; Y 0 . It then follows that either πn ([[X 0 ]]) 6= πn ([[Y 0 ]]) or πn ([[#2; X 0 ]]) 6= πn ([[#2; Y 0 ]]). In both cases the induction hypothesis can be applied. Finally (15) is applied to obtain the required fact. a

Theorem 2.7.3 X 6=bc Y ⇒ PGA1−8 + CEQ@ ` X 6= Y. Proof. If X 6=bc Y then for some P and Q, [[P ; X; Q]] = 6 [[P ; Y ; Q]]. Using the previous proposition PGA1−8 + CEQ@ ` @; P ; X; Q 6= @; P ; Y ; Q. This implies PGA1−8 + CEQ@ ` X 6= Y by the laws of conditional equational logic. a

2.8

Concluding remarks

Polarized process algebra has been used in order to give a natural semantics for programs. The question how to give an equational initial algebra specification of the program algebra (with or without entry instruction) modulo behavioral congruence remains open. As stated in [15] behavioral congruence is decidable on PGA expressions. For that reason an infinite equational specification exists. The problem remains to present such a specification either with a finite set of equations or with the help of a few comprehensible axiom schemes. General specification theory (see [25]) states that a finite equational specification can be found which is an orthogonal rewrite system (see [51, 26]) at the same time, probably at the cost of some auxiliary functions. Following the proof strategy of [25] an unreadable specification will be obtained, however. The problem remains to obtain a workable specification with these virtues. Thus as it stands both finding an initial algebra specification and finding a ‘better’ final algebra specification (only finitely many equations, no additional object) for program algebra with behavioral congruence are open matters. Another question left open for further investigation is whether the entry instruction can be naturally combined with the unit instruction operator as studied in [62]. This seems not to be the case. A similar question can be posed regarding the repetition instruction mentioned in [15].

50

J.A. Bergstra, I. Bethke, and A. Ponse

Chapter 3

Decision Problems for Pushdown Threads Jan A. Bergstra, Inge Bethke, and Alban Ponse

Threads as contained in a thread algebra emerge from the behavioral abstraction from programs in an appropriate program algebra. Threads may make use of services such as stacks, and a thread using a single stack is called a pushdown thread. Equivalence of pushdown threads is shown decidable whereas pushdown thread inclusion is undecidable. This is again an example of a borderline crossing where the equivalence problem is decidable, whereas the inclusion problem is not.

3.1

Introduction

A challenging question in language theory is to decide whether the languages accepted by two different machines in some given class are the same. This question is called the equivalence problem. Another important question, known as the inclusion problem, is that of determining whether one language is a subset of another. The most obvious connection between these two problems is that the latter implies the former, that is, that any algorithm that decides inclusion for some family of languages can also be used to decide equivalence. The question then arises whether the converse holds, i.e., whether there are natural examples of language families with a decidable equivalence problem and an undecidable inclusion problem. In 1973, Bird [27] found that the languages accepted by two-tape Rabin and Scott machines possess a decidable equivalence problem and an undecidable inclusion problem. Valiant explored this question further, finding two other families exhibiting this feature: the languages accepted by deterministic finite-turn pushdown automata [73] and deterministic one-counter pushdown automata [74]. In 1976, Friedman [38] investigated another subclass of deterministic pushdown automata—simple machines that have only one state and operate in real-time—and showed that these languages indeed have an undecidable inclusion problem. More recently, e.g. erasing and nonerasing pattern languages [49, 61] and deterministic context-free languages [31] have been added 51

52

J.A. Bergstra, I. Bethke, and A. Ponse

to this growing list. In this paper we investigate yet another class: pushdown threads, a form of processes describing sequential program behaviour and using the services offered by a single stack. In this approach, threads as contained in a thread algebra emerge from the behavioral abstraction of sequential programs. A basic thread models a finite program behaviour to be controled by some execution environment: upon each action (e.g. a request for some service), a reply true or false from the environment determines further execution. Any execution trace of a basic thread ends either in the (successful) termination state or in the deadlock state. Both these states are modeled as special thread constants. Regular threads extend basic threads by comprising loop behaviour, and are reminiscent of flowcharts [55, 42]. Threads may make use of services, i.e., devices that control (part of) their execution by consuming actions, providing the appropriate reply, and suppressing observable activity. Regular threads using the service of a single stack are called pushdown threads. Apart from the distinction between deadlock and termination, pushdown threads are comparable to pushdown automata. We show that quivalence of pushdown threads is decidable, whereas pushdown thread inclusion is undecidable. The paper is structured as follows: in Section 3.2, we outline the fundamental properties of thread algebra. In Section 3.3, we show that equivalence between pushdown threads is decidable by reducing the equivalence problem for deterministic pushdown automata [64, 66, 70] to our equivalence problem. In Section 3.4, we prove that inclusion is undecidable for pushdown threads. Here we reduce the halting problem for Minsky machines to the inclusion problem—an approach also taken in Janˇcar et al. [48]. In Section 3.5 we shortly discuss a programming notation for the specification of pushdown threads. The paper ends with some conclusions in Section 3.6.

3.2

Threads and services

Basic thread algebra [17]1 , BTA, is a form of process algebra which is tailored for the description of sequential program behaviour. Based on a finite set of actions A, it has the following constants and operators: • the termination constant S, • the deadlock or inaction constant D, • for each a ∈ A, a binary postconditional composition operator  a  . We use action prefixing a ◦ P as an abbreviation for P  a  P and take ◦ to bind strongest. Furthermore, for n ∈ N we define an ◦ P by a0 ◦ P = P and an+1 ◦ P = a ◦ (an ◦ P ). The operational intuition behind this algebraic framework is that each action represents a command which is to be processed by the execution environment of the thread. More specifically, an action is taken as a command for a service offered by the environment. The processing of a command may involve a change of state of this environment. 1

In [15], basic thread algebra is introduced under the name basic polarized process algebra.

EWSCS’06 - Program Algebra and Thread Algebra

53

At completion of the processing of the command, the service concerned produces a reply value. This reply is either true or false and is returned to the thread under execution. The thread P  a  Q will then proceed as P if the processing of a leads to the reply true indicating the successful processing of a, and it will proceed as Q if the processing of a leads to the unsuccessful reply false. BTA can be equipped with a partial order and an approximation operator in the following way: 1. v is the partial ordering on BTA generated by the clauses (a) for all P ∈ BTA, D v P , and (b) for all P1 , P2 , Q1 , Q2 ∈ BTA, a ∈ A, P1 v Q1 & P2 v Q2 ⇒ P1  a  P2 v Q1  a  Q2 . 2. π : N × BTA → BTA is the approximation operator determined by the equations (a) for all P ∈ BTA, π(0, P ) = D, (b) for all n ∈ N, π(n + 1, S) = S, π(n + 1, D) = D, and (c) for all P, Q ∈ BTA, n ∈ N, π(n + 1, P  a  Q) = π(n, P )  a  π(n, Q). We further write πn (P ) instead of π(n, P ). The operator π finitely approximates every thread in BTA. That is, for all P ∈ BTA, ∃n ∈ N π0 (P ) v π1 (P ) v · · · v πn (P ) = πn+1 (P ) = · · · = P. Every thread in BTA is finite in the sense that there is a finite upper bound to the number of consecutive actions it can perform. Following the metric theory of [3] in the form developed as the basis of the introduction of processes in [12], BTA has a completion BTA∞ which comprises also the infinite threads. Standard properties of the completion technique yield that we may take BTA∞ as the cpo consisting of all so-called projective sequences. That is, BTA∞ = {(Pn )n∈N | ∀n ∈ N (Pn ∈ BTA & πn (Pn+1 ) = Pn )} with (Pn )n∈N v (Qn )n∈N ⇔ ∀n ∈ N Pn v Qn and (Pn )n∈N = (Qn )n∈N ⇔ ∀n ∈ N Pn = Qn . For a detailed account of this construction see [7]. In this cpo structure, finite linear recursive specifications represent continuous operators having as unique fixed points regular threads, i.e., threads which can only reach finitely many states. A finite linear recursive specification over BTA∞ is a set of equations Xi = ti (X) for i ∈ I with I some finite index set and all ti (X) of the form S, D, or Xil  ai  Xir for il , ir ∈ I.

54

J.A. Bergstra, I. Bethke, and A. Ponse

Example 3.2.1 We define the regular threads 1. an ◦ D, 2. an ◦ S and 3. a∞ (this informal notation will be often used in the sequel) as the fixed points for X1 in the specifications 1. X1 = a ◦ X2 , . . . , Xn = a ◦ Xn+1 , Xn+1 = D, 2. X1 = a ◦ X2 , . . . , Xn = a ◦ Xn+1 , Xn+1 = S, 3. X1 = a ◦ X1 , respectively. Both an ◦ D and an ◦ S are finite threads; a∞ is the infinite thread corresponding to the projective sequence (Pn )n∈N with P0 = D and Pn+1 = a ◦ Pn . Observe that e.g. an ◦ D v an ◦ S, an ◦ D v a∞ but an ◦ S 6v a∞ . In reasoning with finite linear specifications, we shall from now on identify variables and their fixed points. For example, we say that P is the regular thread defined by P = a ◦ P instead of stating that P equals the fixed point for X in the finite linear specification X = a ◦ X. Furthermore, in a finite linear specification P1 = t1 (P ), ..., Pn = tn (P ),

(3.1)

the threads Pi will also be referred to as states. As another example following this convention, we show that for regular threads P and Q, P v Q is decidable (recall that v is decidable for finite threads). Because one can always take the disjoint union of two recursive specifications, it suffices to show that Pi v Pj is decidable in (3.1) above. This follows with finite approximations from the following assertion (the idea being that any loop has at most length n): ∀i, j ≤ n πn (Pi ) v πn (Pj ) ⇒ Pi v Pj

(3.2)

where πl (Pk ) is defined by πl (tk (P )). To prove (3.2), assume that n > 1 (otherwise the implication follows trivially). Choose i, j and assume that πn (Pi ) v πn (Pj ). Suppose Pi 6v Pj . Then for some k > n, πk (Pi ) 6v πk (Pj ) while πk−1 (Pi ) v πk−1 (Pj ). So there exists a trace of length k from Pi of the form a

b

Pi −−true −→ Pi0 −−false −−→ ... that is not a trace of Pj , while by the assumption the first n actions of this trace are a trace of Pj . These n actions are connected by n+1 states, and because there are only n different states, a repetition occurs in this sequence of states. So the trace witnessing πk (Pi ) 6v πk (Pj ) can be made shorter, contradicting k’s minimality and hence the supposition. Thus Pi v Pj . As a corollary, also P = Q is decidable for regular threads P and Q.

EWSCS’06 - Program Algebra and Thread Algebra

55

A service 2 is a pair hΣ, F i consisting of a set Σ of special actions and a reply function F . The reply function F of a service hΣ, F i is a mapping that gives for each finite sequence of actions from Σ the reply produced by the service. This reply is a Boolean value true or false. Example 3.2.2 Services that will occur in Section 3.3 and 3.4 are 1. C = hΣ, F i with Σ = {inc, dec} consisting of the increase and decrease actions of a natural number counter and the reply function F which always replies true to increase actions and false to decrease actions if and only if the counter is zero. We denote by C(n) a counter with value n. 2. S = hΣ, F i with Σ = {push : i, topeq : i, empty, pop | i = 1, ..., n} for some n where push : i pushes i onto the stack and yields reply true, the action topeq : i tests whether i is on top of the stack, empty tests whether the stack is empty, and pop pops the stack if it is non-empty with reply true and yields false otherwise. We denote by S(α) a stack with contents α ∈ {1, ..., n}∗ . Observe that counters can be seen as particular stacks (take n = 1). In order to provide a specific description of the interaction between a thread and a service, we will use for actions the general notation c.a where c is the so-called channel and a is the so-called co-action. In particular, we will write e.g. c.inc to denote the action which increases a counter via channel c and s.pop to denote the action which pops a stack via channel s . For a service S = hΣ, F i and a finite thread P , the defining rules for P/c S (the thread P using the service S via channel c) are: S/c S D/c S (P  c 0 .a  Q)/c S (P  c.a  Q)/c S (P  c.a  Q)/c S (P  c.a  Q)/c S

= = = = = =

S, D, (P/c S)  c 0 .a  (Q/c S) if c0 6= c, P/c S 0 if a ∈ Σ and F (a) = true, Q/c S 0 if a ∈ Σ and F (a) = false, D if a 6∈ Σ.

where S 0 = hΣ, F 0 i with F 0 (σ) = F (aσ) for all co-action sequences σ ∈ Σ+ . The use operator is expanded to infinite threads P by stipulating P/c S = (πn (P )/c S)n∈N . As a consequence, P/c S = D if for any n, πn (P )/c S = D. Finally, repeated applications of the use operator bind to the left, thus P/c0 S0 /c1 S1 = (P/c0 S0 )/c1 S1 . Example 3.2.3 We consider again the threads an ◦D, an ◦S and a∞ from Example 3.2.1 but now in the versions c.a n ◦D (short for (c.a)n ◦D), c.a n ◦S and c.a ∞ for some channel c and some service S = hΣ, F i with a ∈ Σ. Then (c.a n ◦ D)/c S = D, (c.a n ◦ S)/c S = S 2

In [21] a service is called a state machine.

56

J.A. Bergstra, I. Bethke, and A. Ponse

but c.a ∞ /c S = D. The last identity can also be retrieved as follows: if P = c.a ◦P , then P/c S = (c.a ◦ P )/c S = P/c S 0 . In this way, the computation will diverge, characterizing the rule: “If for a regular thread P the defining rules for P/c S fail to prove P/c S = S or π1 (P/c S) = b ◦ D for some action b, then P/c S = D”. In the next example we show that the use of services may turn regular threads into irregular ones. Example 3.2.4 Let a ∈ A. Consider the following regular process P :

3

P = (c0 .inc ◦ P )  a  ((P  c0 .dec  D)  a  (D  c0 .dec  P )). With the counter C defined in Example 3.2.2, define for n ∈ N the thread Pn by Pn = P/c0 C(n). This definition yields the following infinite recursive specification: P0 = P1  a  (D  a  P0 ) Pk+1 = Pk+2  a  (Pk  a  D) for k ∈ N. Now assume that Pn v Pm for some n > m. Then, by definition of Pm (following the rightmost branches of  a  ), (Pn−m−1  a  D) v (D  a  P0 ). It is clear that v does not hold, thus we obtained a contradiction and Pn 6v Pm . In the same way it follows that Pn 6w Pm if n > m. We conclude that P0 is not regular: for each k ∈ N, the state Pk can be reached, and if n 6= m, then the states Pn and Pm are different. 4 We call a regular thread that uses a stack or a counter as described in Example 3.2.2 a pushdown thread (typically, any thread Pk defined in Example 3.2.4 is a pushdown thread).

3.3

Decidable equality

In this section we prove the decidability of P/s S(α) = Q/s S(β) for regular threads P and Q, and a stack S over some finite data type with contents α, respectively β. We first discuss deterministic pushdown automata (dpda’s) and recall the decidability result establishing dpda-equivalence (S´enizergues [64, 66]). We base our approach on Stirling’s account of this result in [70], as this is closer to our setting. In the sequel we write  for the empty sequence over any alphabet. 3

Note that a linear recursive specification requires (at least) six equations in this case. Note that for all n, Pn v a∞ (this follows from πk (Pn ) v πk (a∞ ) = ak ◦ D for all k) and Pn 6v S. We will use these properties in Section 3.4. 4

EWSCS’06 - Program Algebra and Thread Algebra

57

A pushdown automaton (pda) A is given by a finite set P of states, a finite set S of stack symbols, a finite alphabet A, and a finite set of basic transitions of the form a P x −→ Qα

with P, Q ∈ P, x ∈ S, a ∈ A ∪ {}, and α ∈ S∗ . Expressions of the form P x are further called source-configurations. A configuration of A is any expression P α whose behaviour is determined by the basic transitions and the prefix rule a

a

if P x −→ Qα then P xβ −→ Qαβ. The language accepted by a configuration P α, notation L(A, P α), w is {w ∈ A∗ | ∃Q ∈ P.P α −− → Q} where the extended transitions for words are defined as expected. Note that -transitions are swallowed in the usual fashion and that acceptance is by empty stack.

A deterministic pushdown automaton (dpda) A0 has three restrictions on its basic transitions: a

a



a

• if P x −→ Qα and P x −→ Rβ for a ∈ A, then Q = R and α = β, • if P x −→ Qα and P x −→ Rλ then a = , 

• if P x −→ Qα then α =  (so -transitions can only pop the stack; in [66] this case is referred to as a normalized dpda). Note that the language accepted by any configuration of a dpda A0 is prefix-free: if w is accepted then no proper prefix of w is accepted. For dpda’s it is decidable whether L(A0 , P α) = L(A0 , Qβ) ([64, 66]). With this decidability result we can prove the main result of this section: Theorem 3.3.1 For regular threads P and Q, and a stack S over a finite data type it is decidable whether P/s S(α) = Q/s S(β), where α, β represent the contents of S. Proof. Let S be the (empty) stack controlled by the actions {s.push:i, s.topeq:i, s.empty, s.pop | i = 1, ..., n} for some n ≥ 1. We write S(α) if the stack contains the elements from sequence α ∈ {1, ..., n}∗ with the leftmost element of α on top. Furthermore, s.push:i pushes i onto the stack and yields reply true, the action s.topeq:i tests whether i is on top of the stack, s.empty tests whether the stack is empty, and s.pop pops the stack if it is non-empty (reply true) and yields false otherwise. Finally, we assume that in P and Q there are external actions a0 , ..., am , and that P and Q are given by a single finite linear specification. Using five transformations, we reduce the dpda-equivalence problem as discussed above to the question P/s S(α) = Q/s S(β).

58

J.A. Bergstra, I. Bethke, and A. Ponse

Adapting the stack contents. In order to use the dpda-equivalence result, the stack should be empty upon termination and non-empty upon the start because language acceptance is by empty stack, and defined on configurations with non-empty stack. This can be achieved as follows: • Extend the stack S to S0 as follows: – in order to start with a non-empty stack, add an extra stack symbol 0 and extend all reply functions to actions over stack symbols {0, 1, ..., n} in the obvious way, except for s.pop that leaves 0 on the stack and yields in this case reply false, – define a new action s.pop:all that pops any symbol in {0, 1, ..., n} from the stack with reply true, and yields reply false if the stack is empty. • In the specification of P and Q, – replace each occurrence of s.empty by s.topeq:0 (so, 0 acts as an empty stack marker), – replace any equation R = S by R = s.pop:all ◦ S (where S is fresh), – add the equation S = S  s.pop:all  S. Call the resulting threads P0 and Q0 , respectively, and assume these are given by an appropriate adaptation of the linear specification of P and Q (note that s.push:0 and s.empty do not occur in this specification). It follows straightforwardly that for α, β ∈ {1, ..., n}∗ , P/s S(α) = Q/s S(β) ⇔ P0 /s S0 (α0) = Q0 /s S0 (β0) and that upon S (i.e., termination), the stack S0 is empty.

Replacement of D by explicit loops. In the specification of P0 and Q0 , replace each equation R = D by R = s.push:1 ◦ L (where L is fresh) and add the equation L = s.push:1 ◦ L. Call the resulting threads P1 and Q1 , respectively, for some appropriate adaptation to a linear recursive specification. Again it is straightforward that for α, β ∈ {1, ..., n}∗ , P0 /s S0 (α0) = Q0 /s S0 (β0) ⇔ P1 /s S0 (α0) = Q1 /s S0 (β0) and that upon S, the stack S0 is empty.

Normalization of infinite traces. Let halt be a fresh external action. Replace in P1 and Q1 ’s specification each equation R = Rl  a  Rr with a an external action by R = S  halt  (Rl  a  Rr ). Call the resulting threads P2 and Q2 , respectively. Again it is straightforward that for α, β ∈ {1, ..., n}∗ , P1 /s S0 (α0) = Q1 /s S0 (β0) ⇔ P2 /s S0 (α0) = Q2 /s S0 (β0) and that upon S, the stack S0 is empty. Moreover, each infinite sequence of external actions in P1 /s S0 (α0) or Q1 /s S0 (β0) becomes after this transformation interlarded with halt ◦ S exits, so gives rise to an infinite number of (finite) traces.

Transformation to pda-equivalence. From the linearized specification of P2 and Q2 , construct a pda A1 as follows: for P, the set of states, take those of the linear specification; for S, the set of stack symbols, take {0, ..., n}; and for the alphabet A take {atrue , afalse | a an external action in P2 or Q2 }. As for the basic transitions, • for each state R = Rl  a  Rr with a an external action and i ∈ {1, ..., n}, define a afalse transitions Ri −−true −→ Rl i and Ri −−− −→ Rr i,

EWSCS’06 - Program Algebra and Thread Algebra

59 

• for each state R = Rl  s.push:j  Rr and i ∈ {1, ..., n}, define transitions Ri −−→ Rr ji, • for each state R = Rl  s.topeq:j  Rr and i ∈ {0, 1, ..., n} \ {j}, define transitions   Rj −−→ Rl j, and define a transition Ri −−→ Rr i, 

• for each state R = Rl  s.pop  Rr and i ∈ {1, ..., n}, define transitions Ri −−→ Rl and  R0 −−→ Rr 0,  • for each state R = Rl  s.pop:all  Rr and i ∈ {0, 1, ..., n}, define transitions Ri −−→ Rl .

It follows immediately that for α, β ∈ {1, ..., n}∗ , P2 /s S0 (α0) = Q2 /s S0 (β0) ⇔ L(A1 , P2 α0) = L(A1 , Q2 β0). Note that A1 ’s basic transitions with label  are very limited: either a push or a pop, or they do not change the stack. Also, the transition relation is deterministic: for each source configuration Ri at most one transition is defined.

Transformation to dpda-equivalence. The only remaining problem is that A1 is not normalized, i.e., may have -transitions that do not pop the stack. However, the set of basic transitions can be transformed to one that contains only popping -transitions while preserving language acceptance. Not swallowing -transitions, each source-configuration Ri in A1 with an -transition can be classified by tracing its consecutive transitions. Assume A1 has k states. Because A1 has n+1 stack symbols, it is sufficient to consider at most k(n+1)+1 consecutive  transitions. Let −−→ − → stand for at least 0 and at most k(n+1)−1 -transitions, then the following classes can be distinguished: 



a

a

1. Ri −−→ R0 α −−→ −→ R00 α0 −−→ R000 β where R00 α0 −−→ R000 β is the first occurring A-transition,     2. Ri −−→ R0 α −−→ − → R00 α0 −−→ R000 where R00 α0 −−→ R000 is the first -transition from Ri that empties the stack, 





3. Not 1 or 2, i.e., Ri −−→ R0 jα −−→ −→ R00 mα0 −−→ R000 lβ and in the sequence of associated source-configurations a repetition occurs (and no empty stack). Now define a dpda A2 with P, S and A as in A1 , and with basic transitions as follows:  • in case 1, replace Ri −−→ R0 α by Ri −−a→ R000 β,   • in case 2, replace Ri −−→ R0 α by Ri −−→ R000 ,

• in case 3, simply omit the -transition from Ri, • keep all basic transitions with a label in A from A1 . It is clear that L(A1 , Rα) = L(A2 , Rα). In A2 , language equivalence is decidable as A2 satisfies the dpda-definition given above (which is taken from [70]; note that the requirement in [70] that |α| < 3 in a basic transition P x −−a→ Qα can be trivially fulfilled by introducing auxiliary stack symbols). aa

We note that in the proof above, the transformation step Normalization of infinite traces can be skipped if one considers bisimulation equivalence in the transition graph of a normalized dpda, which is decidable as well (S´enizergues [65]).

We conclude this section with a short comment. The restriction to a stack over a finite data type is not essential for the decidability of equality between pushdown

60

J.A. Bergstra, I. Bethke, and A. Ponse

threads: also for a stack SN over the natural numbers N and regular threads P and Q it holds that P/s SN (α) = Q/s SN (β) is decidable. This can be seen by representing natural numbers in some unary notation and using a second data element as a separator. For example, 011101101111 represents the stack containing 2, 1, 3 (so, the natural number n is represented by n+1 pushes of 1). For any i ∈ N, the actions s.push:i and s.topeq:i can be expressed in this notation, be it a bit cumbersome. The action s.pop is easier to define, and s.empty need not be redefined. Thus given regular threads P and Q, there exists a transformation φ (depending on the stack-actions in P and Q) such that P/s SN (α) = Q/s SN (β) ⇔ φ(P )/s S(α) = φ(Q)/s S(β) where S is the stack over symbols {0, 1} and α transforms a sequence of natural numbers as indicated above. Successor and predecessor for SN can be easily defined using the representation discussed here. This is not the case for any action controlling SN . For instance, an action swap that exchanges the two top-elements of SN is not definable because with this action the functionality of a Minsky machine is obtained (using the even stack positions to hold the values of the first counter, and the odd positions for those of the second counter). So with swap equality is not decidable. Neither is equality of the two top-elements a definable action. Of course, both these actions are definable for stacks over a finite data type.

3.4

Undecidable inclusion

The Minsky machine is a universal model of computation first used in [56, 57]. It is a simple imperative program consisting of a sequence of instructions labelled by natural numbers from 1 to some L. It starts from the instruction labelled 1, halts if stop is reached and operates with two natural number counters c0 and c1 . The instruction set consists of three types of instructions: 1. l : ci := ci + 1; goto l0 2. l : if ci = 0 then goto l0 else ci := ci − 1; goto l00 3. l : stop where i ∈ {0, 1} and l, l0 , l00 ∈ {1, . . . , L}. It should be clear that the execution process is deterministic and has no failure. Any such process is either finished by the execution of the stop instruction or lasts forever. As expected, the halting problem for Minsky machines is undecidable: Theorem 3.4.1 ([56, 57]) It is undecidable whether a Minsky machine halts when both counter values are initially zero.

EWSCS’06 - Program Algebra and Thread Algebra

61

In our setting, a counter C is a service hΣ, F i with increase and decrease actions Σ = {inc, dec} and a reply function F which always replies true to inc and false to dec if and only if the counter value is zero. A Minsky machine canonically defines the equations of a specification of a regular thread: 7 → l : ci := ci + 1; goto l0 l : if ci = 0 then goto l0 7 → else ci := ci − 1; goto l00 7→ l : stop

Ml = ci .inc ◦ Ml0 Ml = Ml00  ci .dec  Ml0 Ml = S.

We call a thread Ml as defined above a Minsky thread, thus a regular thread over S and the counter actions c0 .inc, c0 .dec, c1 .inc, c1 .dec where the channels c0 and c1 refer to the two internal counters C0 and C1 , respectively. 5 The halting problem for Minsky machines can now be rephrased to Theorem 3.4.2 It is undecidable whether for a Minsky thread M it holds that M/c0 C0 (0)/c1 C1 (0) = S. (Of course, if M/c0 C0 (0)/c1 C1 (0) 6= S, it equals D.) The undecidability proof that follows consists of a reduction from the above halting problem to the inclusion problem for the use of a single internal counter—and thus to the inclusion problem for the use of a single internal stack. For technical purposes we introduce the norm of a Minsky thread operating on counters C0 (n) and C1 (m) as the number of counter actions until termination occurs (possibly ∞). The norm is formally defined with help of a transformation θ of finite linear recursive specifications. Definition 3.4.3 (norm) Let M1 be a Minsky thread defined by a finite linear recursive specification E = {Mn = tn (M ) | n = 1, ..., k} (for some k > 0) and let a be some action. Define θ(E) = {θ(Mn ) = θ(tn (M )) | n = 1, ..., k} and define θ(tn (M )) by 1. θ(S) = S 2. For i ∈ {0, 1} and b ∈ {inc, dec}, θ(Ml  ci.b  Mr ) = a ◦ θ(Ml )  ci.b  a ◦ θ(Mr ). For n, m ∈ N define the norm kM1 , n, mk ∈ N ∪ {∞} by kM1 , n, mk = 0 if θ(M1 ) = S ∈ θ(E), and for k > 0, ( πk+1 (θ(M1 )/c0 C0 (n)/c1 C1 (m)) = ak ◦ S and kM1 , n, mk = k if πk (θ(M1 )/c0 C0 (n)/c1 C1 (m)) = ak−1 ◦ D. For n, m ∈ N, kM1 , n, mk = ∞ if for no k ∈ N, kM1 , n, mk = k. 5

We do not have to disambiguate these counters because of the different channel names; however, we do this to enhance readability.

62

J.A. Bergstra, I. Bethke, and A. Ponse

Note that kM, n, mk ∈ N ⇔ M/c0 C0 (n)/c1 C1 (m) = S.

(3.3)

So the question whether kM, n, mk ∈ N is undecidable. We now introduce a transformation ψ of Minsky threads which replaces specific runs with the second counter C1 (0) by regular threads where the C1 -actions are simulated by a single external action a in such a way that termination behaviour is preserved. Moreover, C0 -actions are preceded by the simulation of a c1 .inc ◦ c1 .dec-prefix. The reason for this is that divergence on C0 -actions (as in c0 .inc ∞ ) then also yields an infinite sequence of a-actions, which enables us to use a smooth proof strategy. Definition 3.4.4 (simulation) Let M1 be a Minsky thread defined by a finite linear recursive specification E = {Mn = tn (M ) | n = 1, ..., k} (for some k > 0). Define ψ(E) = {ψ(Mn ) = ψ(tn (M )) | n = 1, ..., k} and define ψ(tn (M )) by 1. ψ(S) = S 2. ψ(Mi  c1 .inc  Mj ) = ψ(Mi )  a  a∞ 3. ψ(Mi  c1 .dec  Mj ) = a∞  a  (ψ(Mi )  a  ψ(Mj )) 4. ψ(Mi  c0 .b  Mj ) = [a∞  a  a ◦ (ψ(Mi )  c0 .b  ψ(Mj ))]  a  a∞ for b ∈ {inc, dec}. We note that a∞ /c0 C0 (n) = a∞ and therefore we omit any such use-application in reasoning about ψ(M )/c0 C0 (n). In order to prove undecidability of inclusion we use the family Pk (k ∈ N) of pushdown threads discussed in Example 3.2.4: Definition 3.4.5 For n ∈ N, define Pn = P/c0 C0 (n) by P = (c0 .inc ◦ P )  a  ((P  c0 .dec  D)  a  (D  c0 .dec  P )). This definition yields the following infinite recursive specification: P0 = P1  a  (D  a  P0 ) Pk+1 = Pk+2  a  (Pk  a  D) for k ∈ N. Note that for all n, Pn v a∞ (see Example 3.2.4) and Pn 6v S. For future reference we derive the following identities: P0 = [P2  a  (P0  a  D)]  a  (D  a  P0 )

(3.4)

and for m > 0, Pm = [Pm+2  a  (Pm  a  D)]  a  (Pm−1  a  D)

(3.5)

EWSCS’06 - Program Algebra and Thread Algebra

63

We shall prove that for any Minsky thread M , M/c0 C0 (0)/c1 C1 (0) = S ⇔ P0 6v ψ(M)/c0 C0 (0). We first prove this equivalence for Minsky threads that use counters with arbitrary initial values (Corollaries 3.4.7 and 3.4.9, respectively). To enhance readability we shall write in proofs often M/ n/ m for M/c0 C0 (n)/c1 C1 (m) , and ψ(M )/ n for ψ(M )/c0 C0 (n). Lemma 3.4.6 Let k ∈ N. For all Minsky threads M and for all n, m ∈ N, kM, n, mk = k ⇒ Pm 6v ψ(M )/c0 C(n).

(3.6)

Proof. By induction on k using case ramification, i.e., considering all possible forms of the equation specifying M . • k = 0. If kM, n, mk = 0, then M = S must be M ’s defining equation and (3.6) follows immediately. • k > 0. If kM, n, mk = k, there are four possible forms for the equation specifying M : a) M = Mi  c0 .inc  Mj . Then kMi , n+1, mk = k−1 and ψ(M )/ n = (a∞  a  a ◦ ψ(Mi )/ n+1)  a  a∞ .

(3.7)

By induction Pm 6v ψ(Mi )/ n+1. Assume Pm v ψ(M )/ n. Then by (3.4) or (3.5) and (3.7), Pm v ψ(Mi )/ n+1, contradicting the induction hypothesis. Hence Pm 6v ψ(M )/ n. b) M = Mi  c0 .dec  Mj . This case is proved similarly, making a case distinction between n = 0 and n > 0. c) M = Mi  c1 .inc  Mj . Then kMi , n, m+1k = k−1 and ψ(M )/ n = ψ(Mi )/ n  a  a∞ .

(3.8)

By induction Pm+1 6v ψ(Mi )/ n. Assume Pm v ψ(M )/ n. Then by definition of Pm and (3.8), Pm+1 v ψ(Mi )/ n, contradicting the induction hypothesis. Hence Pm 6v ψ(M )/ n. d) M = Mi  c1 .dec  Mj . Then ψ(M )/ n = a∞  a  (ψ(Mi )/ n  a  ψ(Mj / n).

(3.9)

Let m = 0. Then kMj , n, 0k = k−1 and by induction P0 6v ψ(Mj )/ n. Assume P0 v ψ(M )/ n. Then by definition of P0 and (3.9), P0 v ψ(Mj )/ n, contradicting the induction hypothesis. Hence P0 6v ψ(M )/ n. Let m > 0. Then kMi , n, m−1k = k−1 and by induction Pm−1 6v ψ(Mi )/ n. Assume Pm v ψ(M )/ n. Then by definition of Pm and (3.9), Pm−1 v ψ(Mi )/ n, contradicting the induction hypothesis. Hence Pm 6v ψ(M )/ n. aa

Combining (3.3) and Lemma 3.4.6 immediately yields Corollary 3.4.7 For all Minsky threads M and for all n, m ∈ N, M/c0 C0 (n)/c1 C1 (m) = S ⇒ Pm 6v ψ(M )/c0 C0 (n).

64

J.A. Bergstra, I. Bethke, and A. Ponse The next result is about the inclusion of finite approximations.

Lemma 3.4.8 For all k ∈ N, Minsky threads M and n, m ∈ N, M/c0 C0 (n)/c1 C1 (m) = D ⇒ πk (Pm ) v πk (ψ(M )/c0 C0 (n)).

(3.10)

Proof. By induction on k with base cases k = 0 and k = 1. If k = 0 then (3.10) follows trivially. If k = 1 and M/ n/ m = D, then M = S is not M ’s defining equation. Therefore, π1 (ψ(M )/ n) = a ◦ D = π1 (Pm ), which proves (3.10). (Note that already in this case it is essential that ψ introduces a-actions in the transformation of c0 -equations.) Assume k ≥ 2 and M/ n/ m = D. Again, M = S can not be M ’s defining equation. Consider the remaining four possibilities for M ’s defining equation: a) M = Mi  c0 .inc  Mj . Then Mi / n+1/ m = D and πk (ψ(M )/ n) = [πk−2 (a∞ )  a  πk−2 (a ◦ ψ(Mi )/ n+1)]  a  πk−1 (a∞ ). By induction, πl (Pm ) v πl (ψ(Mi )/ n+1) for l < k. Assume m = 0. Then by (3.4) πk (P0 ) = [πk−2 (P2 )  a  πk−2 (P0  a  D)]  a  πk−1 (D  a  P0 ) v [πk−2 (a∞ )  a  πk−2 (a ◦ ψ(Mi )/ n+1)]  a  πk−1 (a∞ ) = πk (ψ(M )/ n). Assume m > 0. Then by (3.5) πk (Pm ) = [πk−2 (Pm+2 )  a  πk−2 (Pm  a  D)]  a  πk−1 (Pm−1  a  D) v [πk−2 (a∞ )  a  πk−2 (a ◦ ψ(Mi )/ n+1)]  a  πk−1 (a∞ ) = πk (ψ(M )/ n). b) M = Mi  c0 .dec  Mj . Assume n = 0. Then Mj / 0/ m = D and πk (ψ(M )/ 0) = [πk−2 (a∞ )  a  πk−2 (a ◦ ψ(Mj )/ 0)]  a  πk−1 (a∞ ). By induction, πl (Pm ) v πl (ψ(Mj )/ 0) for l < k. As in case a), it follows that both for m = 0 and m > 0, πk (Pm ) v [πk−2 (a∞ )  a  πk−2 (a ◦ ψ(Mj )/ 0)]  a  πk−1 (a∞ ) = πk (ψ(M )/ n). The case n > 0 is proved similarly. c) M = Mi  c1 .inc  Mj . Then Mi / n/ m+1 = D and πk (ψ(M )/ n) =

πk−1 (ψ(Mi )/ n)  a  πk−1 (a∞ ).

By induction, πl (Pm+1 ) v πl (ψ(Mi )/ 0) for l < k. Assume m = 0. Then πk (P0 ) = πk−1 (P1 )  a  πk−1 (D  a  P0 ) v πk−1 (ψ(Mi )/ n)  a  πk−1 (a∞ ) = πk (ψ(M )/ n). Assume m > 0. Then πk (Pm ) = πk−1 (Pm+1 )  a  πk−1 (Pm−1  a  D) v πk−1 (ψ(Mi )/ n)  a  πk−1 (a∞ ) = πk (ψ(M )/ n). d) M = Mi  c1 .dec  Mj . This case can be proved in a similar style, again making a case distinction between m = 0 and m > 0. aa

EWSCS’06 - Program Algebra and Thread Algebra

65

In the proof above, the cases a) and b) clearly motivate ψ’s definition on c0 -terms: the simulation of a “c1 .inc ◦ c1 .dec-prefix” generates in the case of divergence on C0 the thread a∞ , which is needed to include Pm . Lemma 3.4.8 immediately extends to the inclusion of infinite threads: Corollary 3.4.9 For all Minsky threads M and for all n, m ∈ N, M/c0 C0 (n)/c1 C1 (m) = D ⇒ Pm v ψ(M )/c0 C0 (n). Corollary 3.4.7 and Corollary 3.4.9 connect the halting problem for Minsky machines and inclusion between certain pushdown threads: Corollary 3.4.10 Let M be a Minsky thread. Then M/c0 C0 (0)/c1 C1 (0) = S ⇔ P0 6v ψ(M )/co C0 (0). Since counters can be seen as particular stacks, we can by Corollary 3.4.10 summarize the reduction from the halting problem for Minsky machines to Theorem 3.4.11 It is undecidable whether for a stack S and regular threads P and Q it holds that P/s S(α) v Q/s S(β).

3.5

Programming regular threads

Program Algebra [15], or PGA for short, is a program notation for regular threads. On PGA a hierarchy of program notations is founded, comprising languages that contain more and more sophisticated programming features, but that are all equally expressive. We will show that PGA exactly describes the class of regular threads. Based on a finite set A of basic instructions, PGA has the binary operators “concatenation” and “repetition”, and five kinds of instructions: Concatenation, notation ; . If X and Y are PGA-terms, so is X; Y . Repetition, notation ( )ω . If X is a PGA-term, so is X ω . Basic instruction a ∈ A. It is assumed that upon the execution of a basic instruction, the (executing) environment provides an answer true or false. However, in the case of a basic instruction, this answer is not used for program control. After execution of a basic instruction, the next instruction (if any) will be executed; if there is no next instruction, inaction (i.e., the thread D) will occur. Positive test instruction +a for a ∈ A. The instruction +a executes like the basic instruction a. Upon false, the program skips its next instruction and continues with the instruction thereafter; upon true the program executes its next instruction. If there is no subsequent instruction to be executed, inaction occurs.

66

J.A. Bergstra, I. Bethke, and A. Ponse

Negative test instruction −a for a ∈ A. The instruction −a executes like the basic instruction a. Upon true, the program skips its next instruction and continues with the instruction thereafter; upon false the program executes its next instruction. If there is no subsequent instruction to be executed, inaction occurs. Termination instruction !. This instruction prescribes successful termination (it defines the thread S). Jump instruction #k (k ∈ N). This instruction prescribes execution of the program to jump k instructions forward; if there is no such instruction, inaction occurs. In the special case that k = 0, this prescribes a jump to the instruction itself and inaction occurs, in the case that k = 1 this jump acts as a skip and the next instruction is executed. In the case that the prescribed instruction is not available, inaction occurs. Instruction sequence congruence for PGA-terms is axiomatized by the axioms PGA1-4 in Table 3.1. Here PGA2 is an axiom-scheme that is parametric in n where in PGA, X 1 = X and X k+1 = X; X k . (X; Y ); Z = X; (Y ; Z) (PGA1) n ω ω (X ) = X for n > 0 (PGA2)

Xω; Y = Xω (X; Y )ω = X; (Y ; X)ω

(PGA3) (PGA4)

Table 3.1: Axioms for PGA’s instruction sequence congruence With the axioms PGA1-4 one easily derives unfolding, i.e., X ω = X; X ω . Each closed PGA-term is considered a program of which the behaviour is a regular thread in BTA∞ , viewing A as the set of actions. The thread extraction operator |X| assigns a thread to a closed term X. Thread extraction is defined by the thirteen equations in Table 3.2, where a ∈ A and u is an arbitrary instruction. |!| |a| |+a| |−a|

= = = =

|!; X| |a; X| |+a; X| |−a; X|

S a◦D a◦D a◦D

= = = =

S a ◦ |X| |X|  a  |#2; X| |#2; X|  a  |X|

|#k| |#0; X| |#1; X| |#k + 2; u| |#k + 2; u; X|

Table 3.2: Equations for thread extraction on PGA Two examples: 1. |(#0)ω | = |#0; (#0)ω | = D, 2. |−a; b; c| = = = =

|#2; b; c|  a  |b; c| |#1; c|  a  b ◦ |c| |c|  a  b ◦ c ◦ D c ◦ D  a  b ◦ c ◦ D.

= = = = =

D D |X| D |#k + 1; X|

EWSCS’06 - Program Algebra and Thread Algebra

67

In some cases, the equations in Table 3.2 can be applied from left to right yielding equations of the form |X| = |Y | = ... = |X| and no behaviour, e.g., |(#1)ω | = |#1; (#1)ω | = |(#1)ω |, |(#2; a)ω | = |#2; a; (#2; a)ω | = |#1; (#2; a)ω | = |(#2; a)ω |. In such cases, the extracted thread is defined as D. In all other cases, thread extraction yields a finite (linear) recursive specification, and thus a regular thread. Example 3.5.1 Let P = |(a; +b; #3; −b; #4)ω |. Then P = a◦Q Q = P bR R = P  b  R. From the above considerations it is clear that any PGA-program defines a regular thread. Conversely, each regular thread can be described as the thread extraction of a PGA term. We here only sketch why this is the case. First S = |!| and D = |#0|. Without loss of generality one can assume that every other thread P satisfies π1 (P ) = a◦D for some a, and solves X1 in a finite linear recursive specification X1 = Xi aXj in which S and D occur only in the last two equations. In Example 3.5.1, P is a fixed point for X1 in the tailored specification X1 X2 X3 X4 X5

= = = = =

X2  a  X2 X1  b  X3 X1  b  X3 S D.

(Note that each fixed point for X1 − X3 does not refer to the equations for X4 and X5 .) One can transform any such tailored specification to a program of the form (u1 ; ...; uk )ω in the following way: X1 = X1,l  a1  X1,r .. . Xi = Xi,l  ai  Xi,r .. .

7→

( +a1 ; #c(1, l); #c(1, r); .. . +ai ; #c(i, l); #c(i, r); .. .

Xn = S Xn+1 = D

!; #0)ω .

If each value c(i, j) is chosen such that the jump is to the position in the program that matches the transformation of Xi,l = ti,l (X), thread extraction yields the same recursive specification. Returning to Example 3.5.1, X1 X2 X3 X4 X5

= = = = =

X2  a  X2 X1  b  X3 X1  b  X3 S D

7→

( +a; #2; #1; +b; #7; #1; +b; #4; #9; !; #0)ω .

Clearly, |(a; +b; #3; −b; #4)ω | = |(+a; #2; #1; +b; #7; #1; +b; #4; #9; !; #0)ω |.

68

3.6

J.A. Bergstra, I. Bethke, and A. Ponse

Conclusions

Pushdown threads can be used in program algebra based semantics of sequential or object-oriented programming, for instance as described in [8]. In that approach, a single stack is used to store the arguments of a method call. Furthermore, pushdown threads are important for the theoretical foundation of program algebra itself, for instance admitting easy definitions of programming notations in which recursion can be expressed. This explains our interest in the decidability result proved in Section 3.3. The undecidability of inclusion for pushdown threads is proved using a construction in which one of the counters is “weakly simulated”. This method of Janˇcar is recorded first in 1994 [47], where it is used to prove various undecidability results for Petri nets. In 1999, Janˇcar et al. [48] used the same idea to prove the undecidability of simulation preorder for processes generated by one-counter machines, and this is most comparable to our approach. However, in the case of pushdown threads the inclusion relation itself is a little more complex than in process simulation or language theory because D v P for any thread P . Moreover, threads have restricted branching, and therefore transforming a regular (control) thread into one that simulates one of the counters of a Minsky machine is more complex than in the related approaches referred to above. Also, the particular thread P0 used to prove our undecidability result (Corollary 3.4.10) has to be much more structured than the related nets/processes in [47, 48].

Chapter 4

Virtual Machine-based Projection Semantics (Draft) Jan A. Bergstra and Inge Bethke

Based on an extremely simple program notation more advanced program features can be developed in linear projective program syntax such as conditional statements, while loops, recursion, use of an evaluation stack, object classes, method calls etc. Taking care of a cumulative and bottom up introduction of such complex features while providing appropriate projections into the lower levels of language development keeps all definitions rigorous and ensures a clear meaning of higher program constructs.

4.1

Introduction

The study of programs as text has a profound history in the setting of formal languages and grammars. The path from program texts to program execution has been subject to many investigations and to prove correctness of implementations sophisticated tools have been used in the settings of operational, denotational and axiomatic frameworks. Of current interest are structural operational semantics [2], natural and reduction semantics [50], [37], abstract state machines [43], Scott-Strachey and monadic semantics [59],[58], predicate transformer semantics [33], Hoare [45] and dynamic logic [44], and many more. These different semantic frameworks have been used during the design of programming languages such as Facile, Ada, ML, Prolog, Occam, and Pascal. More recently, operational semantics have been used to provide specifications of Java and the JVM [69], and of the JCVM [67].

4.2

Virtual machines

Virtual machines (VMs) are a popular target for language implementers. They reside as application programs on top of an operating system and export abstract machines that can run programs written in a particular intermediate language (IL). As an intermediate 69

70

J.A. Bergstra, I. Bethke, and A. Ponse

step, programs written in a higher-order language (HL) are first translated into a more suitable form before object or machine code is generated for a particular target machine. Masking differences in the hardware and software layers below the virtual machine, any program written in the high-level language and compiled for these virtual machines will run on them.

WVUT PQRS PQRS WVUT ··· HL1 N HLn NNN p p p p NNN pp NNN ppp p NNN p  pp NN& xppp XYZ[ _^]\ IL 

VM 

Figure 4.1: Outline of a virtual machine design A long-running question in the design of virtual machines has been whether register or stack architectures can be implemented more efficiently with an interpreter. Many designers favour stack architectures since the location of the operands is implicit in the stack location, a prominent example being the stack-based Java virtual machine. In contrast, the operands of register machine instructions must be specified explicitly. In the sequel we shall only consider stack-based VMs. For a translation from stack-based to register-based code and efficiency comparisons see e.g. [41, 68].

4.3

Projection semantics

This article is an approach to program syntax development. However, in contrast to other approaches, projection semantics (PS) aims for practicality and is simple enough to be taught at undergraduate level. Here the emphasis is on instruction stream syntax that views a program as a sequence of instructions and admits a definition of its semantics by means of a projection into a base language. The operational semantics of base programs is considered to be a straightforward matter. For each new notation a projection is outlined, where a projection is a transformation of the program notation to a simpler one that has been defined and equipped with a suitable (projective) semantics before. By chaining appropriate projections each program notation can be translated to a base program. The projection thus obtained

EWSCS’06 - Program Algebra and Thread Algebra

71

represents the semantics of the original program.1 The paper introduces a number of new techniques from the perspective of program algebra (PGA) [14], [15]: returning jump instructions with parameters improving the expressive power of recursion primitives from [6]. The use of an integrated evaluation and instruction counter stack with object heap improves the model for recursive object oriented programming. The distinction of low level instructions, intermediate language level instructions and high-level instructions admits a flexible structure for the design of projections. This paper is about definitions only. The absence of theoretical and proven results might be considered a weakness of the approach. It is our view that this is not the case because the primary aim is to obtain easily understood program notations with a rigorous semantic backing based on an absolute minimum of formal meta-theory. It is claimed that no understanding of any proven theorems or of any involved mathematics or logic of any kind is needed (or even useful) for obtaining a semantic understanding of significant parts of imperative programming languages if the line of thought of projection semantics is followed.

4.4

The base language

The base language is the PS answer to the conceptual question: ‘what is a program?’. The tricky aspect is that at the same time as the base language is claimed to provide this answer PS predicts that this answer may be in need of refactoring at some future stage. PS runs on the unproven assumption that whatever formal definition of a program is given, there will always be a setting in which this definition needs to be compromised or amended in order to get a comprehensible theory. But at the same time a PS answer to any problem needs full precision definitions. There is no room for an open ended concept of program like ‘a text meant to tell a machine what to do’, or—in the absence of precise definitions of ‘unit’ and of ‘deployment’—‘a definition of a software component as a unit of deployment’. Likewise, a machine language may not be defined as a program notation meant for machine execution—in the absence of a clear model of the concept of machine execution—and a scripting language is not definable as a language for writing scripts—unless, again the concept of a script is rigorously defined beforehand. As a start for syntax development instruction stream language with absolute jumps (ISLA) may be taken which features among the sequence of the program notations that have been designed on the basis of PGA under the name PGLD [15]. The basis of the definition of ISLA is formed by the notion of a basic action. Basic actions may be used as instructions in ISLA. A basic instruction a represents a command issued by the program during execution to its context. That context may involve classical ingredients such as a printing device but it may also involve the operation of a value contained in a variable which is often taken simply as something inside a program. The execution of a basic action a takes place during program execution. It is done by the 1

Different projection strategies may project the same program to different pieces of base code. Using polarized process semantics [15],[7] in many cases the semantic equivalence of the different base codes can be established. These matters will not be spelled out in any detail here.

72

J.A. Bergstra, I. Bethke, and A. Ponse

machine architecture that executes the program and it will have two effects: a possible state change in the execution context and a Boolean value which is produced as a result of processing the action—viewed as a request to its environment—and which is subsequently returned to the program under execution where it may be used to decide which is the next instruction to be carried out. For each basic action a the following are ISLA instructions: a, +a and −a. These three versions of the basic actions are called: void basic action (a), positive test instruction (+a), and negative test instruction (−a). A void basic action represents the command to execute the basic action while ignoring the Boolean return value that it produces. Upon completion of the execution of the basic action by the execution environment of the program processing will proceed with the next instruction. The test actions represent the two ways that the Boolean reply can be used to influence the subsequent execution of a program. The only other instructions in ISLA are absolute jumps ##k with k a natural number. An ISLA program is a finite stream of ISLA instructions. Execution by default starts at the first (left most) instruction. It ends if the last instruction is executed or if a jump is made to a nonexisting instruction. In particular, ##0—which will be abbreviated by !—represents program termination because the instruction count is taken to start with 1. The working of test instructions is as follows: if the i-th instruction is +a its execution begins with the execution of a. After completion of that action a reply value is at hand. If the reply was true the run of the program proceeds with instruction number i + 1 if that exists, otherwise execution terminates. Alternatively, if the reply was false the execution proceeds with instruction number i + 2 again assuming its existence, leading to termination in the other case as well. Thus a negative reply enacts that an instruction is skipped. In the case of a negative test instruction −a execution proceeds with instruction number i + 2 at a positive reply and with instruction number i + 1 at reply false. The execution of a jump instruction ##k makes the execution continue with the k-th instruction if that exists. Otherwise the program execution terminates. If ##k is itself the k-th instruction the execution is caught in a never ending loop. Example 4.4.1 Typical ISLA programs are • a; +b; !; c; ##2 which may be read as first do a and then b and then repeat c; b as long as the last reply for b was negative, and terminate after the first positive reply to an execution for b is observed. • −a; ##6; b; c; !; e; f which may be read as first do a, and if a returns true then do b and c, else do e and f , and then terminate. The definition of ISLA in PS has the following intention. At the initial stage of PS development it is reasonable to view ISLA as a definition of the concept of a program. Moreover, any text P is a program provided there is at hand a projection φ which maps the text to an LP NA instruction sequence φ(P ) which, by definition, represents the meaning of P as a program. The position regarding ISLA is therefore not the untenable

EWSCS’06 - Program Algebra and Thread Algebra

73

assertion that every program is identical to an ISLA instruction sequence but the much more flexible assertion that for some entity P to qualify as a program it must be known how it represents an ISLA instruction sequence. By means of the application of the projection that ISLA program is found and the meaning of the entity P as a program is determined. An alternative to ISLA is the program notation ISLR which admits basic actions, positive and negative test actions and the termination instruction !. The jumps are different, however. Only forward jumps (#k) and backward jumps (\#k) (with k a natural number) are admitted. Thus ISLR is ISL with relative jumps. Termination also occurs if a jump is performed outside the range of instructions.2 Example 4.4.2 A typical ISLR program is +a; #3; b; !; c; \#4 which may be read as first do a and, if the reply to a was negative, continue with b and terminate; otherwise enter an infinite repetition of c. ISLA and ISLR are intertranslatable by the mappings ψ : ISLA → ISLR and φ : ISLR → ISLA which transform jumps depending on the place of their occurrence and leave the other instructions unmodified. The mappings are defined by • ψ(u1 ; . . . ; un ) = ψ1 (u1 ); . . . ; ψn (un ) where ( #k − i if k ≥ i, ψi (##k) = \#i − k otherwise. • φ(u1 ; . . . ; un ) = φ1 (u1 ); . . . ; φn (un ) where φi (#k) = ##k + i and ( ##i − k if k < i, φi (\#k) = ! otherwise. Example 4.4.3 The translations of the programs in Example 4.4.1 and 4.4.2 are ψ(a; +b; !; c; ##2) = = ψ(−a; ##6; b; c; !; e; f ) = = φ(+a; #3; b; !; c; \#4) = =

ψ1 (a); ψ2 (+b); ψ3 (!); ψ4 (c); ψ5 (##2) a; +b; !; ; c; \#3, ψ1 (−a); ψ2 (##6); ψ3 (b); ψ4 (c); ψ5 (!); ψ6 (e); ψ7 (f ) −a; #4; b; c; !; e; f, φ1 (+a); φ2 (#3); φ3 (b); φ4 (!); φ5 (c); φ6 (\#4) +a; ##5; b; !; c; ##2.

A virtue of ISLR is that it supports relocatable programming. Programs may be concatenated without disturbing the meaning of jumps. Placing a program behind another program may be viewed as a relocation which places each instruction at an incremented position. This property can be best exploited if programs are used that always use ! for termination. In some cases it is useful to use only a forward jump to the first missing instruction for termination. For programs of that form concatenation corresponds to sequential composition. 2

The program notation ISLR was originally defined in the setting of PGA in [15] under the name PGLC.

74

J.A. Bergstra, I. Bethke, and A. Ponse

Another alternative to ISLA as a base language would be ISLAR, the instruction stream notation with absolute and relative jumps. Throughout this paper, however, we shall take ISLA as base language, and projections will be (chained) mappings to ISLA programs. We shall write A ⊆ B for a program notation B extending A and use the operator φ for projections. In most cases a projection transforms some program notation back to a simpler one. Sometimes the choice of the right order of such steps is important. A formalism for specifying a strategy for chaining projections will not be included below, however. It is assumed that a reader will be easily able to determine the right order of the various projections. A full precision story may need an annotation of all projection functions with domains and codomains. But as it turns out these matters are often clear from the context and merely needed for formalization rather than for rigorous explanation. Therefore that kind of bookkeeping has been omitted, with the implicit understanding that it can always be introduced if additional clarification is necessary.

4.5

Extensions of ISLA with labels and goto’s, conditional constructs and while loops

4.5.1

Projective syntax for labels and goto’s

Labels have been introduced in the program algebra setting in [15] in the program notation PGLDg. Here, however, labels will have the form [s], with s a non-empty alphanumerical string, i.e., a sequence over the alphabet {a, . . . , z, A, . . . , Z, 0, . . . , 9}; ##[s] and ##[s][t] are the corresponding single or chained goto instructions. The intended meaning is that a label is executed as a skip. A single goto stands for a jump to the leftmost instruction containing the corresponding label if that exists and a termination instruction otherwise, and a chained goto of the form ##[s][t] stands for a jump to the leftmost instruction containing the label [s] followed by a jump to the leftmost instruction thereafter containing the label [t] assuming hat they both exist and termination otherwise. The label occurrence that serves as the destination of a single or chained goto is called the target occurrence of the goto instruction. In order to provide a projection for A:GL (program notation A with goto’s and labels) the introduction of annotated goto’s is useful. ##[s]m (##[s][t]m) represents the goto instruction ##[s] (##[s][t]) in a program where the target label is at position m. ##[s]0 (##[s][t]0) represents the case that no target label exists. The projection from A:GLa (A with annotated goto’s and labels) to A and from A:GL to A:GLa are obvious. Projection 4.5.1 Let ISLA ⊆ A. 1. φ : A:GLa → A is defined by φ(u1 ; . . . ; un ) = φ1 (u1 ); . . . ; φn (un ) where (a) φi ([s]) = ##i + 1, (b) φi (##[s]m) = φi (##[s][t]m) = ##m. The other instructions remain unmodified.

EWSCS’06 - Program Algebra and Thread Algebra

75

2. φ : A:GL → A:GLa is defined by φ(u1 ; . . . ; un ) = φ(u1 ); . . . ; φ(un ) where φ(##[s]) = ##[s]m and φ(##[s][t]) = ##[s][t]m with m the instruction number of the target label if it exists and 0 otherwise. The other instructions remain unmodified.

4.5.2

Second level instructions

When transforming a program it may be necessary to insert one or more instructions. Unfortunately that renders the counting of jumps useless. Of course jump counters may be updated simultaneously thus compensating for the introduction of additional instructions. Doing so has proved to lead to unreadable descriptions of projection functions, however, and the device of two level instruction counting will be proposed as a more readable alternative. Instructions will be split in first level instructions and second level instructions. The idea is that expanding projections—projections that replace instructions by non-unit instructions—are given in such way that each instruction sequence replacing a single instruction begins with a first level instruction while all subsequent instructions are taken at the second level. Second level instructions are instructions prefixed with a ∼. First level instructions do not have a prefix; they are made second level by prefixing them with a ∼. First and second level absolute jumps ##k and ∼ ##k will represent a jump to the k-th first level instruction if that exist (and termination otherwise) simply ignoring the second level instructions. The extension of a ISLA based program notation A with second level instructions is denoted with A:SL. For any A ⊇ ISLA, the projection φ : A:SL → A removes second level instruction markers and updates the jumps. Projection 4.5.2 Let ISLA ⊆ A. φ : A:SL → A is defined by φ(u1 ; . . . ; un ) = φ(u1 ); . . . ; φ(un ) where ( ! if k > max, φ(##k) = ##k + l otherwise. Here max is the number of first level instructions occurring in u1 ; . . . ; un and l is the number of second level instructions preceding the k-th first level instruction. The other first level instructions remain unmodified. Moreover, φ(∼ ##k) = φ(##k) and φ(∼ u) = u for all other first level instructions u. Example 4.5.3 φ(∼ a; ∼ +b; ∼ ##2; ##1) = a; +b; !; ##4 The only use of A:SL is as a target notation for projections from an ISLA based program notation. There will be a number of examples of this in the sequel. Notation 4.5.4 For notational convenience we introduce the following abbreviations. We write • a##k for a; ∼ ##k, +a##k for +a; ∼ ##k, and −a##k for −a; ∼ ##k, and likewise • ∼ a##k for ∼ a; ∼ ##k,∼ +a##k for ∼ +a; ∼ ##k, and ∼ −a##k for ∼ −a; ∼ ##k.

76

J.A. Bergstra, I. Bethke, and A. Ponse

4.5.3

Projective syntax for the conditional construct

Conditional constructs can be added to ISLA by using four new forms of instruction: for each basic action a, +a{ and −a{ are conditional header instructions, further }{ is the separator instruction and } is the end of construct instruction. The idea is that in +a{; X; }{; Y ; } after performing a, at a positive reply X is performed and at a negative reply Y is performed. The program notation combining an ISLA based program notation A and these conditional instructions is denoted with A:C. A projection for the conditional construct instructions is found using second level instructions. Due to the use of second level instructions the projection can be given by replacing instructions without the need to update jump counters elsewhere in the program. The projection to second level instructions will be such that e.g. φ(b; +a{; c; −d; ##0; }{; +e; ##4; }; f ) = b; −a##7; c; −d; ##0; ##10; +e; ##4; ##10; f. The general pattern of this projection is as follows: the conditional header instructions are mapped onto a sequence of two instructions, one performing the test and the second instruction containing the second level jump to the position following the projected separator instruction. The separator instruction is mapped to a jump to the position following the projected end of construct instruction, and the end of construct instruction is mapped to a skip, i.e., to a jump to the position thereafter. For this projection to work out some parsing of the program is needed in order to find out which separator instruction matches with which conditional construct header instruction and which end of construct instruction. The annotated brace instructions needed for the projection of the conditional construct instructions are as follows: +a{k, −a{k, }{k, k} with k a natural number. This number indicates the position of instructions that contain the corresponding opening, separating or closing braces, or 0 if such an instruction cannot be found in the program. A:Ca is the extension of a program notation A with annotated versions of the special conditional statements. An annotated version of the program given above is b; +a{6; c; −d; ##0; 2}{9; +e; ##4; 6}; f. The projection from A:Ca to A:SL is now again obvious. Projection 4.5.5 Let ISLA ⊆ A. φ : A:Ca → A:SL is defined by φ(u1 ; . . . ; un ) = φ1 (u1 ); . . . ; φn (un ) where φi (+a{k) φi (−a{k) φi (}{k) φi (k})

= = = =

−a##k + 1 +a##k + 1 ##k + 1 ##i + 1

= = = =

−a##0 +a##0 ##0 ##0

for k > 0 and, φi (+a{0) φi (−a{0) φi (}{0) φi (0})

The other instructions remain unmodified.

EWSCS’06 - Program Algebra and Thread Algebra

77

The projection of A:C to A:Ca involves a global inspection of the entire A:C program. There is room for confusion, for instance, in the case P = a; }; +b{; c; }{; d; }{; e; {. In spite of the fact that the program P is not a plausible outcome of programming in ISLA:C, an annotated version of can be established as a; 0}; +b{5; c; }{0; d; }{0; e; {0, which contains all semantic information needed for a projection. Projection 4.5.6 Let ISLA ⊆ A. φ : A:C → A:Ca is defined by φ(u1 ; . . . ; un ) = φ(u1 ); . . . ; φ(un ) where 1. φ(+a{) = +a{k and φ(−a{) = −a{k with k the instruction number of the corresponding separator instruction if it exists and 0 otherwise, 2. φ(}{) =}{k with k and the instruction number of the corresponding end of construct instruction if it exists and 0 otherwise, 3. φ(}) = k} with k the instruction number of the corresponding separator instruction if it exist and 0 otherwise. The other instructions remain unmodified.

4.5.4

Projective syntax for while loops

The following three instructions support the incorporation of while loops in programs projectible to ISLA: +a{∗, −a{∗ (while loop header instructions) and ∗} (while loop end instruction). This gives the program notation A:W. As in the case of conditional constructs, while constructs will be projected using annotated versions. A:Wa allows the annotated while loop instructions +a{∗k, −a{∗k and k∗}. The projection of annotated while loop instructions is again obvious. Projection 4.5.7 Let ISLA ⊆ A. φ : A:Wa → A:SL is defined by φ(u1 ; . . . ; un ) = φ1 (u1 ); . . . ; φn (un ) where φi (+a{∗k) = −a##k + 1 φi (−a{∗k) = +a##k + 1 φi (k∗}) = ##k for k > 0 and, φi (+a{∗0) = −a##0 φi (−a{∗0) = +a##0 φi (0∗}) = ##0 The other instructions remain unmodified.

78

J.A. Bergstra, I. Bethke, and A. Ponse

The introduction of annotated while braces by means of an annotating projection follows the same lines as in the case of conditional statement instructions. Projection 4.5.8 Let ISLA ⊆ A. φ : A:W → A:Wa is defined by φ(u1 ; . . . ; un ) = φ(u1 ); . . . ; φ(un ) where 1. φ(+a{∗) = +a{∗k and φ(−a{∗) = −a{∗k with k the instruction number of the corresponding separator instruction if it exists and 0 otherwise, and 2. φ(∗}) = k∗} with k the instruction number of the corresponding separator instruction if it exist and 0 otherwise. The other instructions remain unmodified. Notice that a projection of the extension A:C of an ISLA based program notation A obtained by a simultaneous introduction of conditional and while constructs can be given by a concatenation of the projections defined above by starting either with conditional or while instructions. Thus A:CW = (A:C):W → (A:C):Wa → (A:C):SL → A:C → · · · → A. We end this section with an example containing conditional and while construct instructions. Example 4.5.9 Let P = +a{; b; +c{∗; d; ∗}; }{; e; f ; }. P can be read as do a and continue with e and f and terminate if a returns false; otherwise do b and repeat c; d as long as the last reply for c was positive, and terminate after the first negative reply to an execution for c. We may view P as a program in (ISLA:C):W. The annotated version of P given by the above projections is +a{; b; +c{∗5; d; 3∗}; }{; e; f ; } and projection to (ISLA:C):SL yields +a{; b; −c##6; d; ##3; }{; e; f ; }. Projecting to ISLA:C we obtain +a{; b; −c; ##7; d; ##3; }{; e; f ; }. We now proceed with the projection of the conditional constructs. The annotated version is +a{7; b; −c; ##7; d; ##3; }{10; e; f ; 7} and projection to ISLA:SL yields −a##8; b; −c; ##7; d; ##3; ##11; e; f ; ##11. Finally, projecting to ISLA we obtain −a; ##9; b; −c; ##8; d; ##4; !; e; f ; !. The projections discussed in this section are summarized in Figure 4.2.

EWSCS’06 - Program Algebra and Thread Algebra

A:C

A:W



A:CaJ

79

A:GL





A:Wa

A:GLa

JJ JJ JJ JJ $ 

A:SLF

FF FF FF FF "

A

















Figure 4.2: Basic extensions of an ISLA based program notation A

4.6

Extensions using state machines

In some cases a program needs to make use of a data structure for its computation. This data structure is active during the computation only and helps the control mechanisms of the program. Such a data structure is called a state machine [21]. A formalization of this matter involves a refinement of the syntax for basic actions. Any state machine action used in a program is now supposed to consist of two parts, respectively the focus and the method, glued together with a period that is not permitted to occur in either one of them. For the state machine only the method matters because the focus is used to identify to which state machine (or other system component) an atomic instruction is directed. We denote the extension of a program in notation A by the instructions of state machine SM by A/SM. A state machine action a used in the extension will be written as sm.a.

4.6.1

Natural number value

NNV is a very simple data structure holding a single natural number—initially 0—which can be set and returned. The only basic actions performed by NNV are • set(n). The set action always succeeds setting the value to n. • eq(n)3 . This action fails if the value is unequal to n; otherwise it returns true. With a natural number value a case statement nnv .case(m){; X0 ; }{; X1 ; }{; · · · ; }{; Xm−1 ; }{; Xm ; } becomes available providing a multi-way branching based on a natural number. The idea here is that if the value i of NNV lies in the range 0 to m, then Xi is performed 3

Since basic actions are supposed to return Boolean values, we have replaced the return operation by an indicator function.

80

J.A. Bergstra, I. Bethke, and A. Ponse

and execution continues after the last closing brace, otherwise this construct is skipped. The program notation combining an ISLA based program notation A with NNV based case statements is denoted with A:CSnnv . Its projection can be given by the use of annotations and second level instructions. The three annotated brace instructions needed for the projection are as follows: nnv .case(m){k, i}{k, m} with natural numbers k, i, m where k indicates the instruction number of the corresponding separator or end of construct instruction, i the value to be tested and m the upper bound. k is set to 0 in the case of ill formed program syntax. A:CSnnv a denotes the extension with annotated case statements. Projection 4.6.1 Let ISLA ⊆ A. φ : A:CSnnv a → A/NNV:C:SL is defined by φ(u1 ; . . . ; un ) = φ1 (u1 ); . . . ; φn (un ) where φi (nnv .case(m){k) = −nnv .eq(0){; ∼ ##k + 1; ∼}{ φi (i}{k) = −nnv .eq(i){; ∼ ##k + 1; ∼}{ φi (m}) = }; ∼}; · · · ; ∼} {z } | m×

for k > 0 and, φi (nnv .case(m){0) = ##0 φi (i}{0) = ##0 The other instructions remain unmodified. The projection of A:CSnnv to A:CSnnv a involves again a complete inspection of the A:CS program. Projection 4.6.2 Let ISLA ⊆ A. φ : A:CSnnv → A:CSnnv a is defined by φ(u1 ; . . . ; un ) = φ(u1 ); . . . ; φ(un ) where 1. φ(nnv .case(m){) = nnv .case(m){k with k the instruction number of the corresponding separator or end of construct instruction if it exists and 0 otherwise, 2. φ(}{) = i}{k with i the test value and k and the instruction number of the corresponding separator or end of construct instruction if it exists and 0 otherwise, 3. φ(}) = m} with m the upper bound. The other instructions remain unmodified.

EWSCS’06 - Program Algebra and Thread Algebra

4.6.2

81

Instruction counter stacks

ICS has an infinite set of states, one for each stack of natural numbers. The basic actions performed by ICS are: • pop. The pop action fails at an empty stack producing a negative reply; otherwise it succeeds removing the topmost element of the stack and producing a positive reply. • push(n). The push action always succeeds putting n on top of the stack and producing a positive reply • topeq(n). This action fails at an empty stack or if the top of the stack is unequal to n; otherwise it returns true. A typical example of a program written in ISLA/ICS:C is P = +a{; ics.push(0); }{; −ics.pop; !; }; ##1. When running, P can remember the number of positive replies it has obtained on its actions a, enabling it to produce termination after exactly as many subsequent negative replies. This implies that P has infinite state behavior. As a consequence, it is impossible to project ISLA/ICS:C to any notation making use of finite state state machines only. Similar to the case statement for natural number values one can now introduce case statements ics.case(m){; X0 ; }{; · · · ; }{; Xm ; } based on the topmost element of the stack. Annotation and projection are defined in the same fashion. The extension of an ISLA based program notation A with case statements based on an instruction counter stack is denoted A:CSics ; its annotated version is denoted A:CSics a. Projection 4.6.3 Let ISLA ⊆ A. φ : A:CSics a → A/ICS:C:SL is defined by φ(u1 ; . . . ; un ) = φ1 (u1 ); . . . ; φn (un ) where φi (ics.case(m){k) = −ics.topeq(0){; ∼ ##k + 1; ∼}{ φi (i}{k) = −ics.topeq(i){; ∼ ##k + 1; ∼}{ φi (m}) = }; ∼}; · · · ; ∼} | {z } m×

for k > 0 and, φi (ics.case(m){0) = ##0 φi (i}{0) = ##0 The other instructions remain unmodified. Projection 4.6.4 Let ISLA ⊆ A. φ : A:CSics → A:CSics a is defined by φ(u1 ; . . . ; un ) = φ(u1 ); . . . ; φ(un ) where

82

J.A. Bergstra, I. Bethke, and A. Ponse 1. φ(ics.case(m){) = ics.case(m){k with k the instruction number of the corresponding separator or end of construct instruction if it exists and 0 otherwise, 2. φ(}{) = i}{k with i the test value and k and the instruction number of the corresponding separator or end of construct instruction if it exists and 0 otherwise, 3. φ(}) = m} with m the upper bound.

The other instructions remain unmodified. The key application of ICS is to obtain projections for program constructs involving recursion. In preparation of that application for ICS dynamic jumps ##ics.pop—where the counter is based on the top of the current stack—will be needed. The program notation that combines an extension A of ISLA with dynamic jumps is denoted with A:DJ. The projection to A/ICS:C:SL takes into account that in a program u1 ; . . . ; ##ics.pop; . . . ; un at most n different values of the jump counter are needed. Projection 4.6.5 Let ISLA ⊆ A. φ : A:DJ → A/ICS:CSics :SL is defined by φ(u1 ; . . . ; un ) = φ(u1 ); . . . ; φ(un ) where φ(##ics.pop) = ics.case(n){; ∼ ics.pop##0; ∼}{; · · · ; ∼}{; ∼ ics.pop##n; ∼} All other instructions remain unmodified. Having dynamic jumps available, we can now introduce recursion. The instruction pair R##k, ##R represents recursion in the following way: if a returning jump instruction R##k occurring at position i is executed a jump to instruction k is made, moreover the instruction counter i + 1 is placed on an instruction counter stack known under focus ics. Whenever a return instruction ##R is executed a jump is performed to the top of the stack which is simultaneously popped . A:R is the program notation A augmented with returning jump instructions of the form R##k and return instructions ##R. A projection to A/ICS:DJ:SL can be given in the following way. Projection 4.6.6 Let ISLA ⊆ A. φ : A:R → A/ICS:DJ:SL is defined by φ(u1 ; . . . ; un ) = φ1 (u1 ); . . . ; φn (un ) where φi (R##k) = ics.push(i + 1)##k φi (##R) = ##ics.pop All other instructions remain unmodified. The projections discussed in this subsection are summarized in Figure 4.3. Notice that (A/ICS)/ICS=A/ICS. The final projection to A/ICS can be obtained by chaining the appropriate projections discussed in the preceding section.

EWSCS’06 - Program Algebra and Thread Algebra

83

A:R 

A/ICS:DJ:SL 

A/ICS:DJ 

A/ICS:CSics :SL 

A/ICS:CSics 

A/ICS:C:SL 

.. . 

A/ICS Figure 4.3: Extensions using instruction counter stacks

4.6.3

Evaluation stacks

Besides the use of a stack to store instruction counters needed for the projection of recursive jump and return instructions, another very prominent use of a stack is that of an evaluation stack which contains intermediate results of the computation of some value in a data type. For simplicity, we restrict ourselves here to a single primitive type consisting of all alphanumerical strings of bounded length and accompanied by a family of operators. Each operator f has an arity, which is a natural number that determines its number of arguments. In addition to the elements of primitive type, there are references uniquely denoting class instances. Primitive elements and references will be called values. The name of the state machine as outlined below is EVS. It maintains a stack, a heap4 and a value pool. The stack stores primitive types, references to objects and instruction counters; instance data and the class of objects are memorized in the heap. The value pool consists of finitely many pairs with the first element being the spot relative to the value pool and the second element its content. There are two distinguished spots: this and that. this contains the instances for object oriented instance method calls and that the results of subcomputations. The basic actions 4

Here we treat the heap as a rather abstract notion. For a concrete implementation the reader is referred to the appendix.

84

J.A. Bergstra, I. Bethke, and A. Ponse

performed by EVS are: • compute(f, k) (with f the name of an operator with arity k). It is assumed that the arguments of the function are stored on the stack. If there are too few values on the stack the action fails and the state is unchanged. If there are sufficiently many values the reply is positive and the value f (v1 , ..., vk ) is placed on the stack after removing all k arguments from it. • create. This basic action allocates a new reference and puts it on top of the stack. If a new reference is not available this action fails. • store(s). The precondition of this basic action is that the stack is non-empty; otherwise it fails. If the stack is non-empty its top is removed and made to be the content of spot s. The previous content of s, if any, is lost in the process. • load(s). This basic action takes the content of the spot s, which must have been introduced at an earlier stage, and pushes that on the stack. A spot is defined if it has been assigned a value by means of a store instruction at some stage. If the spot is still undefined the action fails. This is a copy instruction because the value is still the content of s as well after successful execution. • getfield (F ). The precondition of this action is that the top of the stack is a reference denoting an instance with field F ; otherwise it fails. If the precondition is met, the value of field F of the instance denoted by the top is pushed from the heap on the stack after having removed the top. • setfield (F ). The precondition of this action is that the top of the stack is a reference followed by a value; otherwise it fails. If the precondition is met, the reference and the value are popped from the stack and field F of the instance denoted by the popped reference is set to the popped value. • instanceof (C). The precondition of this action is that the top of the stack is a reference; otherwise it fails. If the precondition is met, this action returns true if the class of the instance denoted by the reference is C, otherwise it returns false. Afterwards the reference is taken from the stack. • setclass(C). Again, the precondition is that the top of the stack is a reference; otherwise the action fails. If the precondition is met, the class of the instance denoted by the reference is set to C. Afterwards the reference is popped. • pop. This action fails if the stack is empty. Otherwise its succeeds and removes the top from the stack while giving a positive reply. The combination of an evaluation stack and recursion is easily projected. The disadvantage of these projections lies in the fact that this form of projection introduces an instruction counter stack independently of the evaluation stack. This independence of the two stacks immediately gives rise to a Turing complete program notation in spite of the finiteness of the value pool. For that reason a different design will be developed that makes use of a single stack only at the cost of expressive power but—as will be demonstrated in the next section—sufficiently expressive to provide a projection for method calls in an object-oriented setting.

EWSCS’06 - Program Algebra and Thread Algebra

4.6.4

85

Stack fusion

ICS and EVS can be integrated in such a way that both features can be used from essentially a single stack. There are several reasons to pursue this line of design: • Single stack execution environments have less computing power and admit a more thorough automatic analysis for that reason than double stack environments. • It is important to understand which features in program notations require a more expressive execution architecture. For instance security features may involve a stackwalk, and multi-threading may involve multiple stacks, both requiring a Turing complete model of computation. • There is a variety of possibilities for integrated stack design and stack use which provides insight in different forms of method calls and parameter mechanisms. These designs capture fundamental intuitions of program notation design and of program execution. The fused stack S stores instruction counters and values. Its basic actions are: • compute(f, k) (with f the name of an operator with arity k). It is assumed that k values are stored consecutively on the stack. If there are too few values on top of the stack the action fails and the state is unchanged. If there are sufficiently many values the reply is positive and the value f (v1 , ..., vk ) is placed on the stack after removing all k arguments from it. • create. This basic action allocates a new reference and puts it on top of the stack. If a new reference is not available this action fails. • store(s). The precondition of this basic action is that the stack is non-empty; otherwise it fails. If the stack is non-empty its top is removed and made to be the content of spot s. The previous content of s, if any, is lost in the process. • load(s). This basic action takes the content of the spot s, which must have been introduced at an earlier stage, and pushes that on the stack. A spot is defined if it has been assigned a value by means of a store instruction at some stage. If the spot is still undefined the action fails. This is a copy instruction because the value is still the content of s as well after successful execution. • getfield (F ). The precondition of this action is that the top of the stack is a reference denoting an instance with field F ; otherwise it fails. If the precondition is met, the value of field F of the instance denoted by the top is pushed from the heap on the stack after having removed the top. • setfield (F ). The precondition of this action is that the top of the stack is a reference followed by a value; otherwise it fails. If the precondition is met, the reference and the value are popped from the stack and field F of the instance denoted by the popped reference is set to the popped value.

86

J.A. Bergstra, I. Bethke, and A. Ponse • instanceof (C). The precondition of this action is that the top of the stack is a reference; otherwise it fails. If the precondition is met, this action returns true if the class of the instance denoted by the reference is C, otherwise it returns false. Afterwards the reference is taken from the stack. • setclass(C). Again, the precondition is that the top of the stack is a reference; otherwise the action fails. If the precondition is met, the class of the instance denoted by the reference is set to C. Afterwards the reference is popped. • pop. This instruction fails if the stack is empty. Otherwise it succeeds and removes the top from the stack while giving a positive reply. • push(n). The push action always succeeds changing the stack accordingly and producing a positive reply. • topeq(n). This action returns true if the top of the stack equals n; otherwise it returns false. • down(n). This action places the top just below the n-th position from above after removing the top. Thus after down(n) the top has migrated to the n+1-th position from above. If that is impossible the action fails, a negative reply is given and no change is made to the stack.

The projection of recursion is now a simple modification of the projections via case statements and dynamic jumps given in Projection 4.6.3, 4.6.4, 4.6.5 and 4.6.6. Projection 4.6.7 Let ISLA ⊆ A. φ : A:CSs a → A/S:C:SL is defined by φ(u1 ; . . . ; un ) = φ1 (u1 ); . . . ; φn (un ) where φi (s.case(m){k) = −s.topeq(0){; ∼ ##k + 1; ∼}{ φi (i}{k) = −s.topeq(i){; ∼ ##k + 1; ∼}{ φi (m}) = }; ∼}; · · · ; ∼} | {z } m×

for k > 0 and, φi (s.case(m){0) = ##0 φi (i}{0) = ##0 The other instructions remain unmodified. Projection 4.6.8 Let ISLA ⊆ A. φ : A:CSs → A:CSs a is defined by φ(u1 ; . . . ; un ) = φ(u1 ); . . . ; φ(un ) where 1. φ(s.case(m){) = s.case(m){k with k the instruction number of the corresponding separator or end of construct instruction if it exists and 0 otherwise, 2. φ(}{) = i}{k with i the test value and k and the instruction number of the corresponding separator or end of construct instruction if it exists and 0 otherwise,

EWSCS’06 - Program Algebra and Thread Algebra

87

3. φ(}) = m} with m the upper bound. The other instructions remain unmodified. Projection 4.6.9 Let ISLA ⊆ A. φ : A:DJ → A/S:CSs :SL is defined by φ(u1 ; . . . ; un ) = φ(u1 ); . . . ; φ(un ) where φ(##s.pop) = s.case(n){; ∼ s.pop##0; ∼}{; · · · ; ∼}{; ∼ s.pop##n; ∼} All other instructions remain unmodified. Projection 4.6.10 Let ISLA ⊆ A. φ : A:R → A/S:DJ:SL is defined by φ(u1 ; . . . ; un ) = φ1 (u1 ); . . . ; φn (un ) where φi (R##k) = s.push(i + 1)##k φi (##R) = ##s.pop All other instructions remain unmodified. In addition to the dynamic jump, we shall also consider a dynamic chained goto instruction ##[instanceof ][M ] where the first goto depends on the class of the instance this. The projection from A:DCG (A with dynamic chained goto’s) to A/S:C:GL:SL can be given in the following way. Projection 4.6.11 Let ISLA ⊆ A. φ : A:DCG → A/S:C:GL:SL is defined by φ(u1 ; . . . ; un ) = φ(u1 ); . . . ; φ(un ) where φ(##[instanceof ][M ]) = s.load(this); ∼ +instanceof (C1){; ∼ s.pop; ∼ ##[C1][M ]; ∼}{; ∼ +instanceof (C2){; ∼ s.pop; ∼ ##[C2][M ]; ∼}{; · · · ∼ +instanceof (Ck − 1){; ∼ s.pop; ∼ ##[Ck − 1][M ]; ∼}{; ∼ s.pop; ##[Ck][M ]; ∼}; · · · ; ∼} Here C1, . . . , Ck are the classes introduced by setclass instructions in u1 ; . . . ; un . All other instructions remain unmodified. Recursion using returning jumps and return instructions can be made more expressive by means of parameters. In the sequel we write Rm##k (m, k ∈ N) for returning jump instructions with m parameters. Given a program notation A the extension by parametrized returning jumps and returning return instructions is denoted A:Rp. The part of the computation taking place between a returning jump and its corresponding return instruction may be called a subcomputation. Parameters are values

88

J.A. Bergstra, I. Bethke, and A. Ponse

that serve as inputs to a subcomputation. They are put in place before executing a returning jump, and the program part executed as a subcomputation ‘knows’ where to find these parameters. Various strategies can be imagined for arranging the transfer of parameters during returning jump and return instructions. Here we have chosen for an automatic parameter transfer to and from the stack to local spots. The m arguments are placed in the value pool at spots arg1,..,argm and the return value—if there is one—will be expected at the spot that. In some cases a special parameter may be placed in spot this. this is used for the special parameter that plays the role of the target instance for an object oriented instance method call. Returning jump instructions with instance and m parameters will be denoted IRm##k. In preparation of the transfer of the arguments and the instance parameter to their respective spots the contents of these spots have to be placed on the stack in order to allow recovery of their contents after returning from the returning jump. The contents of that does not have to be reconstructed and a copy will not be put on the stack. Just after the returning jump and just before the return instruction the top of the stack contains the instruction counter followed by the contents of this and the m arguments in decreasing order. The projection of the return instruction will now involve the recovery of the several spot values before the returning jump from the data that were placed on the stack. Projection 4.6.12 Let ISLA ⊆ A. φ : A:Rp → A/S:DJ:SL is defined by φ(u1 ; . . . ; un ) = φ1 (u1 ); . . . ; φn (un ) where φi (Rm##k) = s.load(arg1); ∼ s.down(m); .. . ∼ s.load(argm); ∼ s.down(m); ∼ s.push(i); ∼ s.down(m); ∼ s.store(arg1); · · · ; ∼ s.store(argm); ∼ ##k; ∼ s.store(argm); ∼ s.store(argm − 1); · · · ; ∼ s.store(arg1) φi (IRm##k) = s.load(arg1); ∼ s.down(m + 1); .. . ∼ s.load(argm); ∼ s.down(m + 1); ∼ s.load(this); ∼ s.down(m + 1); ∼ s.push(i); ∼ s.down(m + 1); ∼ s.store(this); ∼ s.store(arg1); · · · ; ∼ s.store(argm); ∼ ##k; ∼ s.store(this); ∼ s.store(argm); · · · ; ∼ s.store(arg1) φi (##R) = ##s.pop All other instructions remain unmodified. It is important to notice that for the projection of a specific program only a finite number of down(i) instructions is used. This implies that the stack manipulations do not reach the expressive power of a full stackwalk, but rather may be simulated (per program) from a stack and a finite memory state machine, or more easily obtained using a bounded value buffer.

EWSCS’06 - Program Algebra and Thread Algebra

89

The projections of recursion instructions are summarized in Figure 4.4. The final projection to A/S can be obtained by chaining the appropriate projections discussed in the preceding sections. A:R L

LL LL LL LL L&

qq qqq q q qx qq

A:Rp

A/S:DJ:SL 

A/S:DJ 

A/S:CSs :SL 

A/S:CSs 

A/S:C:SL 

.. . 

A/S Figure 4.4: Projections of recursion and parametrized recursion with automatic parameter and result transfer using a fused stack

4.7

Intermediate level programs

The design of instructions can be viewed as proceeding in three phases. Until this point only so-called low level instructions have been designed. Low level instructions have either fixed projections or their projection trivially depends on the program context. Program context dependence occurs for instance with a closing brace instruction for which the projection may depend on the index of its corresponding opening brace instruction. The second design phase introduces an intermediate language for highlevel programming constructs. In this section we proceed with the second design phase. A typical ILN program consists of a sequence of labeled class parts. E.g., an ILN program with 4 classes looks as follows: [C1]; CB 1; . . . ; [C4]; CB 4. The subprograms CB 1, . . . , CB 4 are so-called class bodies, which consist of zero or

90

J.A. Bergstra, I. Bethke, and A. Ponse

more labeled method body parts with end markers [M 1]; MB 1; end; . . . ; [M k]; MB k; end. In addition to the usual labels, ILN has the following instruction set. new(C) This instruction pushes a new reference to an instance of class C onto the stack. stack push The instruction E ⇒ represents pushing an entity with name E onto the stack. E may be a single non-empty alphanumerical string or of the form E.F , i.e., a string consisting of two alphanumerical parts glued together with a period. top from stack The instruction ⇒ E takes the top from the stack and places that entity on the place denoted with E. Again, E may be a single non-empty alphanumerical string or of the form E.F . method end marker The method end marker end serves as the end of a method. function call A function call fc(f, n) represents the call of the function f from a given operator family with arity n. The algorithmic content of an operator call is not given by the program that contains it. class method call A class method call instruction has the form mc(C, M, n). Here M is a method, C a class and n is the number of arguments. We tacitly assume that the names occurring in the stack push and pop instructions of M are amongst arg1, . . . , argn. instance method call An instance method call instruction has the form mc(M, n). Here M is a method and n is the number of arguments. We tacitly assume that the names occurring in the stack push and pop instructions of M are amongst this, arg1, . . . , argn. ILN instructions can be projected to ISLA/S:DCG:GL:Rp. This projection carries with it an implicit run-time model. All intermediate language notations deal with a stack machine model. The degrees of freedom concern the precise details of argument transfer before and after recursion and argument protection during a recursive call. Many alternatives can be imagined for that model. Here we use the state machine S for an evaluation stack integrated with an instruction counter stack in fused style and accessible under focus ‘s’.

Projection 4.7.1 φ : ILN → ISLA/S:DCG:GL:Rp is defined by φ(u1 ; . . . ; un ) =

EWSCS’06 - Program Algebra and Thread Algebra

91

φ1 (u1 ); . . . ; φn (un ) where φi (new(C)) φi (E ⇒) φi (⇒ E) φi (E.F ⇒) φi (⇒ E.F ) φi (end) φi (fc(f, m)) φi (mc(C, M, m)) φi (mc(M, m))

= = = = = = = = =

s.create; s.setclass(C) s.load(E) s.store(E) s.load(E); s.getfield (F ) s.load(E); s.setfield (F ) ##R s.compute(f, m) Rm##2k + l + i + 2; ##2k + l + i + 3; ##[C][M ] IRm##2k + l + i + 2; ##2k + l + i + 3; ##[instanceof ][M ]

Here k is the number of method calls and l the number of creation and field pushing and popping instructions occurring in u1 ; . . . ; ui−1 . In a setting with relative jumps one can replace the complex absolute jumps by relative jumps of length 2. Labels remain unmodified. A simple ILN program P and its projection are given below. P consists of a single class part labeled [C] with method parts [CC], [get] and [M ]. [CC] is a class constructor initializing the F field of any object of this class with default value 0, [get] is an instance method returning the value of the F field of the calling object, and [M ] is a static method creating two objects of this class—X and Y —by calling the constructor for X but setting the F field of Y by calling [get] with parameter X. [C]; [CC]; fc(0, 0); ⇒ this.F ; end; [get]; arg1.F ⇒; ⇒ that; end; [M ]; new C; ⇒ X; X ⇒; mc(CC, 0); new C; ⇒ Y ; X ⇒; mc(C, get, 1); that ⇒; ⇒ Y.F ; end

Projection into ISLA/S:DCG:GL:RP yields [C]; [CC]; s.compute(0, 0); s.load(this); s.setfield (F ); ##R; [get]; s.load(arg1); s.getfield (F ); s.store(that); ##R; [M ]; s.create; s.setclass(C); s.store(X); s.load(X); IR0##19; ##20; ##[instanceof ][CC]; s.create; s.setclass(C); s.store(Y ); s.load(X); R1##26; ##27; ##[C][get]; s.load(that); s.load(Y ); s.setfield (F ); ##R

4.8

A high-level program notation HLN

In the third phase we deviate from the projective syntax paradigm and introduce instructions at a level close to the high-level program notations used by human programmers and computer software users. In this section a toy high-level program notation HLN is developed. Its meaning is given by a compiler projection into ILN. Like ILN programs HLN programs are

92

J.A. Bergstra, I. Bethke, and A. Ponse

supposed to have a certain fixed structure. In this structure a program is a series of class parts as depicted in Figure 4.5. Class parts consist of a number of segments. class C{ instancef ields{ F 1 = exp1; .. . F n = expn; } M 1(E1, . . . , En){X} .. . } Figure 4.5: The structure of class parts in HLN These segments can be of two kinds: instance field parts and method declarations. The compiler projection will transform the list of instance field parts in a class to a class constructor for that class with label [CC]. Method declarations—which have the form M (E1, . . . , En){X} and are named different from CC—will be projected to class method parts. HLN has the following expressions. exp := E | E.F | f (exp1, . . . , expn) | I.M (exp1, . . . , expn) | C.M (exp1, . . . , expn) with f an n-ary operator from a given operator family, I a class instance calling an n-ary instance method and C.M (exp1, . . . , expn) an n-ary class method call. Assignments and statements are of the form E E.F E E.F

= exp = exp = new C = new C return exp I.M (exp1, . . . , expn) C.M (exp1, . . . , expn) with exp, exp1, . . . , expn expressions. The projections to ILN can be given by φ(E = exp) φ(E.F = exp) φ(E = new C) φ(E.F = new C) φ(return exp) φ(I.M (exp1, . . . , expn)) φ(C.M (exp1, . . . , expn))

= = = = = = =

φ(exp); ⇒ E φ(exp); ⇒ E.F new C; mc(CC, 0); ⇒ E new C; mc(CC, 0); ⇒ E.F φ(exp); ⇒ that φ(exp1); . . . ; φ(expn); I ⇒; mc(M, n) φ(exp1); . . . ; φ(expn); mc(C, M, n)

where the expressions exp, exp1, . . . , expn are projected to values residing on top of the

EWSCS’06 - Program Algebra and Thread Algebra

93

stack by φ(E) φ(E.F ) φ(f (exp1, . . . , expn)) φ(I.M (exp1, . . . , expn)) φ(C.M (exp1, . . . , expn))

= = = = =

E⇒ E.F ⇒ φ(exp1); . . . ; φ(expn); fc(f, n) φ(exp1); . . . ; φ(expn); I ⇒; mc(M, n); that ⇒ φ(exp1); . . . ; φ(expn); mc(C, M, n); that ⇒

The meaning of class parts can finally be given by φ(class C{X}) = [C]; φ(X) φ(instancef ields{F 1 = exp1; . . . ; F n = expn}) = [CC]; φ(this.F 1 = exp1); ... φ(this.F n = expn); end φ(M (E1, . . . , En){X}) = [M ]; E1 ⇒; . . . ; En ⇒; φ(E1 = arg1); ... φ(En = argn); φ(X); ⇒ En; . . . ; ⇒ E1; end Note that the projection of HLN to ILN is dynamically correct. That is, the elements on the stack meet the requirements of the current instructions: whenever an instruction counter is expected, the top of the stack will contain one, and whenever n values are expected, the n uppermost elements will be values. We end this section with a typical HLN program and its projection into ILN. Let P be the the following program class A{ instancef ields{F = 0; } get(){return this.F ; } set(E){this.F = E; } inc(E){this.set(this.get() + E); } } class B{ get(E){return E.F ; } test(){ X = new A; Y = new A; X.set(1); X.inc(1); Y.F = B.get(X); }

94

J.A. Bergstra, I. Bethke, and A. Ponse

Assuming that the constants 0, 1 and addition + are predefined operators, P can be projected into ILN by [A]; [CC]; fc(0, 0); ⇒ this.F ; end; [get]; this.F ⇒; ⇒ that; end; [set]; E ⇒; arg1 ⇒; ⇒ E; E ⇒; ⇒ this.F ; ⇒ E; end; [inc]; E ⇒; arg1 ⇒; ⇒ E; this ⇒; mc(get, 0); that ⇒; E ⇒; fc(+, 2); this ⇒; mc(set, 1); end; [B]; [get]; E ⇒; arg1 ⇒; ⇒ E; E.F ⇒; ⇒ that; ⇒ E; end; [test]; new A; mc(CC, 0); ⇒ X; new A; mc(CC, 0); ⇒ Y ; fc(1, 0); X ⇒; mc(set, 1); fc(1, 0); X ⇒; mc(inc, 1); X ⇒; mc(B, get, 1); that ⇒; ⇒ Y.F ; end

4.9

Concluding remarks

At this point a simple program notation for object oriented programming has been provided with a projective syntax. Many more features exist and one might say that the project of projective syntax design has only been touched upon. Still the claim is made that the above considerations provide a basis for the design of projective syntax for much more involved program notations. For the moment the primary application area of this work is considered to be the teaching of programming concepts. With that objective in mind it is proposed as a reasonable claim that the given reconstruction of a subset of object oriented programming provides a systematic insight in the meaning of the constructions covered. The fact that these techniques are amenable for teaching, and more importantly that doing so provides a basis for the understanding of more involved aspects of programming cannot be taken for granted, however. Practical experience will have to clarify this claim.

4.10

Appendix: A Concrete heap

In this appendix we provide a more detailed and concrete, and therefore less abstract version of the heap. With BinSeq we denote the finite sequences of 0’s and 1’s. Three subsets of BinSeq are at the basis of the concrete heap: • Loc is a finite or infinite set of so-called locations, (in practical cases it must be finite, in some formal modeling cases an infinite set of locations may be more amenable), • Ic, the finite or infinite collection of instruction counters. Here the most plausible choice is that Ic contains binary forms of all natural numbers,

EWSCS’06 - Program Algebra and Thread Algebra

95

• Pval, the collection of primitive values, this is a finite set in most practical cases, just like Loc. The concrete heap is used in combination with the stack. We will describe the setting in the fused case. On the stack one finds • references, taking the form rw with w in Loc, e.g. r00110; the set of rw’s with w in Loc is denoted Ref below, • instruction counters taking the form iw with w in Ic, and • primitive values taking the form w with w in Pval. In any state of computation the spots have either no meaning (the initial state for all spots) or, if a spot has a meaning, that is either a reference rw or a primitive value. Several collections of names are used: ClassNames, FieldNames and SpotNames. Any convention will do and these sets may overlap without generating confusion. A state of the concrete heap consists of the following sets: • Obj, a subset of Loc. Obj contains those locations that contain an object. Initially a computation will start with Obj empty. • Classification, a subset of Obj × ClassNames, which is in fact a partial function from Obj to Classes. • Fields, a subset of Obj × FieldNames × (Ref ∪ Pval). Again we assume that Fields is a partial function from Obj × FieldNames to Ref ∪ Pval. • Spots, a subset of SpotNames × (Ref ∪ Pval). It is important to make a distinction between vital objects and garbage objects. Vital objects are those that can be reached from spots containing a reference and references positioned anywhere on the stack via zero or more selections of fields containing a reference. Non-vital objects are called garbage objects. After each method execution garbage objects are removed and so are their outgoing fields and classifications. Now the operations of the heap can be interpreted as follows: • create. If Obj = Loc then return false; otherwise find w in Loc - Obj, place rw on the top of the stack, put w in Obj, and then return true. • getfield (f ). If the top of the stack is not a reference or if the top of the stack contains rw, say, but Fields(rw, f) is not defined return false; otherwise remove the top of the stack, place Fields(rw,f) on the stack and return true. • setfield (f ). If the top of the stack is a reference rw, say, and if it lies on top of a value v in Ref ∪ Pval, pop the two uppermost items from the stack, define Fields(rw, f) = v and return true; otherwise return false. • instanceof (C). If the top of the stack is not a reference return false; if the top of the stack contains rw, say, pop the stack, return true if Classification (rw)=C, and return false otherwise.

96

J.A. Bergstra, I. Bethke, and A. Ponse • setclass(C). If the top of the stack is not a reference return false; if the top of the stack contains rw, say, define Classification(rw)=C, pop the stack and return true.

In this concrete heap a reference takes the form rw and spots may also be considered references. In other models of a heap references may work differently so this description of a concrete heap cannot be taken as an analysis of what constitutes a reference in general.

Chapter 5

A Bypass of Cohen’s Impossibility Result Jan A. Bergstra and Alban Ponse

Detecting illegal resource access in the setting of grid computing is similar to the problem of virus detection as put forward by Fred Cohen in 1984. We discuss Cohen’s impossibility result on virus detection, and introduce “risk assessment of security hazards”, a notion that is decidable for a large class of program behaviors.

5.1

Introduction

Grid computing poses many challenges which are known from computer science, though now at an integrated level. For semantic work about the grid it is hard to reach that level of integration as well. An example of semantic work on grid computing is in [60], in which the authors Nemeth and Sunderam point out that convincing definitions of what constitutes a grid are still hard to obtain. They set out to provide a definition, and in the preparation of the formal work a table is presented with a comparison of grids and conventional distributed systems. It is stated in their Table 1 that in a grid, access to a node may not imply access to all of its resources and that users working from another node may have little information on accessible resources. We will study this aspect in particular under the assumption that a node takes the responsibility to prevent illegal access to resources by tasks it accepts to execute. We try to find a simplest possible model for this issue and use thread algebra in combination with program algebra. These techniques were introduced in [16] and [15], respectively. (The single thread version of thread algebra is named polarized process algebra, see [15]). This leads to the preliminary conclusion that a fair amount of phenomena in concurrent processing may be understood and formalized along those lines, in particular phenomena where non-determinism is immaterial. It seems to be the case that many characteristic aspects of grid computing can be analyzed using the paradigm of strategic interleaving as put forward in [16]. That means that instead of taking a combinatorial explosion of different runs into account, a limited portfolio of interleaving strategies may 97

98

J.A. Bergstra, I. Bethke, and A. Ponse

be used to characterize vital phenomena, including issues concerning thread mobility and access control. Deterministic strategies inherit from planning theory as discussed in [35]. It appears that detecting illegal resource access is formally very similar to the problem of virus detection as put forward by Cohen in 1984. There is a vast amount of literature on virus detection, and opinions seem to differ wildly. Many authors agree that malcode contains all others, and that both a virus and a worm can replicate. Furthermore, a worm is more autonomous than a virus. Some authors claim that a virus can only replicate as a consequence of actions of users, and that sound education and awareness can protect users from acting with such effect. So, a virus uses a user for its replication; that user may or may not be a victim of the virus’ harmful action at the same time. Unclear is if each of these users must be a human one or if background processes in a machine can also be “used” as users. This paper focuses on virus detection and discusses two fundamental questions. First, we consider Cohen’s result about the impossibility of a uniform algorithm (or tool) for detecting (forecasting) viruses in programs [32]. This is done in the setting of the program algebra PGA [15]. Then, we define a different notion of testing — security hazard risk assessment — with which the occurrence of security hazards is decidable for a large class of program behaviors. However, if divergence (the absence of halting) is considered also as a security hazard, decidability is lost. The paper is organized as follows: in Section 5.2 and 5.3 we introduce some basics of program algebra and the setting in which we will analyze code security risks. Then, in Section 5.4, we consider Cohen’s impossibility result and some related issues. In Section 5.5 we introduce our notion of security hazard risk assessment. The paper is ended with some conclusions.

5.2

Basics of Program Algebra

Program algebra (PGA, [15]) provides a very simple notation for sequential programs and a setting in which programs can be systematically analyzed and mapped onto behaviors. Program behaviors are modeled in thread algebra. Finally, we consider some other program notations based on program algebra.

The program Algebra PGA. In PGA we consider basic instructions a, b, ... given by some collection B. Furthermore, for each a ∈ B there is a positive test instruction +a and a negative test instruction −a. The control instructions are termination, notation !, and (relative) jump instructions #k (k ∈ N). Program expressions in PGA, or shortly PGA-programs, have the following syntax: • each PGA-instruction is a PGA-program, • if X and Y are PGA-programs, so is their concatenation X; Y , • if X is a PGA-program, so is its repetition X ω .

EWSCS’06 - Program Algebra and Thread Algebra

99

The behavior associated with the execution of PGA-programs is explained below. Instruction congruence of programs has a simple axiomatization, given in Table 5.1. The Table 5.1: Axioms for PGA’s instruction sequence congruence. (X; Y ); Z = X; (Y ; Z) (X n )ω = X ω for n > 0

(PGA1) (PGA2)

Xω; Y = Xω (X; Y )ω = X; (Y ; X)ω

(PGA3) (PGA4)

axioms PGA1-4 imply Unfolding, i.e. the law X ω = X; X ω , and PGA2-4 may be replaced by Unfolding and the proof rule Y = X; Y ⇒ Y = X ω . Thread Algebra. Execution of PGA-programs is modeled in thread algebra. Given B, now considered as a collection of actions, it is assumed that upon execution each action generates a Boolean reply (true or false). Now, behavior is specified in thread algebra by means of the following constants and operations: Termination. The constant S represents (successful) termination. Inaction. The constant D represents the situation in which no subsequent behavior is possible. (Sometimes the special thread D is called deadlock or divergence.) Post conditional composition. For each action a ∈ B and threads P and Q, the post conditional composition P  a  Q describes the thread that first executes action a, and continues with P if true was generated, and Q otherwise. Action prefix. For a ∈ B and thread P , the action prefix a ◦ P describes the thread that first executes a and then continues with P , irrespective of the Boolean reply. Action prefix is a special case of post conditional composition: a ◦ P = P  a  P . Behavior Extraction: from program algebra to thread algebra. The behavior extraction operator |X| assigns a behavior to program X. Instruction sequence equivalent programs have of course the same behavior. Behavior extraction is defined by the thirteen equations in Table 5.2, where a ∈ B and u is a PGA-instruction. Table 5.2: Equations for behavior extraction on PGA. |!| |a| |+a| |−a|

= = = =

S a◦D a◦D a◦D

|!; X| |a; X| |+a; X| |−a; X|

= = = =

S a ◦ |X| |X|  a  |#2; X| |#2; X|  a  |X|

|#k| |#0; X| |#1; X| |#k+2; u| |#k+2; u; X|

= = = = =

D D |X| D |#k+1; X|

Some examples: |(#0)ω | = |#0; (#0)ω | = D and, further taking action prefix to bind

100

J.A. Bergstra, I. Bethke, and A. Ponse

stronger than post conditional composition, |−a; b; c| = = = =

|#2; b; c|  a  |b; c| |#1; c|  a  b ◦ |c| |c|  a  b ◦ c ◦ D c ◦ D  a  b ◦ c ◦ D.

In some cases, these equations can be applied (from left to right) without ever generating any behavior, e.g., |(#1)ω | = |#1; (#1)ω | = |(#1)ω | = . . . |(#2; a)ω | = |#2; a; (#2; a)ω | = |#1; (#2; a)ω | = |(#2; a)ω | = . . . In such cases, the extracted behavior is defined as the thread D. It is also possible that behavioral extraction yields an infinite recursion, e.g., |aω | = |a; aω | = a ◦ |aω |, and therefore, |aω | = a◦|aω | = a◦a◦|aω | = a◦a◦a◦|aω | · · · . In such cases the behavior of X is infinite, and can be represented by a finite number of behavioral equations, e.g., |(a; +b; #3; −b; #4)ω | = P and P = a ◦ (P  b  Q), Q = P  b  Q. The program notations PGLB and PGLC. The program notation PGLB is obtained from PGA by adding backwards jumps \#k and leaving out the repetition operator. For example, the thread defined by PGLB-program +a; \#1; +b behaves as (+a; #4; +b; #0; #0)ω , i.e., as P in P = P  a  b ◦ D. This is defined with help of a projection function pglb2pga that translates PGLBprograms in a context-dependent fashion. For a PGLB-program X we write |X|pglb = |pglb2pga(X)| (see further [15]). The language PGLC is the variant of PGLB in which termination is modeled implicitly: a program terminates after its last instruction has been executed and that instruction was no jump into the program, or it terminates after a jump outside the program. The termination instruction ! is not present in PGLC. For example, |+a; #2; \#2; +b|pglc = |+a; #2; \#2; +b; !; !|pglb = |(+a; #2; #6; +b; !; !; #0; #0)ω | = P for P = b ◦ S  a  P (see [15] for precise definitions of |X|pglc and |Y |pglb .)

5.3

Detecting Access to a Forbidden Resource

In this section we introduce the setting in which we will analyze code security risks. We now consider a thread algebra with actions in “focus-method” notation, i.e., actions

EWSCS’06 - Program Algebra and Thread Algebra

101

of the form f.m where f is the focus and m the method. A forbidden resource is a resource that may not be accessed by threads of ordinary security clearance. A focus containing a forbidden resource is called a high risk focus. The state of affairs in which a thread plans to access a forbidden resource is called a security hazard. Let P be some thread that uses communication with the following typical resources He , Hf and Hg :

P

e

−→ He (external focus)

f ↓ & g Hg (low risk focus, no security hazard)

Hf (high risk focus, security risk) The reply of a basic instruction e.m will be determined by the resource He . Likewise, instructions with focus f or g communicate with Hf and Hg , respectively. Now, execution is secure if no f.m is called until termination or first call of some e.m (to the external focus). A thread can have low risk actions (secure execution expected) and high risk actions (insecure execution expected). For example, S — a low risk behavior (no security hazard), f.m ◦ S — a high risk behavior (security hazard), f.m ◦ S  g.m  g.m ◦ S — risk depends on Hg (potential security hazard).

Suppose in some network, a site C receives the description p in some programming language PGLX of a thread P = |p|pglx to be run at C. Then P  sctest.ok  S formalizes a way for C to run P only if its security has been cleared: the test action sctest.ok (security clearance test) models this type of testing, yielding true if P is secure. In terms of the above modeling, such type of testing may be performed by a secure resource like Hg with focus sctest (thus Hsctest ). Alternatively, one can consider a test resource Hasctest (alternative security clearance test) which produces true in P  asctest.ok  S if P has a security hazard. This resource may be implemented by always returning false. Consequently, the better option is to require that if in P  asctest.ok  Q the test asctest.ok yields false, the security of thread Q is guaranteed. In the next section we show that such a seemingly natural test action is self-contradictory; in Section 5.5 we propose a variant of sctest.ok that is not manifestly self-contradictory.

102

5.4

J.A. Bergstra, I. Bethke, and A. Ponse

Security Hazard Forecasting

In this section we consider a security hazard forecasting tool and establish a formal correspondence between security hazard detection (a thread plans to access a forbidden resource) and the virus detection problem put forward by Fred Cohen in 1984. Let SHFT be a Security Hazard Forecasting Tool with focus shft, thus a resource that forecasts a security hazard. As assumed earlier, a security hazard is in our simple setting a call (action) f.m for some m. Furthermore, let shft.test be the test that uses SHFT in the following way: in P  shft.test  Q, the action shft.test returns true if P has a security hazard, and false if Q has no security hazard. Theorem 5.4.1 A Security Hazard Forecasting Tool cannot exist. Proof. Consider S  shft.test  f.m ◦ S. If the test action shft.test returns false, then f.m ◦ S will be performed, which is a security hazard; if true is returned, then S is performed and no security hazard arises. a

The particular thread used in the proof above illustrates the impossibility of predicting that a thread (or a program) contains a virus, a general phenomenon that was described in Cohen’s famous 1984-paper [32] and that will be further referred to as Cohen’s impossibility result. For the sake of completeness, we recall Cohen’s line of reasoning. In the pseudo-code below (taken from [32]), D is a decision procedure that determines whether a program is (contains) a virus, ~D stands for its negation, and next labels the remainder of some (innocent) program: program contradictory-virus:= {1234567; subroutine infect-executable:= {loop:file = get-random-executable-file; if first-line-of-file = 1234567 then goto loop; prepend virus to file; } subroutine do-damage:= {whatever damage is to be done} subroutine trigger-pulled:= {return true if some condition holds} main-program:= {if ~D(contradictory-virus) then {infect-executable;

EWSCS’06 - Program Algebra and Thread Algebra

103

if trigger-pulled then do-damage; } goto next; } } In PGLC, the program contradictory-virus can be represented by the following term CV: CV = #8; Pre; #3; −shft.test(CV); \#8; Next where Pre abbreviates the six instructions that model the security hazard: Pre = file:=get-random-executable-file; +first-line-of-file=1234567; \#2; prepend; +trigger-pulled; do-damage and Next models the remainder of the program. Applying behavior extraction on this program yields |CV|pglc = |Next|pglc  shft.test(CV)  |Pre; #3; −shft.test(CV); \#8; Next|pglc = |Next|pglc  shft.test(CV)  |Pre; Next|pglc So, S  shft.test  f.m ◦ S is indeed a faithful characterization of Cohen’s impossibility result. We note that even with the aid of universal computational power, the problem whether a thread has a security hazard (issues an f.m call) is undecidable. This problem can be seen as a variant of the unsolvability of the Halting Problem, i.e., Turing’s impossibility result. Cohen’s impossibility result needs the notion of a secure run (no security hazards), as well as a secure program or behavior (a thread that will have secure runs only). So, Cohen’s impossibility result emerges if: • secure runs exist, • secure threads exist, • there is a full match between these two, • forecasting is possible. Now there is a difficulty with forecasting: if shft.test returns false one hopes to proceed in such a way that the security hazard is avoided (why else do the test?). But that is not sound as was shown above. Thus we conclude: this type of security hazard forecasting is a problematic idea for the assessment of security hazards.

104

J.A. Bergstra, I. Bethke, and A. Ponse

5.5

Security Hazard Risk Assessment

In this section we introduce a security hazard risk assessment tool, taking into account the above-mentioned considerations. This tool turns out to be a much more plausible modeling of testing the occurrence of security hazards. However, if we add divergence (the absence of halting) as a security risk, the tool can not exist. The following security hazard risk assessment tool SHRAT with focus shrat may be conceived of as assessing a security hazard risk. In P  shrat.ok  Q the test action shrat.ok returns true if P is secure, and false if P is insecure (then P is avoided and Q is done instead). This is a more rational test than shft.test because it tests only a single thread (its left argument). Using an external focus e, the test action shrat.ok in (P1  e.m  P2 )  shrat.ok  Q yields true because e.m is seen as an action that is beyond control of security hazard risk assessment. For testing shrat.ok actions we can employ backtracking: at P  shrat.ok  Q, 1. Temporarily remove thread (or loaded program), 2. Place P instead  ⇒ backtrack if possible, otherwise true,  S or D or e.m ⇒ backtrack if possible, otherwise false, 3. Execute1 until f.m  0 P  shrat.ok  Q0 ⇒ restart 1 with P 0  shrat.ok  Q0 , The backtracking in this algorithm may require the testing of threads that are no direct subthreads of the original one, e.g., in (P1  shrat.ok  P2 )  shrat.ok  Q first the leftmost shrat.ok action is evaluated. If this yields false (so P1 contains a security hazard), then P2  shrat.ok  Q is evaluated. For finite threads this is a terminating procedure and not problematic. Evaluation of shrat.ok actions can be extended to a larger class of threads. A regular thread P1 over B is defined by a finite system of equations over P = P1 , ..., Pn (for some n ≥ 1) of the following form: P1 = F1 (P ) .. . Pn = Fn (P ) with Fi (P ) ::= S | D | Pi,1  ai  Pi,2 for Pi,j ∈ {P1 , ..., Pn } and ai ∈ B. Consider P1  shrat.ok  Q, thus F1 (P )  shrat.ok  Q. Again we can decide the outcome of the test action shrat.ok by doing a finite number of substitutions, linear in n. (Loops and divergence are not considered security hazards.) This leads to the following result: 1

Here “execute” means that upon a test action a, both branches should be inspected.

EWSCS’06 - Program Algebra and Thread Algebra

105

Theorem 5.5.1 For regular threads, the tool SHRAT is possible. We give a simple example: if P1 = P2  a  P1 P2 = P1  f.m  P1

(= f.m ◦ P1 ),

then shrat.ok in (P2 aP1 )shrat.okQ yields true if it does in both P1 shrat.okQ and P2  shrat.ok  Q. Obviously, it does not in the latter case, so this thread equals Q. A slightly more complex example (including the evaluation of the various shrat.ok tests): P1 P2 P3 P4 P5 P6 P7 P8

= = = = = = = =

P2  shrat.ok  S P3  a  P4 P2  shrat.ok  P6 P7  shrat.ok  P8 a ◦ P2 f.m ◦ P2 f.m ◦ P2 a ◦ S.

(true) (true) (false)

Omitting the shrat.ok-tests, thread P1 has behavior P as defined in P = a ◦ P  a  a ◦ S. Thus, evaluation of the reply of shrat.ok is decidable for regular threads. We conclude that Cohen’s impossibility result does not apply in this case; apparently, that result is about forecasting. Of course, the decidability of the evaluation of shrat.ok actions is lost if a Turing Tape is used as a resource. Divergence Risk Assessment. If we consider divergence as a security hazard, say by focus drat and resource DRAT (a Divergence Risk Assessment Tool), we have a totally different situation: in the thread defined by P = P  drat.ok  S we then obviously want that the test action drat.ok returns the answer false. It is well-known that in general, DRAT cannot exist, as it is equivalent with solving the Halting Problem. Now, involving divergence as a security hazard in shrat.ok actions, we also find that in P = P  shrat.ok  f.m ◦ S the test should yield false (otherwise divergence). However, this yields a problem: in P = P  shrat.ok  S this goes wrong: the halting problem (Turing’s impossibility result) wins, and hence the backtracking model is not suitable anymore.

106

5.6

J.A. Bergstra, I. Bethke, and A. Ponse

Conclusions

In [21], we provide a formal treatment of the (potential) interaction of a thread P with resources He , Hf and Hg . Notation for that situation is ((P/e He )/f Hf )/g Hg or equivalently, P/e He /f Hf /g Hg . In the present paper we considered all communications of P with a resource Hh implicit and wrote P instead. In other words, an expression like P  h.m  Q as occurring in this paper is considered to abbreviate (P  h.m  Q)/h Hh , and this type of interaction is formalized in [21]. In [22] we provide a process algebraic semantics of threads (P  h.m  Q)/h Hh . In [46] it is stated that in order to constitute a grid, a network must implement the Globus GRAM protocol. This is a far more constrained definition than the concept based definitions that occur in [60]. We do not use that characterization because it is too complex for a brief theoretical paper. How do our results relate to GRAM? Secure resource allocation on the grid requires an underlying theoretical basis. What we put forward is that under a very simple definition of a resource allocation risk, Cohen’s impossibility result need not constrain the options for automatic detection as much as one might think. On the contrary, if security risk avoidance is adapted as a strategy, Cohen’s impossibility result disappears.

Bibliography [1] ASCII Table and Description. http://www.lookuptables.com/. [2] L. Aceto, W.J. Fokkink, and C. Verhoef. Structural operational semantics. In [24]:197–292, 2001. [3] J.W. de Bakker and J.I. Zucker. Processes and the denotational semantics of concurrency. Information and Control, 54(1/2):70-120, 1982. [4] J.C.M. Baeten and J.A. Bergstra. Process algebra with a zero object. In: Proceedings CONCUR’90, Amsterdam (J.C.M. Baeten and J.W. Klop, eds.), Lecture Notes in Computer Science 458:83-98, Springer-Verlag 1990. [5] Jos C.M. Baeten, Jan Karel Lenstra, Joachim Parrow, and Gerhard J. Woeginger (eds). Automata, Languages and Programming, 30th International Colloquium, ICALP 2003, Lecture Notes in Comput. Sci. 2719, Springer, 2003. [6] J.A. Bergstra and I. Bethke. Molecular dynamics. Journal of Logic and Algebraic Programming, 51(2):193–214, 2002. [7] J.A. Bergstra and I. Bethke. Polarized process algebra and program equivalence. In [5]:1– 21, 2003. In this issue. [8] J.A. Bergsta, I. Bethke. Linear Projective Program Syntax. Logic Group Preprint Series 233, Utrecht University, Department of Philosophy, October 2004. [9] J.A. Bergstra, I. Bethke, and A. Ponse. Process algebra with iteration and nesting. The Computer Journal, 37(4):243-258, 1994. [10] J.A. Bergstra, I. Bethke, and A. Ponse. Decision problems for pushdown threads. Electronic report PRG0502, Programming Research Group, University of Amsterdam, June 2005. Available at www.science.uva.nl/research/prog/publications.html. In this issue. [11] J.A. Bergstra, I. Bethke, and P.H. Rodenburg. A propositional logic with 4 values: true, false, divergent and meaningless. Journal of Applied Non-Classical Logics, 5:199-217, 1995. [12] J.A. Bergstra and J.W. Klop. Process algebra for synchronous communication. Information and Control, 60(1-3):109-137, 1984. [13] J.A. Bergstra and M.E. Loots. Program algebra for component code. Formal Aspects of Computing, 12(1):1–17, 2000. [14] J.A. Bergstra and M.E. Loots. Program algebra for component code. Formal Aspects of Computing, 12(1):1–17, 2000. [15] J.A. Bergstra and M.E. Loots. Program algebra for sequential code. Journal of Logic and Algebraic Programming, 51(2):125–156, 2002. [16] J.A. Bergstra and C.A. Middelburg. Thread algebra for strategic interleaving. Computing Science Report 04-35, Eindhoven University of Technology, Department of Mathematics and Computing Science, November 2004.

107

108

J.A. Bergstra, I. Bethke, and A. Ponse

[17] J.A. Bergstra and C.A. Middelburg. A thread algebra with multi-level strategic interleaving. In S.B. Cooper, B. Loewe and L. Torenvliet, editors, CiE 2005, Springer-Verlag, LNCS 3526:35-48, 2005. [18] J.A. Bergstra and A. Ponse. Bochvar-McCarthy logic and process algebra. Notre Dame Journal of Formal Logic, 39(4):464-484, 1998. [19] J.A. Bergstra and A. Ponse. Register-machine based processes. Journal of the ACM, 48(6):1207-1241, 2001. [20] J.A. Bergstra and A. Ponse. Non-regular iterators in process algebra. Theoretical Computer Science, 269 (1-2):203-229, 2001. [21] J.A. Bergstra and A. Ponse. Combining programs and state machines. Journal of Logic and Algebraic Programming, 51(2):175-192, 2002. [22] J.A. Bergstra and A. Ponse. Execution architectures for program algebra. Logic Group Preprint Series 230, Dept. of Philosophy, Utrecht University, 2004. In this issue. [23] J.A. Bergstra and A. Ponse. A bypass of Cohen’s impossibility result. In P.M.A. Sloot, A.G. Hoekstra, T. Priol, A. Reinefeld, M. Bubak. Advances in Grid Computing - EGC 2005, Springer-Verlag, LNCS 2470:1097-1106, 2005. In this issue. [24] J.A. Bergstra, A. Ponse, and S.A. Smolka (eds). Handbook of Process Algebra, NorthHolland, 2001. [25] J.A. Bergstra and J.V. Tucker. Equational specifications, complete rewriting systems and computable and semi-computable algebras. Journal of the ACM, 42(6):1194–1230, 1995. [26] I. Bethke. Completion of equational specifications. In Terese, editors, Term Rewriting Systems, Cambridge Tracts in Theoretical Computer Science 55, pages 260–300, Cambridge University Press, 2003. [27] M. Bird. The equivalence problem for deterministic two-tape automata. Journal of Computer and System Science, 7(2):218-236, 1973. [28] E. B¨orger (ed). Specification and Validation Methods, Oxford University Press, 1995. [29] F.-J. Brandenburg, G. Vidal-Naquet and M. Wirsing (eds). STACS’87, Proc. Symp. on Theoretical Aspects of Computer Science, Lecture Notes in Comput. Sci. 247, Springer, 1987. [30] S.D. Brookes, C.A.R. Hoare, and A.W. Roscoe. A theory of communicating sequential processes. Journal of the ACM, 31(8):560–599, 1984. [31] S. Christensen, H. H¨ uttel, and C. Stirling. Bisimulation equivalence is decidable for all context-free processes. Information and Computation, 121(2):143-148, 1995. [32] F. Cohen. Computer viruses: Theory and experiments. Computers & Security, 6(1):22-35, 1987. [33] E.W. Dijkstra. Guarded commands, non-determinacy, and formal derivations of programs. Commun. ACM, 18:453–457, 1975. [34] W.J. Fokkink. Introduction to Process Algebra. Texts in Theoretical Computer Science. Springer-Verlag, 2000. [35] E. Deelman, J. Blythe, Y. Gil, C. Kesselman, G. Mehta, K. Vahi, K. Blackburn, A. Lazzarini, A. Arbree, R. Cavanaugh, and S. Koranda. Mapping abstract complex workflows onto grid environments. Journal of Grid Computing, 1:25-39, 2003. [36] M.A. Ertl (ed). Advances in interpreters, virtual machines and emulators. Science of Computer Programming, 57(3):251–380, 2005.

EWSCS’06 - Program Algebra and Thread Algebra

109

[37] M. Felleisen and D.P. Friedman. Control operators, the SECD machine, and the λ-calculus. In [76]:193–217, 1987. [38] E.P. Friedman. The inclusion problem for simple languages. Theoretical Computer Science, 1:297-316, 1976. [39] D. Gabbay and F. Guenthner (eds). Handbook of Philosophical Logic, volume II, Reidel Publishing Company, Dordrecht, 1984. [40] M. Gardner. Knotted Doughnuts and Other Mathematical Entertainments. New York: W. H. Freeman, 1986. [41] D. Gregg, A. Beatty, K.Casey, B. Davis, and A.Nisbet. The case for virtual register machines. In [36]:319–338, 2005. [42] S. Greibach. Theory of Program Structures: Schemes, Semantics, Verification, SpringerVerlag, 1975. [43] Y. Gurevich. Evolving algebras 1993: Lipari guide. In [28]:9–36, 1995. [44] D. Harel. Dynamic logic. In Handbook of Philosophical Logic, volume II. D. Reidel Publishing Company, 1984. [45] C.A.R. Hoare. An axiomatic basis for computer programming. Commun. ACM, 12:576– 580, 1969. [46] S. Hwang and C. Kesselman. A flexible framework for fault tolerance in the grid. Journal of Grid Computing, 1(3):251-272, 2003. [47] P. Janˇcar. Decidability questions for bisimilarity of Petri nets and some related problems. Proceedings of STACS94, Springer-Verlag, LNCS 775:581-592, 1994. [48] P. Janˇcar, F. Moller, and Z. Sawa. Simulation problems for one-counter machines. Proceedings of SOFSEM’99: The 26th Seminar on Current Trends in Theory and Practice of Informatics, Springer-Verlag, LNCS 1725:398-407, 1999. [49] T. Jiang, A. Salomaa, K. Salomaa, and S. Yu. Inclusion is undecidable for pattern languages. ICALP 93, Springer-Verlag, LNCS 700:301-312, 1993. [50] G. Kahn. Natural semantics. In [29]:22–39, 1987. [51] J.-W. Klop. Term rewriting systems. In Handbook of Logic in Computer Science, volume II, pages 1–116. Oxford University Press, 1992. [52] S.T. Kuhn. Prisoner’s Dilemma. http://plato.stanford.edu/entries/ prisoner-dilemma/ (including many references), 2003. [53] J. van Leeuwen (ed). Handbook of Theoretical Computer Science, volume B: Formal Models and Semantics, Elsevier Science Publishers, Amsterdam; and MIT Press, 1990. [54] H.R. Lewis and C.H. Papadimitriou. Elements of the Theory of Computation (2nd edition). Prentice-Hall, 1997. [55] Z. Manna. Mathematical Theory of Computation. McGraw-Hill, New-York, 1974. [56] M.L. Minsky. Recursive unsolvability of Post’s problem of ”Tag” and other topics in theory of Turing machines. Annals of Mathematics, 74(3):437-455, 1961. [57] M.L. Minsky. Computation: Finite and Infinite Machines, Prentice-Hall International, Englewood Cliffs, New York, 1967. [58] E. Moggi. Notions of computation and monads. Information and Control, 93:55–92, 1991. [59] P.D. Mosses. Denotational semantics. In [53]:575–631, 1990.

110

J.A. Bergstra, I. Bethke, and A. Ponse

[60] Zs. Nemeth and V. Sunderam. Characterizing grids: attributes, definitions, and formalisms. Journal of Grid Computing, 1:9-23, 2003. [61] E. Ohlebush and E. Ukkonen. On the equivalence problem for E-pattern languages. Theoretical Computer Science, 186(1/2):231-248, 1997. [62] A. Ponse. Program algebra with unit instruction operators. Journal of Logic and Algebraic Programming, 51(2):157–174, 2002. [63] H. Rogers. Theory of Recursive Functions and Effective Computability. McGraw-Hill Book Co., 1967. Reprinted, MIT Press, 1987. [64] G. S´enizergues. L(A) = L(B)? Technical report 1161-97, LaBRI, Universit´e Bordeaux, 1997. Available at www.labri.u-bordeaux.fr. [65] G. S´enizergues. Decidability of bisimulation equivalence for equational graphs of finite out-degree. Proceedings IEEE 39th FOCS, 120-129, 1998. [66] G. S´enizergues. L(A)=L(B)? decidability results from complete formal systems. Theoretical Computer Science, 251:1-166, 2001. [67] I.A. Severoni. Operational semantics of the Java Card Virtual Machine. Journal of Logic and Algebraic Programming, 58:3–25, 2004. [68] Y. Shi, D. Gregg, A. Beatty, and M.A. Ertl. Virtual machine showdown: stack versus registers. In [75]:153–163, 2005. [69] R. St¨ ark, J. Schmid, and E. B¨ orger. Java and the Java Virtual Machine. Definition, Verification, Validation. Springer, 2001. [70] C. Stirling. Decidability of DPDA equivalence. Theoretical Computer Science, 255:1-31, 2001. [71] V. Stoltenberg-Hansen, I. Lindstr¨ om, and E.R. Griffor. Mathematical Theory of Domains, Cambridge Tracts in Theoretical Computer Science 22, Cambridge University Press, 1994. [72] A. Turing. On computable numbers, with an application to the Entscheidungsproblem. Proceedings of the London Mathematical Society, Ser. 2, 42:230-265, 1937. Corrections: ibid 43:544-546, 1937. [73] L.G. Valiant. The equivalence problem for deterministic finite-turn pushdown automata. Information and Control, 25(2):123-133, 1974. [74] L.G. Valiant and M. Patterson. Deterministic one-counter automata. Journal of Computer and System Science, 10(3):340-350, 1975. [75] VEE ’05: Proceedings of the 1st ACM/USENIX international conference on Virtual Execution Environments. Chicago, IL, USA. ACM Press, USA, 2005. [76] M. Wirsing (ed). Formal Description of Programming Concepts III, Proc. IFIP TC2 Working Conference, Gl. Avernaes, 1986, North-Holland, 1987.