A Parallelizing Compiler for the Functional Programming Language EVE

Diplomarbeit

zur Erlangung des akademischen Grades “Diplom-Ingenieur” in der Studienrichtung

Informatik

eingereicht von

Hans Wolfgang Loidl

Angefertigt am Institut für Mathematik

Technisch-Naturwissenschaftliche Fakultät Johannes Kepler Universität Linz

Eingereicht bei

o.Univ.-Prof. Dr. Bruno Buchberger

Mai 1992

Abstract

This thesis describes the design and implementation of an automatically parallelizing compiler for the purely functional programming language EVE. The target language of the compilation process is the assembler code of the abstract dataflow machine ADAM that has been implemented in the frame of another thesis. In this compilation process dataflow graphs are used as an intermediate language. These dataflow graphs must then be translated into a controlflow program with explicit statements for creating and synchronizing parallel threads of control. A prototype version of the EVE compiler is running.

Keywords: Parallel Computation, Compilation, Automatic Parallelization, Functional Programming, Dataflow Architectures

Contents

1 Introduction 6

2 Parallel Functional Programming 11

2.1 Introduction to Functional Programming . . . . . . . . . . . . . . . . . . . . . 11

2.1.1 Strictness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.2 Explicit Parallelism in Functional Languages . . . . . . . . . . . . . . . . . . 17 2.3 Parallel Implementation of Functional Languages . . . . . . . . . . . . . . . . 18

2.3.1 The Lazy SECD Machine . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.3.2 Graph Reduction Machines . . . . . . . . . . . . . . . . . . . . . . . . 19 2.3.3 Dataflow Machines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

2.4 Using Annotations to Express Parallelism . . . . . . . . . . . . . . . . . . . . 24

3 ADAM & EVE 27

3.1 The ADAM Abstract Dataflow Machine . . . . . . . . . . . . . . . . . . . . . 27 3.1.1 The Dataflow Principle . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.1.2 Differences between the ADAM and a Pure Dataflow Machine …. 29 3.1.3 The Global Sructure of the ADAM Machine . . . . . . . . . . . . . . . 31

3.2 The EVE Programming Language . . . . . . . . . . . . . . . . . . . . . . . . 35 3.2.1 Characteristics of the EVE Programming Language . . . . . . . . . . 35 3.2.2 EVE Program Structure . . . . . . . . . . . . . . . . . . . . . . . . . . 37 3.2.3 Type Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 3.2.4 Constant Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 3.2.5 Function Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 3.2.6 Conditional Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . 51 3.2.7 Case Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 3.2.8 Block Expressions (Local Definitions) . . . . . . . . . . . . . . . . . . 53 3.2.9 Higher Order Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 55 3.2.10 Strictness Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57 3.2.11 Error Behaviour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 3.2.12 Examples of EVE Programs . . . . . . . . . . . . . . . . . . . . . . . . 59 3.2.13 Syntax Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 3.2.14 Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 3.2.15 Input File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 3.2.16 Lexical Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 3.2.17 Context-Free Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 3.2.18 Keywords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64

3.3 Predefined Identifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 3.3.1 Predefined Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 3.3.2 Predefined Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 3.3.3 Predefined Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

4 The EVE Compiler 68 4.1 The Structure of the Compilation . . . . . . . . . . . . . . . . . . . . . . . . . 69 4.2 First Pass . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

4.2.1 Lexical Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 4.2.2 Syntax Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75

4.3 Second Pass . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 4.3.1 The Principle of λ-lifting . . . . . . . . . . . . . . . . . . . . . . . . . 78 4.3.2 The λ-lifting Algorithm in the EVE Compiler . . . . . . . . . . . . . . 80 4.3.3 Further Transformations on the Abstract Syntax Tree . . . . . . . . . 83

4.4 Third Pass . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 4.4.1 From EVE Programs to Dataflow Graphs . . . . . . . . . . . . . . . . 86 4.4.2 From Dataflow Graphs to ADAM Assembler Code . . . . . . . . . . . 102

5 Experiments 119

5.1 Benchmarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 5.1.1 Benchmark Programs in EVE . . . . . . . . . . . . . . . . . . . . . . . 119 5.1.2 Timings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120

5.2 Comparison of Two Circular Prime Number Generators . . . . . . . . . . . . 121 5.3 Sources of Optimization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123

6 Conclusion 129 A Attribute Grammar of the First Pass 131

A.1 Altered Context-Free Syntax of EVE . . . . . . . . . . . . . . . . . . . . . . . 132 A.2 Declarations of Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

A.2.1 Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 A.2.2 Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 A.2.3 Attributes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 A.2.4 Further Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135

A.3 Semantic Procedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 A.4 Attribute Grammar of the First Pass . . . . . . . . . . . . . . . . . . . . . . 137

B Denotational Semantics of the Code Generation 152 B.1 Abstract Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 B.2 Semantic Algebras . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155

B.2.1 Variables (atomic domain) . . . . . . . . . . . . . . . . . . . . . . . . 155 B.2.2 Flags for checking compound objects (atomic domain) . . . . . . . . . 155 B.2.3 Handling of Registers . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 B.2.4 Handling of Labels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 B.2.5 Handling of Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 B.2.6 Generated Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 B.2.7 Binding Registers, Labels and Flags to Variables . . . . . . . . . . . . 157 B.2.8 State of the resources that must be handled . . . . . . . . . . . . . . . 158 B.2.9 The result of the valuation function . . . . . . . . . . . . . . . . . . . 158

B.3 Valuation Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 B.3.1 Valuation Function of Expression . . . . . . . . . . . . . . . . . . . . . 161 B.3.2 Valuation Function of Definition . . . . . . . . . . . . . . . . . . . . . 184 B.3.3 Valuation Function of Program . . . . . . . . . . . . . . . . . . . . . . 189

B.4 Inline Code for Predefined Functions . . . . . . . . . . . . . . . . . . . . . . . 189

C Description of the ADAM Instruction Set 194 C.1 Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194 C.2 Semantics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198

C.2.1 Arithmetical-Logical Instructions . . . . . . . . . . . . . . . . . . . . . 199 C.2.2 Control Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204 C.2.3 Function Call Instructions . . . . . . . . . . . . . . . . . . . . . . . . . 206 C.2.4 Storage Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208 D EVE Test Programs 210

E Definition Modules of the EVE Compiler 221 E.1 Top Level Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223 E.2 Data Encapsulations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 234 E.3 Special Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295

List of Tables

3.1 Integer Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 3.2 Integer Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 3.3 Boolean Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 3.4 Special Characters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 3.5 Character Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.6 Comparison Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.7 Generic List Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3.8 Runtime Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 3.9 The US-ASCII Character Set . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 3.10 Predefined Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 3.11 Predefined Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 3.12 Predefined Integer Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 3.13 Predefined Boolean Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 3.14 Predefined Character Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 66 3.15 Predefined Comparison Functions . . . . . . . . . . . . . . . . . . . . . . . . . 66 3.16 Predefined List Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 3.17 Error Test Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

4.1 Top Level Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.2 Data Encapsulations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.3 Special Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 4.4 Terminal Classes and their Attributes . . . . . . . . . . . . . . . . . . . . . . 73 4.5 Representation of Terminal Symbols . . . . . . . . . . . . . . . . . . . . . . . 74

5.1 Timings of the Benchmark Programs . . . . . . . . . . . . . . . . . . . . . . . 120 5.2 Timings of Two Circular Prime Number Generators . . . . . . . . . . . . . . 121

D.1 EVE programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210 E.1 Top Level Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 E.2 Data Encapsulations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222 E.3 Special Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222

List of Figures

2.1 Sequential Graph Reduction of the Expression (2 + 3) ∗ (4 − 1) . . . . . . . . 20 2.2 Parallel Graph Reduction of the Expression (2 + 3) ∗ (4 − 1) . . . . . . . . . . 21 2.3 A Dataflow Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23

3.1 A Dataflow Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3.2 A Dataflow Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.3 An ADAM Assembler Program . . . . . . . . . . . . . . . . . . . . . . . . . . 30 3.4 An Optimized ADAM Assembler Program . . . . . . . . . . . . . . . . . . . 30 3.5 An ADAM Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 3.6 EVE Program: Drop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 3.7 EVE Program: Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60

4.1 Structure of the Compilation . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 4.2 Translation of a Call of a Predefined Function into a Special Node of the Tree 83 4.3 Translation of a CASE Expression into an IF-THEN-ELSE expression …. 84 4.4 Description of the Code Generation . . . . . . . . . . . . . . . . . . . . . . . . 85 4.5 The Interface of a Dataflow Graph . . . . . . . . . . . . . . . . . . . . . . . . 87 4.6 Dataflow Graph of a Constant: c . . . . . . . . . . . . . . . . . . . . . . . . . 88 4.7 Dataflow Graph of a Variable: v . . . . . . . . . . . . . . . . . . . . . . . . . 88 4.8 Dataflow Graph of a Binary Operation: E_{1} op E_{2} . . . . . . . . . . . . . . . . 89 4.9 Internal Structure of a Signal Tree . . . . . . . . . . . . . . . . . . . . . . . . 90 4.10 Dataflow Graph of a Function Call: f[E_{1}, …, E_{n}] . . . . . . . . . . . . . . . . 91 4.11 Dataflow Graph of the Head of a Conditional Expression . . . . . . . . . . . . 93 4.12 Dataflow Graph of the Body of a Conditional Expression . . . . . . . . . . . 94 4.13 Dataflow Graph of a Local Definition: {v_{1} == E_{1}, …, v_{n} == E_{n}}E . . . . . 95 4.14 Dataflow Graph of a Tuple Construction: [E_{1}, …, E_{n}] . . . . . . . . . . . . . 96 4.15 Dataflow Graph of a Tuple Selection: π_{i} E . . . . . . . . . . . . . . . . . . . . 97 4.16 Dataflow Graph of a Function Application: E[E_{1}, …, E_{n}] . . . . . . . . . . . 98 4.17 Dataflow Graph of a Curried Expression: < f, v_{1}, …, v_{n} > . . . . . . . . . . . 99 4.18 Dataflow Graph of a Simple Definition: v == E . . . . . . . . . . . . . . . . . 100 4.19 Dataflow Graph of a Function Definition: f[v_{1}, …, v_{n}] == E . . . . . . . . . 101 4.20 Dataflow Graph of a Definition Sequence (Top Level): D_{1}, …, D_{n} . . . . . . 102 4.21 ADAM Assembler Code of a Constant: c . . . . . . . . . . . . . . . . . . . . 105 4.22 ADAM Assembler Code of a Binary Operation: E_{1} op E_{2} . . . . . . . . . . . 106 4.23 ADAM Assembler Code of a Function Call: f[E_{1}, …, E_{n}] . . . . . . . . . . . 107 4.24 ADAM Assembler Code of a Conditional Expression . . . . . . . . . . . . . . 109 4.25 ADAM Assembler Code of a Local Definition: {D}E . . . . . . . . . . . . . 111 4.26 ADAM Assembler Code of a Tuple Construction: [E_{1}, …, E_{n}] . . . . . . . . 112 4.27 ADAM Assembler Code of a Tuple Selection: π_{i} E . . . . . . . . . . . . . . . 113 4.28 ADAM Assembler Code of a Function Application: E[E_{1}, …, E_{n}] . . . . . . 114 4.29 ADAM Assembler Code of a Curried Expression: < f, v_{1}, …, v_{n} > . . . . . . 115 4.30 ADAM Assembler Code of a Simple Definition: v == E . . . . . . . . . . . . 116 4.31 ADAM Assembler Code of a Function Definition: f[v_{1}, …, v_{n}] == E …. 117 4.32 ADAM Assembler Code of a Definition Sequence: D_{1}, …, D_{n} . . . . . . . . . 117

5.1 Global Structure of Both Prime Number Generators . . . . . . . . . . . . . . 122 5.2 Structure of a Process in Wentworth’s Prime Number Generator . . . . . . . 122 5.3 Structure of a Process in Schreiner’s Prime Number Generator . . . . . . . . 123 5.4 ADAM Assembler Code for a Local Definition in EVE . . . . . . . . . . . . 125 5.5 ADAM Assembler Code for an EVE Function . . . . . . . . . . . . . . . . . 126 5.6 Dataflow Graph of x*(y+z) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 5.7 Optimized Dataflow Graph of x*(y+z) . . . . . . . . . . . . . . . . . . . . . . 127

C.1 The ADAM Instruction Set (Summary) . . . . . . . . . . . . . . . . . . . . . 195

Acknowledgements

Most of all, I want to thank Prof. B. Buchberger, whose enthusiasm in his research inspired me to work in the area of symbolic computation. And I want to thank him and all the other people at RISC who helped in building such a great environment here in Hagenberg: It’s really fun to work here.

I want to thank Wolfgang Schreiner very cordially for guiding me in the whole process of my diploma thesis and for his help he has given me so often not only in the frame of my thesis. His outstanding work he has done in his diploma thesis, which was the foundation I was able to base my work on, was itself the best start I could get and was a guideline for my whole work. And I want to express my thanks to all the people in the Parallel Computation Lab at RISC whose work and whose experiences were and are extremely valuable. Especially, I want to thank Kurt Siegl, Peter Zettler, Klaus Kusche, Andi Neubacher and Dr. Hoon Hong, who as the head of our lab gave me very useful advices for my thesis. I hope that the collaboration of us all will be as productive in the future as it was in the past.

Among all the colleagues at our institute I am very grateful to those who always took care about our working environment and helped to facilitate the work of us all. Especially, I want to thank Andi Neubacher and Peter Pregler for providing means to increase the motivation in all phases of my work.

Furthermore I want to acknowledge the work of other scientists all over the world that was helpful for me in writing my thesis. Especially, I am grateful to K. Traub, who presents his work in such a way that other people can get the maximal usage out of it.

Finally, I want to thank all colleagues at RISC for creating such a warm atmosphere that is the ideal environment for really productive work.

Chapter 1

Introduction

In the development of computer science during the last decades one can see two major trends that compete with each other:

• One trend tries to increase the abstraction in programming,

• another trend tries to increase the efficiency in programming.

The overall development of modern imperative programming languages mainly deals with the first trend. The reason for this is the fact that all these programming languages are derived from the structure of a von-Neumann processor and are therefore nothing but abstractions from the assembler language. From this one can see that in imperative languages efficiency has always been the most important aspect and only those abstractions were performed that did not cause much problems with respect to efficiency. However, when writing really large program packages it shows that the abstractions that have been introduced are not really sufficient to create such big packages in a convenient way. This has led to the development of the concept of object oriented programming.

Contrary to this kind of programming languages functional programming languages deal with the second trend as they are already on a quite high level of abstraction. They are not abstractions of some machine oriented language but they are an attempt to use at least a part of the usual mathematical language as a programming language. Therefore, the main problem of these languages has always been their lack of efficiency compared to imperative programming languages. However, in the last years big progress has been made in implementing such languages efficiently. Already in [Kranz et al., 1986] a compiler for Scheme has been presented that creates code that is in some cases as efficient as C code. Currently, the implementations of Standard ML (in the New Jersey version) (see [Appel and MacQueen, 1987], [Appel, 1989]) and of Scheme (see [Kranz, 1988]) are some of the most efficient implementations of such languages.

One important possibility to increase the efficiency of programs is parallel computation. The idea of this concept is to divide a program in several parts that can be computed independently and therefore on different processors. The problem that imperative languages have with parallelism is the fact that they still reflect the idea of having just one thread of control. Therefore, the language model has to be extended to incorporate the idea of several independent processes that communicate with each other. Some of the models that extend imperative languages with a concept of parallelism are:

• The model of Communicating Sequential Processes (CSP) (see [Hoare, 1985]),

• a model for an entire Parallel Program Design (see [Chandy and Misra, 1988]),

• a Calculus of Communicating Systems (see [Milner, 1980]), which presents a mathematical model for parallel computation,

• the Actor Model (see [Agha, 1986]),

• the Linda model (see [Carriero and Gelernter, 1990]).

Many of the current parallel imperative programming languages are based on the concept of CSP. The most prominent of these languages are:

• Parallel C . As nowadays C is one of the most used programming languages it has also been one of the first languages that was extended to incorporate parallel features. Therefore, quite many parallel extensions to C have been implemented. Some of these implementations of parallel C are described in [Gehani and Roome, 1986], [Inmos, 1989] and [Poplet, 1987].

• Occam. This language has been designed especially for parallelism. It is a Pascallike language without recursion that is only implemented on transputers (see [Inmos, 1988]).

• Modula-P. This language in some sense extends Occam with the sequential constructs that are known from Modula-2 e.g. for separating a program into various modules (see [Vollmer, 1990]).

The problem for the programmer with this kind of languages is the fact that parallelism adds an entire new level of difficulty to programming. These new problems that arise in parallel programming are:

• One has to split the program in processes of appropriate size and one has to decide in which order to execute these processes determining which computations can be done in parallel. For getting efficient programs, the size of the different processes (the granularity) must be appropriate. If it is too large, probably not all processors are used all the time; if it is too small the overhead of synchronization and communication might become too big.

• One has to specify on which processor each of these processes shall be executed. This problem is referred to as the mapping problem. This mapping can be fixed at compile time or it can be deferred to the runtime. The idea behind the latter strategy is to avoid a situation where some processors have no work whereas others are heavily loaded. However, to distribute the work correctly, information about the load of all processors is necessary. But getting this information includes some overhead. Hence, the right strategy is always a trade-off between these two extremes. This distribution of work among the processors is called load balancing.

• One has to handle the communication between the processes in a correct way. Especially, it has to be guaranteed that never a situation occurs where in a set of processes all processes wait for a message from another process of this set. As all processes are blocked in this case this is called a deadlock. On some architectures it is even necessary to explicitly program the routing of messages between different processors.

As some of the above specifications depend on the number of processors that are used or on the topology of the connections between these processors the resulting programs are generally not portable between different architectures. Therefore, one loses a big part of the abstraction one has achieved with sequential imperative languages.

Another approach to getting parallel imperative programs is to automatically parallelize imperative programs. The idea behind this approach is that with an automatically parallelizing compiler it should be possible to generate code that can be executed in parallel. By this, it would be possible to increase the efficiency of already existing imperative programs. This is especially interesting as with this method it should be possible to execute existing programs in parallel without having to change these programs at all. However, the big problem with this approach is the fact that in the imperative program almost all of the parallelism that might have been contained in the given problem has been destroyed in order to get one single thread of control. Therefore, it is an extremely difficult task to regain this parallelism by an automatically parallelizing compiler. Hence, today it usually can not be avoided to help the automatically parallelizing compiler by providing some additional information about the execution of the program.

Contrary to these imperative languages, functional languages do not define a sequential thread of control. Moreover, they define a partial ordering on the operations that have to be executed in order to get a result. As all operations that do not depend on each other (i.e. that are incomparable in that partial ordering) can be executed in parallel, we can say that all functional languages contain some implicit parallelism. This presence of implicit parallelism is the main reason why functional languages are well suited for parallelism. Of course, it is also possible to extend the language with constructs for explicit parallelism^{1}. By using such an extended language, some tasks (like splitting the program in parallel processes of the appropriate size) that could also be done by a compiler are left to the programmer in order to get more efficient programs. A more detailed discussion of parallel functional programming will be given in Chapter 2.

At the RISC institute the interest for parallel computation comes from many very time consuming algorithms especially from computer algebra. Some of these algorithms whose parallelization are especially interesting for us are the Gröbner Bases algorithm by B. Buchberger (see [Buchberger, 1985]) and the algorithm for Cylindric Algebraical Decomposition by G. Collins (see [Collins, 1975]). In this area of symbolic computation, functional languages are very interesting as they allow to express a program in a similar way as in the usual mathematical notation. In another thesis ([Siegl, 1990]) it has already been shown that by using a declarative language it is possible to get a significant speed-up for the Gröbner Bases algorithm. Therefore, at the RISC institute the ADAM & EVE project has been started in 1989. The main goal of this project was to study the behaviour of programs in the functional programming language EVE that have been compiled to the assembler code

1Such an approach for explicit parallelism in a functional language will be shown in Section 2.2.

of the abstract dataflow machine ADAM^{2}. This approach to parallelism has the following advantages:

• The EVE language is well suited for solving problems in the area of symbolic computation^{3}.

• The EVE language is implicitly parallel. The programmer does not have to include any statements concerning parallelism. However, in order to get efficient parallel programs one has to take the data dependencies between the various objects in an EVE program into account. See Section 5.2 for a comparison of the parallel behaviour of two similar programs with different data dependencies.

• As the ADAM machine simulates dataflow behaviour on a controlflow machine, conventional hardware can be used to execute EVE programs.

This thesis forms the second part in the ADAM & EVE project. The first part consisted of the implementation of the ADAM machine on a transputer network and the design of the EVE language. This part is described in [Schreiner, 1990].

The structure of the whole thesis is as follows:

• After the introduction in this chapter, Chapter 2 gives a survey of parallel functional programming. This chapter first gives a general introduction to functional programming. Afterwards the introduction of explicit parallelism into a functional language is discussed. Finally, some abstract machines for the implementation of functional languages are presented and it is described how these abstract machines can be parallelized.

• Chapter 3 gives a short description of the internal structure of the ADAM machine. As the design and implementation of this machine was the topic of the first part of the ADAM & EVE project here only a summary of the results of this part will be given. Furthermore, this chapter shows the specification of the EVE language. In the realization of this project some modifications have been performed on the EVE language. These modifications are emphasized in this chapter^{4}.

• The main part of the whole diploma thesis is Chapter 4 where the implementation of the EVE compiler is discussed in detail. In this chapter the translation of the EVE language into ADAM assembler code is discussed in two steps. In the first step the translation of all EVE constructs into dataflow graphs is shown. In the second step these dataflow graphs are translated into ADAM assembler code.

• Chapter 5 contains a discussion of the experiments that have been performed with this compiler. There also some experiences with implicit parallel programming and the translation to code that simulates a dataflow behaviour are presented. Furthermore, some ideas for optimizing the generated code are discussed there.

2Actually, the ADAM machine is a hybrid dataflow/controlflow machine. For a detailed discussion of this machine see Chapter 3.

3Compared with other current functional languages EVE is rather austere and therefore not as convenient to use as these languages. However, it has all the principle advantages of a functional language.

4However, the specification of EVE in this thesis is complete and therefore no other references are needed to understand the details of the language.

• Chapter 6 summarizes the results of this thesis but also the results of the whole ADAM & EVE project.

• In Appendix A a formal description of the syntax analysis (the first pass in the compiler) is presented in the form of an attribute grammar.

• Appendix B shows a formal description of the code generation in the EVE compiler. This description is presented in the form of a denotational semantics. As this way of describing the code generation hides the operational aspect, it should be quite easy to read. Nevertheless, it gives a complete description of the process.

• Appendix C gives a summary of the ADAM instruction set and it discusses syntax and semantics of each ADAM instruction.

• Appendix D shows some EVE programs with which benchmarks have been made. Furthermore, these programs are referred to in Section 3.2 as typical examples for EVE programs.

• Finally, Appendix E shows the definition modules of all parts of the EVE compiler. As these definition modules are documented, this also gives a description of each procedure that has been used in the Appendices A and B.

Parallel Functional Programming

This chapter gives a general survey of parallel functional programming. In the first section an introduction to functional programming will be given. This part will be quite general without discussing details of the EVE language. However, at some points EVE syntax will be used for some expressions (these parts are written in typewriter style). Although the concrete syntax of EVE will be discussed in Section 3.2 the semantics of the expressions that are used in this chapter should be obvious from the context. Furthermore, it is discussed in this chapter how explicit and how implicit parallelism can be used in a functional language. For that some abstract machines for implementing functional languages are presented and it is shown how parallelism can be used in these abstract machines.

2.1 Introduction to Functional Programming

In this section we give a short introduction to functional programming in general. Due to the lack of space only the most important features of functional programming languages will be discussed here. For a detailed introduction the interested reader may refer to [Bird and Wadler, 1988] and [Field and Harrison, 1988]. Both books give an excellent introduction to the various aspects of functional programming without requiring any previous knowledge in that field.

During the last years many functional languages have been developed. The most prominent functional languages today are the following:

• Haskell (see [Hudak et al., 1991]) is the most modern functional language. It has been designed by a committee that represents the modern school of functional programming. It is an attempt to create a standard functional language and by that to base the research in this area on a common language. This approach is motivated by the fact that most of the other functional languages differ just in syntactic details but do not include important semantic differences. The innovative new feature of Haskell is the handling of classes that form a hierarchical structure of types with the corresponding functions on it. For an introduction to Haskell see [Hudak and Fasel, 1992]. A definition of the static semantics of Haskell can be found in [Peyton Jones and Wadler, 1991].

• SASL (see [Turner, 1976]) was one of the first languages that included the concept of non-strictness. It also already included some kind od pattern matching. Note that SASL was the predecessor of Miranda.

• Miranda^{1} (see [Turner, 1985]) is one of the most popular functional languages that has a syntax that is in many areas very similar to that of Haskell.

• Standard ML (SML) (see [Milner, 1984], [Wikström, 1987], [Harper, 1990]) has as its main advantage a rich type system. Furthermore, this language has also become very popular since there exist very efficient compilers for many different machines. However, one major difference to the above languages is the fact that SML has a strict semantics of user defined functions. For a discussion of the notion of strictness see Section 2.1.1.

• Lazy ML (LML) (see [Johnsson, 1987], [Augustsson, 1987]) is the version of ML with a non-strict semantics. The compiler for this language that is described in the above papers is based on the G-machine, which is one of the most important abstract machines for graph reduction (see Section 2.3.2).

• Hope (see [Burstall et al., 1980]) is used in [Field and Harrison, 1988] to describe functional programming in general and the implementation of functional programming languages.

• Ponder (see [Fairbairn, 1985]).

• Orwell (see [Wadler, 1985]).

• Id (see [Nikhil, 1988]) is a functional programming language augmented with a parallel data-structuring mechanism called I-structures. It is especially interesting for us as it was designed for a parallel implementation on a dataflow machine (see also [Traub, 1986]).

• FL (see [Backus et al., 1990]) has developed out of the ideas that were presented in the famous talk of J. Backus ([Backus, 1978]) that marks the actual start of modern functional programming. However, its syntax notably differs from the syntax in most other functional languages.

The language that will be used in the language specific parts of this chapter is EVE (these parts are written in typewriter style). However, as we will concentrate on general concepts of functional programming in this chapter, the results can be also applied to most of the other functional languages mentioned above.

The main characteristic of functional languages is referential transparency. This means that the result of the evaluation of an expression only depends on the values of the free variables in that expression. Or in other words: The evaluation of an expression does not perform any side-effect. For functions that means that two calls of a function with the same parameters and same values of global variables always yield the same result.

One consequence of this characteristic is the fact that there are no variables in the sense of variables in imperative languages. There a variable is considered as a box where one can put values into and take values out of. Contrary to this box model, in functional languages a variable stands for a certain value. Therefore, one can say that the variable is bound to this value. This is also sometimes called the single assignment rule of functional languages (although this is no assignment in the usual sense).

1Miranda is a trademark of Research Software Limited.

Another consequence of referential transparency is the fact that for any expression it is possible to evaluate its subexpressions in any order, especially they can be evaluated in parallel. The theoretical justification for this is given by the Church-Rosser Property (an exact formulation and a proof of this property can be found in [Barendregt, 1984]). This property states that for the evaluation of an expression to its result any evaluation strategy may be chosen. This property is the basis for all parallel implementations of functional programming languages.

To explain this property with a concrete example let us look at the following expression:

square (1 + 2)

where the function square is defined as follows:

square x = x ∗ x.

One possible evaluation strategy of this expression is first to evaluate the argument (1 + 2) and then to use the definition of square to get the final result of the expression:

\begin{matrix}square (1 + 2) → & square 3 \\ → & 3 ∗ 3 \\ → & 9. \end{matrix}

However, another possible evaluation strategy is to insert the unevaluated argument (1 + 2) into the definition of square and to evaluate the argument afterwards:

\begin{matrix}square (1 + 2) → & (1 + 2) ∗ (1 + 2) \\ → & 3 ∗ 3 \\ → & 9. \end{matrix}

From this evaluation sequence one can see how subexpressions can be evaluated independent of each other.

One interesting feature about these different kinds of evaluation strategies is that they express different degrees of expressive power of the underlying language. Therefore, the choice of the evaluation strategy depends on the definition of the semantics of the underlying language. This is discussed in more detail in Section 2.1.1.

It is beyond the scope of this thesis to give a detailed introduction to all aspects of functional programming. Therefore, we give, after having explained the basic idea of functional programming, just a short sketch of the most important features of functional programming languages:

• Functional programming languages offer a very convenient way of defining compound data structures and of handling such data structures. Typical classes of types that can be defined are lists and tuples.

• In most functional programming languages it is allowed to use patterns instead of simple variables at the left hand side of a function definition. When such the function is called the structure of this argument is compared wih the pattern in the function definition. Only when they match the right hand side is evaluated and taken as the result of the function. In the evaluation of the right hand side the variables in the pattern are bound to the appropriate components of the argument. This whole mechanism is called pattern matching. With this construct it is possible to develop an equational style of defining functions that has a strong resemblance to the usual mathematical notation.

• A very powerful construct of functional languages are higher order functions. These are functions that take other functions as arguments or yield a function as a result. The power of such functions lies in their flexibility. For example in most functional languages there is a function map that applies another function f that is an argument of map on each element of a list l that is the second argument of map. By using this function map with different functions f one can solve many different problems.

• Many functional languages allow a special way of defining lists that bears a strong resemblance to the way how sets are usually defined in mathematical notation (ZFnotation). This construct is called a list comprehension. However, as some functional languages also have the builtin type of an array available, in these languages this construct is enhanced to the construct of an array comprehension. Note that especially array comprehensions contain much parallelism as one can describe quite complex data dependencies with this construct. For a detailed discussion of this so called wave front parallelism in array comprehensions see [Hudak, 1986a].

• Another possibility that offer most functional languages is to call a function with fewer parameters than it was defined. The result of this currying is a function in the remaining arguments.

• User defined operations can usually be defined as prefix or as infix operations. Furthermore, prefix operations can be transformed into infix operations and vice versa. This flexibility in the usage of user defined functions increases the readability of functional programs very much.

For a detailed explanation of all these functional language constructs see [Bird and Wadler, 1988].

2.1.1 Strictness

One of the most important features of a functional language is its strictness property. Formally, strictness can be defined as follows:

Definition 1 (Strictness) An n-ary function f ist strict in its i-th argument iff

∀x_{1}, …, x_{i−1}, x_{i+1}, …, x_{n} : f(x_{1}, …, x_{i−1}, ⊥, x_{i+1}, …, x_{n}) = ⊥

where ⊥ stands for non-termination or an error.

If a function is strict in its i-th argument it is safe to evaluate that argument before the function itself is called. The reason for that is that if the evaluation of the argument does not terminate (and thus yields ⊥) the above definition tells us that also the whole function application does not terminate. On the other side if a function is non-strict in its i-th argument the function might return a result even when the evaluation of the i-th argument does not terminate. To make this clearer let us look at the usual if-then-else construct:

                            IF E_{1} THEN E_{2} ELSE E_{3}.

This construct is obviously strict in the first argument (E_{1}) as it can not return a result when the evaluation of the boolean expression E_{1} does not terminate. However, it is non-strict in the other two arguments (E_{2} and E_{3}) as only one of these arguments will actually be evaluated.

One can generalize this definition of strictness to data structures when we define a data structure to be strict iff its constructor function is strict. The consequence of a non-strict data structure is that the data structure can be used although not all of the components of this data structure have been evaluated. Therefore, in any implementation of non-strict data structures it must be guaranteed that a value is written into a field of a data structure before this field is actually read.

The strictness property of a language determines the expressive power of the language. The reason for that is the fact that the evaluation strategy is determined by the strictness property of the language. If the language is strict (i.e. if all user defined functions are strict in all arguments) then an eager evaluation strategy can be chosen that evaluates the arguments of the function before the function itself is called (therefore this evaluation strategy is also often called call-by-value strategy). Note that all imperative languages are strict and therefore use this evaluation strategy. Some examples for strict functional programming languages are SML and Hope. However, when the language is non-strict usually a lazy evaluation strategy is used, that evaluates the argument of the function as soon as it is needed inside the function but usually not already when the function is called (therefore this evaluation strategy is also often called call-by-need strategy). Note that this is not the only possible evaluation strategy that fulfills non-strictness. Other possible evaluation strategies would be a “parallel non-strict” strategy that evaluates the argument in parallel with the function. If this strategy is fair a non-termination of the evaluation of the argument does not necessarily mean that the evaluation of the function does not terminate either. Another evaluation strategy that conforms to a non-strict semantics but that is different from the lazy evaluation strategy is the lenient evaluation strategy that is described in [Traub, 1991]. Examples for functional languages with a non-strict semantics are Haskell, Miranda, LML, SASL, Ponder, Orwell.

The increase in expressive power in non-strict languages has two consequences:

• In non-strict languages circular data structures can be defined. One example for such a data structure is an (conceptually) infinite list of ones:

         ones : IntList == 1 :: ones^{2}.

2The type IntList is defined as follows:

                                   IntList == LIST OF INT.

Such a list is called a circular list. As this list can be represented by only one cell in which the tail points to the cell itself, this is a very efficient representation of a list.

• In non-strict languages circular data dependencies can be expressed. One example for that is the following definition of a list of all Fibonacci numbers:

    AddLists[l1,l2] : IntList[IntList,IntList] ==
       IF NULL[l1]
         THEN l2 ELSE
       IF NULL[l2]
         THEN l1
         ELSE {l1hd : INT == HEAD[l1];
               l1tl : IntList == TAIL[l1];
               l2hd : INT == HEAD[l2];
               l2tl : IntList == TAIL[l2];

}

              l1hd+l2hd :: AddLists[l1tl,l2tl];
    MAIN : IntList == 1 :: 1 :: AddLists[MAIN,TAIL[MAIN]];

The idea of this definition is to get the list of all Fibonacci numbers starting from the third element by componentwise adding the list of all Fibonacci numbers with the list of all Fibonacci numbers starting with the second element. Hence, the third element of the list MAIN is the sum of the first and the second element. Therefore, in the list MAIN there is a circular data dependency as the i-th element of the list depends on the (i − 1)-st and on the (i − 2)-nd elements. However, there is no circular data structure created as it was the case in the list ones above. As each element of the list fibs is only evaluated once and can then be used for the evaluation of succeeding list elements this is already a definition where the computation of the i-th element takes linear time (contrary to the straightforward definition that takes exponential time). From this one can see that circular definitions are also very time efficient. For a detailed discussion of the efficiency of such circular definitions see [Loidl, 1991].

However, a non-strict language has not only more expressive power but it also contains more inherent parallelism than a strict language:

• In a non-strict language the function itself can be evaluated in parallel with its arguments. When the function returns a value even before all arguments have been finished the result need not explicitly synchronized with the unevaluated arguments.

• In a non-strict language a data-structure may be used before all of its components have been evaluated. This fact allows a very convenient way to express the so called producer-consumer parallelism, where one process (the producer) creates a big data structure and another process (the consumer) performs some operations on the components of this data structure. Due to the non-strict semantics of data-structures, both processes may be executed in parallel when they are connected with an ordinary function composition. To make this clearer, let us look at the following example:

    producer[n] : IntList[INT] ==  n :: producer[n+1]

         consumer[l] : IntList[IntList] ==
            { hd : INT == HEAD[l];
              tl : IntList == TAIL[l]

}

            square[hd] :: consumer[tl];
         squares : IntList == consumer[producer[1]];

In this example a list of the squares of all natural numbers is computed by executing the functions producer and consumer in parallel. As soon as producer has created a list element, this element can be taken by consumer and squared. Note that this definition of squares is equivalent to the following definition (the function From[1] creates a list of all integers starting from 1):

         squares : IntList == map[square,From[1]]

Even the runtime behaviour is similar to the above definition as map is a non-strict function that can be evaluated in parallel with From[1]. However, the first definition makes the producer-consumer structure more explicit.

Because of these advantages of non-strict languages compared to strict languages, we will only discussed non-strict languages in the further chapters.

2.2 Explicit Parallelism in Functional Languages

As it has already been mentioned in Chapter 1, functional languages are well suited to parallel computation. The main reason for that is the fact that these languages contain much inherent parallelism that can be exploited by a compiler. However, it is also possible to add explicit parallelism to these languages. In this section we will discuss this approach to parallelism.

If one wants to enhance a programming language by explicit parallel constructs one has to implement the following operations:

• The creation of a new thread of control,

• the synchronization of several threads of control and

• the communication between different threads of control.

In [Reppy, 1991] this has been done by extending Standard ML yielding an explicitly parallel functional language that is called CML. In CML the creation of a new thread of control is performed by a call to the function spawn. This function takes another function as argument that will be evaluated in the new thread of control. The return value of the spawn function is an identifier of the new thread. The thread terminates when the evaluation of the function has been finished or when a special function exit is called. For the communication between the different threads channels are used. Via these channels synchronous message passing is possible by using the functions send for sending a message on a channel and accept for reading a message from a channel. However, in [Reppy, 1991] it is also shown how a dataflow like style of programming can be used by implementing streams in this model. Furthermore, it is shown how futures as they are introduced in [Halstead, Jr., 1985] can be simulated in this model. A new data type events is added to the language that represents synchronous operations. To make the handling of parallelism in that model easier some functions are defined on this new data type. The actual simulation of parallelism is performed by passing continuations.

Note that with this way of introducing parallelism to a functional language referential transparency is lost. However, with SML this is not so serious as it is an impure functional language in the sense that it contains instructions that perform side-effects.

A similar approach has been taken in [Cooper and Morrisett, 1990]. The functions for the creation and synchronization of threads are similar to those in the above paper. However, the later thread package also contains an own data type for mutual exclusion locks. Furthermore, in [Cooper and Morrisett, 1990] the implementation of this package under the operating system Mach is discussed in detail.

However, both papers show that the problems in parallel programming that arise when using explicit parallelism in a functional programming language are essentially the same as the problems with explicit parallelism in an imperative programming language. On the other hand, it is possible to implement with this approach quite efficient parallel algorithms as one has direct influence on the parallel behaviour of the program but one can not exploit the implicit parallelism of the functional programming language.

2.3 Parallel Implementation of Functional Languages

In order to extract the implicit parallelism out of a functional program one needs an appropriate abstract machine into which the functional program is translated. However, for the sequential implementation of functional languages several abstract machines have been introduced in the literature. These machines vary in their degrees of abstraction from the physical machine model. But even more important, they are based on different models of computation that can be distinguished by the way how the computation process is driven.

For a parallel implementation of functional languages it is now essential how parallelism can be expressed in these different models of computation. Luckily, most of these models are on a quite high level of abstraction such that they do not reflect the sequential computation model of a von-Neumann processor any more. The reason for that high level of abstraction is the fact that the functional languages themselves are on a very high level of abstraction.

The most important kinds of abstract machines for the compilation of functional programming languages that have been introduced so far are:

• The lazy SECD machine,

• graph reduction machines,

• dataflow machines.

One of the best books dealing with the sequential implementation of functional programming languages is [Peyton Jones, 1987]. In this book a graph reduction machine is used as underlying abstract machine. In one chapter an outlook to parallel graph reduction is given. The new book [Peyton Jones and Lester, 1992] gives a practical approach to understanding implementations of non-strict functional languages using lazy graph reduction. Concerning parallelism, the parallel G-machine is presented in this book. Furthermore, [Field and Harrison, 1988] also discusses functional languages and their sequential implementation. However, in this book all of the three abstract machine models that are discussed here are covered. For a general survey of parallel functional programming including presentations of sequential and parallel implementation techniques and of parallel functional programming techniques see [Kelly, 1989]. Another discussion of the parallel implementation of functional programming languages that is mainly based on parallel graph reduction machines can be found in [Peyton Jones, 1989]. Furthermore, [Szymanski, 1991] discusses various approaches to parallel functional programming, each of which is presented by another author.

In the following sections we will now study the parallelism that is contained in each of these abstract machines. For that we will for each abstract machine first present the model of computation upon which it is based. Then it will be discussed how much parallelism can be exploited in this model. Furthermore, for each machine examples of sequential and parallel implementations will be given.

2.3.1 The Lazy SECD Machine

The SECD machine is a stack based machine that has been introduced in [Landin, 1964]. However, the classical SECD machine can only be used together with eager evaluation i.e. the arguments are evaluated before they are passed to a function (call-by-value). In order to get a machine that can handle lazy evaluation i.e. the arguments are only evaluated when their results are really needed (call-by-need) some changes in the machine have to be performed. The main change is the introduction of a suspension consisting of an expression and an environment. Such a suspension represents an unevaluated argument of a function.

One of the first extensions of the SECD machine for lazy evaluation is given in [Burge, 1975]. Such a lazy SECD machine has been used in an early implementation of SASL (see [Turner, 1975]). Another variant of the lazy SECD machine is the FLFM machine (see [Takeichi, 1986]) upon which an implementation of the language RUFL is based (see [Wentworth, 1989a]). As this is a rather conventional model of computation the whole evaluation process is driven by an explicit controlflow. Therefore, this model can be called a control-driven model.

As the SECD machine is based on the principle of a single controlflow it is quite badly suited for a parallel implementation. Moreover, even in the sequential case the other models have proven to be as efficient as the lazy SECD machine. But on the other hand it is easier to compile a functional program to the code of a graph reduction or a dataflow machine as these two machines are on a higher level of abstraction. Therefore, only these two models will be discussed with respect to their parallelism.

2.3.2 Graph Reduction Machines

The idea in this model is to represent a functional program as a graph and to execute the program by applying transformations (‘rewriting’) on this graph. This idea is shown in Figure 2.1 where the expression (2 + 3) ∗ (4 − 1) is evaluated by performing rewrite operations on its graphical representation. Note that it is in general not necessary to evaluate the arguments before the rewriting of the function can be performed. In Figure 2.1 the arguments are evaluated first because the top-level function (∗) is strict. If it were nonstrict the values of the arguments would not be needed and the rewriting of the whole function could be performed immediately. Only when the function is strict the evaluation of the arguments has to be demanded before the rewriting of the function itself can be done. Due to this behaviour this model is also called a demand-driven model. It is especially useful for implementing lazy evaluation.

In general, these rewrite rules can be a fixed set of rules as in the case of the SKI-reduction machine that is described in [Turner, 1979] or the rules might be derived as so called ‘supercombinators’ from the given program itself^{3}. This method has first been used in [Hughes, 1982].

[figure: ∗; @; @; @; +; 3; 2; @; @; @; −; 1; 4; ∗; @; 5; @; 3; 15; ∗; @; 5; @; @; @; −; 1; 4]

Figure 2.1: Sequential Graph Reduction of the Expression (2 + 3) ∗ (4 − 1)

One machine for the execution of super-combinators is the Three Instruction Machine (TIM) ([Fairbairn and Wray, 1987]) which is used for the implementation of the functional language Ponder. This super-combinator approach has also been taken in the G-machine (see [Johnsson, 1987], [Augustsson, 1987]) and this has led to one of the most efficient implementations of a lazy functional language (LML) up to now. The characteristic novelty in the G-machine is the fact that each function is translated into a piece of code that simulates the process of graph reduction. Therefore, when a function is called this code sequence will be executed instead of performing an interpretive graph reduction. An improved version of the G-machine, namely the spinless, tagless G-machine is currently used in an implementation of Haskell at

3For an explanation of the notion of ‘super-combinator’ and a further discussion of the differences between these two approaches see Section 4.3.

the University of Glasgow. In [Peyton Jones, 1991] this spineless, tagless G-machine is presented. Especially, the differences to the original machine and the mapping of the machine onto hardware by using C as a portable assembler are discussed in detail there.

Now let us consider how a parallel implementation of this machine could be realized. The obvious idea to introduce parallelism into this model is to simultaneously rewrite independent subgraphs. Due to referential transparency the result with this reduction strategy will be the same as the result with a sequential reduction strategy.

[figure: ∗; @; @; @; +; 3; 2; @; @; @; −; 1; 4; ∗; @; 5; @; 3; 15]

Figure 2.2: Parallel Graph Reduction of the Expression (2 + 3) ∗ (4 − 1)

Figure 2.2 shows the parallel reduction process for the same expression as in Figure 2.1. Note that in this case one step less is needed as the subexpressions (2 + 3) and (4 − 1) can be evaluated in parallel.

We will now discuss the principle structure of an algorithm for parallel programmed graph reduction. For that we assume an architecture consisting of the following components:

• One graph that represents the actual state of the program,

• a memory containing code for the evaluation of every function in the program (as it has been described for the G-machine this code simulates the reduction of a graph without performing operations on the graph itself),

• a task pool containing all those subgraphs that have to be reduced,

• several processors.

Algorithm for parallel programmed graph reduction:

1. The root of the graph is put into the task pool.

2. One of the idle processors takes a task out of the task pool.

3. When a processor comes in the evaluation of his task to a call of a function (f E_{1} … E_{n}) he determines which expressions are needed for the evaluation of f^{4}. The expressions

4This can be done by performing a strictness analysis on the function f at compile time. As a result this analysis yields the information which arguments of the function are in any case needed for the evaluation of f itself.

that are needed for the evaluation of f are put into the task pool and the evaluation of the function application is blocked.

4. Other processors take these expressions out of the task pool and evaluate them.

5. As soon as all necessary expressions have been evaluated the application of f can be evaluated^{5}.

Although the above algorithm is very general, it shows the principle handling of parallelism. Especially, it shows that the creation of parallelism only occurs in function applications. Furthermore, strictness analysis is used to determine which expressions can be evaluated in parallel. To be more exact, strictness analysis is used to create parallelism^{6}. As only those expressions are evaluated in parallel that are definitely needed for the evaluation of the function this model is based on the idea of conservative parallelism.

Some machines that implement this model of parallel graph reduction are:

• The ALICE ([Cripps et al., 1987]) machine was the first hardware implementation of a parallel graph reduction machine. It is based on transputers. The programming language for this machine is Hope.

• The Flagship machine ([Watson et al., 1988]) is the successor of the ALICE machine. This machine contains special hardware support for the transmission of whole subgraphs between different processors.

• The GRIP (Graph Reduction in Parallel) ([Peyton Jones et al., 1987]) machine is a shared-memory multiprocessor designed for efficient parallel evaluation of functional languages, using compiled graph reduction.

• The PAM (Parallel Abstract Machine) ([Loogen, 1990]) is another abstract machine for programmed parallel graph reduction that has been implemented on a network of transputers.

• In [Goldberg, 1988] the implementation of the abstract graph reduction machine Alfalfa on two different hardware architectures (one shared-memory machine and one distributed memory machine) is presented. However, Alfalfa is actually a hybrid of a graph reduction machine and of a conventional stack oriented machine. It is programmed in the functional programming language ALFL (see [Hudak, 1984]), which bears a strong resemblance to SASL.

• Furthermore, several parallel extensions of the G-machine have been presented in the last years e.g. in [George, 1989], [Burn, 1988] and [Augustsson and Johnsson, 1989].

2.3.3 Dataflow Machines

This model is based on the idea to view the data as the dynamic entities, which flow between a collection of operations, which are the static transformers that manipulate this data. The

5In order to notify the function f about the availability of all necessary expressions, each of these expression carries a reference to this application and sends its result to this reference when it has been evaluated.

6Note that in the dataflow model it is the other way round.

collection of operations forms a directed acyclic graph with the free variables as its input arcs and the result of the computation as its output arc. This graph is then called a dataflow graph. Note that in this model the computation is driven by the availability of data (data-driven model). Therefore, this model is especially appropriate for implementing eager evaluation. However, e.g. in [Field and Harrison, 1988][Chapter 14] it is shown how a demand-driven dataflow machine can be realized.

To explain this basic idea of the dataflow model we examine the following mathematical expression:

\begin{aligned}(a + b) ∗ (−y) where & a = −x \\ & b = x/y \end{aligned}

Figure 2.3 shows the dataflow graph that corresponds to this expression. In this dataflow graph the nodes represent operators and the arcs between the nodes represent the data dependencies between those operators. The input arcs denote the free variables of the expression and the output arc denotes the result of the expression. In the execution of the program data tokens that carry values flow along these arcs. Whenever at an operator tokens are present at each input arc this operator fires i.e. it takes these tokens from the input arcs, performs an operation on their values and it creates an output token with the computed result.

When one wants to compile a functional language to a dataflow graph there are more than only the arithmetical/boolean operations required. For example the call of another function is in the dataflow graph realized by creating a dynamic link from the call of the function to the function definition itself such that the arguments of the function can be passed to its definition along this arc. A list of all necessary operations with a description of each operation can be found in [Field and Harrison, 1988][Chapter 14]. For a more detailed description of the dataflow model see Chapter 3.1.

It should be clear from this description of dataflow machines that this architecture is very appropriate for a parallel implementation as it entirely lacks the concept of a controlflow. Moreover, the only restriction to the parallelism in a dataflow machine is given by the data dependencies between different operations (these are the arcs in the dataflow graph). Therefore, a dataflow machine can extract the maximal parallelism out of a given program. However, this usually introduces too much parallelism to be very efficient. Therefore, one of the main trends in the research of dataflow machines goes into the direction of restricting this parallelism to get bigger blocks of code that can then be evaluated in parallel.

One approach to solve this problem is the introduction of functional quads as they are described in [Traub, 1991]. This is a model of functional program execution where the whole program is split into a set of sequential threads. These sequential threads are segments of sequential code, where the instructions are ordered at compile time. However, the order in which the sequential threads execute is not fixed at compile time but it is allowed to vary according to the input data. The run-time switching between the threads is directed only by the communication of values between threads. The scheduling of the threads is controlled by tests to determine whether another subexpression has been already evaluated. These tests are realized by checking presence bits. This reminds of the concept of I-structures in the TTDA architecture.

One main idea behind the implementation of non-strict functional languages as it is presented in the above book is to use an evaluation strategy that has not as much expressive power as

[figure: /; −; +; ∗; −; x; y; 0 :; 1 :; 2 :; 3 :; 4 :; 5 :; 6 :; a; b]

Figure 2.3: A Dataflow Graph

lazy evaluation but still conforms to the non-strict semantics of the underlying functional language. As this evaluation strategy, which is called lenient evaluation, does not include as much run-time overhead as the lazy evaluation strategy, this results in more efficient code. With lenient evaluation expressions must be delayed until their input data are available, but not necessarily longer. Hence, it is possible to express with lenient evaluation complicated data dependencies like cyclic data dependencies. But it is not any more possible to use infinte data structures. However, it is still possible to realize infinite data structures with explicit delay and force statements.

Related work has been presented in [Sarkar and Hennessy, 1986], where a technique for partitioning dataflow code into “macro-dataflow” nodes is discussed, and in [Hudak and Goldberg, 1985] such a partitioning is shown for the model of serial combinators.

One machine in which the dataflow model is realized in hardware is the “Manchester Dataflow Computer” that is described in [Ruggherio and Sargeant, 1987]. Another dataflow architecture that was extensively simulated in software is the “TTDA Tagged Token Dataflow Architecture” at the MIT (see [Arvind and Nikhil, 1988]). The architecture that is used there strongly influenced the design of the ADAM machine at RISC (see [Iannucci, 1988]). Moreover, the literature on compiling the functional language Id to the assembler code of the TTDA architecture treats many of the problems that also had to be solved in the implementation of the EVE compiler (see [Traub, 1986]). For the successor architecture, the ETS Explicit Token Store architecture “Monsoon” ([Papadopoulos, 1988]) also a hardware implementation has been realized in a cooperation between the MIT and Motorola (see [Papadopoulos and Culler, 1990]). Another dataflow machine that has been simulated but not built in hardware is “The Stollmann Dataflow Machine” that is presented in [Treleaven, 1990]. However, this machine is mainly intended for applications in database management.

2.4 Using Annotations to Express Parallelism

In the previous sections it has been emphasized that a functional program that should be executed in parallel need not have any statements for explicit parallelism as functional languages are inherently parallel. However, it is a quite difficult task for a compiler to extract parallelism of an appropriate grain size from the program, to schedule and to map these blocks on the available processors in such a way that one really gets an efficient program. Therefore, one might argue that functional programming languages should hide details like scheduling and mapping from the programmer but also to offer the programmer the possibility to have direct influence on these details. The most convenient way to realize this is by using annotations in the functional language to express e.g. the scheduling or the mapping of a functional program.

The most prominent such concept is that of para-functional programming. This notion was coined by P. Hudak in [Hudak, 1986b]. Para-functional programming is an extension to the functional programming paradigm that allows to express operational details like scheduling or mapping without losing any of the benefits of functional languages, like the ability to perform equational reasoning on functional programs. Thereby, the functional behaviour of the program is still separated from the operational behaviour. Another advantage of this approach of using annotations is the fact that it can be used with any functional language.

With the annotations that are introduced via the concept of para-functional programming, the operational behaviour of a program can be influenced in two ways:

• One can specify a special evaluation order of the program and

• one can specify how to map a program to a machine.

Controlling Evaluation Order. The default evaluation order is lazy evaluation. However, this can be changed for any expression in the program by using a scheduled expression of the following form:

     exp sched sexp

where exp, the body of the scheduled expression, is another expression, sched is a keyword and sexp is a schedule whose structure is described below. Note that a subexpression in exp can be labelled by using the form lab@exp for the labelled expression.

A schedule defines a partial order on events, of which for every labelled expression lab@exp there are three kinds:

• The demand for the evaluation of exp, denoted by Dlab,

• the start of the evaluation of exp, denoted by ^lab,

• the end of the evaluation of exp, denoted by lab^.

Furthermore, the following operations are defined on schedules:

• s1.s2 denotes the concatenation of the schedules s1 and s2 (i.e. every event in s1 precedes every event in s2).

• s1|s2 denotes the concurrence of the schedules s1 and s2 (i.e. s1 and s2 are running in parallel).

With these constructs one can form the following scheduled expressions:

• (e0 m@e1 n@e2) sched Dm|Dn. This declares that the demands for the evaluations of the expressions labelled with m and n will begin in parallel at the point when the entire expression begins evaluation. Thus, this expressions denotes speculative parallelism of the subexpressions e1 and e2.

• o@(l@e0 m@e1 n@e2) sched l.m.n.o. This declares a left-to-right call-by-value semantics. Note that in a schedule lab is the abbreviation for Dlab.lab^. However, this schedule does not prohibit parallelism inside e0, e1 or e2.

Mapping Program to Machine. For denoting which expressions shall be evaluated on which processors a mapped expression of the following form is used:

     exp on pid

where exp, the body of the mapped expression, is an expression, on is a keyword and pid is the number of the processor on which it shall be evaluated.

Such an expression can be used for example to evaluate the two components of an addition on two different processors:

     (f x on 0) + (g y on 1)

With this expression the function call f x will be evaluated on processor 0 and the function call g y will be evaluated on processor 1. Note, that since + is a strict operation, both function calls will be evaluated in parallel due to the default evaluation strategy of lazy evaluation.

It is also possible to use functions in computing the processor identifier. Thereby, one can realize a mapping that is relative to the current processor (the predefined identifier self always contains the identifier of the current processor).

For a more detailed explanation of the concept of para-functional programming in general and especially of scheduling and mapping see [Hudak, 1989]. In this paper also some bigger examples are given that show how this concept can be used to create parallel functional programs with a special operational behaviour.

ADAM & EVE

The ADAM & EVE project has been started at RISC in 1989. The goal of this project is to study the behaviour of programs in the functional programming language EVE that have been compiled to the assembler code of the abstract hybrid dataflow/controlflow machine ADAM. The project has been influenced by the work on dataflow architectures at the MIT (see [Iannucci, 1988], [Traub, 1986]). However, the ADAM & EVE project puts an emphasis on using standard hardware instead of a dedicated dataflow machine. Therefore, the first part of this project was the implementation of the abstract dataflow machine ADAM that allows to simulate dataflow behaviour on conventional hardware. The second part of the project, which is described in this thesis, is the implementation of a compiler for the functional programming language EVE that generates assembler code for the ADAM machine. This code directly reflects the structure of a dataflow-graph that can be derived from the given EVE program. Therefore, the resulting ADAM assembler program is a parallel program.

This chapter contains a condensed description of the ADAM machine and of the EVE language as it is given in [Schreiner, 1990]. However, as the EVE language has been slightly modified the language description part contains some changes to the above thesis that are explicitly emphasized.

3.1 The ADAM Abstract Dataflow Machine

The ADAM machine is an abstract hybrid dataflow/controlflow machine. The ADAM processor has a conventional two-address instruction set augmented by primitives for the creation and synchronization of parallel tasks that allow the simulation of dataflow behaviour. Special memory units on each ADAM module are responsible for the management of non-strict data structures. With this “hardware support” for non-strict data structures it becomes easier to synchronize several processes that work on such a non-strict data structure.

In this thesis, the ADAM machine will only be described as far as it is necessary to understand the meaning of an ADAM assembler program. For more information about the implementation of the ADAM machine see [Schreiner, 1990].

3.1.1 The Dataflow Principle

For explaining the basic principle of a dataflow graph let us look at the following mathematical expression:

\begin{aligned}(a + b) ∗ (−y) where & a = −x \\ & b = x/y \end{aligned}

This expression can be translated into a dataflow graph by translating each operation in the expression into a node of the graph with one input arc for every operand of the operation and with one output arc. Therefore, the top-level multiplication is translated into a node with two input arcs (one for (a + b) and one for (−y)) and with one output arc, representing the result value of the whole expression. The two input arcs have to be connected with the output arcs of the subexpressions (a + b) and (−y). By recursively applying this translation on all subexpressions one gets the dataflow graph of Figure 3.1.

[figure: /; −; +; ∗; −; x; y; 0 :; 1 :; 2 :; 3 :; 4 :; 5 :; 6 :; a; b]

Figure 3.1: A Dataflow Graph

The whole graph has now one input arc for each free variable of the given expression. The output arc represents the result of the expression. This dataflow graph shows the datadependencies between the various variables in the expression. Therefore, it can be used as the basis for the so called data-driven execution model. In this model values are represented by tokens that flow along the arcs of the dataflow graph and trigger the execution of the instructions in the nodes of the graph. When a token reaches a node representing a binary instruction it has to wait for the arrival of a partner token on the other input arc (port) of the node. As soon as both tokens are available the node “fires” i.e. the instruction is performed on the input tokens (thereby consuming these tokens) and a new token with the result of the instruction is created and sent away via the output arc. This is called the firing rule of the dataflow graph.

Looking back at Figure 3.1 we notice that as soon as a value arrives at the y input, the node number 4 in the graph may fire, sending the result to the node number 6. However, this node has to wait for the arrival of a token on the other input arc i.e. from node number 5. From the above firing rule it should be clear that this data-driven execution model bears an inherent parallelism, since the order of the evaluations in the nodes of the graph is only determined by the data-dependencies between the various objects in the graph.

0 :  COPY 2, 3.l
1 :  COPY 3.r, 4
2 :  NEG  5.l
3 :  DIV  5.r
4 :  NEG  6.r
5 :  ADD  6.l
6 :  MUL  7

Figure 3.2: A Dataflow Program

The dataflow-graph in Figure 3.1 can be quite straightforwardly translated into a dataflow program by translating every node in the dataflow-graph into one line of the program. Each line has a number as label and contains the instruction that has to be performed and the successor instruction to which the result has to be sent (in the case of a copy instruction two successor instructions have to be included). If the successor instruction is a binary instruction, then also the port, to which the result is sent (l for “left port” and r for “right port”) has to be included in the line of code. By this consideration, it is quite obvious that Figure 3.2 is the translation of the dataflow-graph in Figure 3.1. Such a program can be executed by a dataflow machine.

3.1.2 Differences between the ADAM and a Pure Dataflow Machine

One of the main disadvantages of the pure dataflow model is the fact, that many copy instructions have to be performed as it is impossible in this model to share data. Hence, whenever a token is needed by two different instructions it has to be copied. Therefore, the well known notion of a register has been introduced in the ADAM machine. This allows to replace the duplication mechanism of the pure dataflow model by sharing data that is contained in registers. However, to maintain the correct execution order an explicit synchronization instruction has to be introduced that “guards” every binary instruction as it can be seen in Figure 3.3. The first argument of this synchronization instruction (WAIT)

0 :  COPY x_{1}, x_{2}, 2
1 :  COPY y_{1}, y_{2}, 4
2 :  SPLIT 3, 6
3 : NEG x_{1}, x_{1}, 8
4 :  SPLIT 5, 6
5 : NEG y_{2}, y_{2}, 10
6 :  WAIT e_{0}, 7
7 : DIV x_{2}, y_{1}, 8
8 :  WAIT e_{1}, 9
9 : ADD x_{1}, x_{2}, 10
10 :  WAIT e_{2}, 11
11 :  MUL  x_{1}, y_{2}, 12

Figure 3.3: An ADAM Assembler Program

is a so called event. An event has two possible states: “occurred” and “not occurred”. Initially it is set to “not occurred”. As soon as it is reached by the first thread of control (this corresponds to the arrival of a token in the dataflow model) it is set to “occurred” and this thread is blocked. When another thread of control reaches this instruction the state of the event is reset to “not occurred” and a jump to the next instruction is performed, which is usually a binary instruction. Now, it is guaranteed that both registers that are affected by the binary instruction contain values. The instruction is now performed on the two registers and the result is written to the first register, as it is done for all two-address instructions of the ADAM assembler code. Furthermore, one thread of control continues at the successor instruction, whose label is the last component of each line. Note that now the threads of control announce the availability of a value in the corresponding register, where in the pure dataflow model tokens that carry values have been sent. Summarizing one can say that in the ADAM model arcs of the dataflow graph denote registers and nodes represent two-address instructions operating on these registers.

0 :  WAIT e_{0}, 1
1 :  COPY x_{2}, x_{1}, 2
2 :  DIV  x_{2}, y_{1}, 3
3 :  NEG  x_{1}, x_{1}, 4
4 :  ADD  x_{1}, x_{2}, 5
5 :  NEG  y_{1}, y_{1}, 6
6 :  MUL  x_{1}, y_{1}, 7

Figure 3.4: An Optimized ADAM Assembler Program

As one can see from Figure 3.3 the ADAM machine has a conventional two-address instruction set. The only instructions that are needed to introduce parallelism are:

• The SPLIT instruction that splits the controlflow into two parallel threads of control.

From the dataflow point of view, this instruction represents a node that takes one input token and splits it into two independent output tokens. This construct is used to create parallelism.

• The WAIT instruction that joins two parallel threads of control in the controlflow into one thread. From the dataflow point of view, this instruction represents a node that takes two input tokens and merges them into one output token. This construct is used to synchronize parallel parts of the program.

The approach taken in the ADAM machine has two important advantages in comparison to a pure dataflow machine:

• In contrast to a pure dataflow machine the ADAM machine needs not explicitly duplicate data that is referenced by more than one instruction. Instead in the ADAM machine the data is shared in common registers.

• As the synchronization is done explicitly in the ADAM machine the compiler may choose to avoid some synchronizations whenever this is safe. Thereby, the compiler can serialize the code and increase the efficiency of the whole program.

To decide whether it is safe to serialize some parts of the code, a formal analysis of the program, called strictness analysis, has to be performed. The strictness analysis tells the compiler, which arguments of a function are strict and can therefore be evaluated sequentially without violating the non-strict semantics of the language.

Figure 3.4 shows an optimized version of the ADAM assembler program in Figure 3.3. As the original expression contains only arithmetical operations, which are strict in all arguments, the whole program can be evaluated sequentially. The only necessary synchronization in the whole arithmetical expression is at the begin of the program where a wait for the two free variables x and y has to be performed.

3.1.3 The Global Sructure of the ADAM Machine

In this section the global structure of the ADAM machine will be discussed as far as it necessary for understanding the operational behaviour of the ADAM machine. Especially, some aspects of the memory management have to be discussed.

The ADAM machine consists of several equally constructed worker modules that are connected by a network of channels. The machine is based on the principle of distributed memory, so information that is needed by a remote module has to be explicitly sent from the module that contains the information to the one that needs the information. There is no possibility of sharing data between several modules.

Figure 3.5 shows the structure of one module in the network. Each module consists mainly of three parts:

• The Arbiter is responsible for the communication between the local module and the other modules.

Processor Unit

Memory Unit

Arbiter

[figure] [figure]

From Network

To Network

Figure 3.5: An ADAM Module

• The Processor Unit executes the given program and thereby performs the actual computation. The instruction set of this unit consists mainly of two-address instructions where the first argument gets the result of the computation. Therefore, the meaning of e.g.

ADD r_{1}, r_{2}, +1

is

r_{1} := r_{1} + r_{2}

and continue the execution with the next line. Apart from such arithmetical-logical instructions there are also instructions affecting the controlflow (like the well known JUMP instruction), instructions for calling functions (either first order or higher order) and instructions affecting the storage of all data structures. For a description of the meaning of all ADAM instructions see Appendix C.

• The Memory Unit contains a store holding tuples, i.e. data structures of variable size. For the construction of tuples, for the selection of parts of the tuple and for writing values into parts of a tuple special messages have to be sent to the memory unit. These messages and the operational behaviour of the memory unit will be discussed below.

Memory Management

The memory unit of an ADAM module deserves further examination. As the EVE language is defined to be a non-strict programming language and therefore uses non-strict data structures the ADAM machine must also be able to handle such non-strict data structures. This handling is done by the memory unit of an ADAM module.

The main problem of non-strict data structures is how to synchronize the parallel processes that want to write to or read from the data-structure. The way for solving this problem that has been chosen for the ADAM machine is the usage of so called I-structures as they are discussed in [Traub, 1986] and [Arvind et al., 1987]. The idea of this approach is to associate with each field of the data structure a full/empty flag. This flag indicates, whether a value has already been written into this field or not. The status of this flag determines the action that has to be taken when one of the following messages arrives at the memory unit:

• Allocation Request: ADAM instruction:

ALLOC constant, label

A new tuple of the size constant is allocated and all flags of the tuple are set to empty. A reference to the tuple is returned by the memory unit and arrives at the next line in the ADAM assembler program.

• Read Request: ADAM instruction:

READ constant, register, label

With this instruction the value of field number constant of the tuple to which register points is read. If a read request arrives one has to distinguish two cases:

Case flag=full: In this case the value of the requested field is returned.

Case flag=empty: In this case the read request is put into a queue of deferred reads.

• Write Request: ADAM instruction:

WRITE constant, register, register

With this instruction the contents of the first register is written into the field number constant of the tuple to which the second register points. Again a case distinction is necessary:

Case flag=full: This case violates the single-assignment rule. As this case can not occur in correct ADAM code the program is aborted in this case.

Case flag=empty: If there is a queue of deferred reads associated to this field, then the value that will be written into the field is sent as answer to each of the read requests. Afterwards, the value is written into the specified field.

The important advantage of a non-strict data structure in comparison to a strict data structure with respect to parallelism lies in the fact that in a non-strict data structure there is a separation between the creation and the usage of the data structure. For example, after having created a non-strict data structure two references to it may be immediately sent to two different processes:

• One process (the producer) writes values into the data-structure,

• the other process (the consumer) reads values from this data-structure.

Since the data structure is non-strict, both processes can work at the same time. If the data structure were defined strict, the consumer would have to wait until the producer has computed the whole data structure. With this kind of producer-consumer parallelism it is possible to achieve a much higher degree of parallelism than in a strict language.

Another advantage of non-strict data structures stems from the fact, that the three ADAM instructions ALLOC, READ and WRITE are implemented as split-phase operations. This means that after having submitted one of the above requests the processor immediately interrupts the execution of the actual thread and starts another one. The interrupted thread is only continued at the arrival of the desired value. The advantage is now that, by this implementation, the ADAM machine tolerates extremely long memory latencies.

Another implementation detail that affects the operational behaviour of the ADAM machine is the implementation of the garbage collector. In the ADAM machine a weighted reference counting algorithm has been implemented (see also [Bevan, 1987]). The principle of this algorithm is the following: To each reference a weight and to each data structure a reference count is associated. Then, the following invariant holds: The reference count of a data structure is equal to the sum of the weights of all references to it.

To achieve this behaviour additional actions have to be taken when a request arrives at the memory unit:

• Allocation Request: The newly allocated tuple gets a power of two as its initial weight. It suffices to store the binary logarithm of the weight in the reference that is returned as the weight will always be a power of two.

• Deleting References: If a reference is not used any more it is deleted and the reference count of the referenced tuple is decreased by the weight of this reference. If thereby the reference count of the tuple becomes zero, the tuple may be reclaimed.

• Duplicating References: After the duplication each of the references gets half of the weight of the original reference. However, if the original reference has already weight one the reference count of the referenced tuple is set to ∞. As this reference count will never change any more the referenced tuple will never be reclaimed.

This garbage collection algorithm has the following advantages:

• The garbage collection is done on the fly,

• it is simple an fits very well for a parallel distributed memory system.

However, this algorithm also has some disadvantages:

• Cyclic structures will never be reclaimed as there is always a reference to each of the parts of the structure although the structure itself is not needed any more. This disadvantage could be overcome by implementing an additional garbage collector that reclaims such cyclic structures (e.g. a mark and scan, or a copying garbage collector) and use this garbage collector when the weighted reference garbage collector can not reclaim enough space.

• Every duplication of references involves their modification.

As we will see in the code generation step of the compiler one has to keep in mind that a tuple whose reference count drops to zero will be automatically reclaimed. And as instructions like READ and WRITE decrease the count of the structure, one has to explicitly guarantee that the reference will not be applied any more.

3.2 The EVE Programming Language

This section provides a reference manual for the functional programming language EVE. The goal in designing the language EVE was to come up with a programming language that allows an easy way of programming the ADAM machine. As it is one of the most important advantages of functional programming languages that they are highly suited to parallel computation we have chosen such a functional programming language. It would also be possible to use an imperative language that is extended by constructs for explicit parallelism. However, we prefer to use a functional programming language as it hides the details of parallelism from the programmer^{1}. Therefore, EVE does not contain any constructs for explicit parallelism. Moreover, the implicit parallelism of a program will be automatically extracted by the EVE compiler (for a description how this is done see Chapter 4). The EVE languages incorporates most of the important features of todays functional programming languages but it lacks some of the constructs that can be expressed by existing features. This strategy has been chosen to make a prototype implementation of the compiler easier as the language is kept quite small.

This reference manual is based on the reference manual for EVE that has been published in [Schreiner, 1990]. However, some minor changes of the language definition have been performed when the compiler was implemented. These changes in the language definition are explicitly marked in this reference manual.

3.2.1 Characteristics of the EVE Programming Language

Here the most important characteristics of EVE are listed together with a discussion of the effects of these characteristics with respect to parallelism:

• EVE is purely functional.

EVE contains no constructs that perform side-effects. Therefore, EVE if referentially transparent. The result value of an EVE function only depends on the values of the parameters and of the global variables that are used in the function. Variables can be viewed as names for expressions. Therefore, we do not say that a value is assigned but that it is bound to a variable. In all, the semantics of the programming language EVE is similar to the semantics of the usual mathematical notation.

• EVE is non-strict.

In EVE a user-defined function may return a value before finishing all of its computation. Analogously, a data structure may be read before all of its components have been written into the data structure. This feature enables the user to exploit the producer-consumer parallelism, which has been described in Section 2.1.1, in a very straightforward way. For example function composition can be used to combine a producer process with a consumer process without introducing an additional synchronization point between the two processes as it would be the case in a strict programming language.

1For a discussion of the advantages of parallel functional programming compared with parallel imperative programming see Chapter 1.

• EVE is strongly typed.

For every newly defined object an explicit type annotation has to be given. The result of this is that all types can already be checked at compile time. There is also no type inference necessary. On the other hand, this yields a monomorphic language.

• EVE is higher-order.

In EVE functions are first order objects. That means that a function can be a parameter of another function, it can be the result of another function and it can be stored in a data structure.

• EVE is safe.

EVE programs never produce runtime errors that force a halt of the execution. Instead of aborting the computation a special error value is returned. This is important since it might turn out that the erroneous value is not needed for the result at all. In this case the result might be valid although the computation of some part has yielded an error.

As it has already been mentioned above, EVE lacks some of the features of todays functional programming languages^{2}. However, most of these features can be expressed with the existing features in EVE. The features that are not supported are:

• Pattern Matching,

• Type Inference and Polymorphism,

• Lambda Expressions and Curried Functions,

• List and Array Comprehensions,

• Delayed Evaluation,

• Abstract Data Types and Modules,

• Classes of Types.

Some remarks on the notation that is used:

• EVE program code is written in typewriter style e.g.

      f[x]+1 < a.

• Non-terminal symbols of the EVE grammar are written in italic e.g. Identifier.

• Comments start with a -- symbol and range to the end of a line e.g.

      -- This is a comment.

• Note that EVE is case sensitive. So the following identifiers are different

2For a description of the most important features of functional languages see Section 2.1.

    ident, Ident.

• Keywords and predefined identifiers are written in capital letters e.g.

    IF, CASE, HEAD, NULL.

3.2.2 EVE Program Structure

The overall program structure of an EVE program looks as follows:

Definition_{1} ; ; …; Definition_{n} ;

In every definition an identifier is bound to an object. Corresponding to the kind of this object one can distinguish between three different kinds of definitions:

• Type definitions,

• Constant definitions,

• Function definitions.

The details of these kinds of definitions are discussed in the next sections.

However, the prototype compiler imposes some restrictions on this general structure of an EVE program:

• The definition of an identifier in the EVE program must precede its usage. Therefore, only the identifiers that are defined in Definiton_{1} – Definition_{i} can be used in the body of the i-th definition. To be able to write mutually recursive programs, in which such a linear ordering of the definitions is not possible, a forward definition of types, constants and functions is possible.

• Every identifier may be defined at most once in its level of scope. More than one definition in the same level of scope will yield a semantic error of the compiler. But of course it is possible to use the same variable name in different blocks e.g. in different nestings of local definitions or in different functions.

• On the top level there are no constant defintions allowed except for the last definition that must define the constant MAIN. Note: This restriction was not contained in the previous version of the reference manual. The result value of an execution of the EVE program is the value of this constant MAIN. This differs to the description in [Schreiner, 1990], where a dialogue shell for calling the compiled functions is described.

42 CHAPTER 3. ADAM & EVE

Definitions

3.2.3 Type Definitions

The general form of a type definition in EVE is the following:

Ident == Type. With this definition the identifier Ident is bound to the type expression Type. As an exception to this form a forward defintion of a type looks as follows:

Ident. Such a definition is necessary when mutually recursive type definitions are used^{3}. The types in EVE can be distinguished in atomic types and compound types. Before both kinds of types will be discussed the rule is given that decides whether two types are equal. Two types in EVE are equal, iff

• Both types are the same atomic type or

• both types are compound types with the same top level type constructor and all

corresponding components of the two types are the same. Note: Differently to the description in [Schreiner, 1990], the equality of types is based on the structure of the types rather than on the names. Therefore, this approach to the equality of types is also called structure equivalence. One of the advantages of this approach is that type conversions between types that have the same structure but different names become superfluous. Atomic Types In EVE the following atomic types are present:

• Integers (INT),

• Booleans (BOOL),

• Characters (CHR). All of these atomic types are totally ordered sets. Integers

3Directly recursive type definitions are also allowed. For that no forward definition is necessary.

The Data Type. The atomic type INT consists of all integer numbers between the two predefined constants MININT and MAXINT. The total ordering on this type is the usual ordering on integer numbers. All prefix comparison functions on integers have an I instead of the * in Table 3.6. Hence, the test if the integer 0 is smaller than the integer 1 according to the ordering on integers can be written as follows:

     0 < 1

or

     ILT[0,1].

The actual values of these two constants may vary between different implementations. However, the following two propositions must hold for every implementation of an EVE compiler:

   • MININT < 0 < MAXINT,
   • INT == {n | MININT ≤ n ≤ MAXINT, n ∈ N}.

If an object becomes larger than MAXINT or smaller than MININT during the computation, a special error value is returned (the same happens if a division by zero occurs). See Section 3.2.11 for a discussion of the handling of error values.

Note: In the prototype implementation of the EVE compiler the range of integer constants is even more restricted than described above. Although, the values of MAXINT and MININT that can be handled by the ADAM machine are 2^{30} −1 and −2^{30}, respectively, every integer number in the source code must be in the range of 2^{16} − 1 and −2^{16}. This has the effect that the statement

       n : INT == 65536

will yield an error by the compiler whereas the following statements are accepted by the compiler:

      n : INT == 65535+1 ;
      m : INT == MAXINT.

Operations on the Data Type. The operations that are defined on this atomic type of fixed-size integer values (ordered by their precedence) are shown in Table 3.1. The meaning of these operations should be obvious.

When splitting a number n into a quotient and a remainder modulo a given number m the following propositions always hold:

• n = (n / m) * m + (n % m) ,

• 0 ≤ n % m < m .

Operator Function Name Description
-          NEG  Negation (unary)
*          MUL  Multiplication
/          DIV  Division
%          MOD  Remainder
+          ADD  Addition
-          SUB  Subtraction (binary)

Table 3.1: Integer Operators

Function Description
ABS  Absolute Value
ODD  Test, if number is odd

Table 3.2: Integer Functions

By default all of these binary operations are left associative. However, as in mathematical notation parentheses can be used to group subexpressions together. To distinguish the unary negation from the binary subtraction parentheses must be put around a negated expression. A valid EVE expression is then for example:

      a-b-(-c)*(d+e).

If one uses the predefined functions instead of the operations one gets the following expression:

      SUB[SUB[a,b],MUL[NEG[c],ADD[d,e]]].

Note: In the prototype compiler it is not possible to use any predefined function as an first order object as these functions are translated to inline code and are not present as independent functions. Therefore, the functions corresponding to the integer operations will be rarely used. However, if one needs a first order function for e.g. multiplication one has to define the function

      Mul[x,y] : INT[INT,INT] == x*y

which then can be used as e.g. the parameter of a higher order function (see also Section 3.2.9 for a discussion of higher order functions).

Additionally, to the functions and operations in Table 3.1 two predefined functions that are listed in Table 3.2 can be used on integer values. The semantics of these functions is the following:

Operation Function Name Description
~           NOT  Negation
&           AND  Conjunction
|           OR  Disjunction

Table 3.3: Boolean Operators

• ABS[i] yields the absolute value of the integer i,

• ODD[i] yields TRUE iff i is odd (i.e. i % 2 # 0).

Booleans

The Data Type. This atomic type consists only of two distinct values that are represented in EVE by TRUE and FALSE. The ordering on this type is defined by the following rule:

     FALSE < TRUE.

All prefix comparison functions on booleans have a B instead of the * in Table 3.6. Hence, the test if FALSE is smaller than TRUE according to the ordering on booleans can be written as follows:

     FALSE < TRUE

or

     BLT[FALSE,TRUE].

Operations on the Data Type. Table 3.3 shows the available operations on boolean values and the corresponding predefined functions. Again this table is ordered by the precedence of the operations. As for the integer numbers there exists an infix and a prefix version for each of the operations on the boolean values.

One important characteristic of the functions AND (&) and OR (|) is that they are non-strict in their second arguments. Hence, if the first argument of an AND operation is FALSE then the result of the whole operation is also FALSE regardless of the result of the second argument (this might also be an error value). The same holds for the OR operation with TRUE instead of FALSE. This behaviour is also called short circuit discipline.

Characters

Special Character Description  ASCII Code
\'  ’ in character constants  044
\"  ” in string constants  034
\\        \ itself  092
\b  Backspace  008
\f  Form Feed  012
\n  Line Feed  010
\r  Carriage Return  013
\t  Horizontal Tabulator  009
\_String Interrupt  —

Table 3.4: Special Characters

The Data Type. The atomic type CHAR contains all characters of the US-ASCII character set, which can be found in Table 3.9 in Section 3.2.13. The ordering on the characters can also be seen from this table as a character with a lower ASCII code number is smaller than one with a higher number. Therefore, when ORD[c] yields for a character c its corresponding ASCII code number then the ordering on CHAR can be described as follows:

Let c_{1}, c_{2} ∈ CHAR. Then c_{1} ≤ c_{2} iff ORD[c_{1}] ≤ ORD[c_{2}].

All prefix comparison functions on characters have a C instead of the * in Table 3.6. Hence, the test if the backslash character is larger than the digit 1 according to the ordering on characters can be written as follows:

     '\\' > '1'

or

     CGT['\092','1'].

In an EVE program a character must be enclosed in single quotation marks (’). The following characters may be used within these quotation marks:

• Any printable character from the ASCII character set (except backslash (\) and a single quotation mark (’)).

• An abbreviation of the form \l where l is a character code representing a special character (e. g. '\n' (Newline)). Table 3.4 shows a list of all special characters that are allowed in an EVE program.

• A code sequence of the form \ddd, where ddd is a three digit decimal ASCII code number from 000 to 255 (e.g. '\036' for $).

Note that the backslash character may only be used as an escape character or as the starting character of a code sequence (any other use is illegal and yields a lexical error of the compiler).

Function Description
CAP  Capitalize a letter
CHR  Convert ASCII code to character
ORD  Convert character to ASCII code

Table 3.5: Character Functions

Operator Function Description

                         <       *LT  Less

<=       *LE  Less or Equal
=       *EQ  Equal
#       *NE  Not Equal
<>       *NE  Not Equal
>=       *GE  Greater or Equal
>       *GT  Greater

Table 3.6: Comparison Operators

Operations on the Data Type. Table 3.5 shows all predefined functions that are defined on characters. Their semantics is the following:

• CAP[c] yields the capitalized character of c if c is a letter, otherwise it yields c itself.

• CHR[i] yields the character that is encoded by the ASCII code of i.

• ORD[c] yields the ASCII code that corresponds to the character c.

Note: One change to the description of the lexical structure of EVE in [Schreiner, 1990] is the fact that now not only the symbol # but also the symbol <> can be used to denote inequality.

Compound Types

In EVE there are three type constructors that allow to build compound data types. These type constructors are:

• the List Constructor (LIST OF),

• the Tuple Constructor (TUPLE OF),

• the Union Constructor (UNION OF).

Lists

Function Description
::  Construction of a new list (infix)
CONS Construction of a new list (prefix)
HEAD Selection of the list head
TAIL Selection of the list tail
NULL Test a list for emptiness

Table 3.7: Generic List Functions

The Data Type. A list is a linearly ordered sequence of values of equal type^{4}. A definition of a list looks as follows:

Ident == LIST OF Simple Type

By this definiton the identifier denoted by Ident is bound to a list type whose elements have the type denoted by Simple Type. As it is not possible to define anonymous types in EVE, Simple Type must be the name of a previously defined type. Examples of list types are:

       IntList  == LIST OF INT ;
       Word     == LIST OF CHAR;
       Sentence == LIST OF Word.

Operations on the Data Type. By defining a list type there is also an implicit constructor function of the same name available. Applying this constructor function to an arbitrary number of expressions of the correct type yields a list of this type. Some examples of objects of list type are:

       IntList[1,2,3,4,5] ;
       IntList[].

Note that also the empty list needs a constructor function in front of itself.

Apart from this constructor function, of which there is one for each list type, there are also some generic list functions, which operate on all kinds of lists. These functions can be found in Table 3.7. As these functions are not first-order objects, they can not be used as arguments for higher-order functions. The semantics of these functions is the following:

• The infix function :: constructs a new list by taking its first argument as the head of the new list and the second argument as the tail of the new list. Therefore, a list that has been constructed by using a constructor function can be viewed as a short notation for a list that results from applying the :: function on each element in a right associative way. For example, the above list of integers from 1 to 5 could also be written in the following way:

4Note that by this definition a list may be infinitely long.

        1 :: (2 :: (3 :: (4 :: (5 :: IntList[]))))

Since :: is right associative the parentheses can also be omitted.

• The prefix function CONS has the same semantics as the infix function ::. Therefore, another possibility of denoting the above list would be to write:

        CONS[1,CONS[2,CONS[3,CONS[4,CONS[5,IntList[]]]]]]

• The functions HEAD and TAIL extract the first element and the rest of a list, respectively. If the list is empty an error value is returned for both functions. As these two functions are the selector functions that correspond to the constructor function ::, the following proposition holds for any non-empty list l:

l = HEAD[l] :: TAIL[l].

One special feature of EVE is the following possibility to use a special syntax for denoting the application of a list selector to a list:

List.Selector.

This notation has the advantage that the parentheses get superfluous in a selection, which improves the readability for deeply nested selections. Compare, for example,

          HEAD[TAIL[l]]

with

          l.TAIL.HEAD

As it can be seen from this last example, the selection is left associative. But as the selection uses a postfix notation, the last expression has to be read from right to left.

• The function NULL tests whether a given list is empty or not. This function is very important since EVE lacks the possibility of pattern matching. Therefore, each function that operates on lists, usually must first check whether the list is empty or not^{5}.

5For examples of such functions see Section 3.2.12.

A Predefined List Type: Strings. Contrary to the three predefined types integers, booleans and characters, which are atomic types, the predefined type of strings is a compound type. Per definitionem a string is nothing but a list of characters. Therefore, the type of a string could be defined by the following type definition:

      STR == LIST OF CHAR.

As a string is a list type, all list functions can also be applied to strings. A string constant of the form

      "String constant"

is then just a shorthand for the list constant

      STR['S','t','r','i','n','g',' ','c','o','n','s','t','a','n','t'].

Inside a string constant, which has to be enclosed by double quotation marks ("), the same characters are valid as in character constants with one exception:

• If a double quotation mark should be used inside the string, the escape character, which is the backslash (\), has to be put in front of it. Hence,

             "\""

is equivalent to

             STR['"'].

Note: The splitting of strings over several lines, as it is described in [Schreiner, 1990][page 53] is not supported by the prototype compiler.

Tuples

The Data Type. Tuples in EVE represent cartesian products from set theory. Contrary to a list each tuple has a fixed size and different fields of a tuple may have different types. The general form of a tuple definition looks as follows:

Ident == TUPLE OF Ident_{1} : Type_{1}, …, Ident_{n} : Type_{n}

Ident_{1}, …, Ident_{n} are the names of the fields of the tuple and Type_{1}, …, Type_{n} are the corresponding types. Some examples for tuple definitions are:

      Coordinate == TUPLE OF x : INT, y : CHAR;
      Address    == TUPLE OF street : STR, number : INT.

The names of the fields in a tuple are all local to the tuple itself, in which they are defined. Therefore, the same name of a field can be used in different tuples. It is also possible to have the same name for a field in different levels of the tuple. But as there are no anonymous types possible in EVE, this is just a corollary from the first rule.

Operations on the Data Type. As with lists there is an implicit constructor function available, which has the same name as the tuple type itself. Therefore, a tuple can be constructed in the following way:

      Coordinate[3,'A'].

In this object the x-field has the value 3 and the y-field has the value 'A'.

These values can be extracted by the selector functions that are also implicitly available. The general form of a selector function is:

Tuple.Selector

where Tuple denotes an object of tuple type and Selector is the name of one field of that tuple. Therefore, from the above example the components can be extracted by the following expressions:

      Coordinate[3,'A'].x ;
      Coordinate[3,'A'].y ;

Unions

The Data Type. Unions in EVE represent disjoint unions of set theory. Hence, a union is a data structure that can be bound to values of different types, where these different types are distinguished by the tags that are associated with them. Therefore, a union can also be called a collection of types. The usage of such a type is useful when the type of an object is not known at compile time, but when there are only a fixed set of possible types. The general form of a union definition looks as follows:

Ident == UNION OF Ident_{1} == Type_{1}, …, Ident_{n} == Type_{n} .

Ident_{1}, …, Ident_{n} are the variants of the union and Type_{1}, …, Type_{n} are the corresponding types. Some examples for union definitions are:

      Data    == UNION OF Num == INT, Word == STR;
      Person  == UNION OF Man == Male, Woman == Female;

As in the tuple definition, the names of the variants are local to the definition of the union. Therefore, the same name may be used for variants in different unions.

Operations on the Data Type. Differently to lists and tuples there is no constructor function for unions. Instead, there is an implicit conversion function that converts an expression of a type of one variant into the union type. The general form of this conversion function is the following:

UnionType$Variant[Expression]

where UnionType is the name of the union, Variant is the name of a variant of this union. Expression must be an expression of the same type as Variant otherwise a type error occurs. Note that the whole expression has now the type of an union and is therefore different from the type of Expression. Some examples for applying a conversion function to an expression are:

      Data$Num[317],
      Data$Word["Hello"].

Both of these expressions have the type Data, which is different from INT and from STR. With union definitions also mutually recursive definitons make sense as the following example shows:

      Subtree;
      Tree    == UNION OF Node == INT, Inner == Subtree;
      Subtree == TUPLE OF value : INT, left : Tree, right : Tree;

This example should also make clear why forward type definitions are necessary to define mutually recursive types^{6}.

The selector function for the union type is the case construct that will be discussed in Section 3.2.7.

Functions

The Data Type. In order to use higher order functions it is possible in EVE to define function types^{7}. Hence, there is an own type constructor that takes n + 1 types and yields a function type with the cartesian product of the types 2 … n as domain and with the first type as its image. The general form of a type definition looks as follows:

Ident == SimpleType_{1} [ SimpleType_{2}, … SimpleType_{n+1} ]

As it is described in Section 3.2.5, such an identifier that has been bound to a function type can be used in a function definition for defining the type of the function.

6Tuples could also be defined mutually recursive, but usually that does not make sense as contrary to a union this would yield an infinite recursion of the type.

7For a discussion of higher order functions see Section 3.2.9.

3.2.4 Constant Definitions

The general form of a constant definition in EVE is the following:

Ident : SimpleType == Expression.

This constructs binds the value that is the result of evaluating Expression to the identifier Ident. As EVE is strongly typed, the type of the identifier has to be given explicitly in SimpleType. This type must be the same as the type of Expression. Some examples for constant defintions are:

      i : INT  == (2+3)*4;
      s : STR  == "Concrete Mathematics";
      b : BOOL == (i > 0) & ("test".HEAD = 't');

As the scope of an identifier starts from the point of its definition, the definition of an identifier must always precede its usage. However, it is possible to use the identifier that is just being defined inside the expression on the right hand side. Hence, the following definition is legal:

      IntList == LIST OF INT;
      ones : IntList == 1 :: ones;

By this definition, the identifier ones is bound to an infinite list, in which every element is the integer value 1. This is an example of a directly recursive definition of a data object. However, it is also possible to give mutually recursive definitions. In this case, a forward definition has to be used to fix the type of the identifier without actually defining its value. An example of such a mutually recursive definition is:

      IntList == LIST OF INT;
      x2 : IntList;
      x1 : IntList  == 1 :: x2;
      x2 : IntList  == 2 :: x1.

This defines a list with the alternating values 1 and 2. The general form of a forward constant definition is:

Ident : SimpleType.

Due to the semantics of EVE as a purely functional language, each identifier may be bound at most once in one scope. If the same identifier is bound in different scopes then these are actually two different objects that happen to have the same name.

3.2.5 Function Definitions

The general form of a function definition in EVE is:

Ident [Ident_{1}, …,Ident_{n}] : SimpleType [SimpleType_{1}, …,SimpleType_{n}] == Expression

By this definition the identifier Ident is bound to a function with the formal parameters Ident_{1}, …, Ident_{n}, which have the types SimpleType_{1}, …, SimpleType_{n}, respectively. The type of the return value is defined by SimpleType. Expression must have the type SimpleType. It is allowed to use global variables in Expression. These are variables that are different from all formal parameters and that are not bound by local definitions in Expression.

As it is possible to define a function type in EVE, there is a second possibility of defining a function:

Ident [Ident_{1}, …,Ident_{n}] : SimpleType == Expression.

In this definition SimpleType must be a function type and the type of the return value of this function type must be the type of Expression.

One example for a function definition is the following definition of the famous factorial function:

      Fact[n] : INT[INT] ==           -- Factorial
       IF n=0
         THEN 1
         ELSE n * Fact[n-1].

An alternative way of defining this function is to bind an identifier to this function type and then to just use the identifier in the function definition. This might be favourable if there are many functions of the same type and it is absolutely necessary if one wants to realize higher order functions^{8}. Such a function definition then looks as follows:

      IntIntFct == INT[INT];
      Fact[n] : IntIntFct ==         -- Factorial
       IF n=0
         THEN 1
         ELSE n * Fact[n-1].

The following example shows how global variables can be used in EVE. One important feature of EVE is that it is statically scoped. This means that when a global variable is used the statically enclosing block of the program is searched for this variable and not the block from which the function has been called. To make this clear look at the following example:

8See Section 3.2.9 for a more detailed discussion of higher order functions.

      a : INT  == 1;
      f[x] : INT[INT] == a * x;
      g[x] : INT[INT] ==
       { a : INT  == 2 }       -- local definition, defining a
       f[x] + 1.

As a is neither defined in the expression on the right hand side of the function definition of f nor a formal parameter of f, it is a global variable. As EVE is statically scoped the value of a inside f is 1 (the value would be 2 if EVE was dynamically scoped).

Further examples for function definitions can be found in Section 3.2.5 where the structure of an expression is discussed.

Analogously to type definitions and constant definitions it is necessary to use forward definitions if one wants to define mutually recursive functions. The general form of a function forward definition is either

Ident [Ident_{1}, …,Ident_{n}] : SimpleType [SimpleType_{1}, …,SimpleType_{n}]

or

Ident [Ident_{1}, …,Ident_{n}] : SimpleType .

Such a forward definition only fixes the type of the function and the names and types of its parameters such that the function can be used in another definition. When the function is then actually defined it must have the same function head (names and types of function and formal parameters) as in the forward definition.

Expressions

After having discussed the structure of definitions in the previous section it remains to discuss the structure of an expression that appears on the right hand side of a definition.

3.2.6 Conditional Expressions

The general form of a conditional expression in EVE is:

IF Expression_{1} THEN Expression_{2} ELSE Expression_{3}

In this construct Expression_{1} must have the type BOOL and Expression_{2} and Expression_{3} must have the same type. The semantics is the following: If Expression_{1} evaluates to TRUE the result of the construct is the result of evaluating Expression_{2}. If Expression_{1} evaluates to FALSE the result of the the construct is the result of evaluating Expression_{3}. If the evaluation of Expression_{1} yields an error the result of the construct is ERROR. Due to this behaviour, the conditional construct is strict in its first argument but non-strict in the other two arguments^{9}.

For the implementation of this construct it is important that Expression_{1} must be evaluated before one can decide if Expression_{2} or Expression_{3} yields the result. Hence, there is no speculative parallelism used in the implementation of this construct.

An example for a conditional expression is the expression defining the factorial function:

      IF n=0 THEN 1 ELSE n * fact[n].

3.2.7 Case Expressions

The general form of a case expression in EVE is:

CASE Expression { c_{1} : Variant_{1} } Expression_{1}

⋯

{ c_{n−1} : Variant_{n−1} } Expression_{n−1} ELSE Expression_{n}

Expression must be of union type and Variant_{1}, …, Variant_{n−1} must be the name of the variants of this union type. Each of the identifiers c_{1}, …, c_{n−1} scopes over its corresponding expression. The semantics of this construct is the following: Expression is evaluated and its value is tested by each of the branches for its actual variant (i.e. for the actual type of this value). Since the Variants_{1}, …, Variants_{n−1} must be non-overlapping at most one variant has the same type as the actual variant of this value (let this variant be the i-th variant). Now, the identifier c_{i} is bound to the value of Expression. The result of the whole construct is then the result of Expression_{i}. If no variant has the same type as the actual variant of Expression, then the result of Expression_{n} is the result of the whole expression.

If the variants tested by the case construct are exhaustive i.e. if each of the variants of the union type is tested by one branch, the else branch of the construct has to be omitted.

Note: The prototype compiler does not check whether the variants are non-overlapping. If there are overlapping variants and the actual variant of Expression is such an overlapping variant then the first branch that has the same type as the actual variant is taken. The prototype compiler also does not check if the variants in the case construct are exhaustive. Hence, there might be an ELSE branch in a case construct with exhaustive variants. However, this does not change the operational behaviour of the EVE program. The prototype compiler also accepts programs with an case expression that has neither exhaustive variants nor an ELSE branch. In this case an error value is returned at runtime when the actual variant of the evaluated Expression is different from all branches in the case expression.

The following example shows how to define a union type, how to define constants of union type and how to use a case construct as a selector function on this union type by defining a function on an object of this type:

9For a more detailed discussion on the strictness properties of EVE see Section 2.1.1.

      -- Type Definitions
      Data  == UNION OF
                 Num     == INT,
                 Letter  == CHAR,
                 Flag    == BOOL;
      -- Constant Definitions
      dInt  : Data  == Data$Num[2];
      dChar : Data  == Data$Letter['a'];
      dBool : Data  == Data$Flag[TRUE];
      -- A case expression on that type
      Invert[d] : Data[Data] ==
        CASE d
          { n : Num }    Data$Num[-n]
          { l : Letter } Data$Letter[CAP[l]]
          { f : Flag }   Data$Flag[~f] ;

The type definition shows how to define a collection of the types INT, CHAR and BOOL where the tags of these types are Num, Letter and Flag, respectively^{10}. In the next three lines it is shown how to define constants of this union type by converting an expression of one of the three possible types into the union type. Finally, a function Invert is defined, that takes an object d of type Data as input and yields the “inverted” object of the same type as result. This inversion is defined differently in each of the variants of the input object:

• An integer value is inverted by changing its sign,

• a character is inverted by capitalizing it^{11},

• a boolean value is inverted by applying the boolean negation on it.

After having performed this inversion, the resulting object has to be converted back to the union type Data. The result of the case expression (and therefore of the whole function) is therefore an object of type Data. Note that this case expression has no ELSE branch since the variants are exhaustive.

Note that the case expression is strict in Expression as it will be always evaluated but nonstrict in Expression_{1}, …, Expression_{n} as only one of these expressions will be evaluated^{12}.

3.2.8 Block Expressions (Local Definitions)

The general form of a block expression (or local definition) in EVE is:

10See Section 3.2.3 for a discussion of union types.

11See Table 3.5 for a description of the predefined function CAP.

12For a more detailed discussion on the strictness properties of EVE see Section 2.1.1.

     {

Definition_{1} ; ⋯ Definition_{n} ;

     }

Expression

In many functional languages the keyword LET is used instead of the symbol { and IN instead of the symbol }. In this construct identifiers are defined locally i.e. they scope over their own bodies, over all succeeding definitions and over the Expression. The value of the whole expression is the result of evaluating Expression. Usually, all the identifiers that are bound by Definition_{1}, …, Definition_{n} occur in Expression.

An example of such a block expression is the following:

      {
       a : INT == 3;
       b : INT == a+2;
      }
      (a+b) * 7

As the definition of a precedes the definition of b, a can be used in the definition of b. As the values for a and b in the expression of the block expression are taken from these local definitions the whole construct evaluates to 56.

It is also possible to have nested block expressions. Since each block opens a new scope for bindings it is possible to bind an identifier that has also been bound outside (e.g. by an enclosing block expression). On the other hand, if an identifier that is used in an expression is not defined in the current block expression the enclosing block is searched for a binding of this identifier. If there is no binding in this block either, again the enclosing block of this block is searched and so on. This strategy is also called lexical scoping.

An example showing the features that have been discussed above is the following:

      { a : INT == 3 }
      {
        b : INT == a;
        a : INT == 5
      }
      a + b

In this example there are two nested block expressions: The outer block binds a to the integer value 3, and the inner block expression binds b to the value of a in the enclosing expression (i.e. to the integer value 3) and then a is bound to the integer value 5. As the inner block opens a new scope, this is no redefinition. Instead, it is an ordinary binding of an identifier that happens to have the same name as an identifier in the enclosing block. Now, in the expression of the inner block the value for a of this inner block is taken. The result of the whole expression is therefore the integer value 8.

3.2.9 Higher Order Functions

After having discussed the basic constructs of EVE in the previous sections, now some of the more advanced features of EVE have to be described. These features are typical for functional languages and are therefore also incorporated in this very small language.

One important feature of EVE is the fact that it supports higher order functions. Higher order functions are functions that take other functions as arguments or yield a function as a result. This means that in EVE functions are first order objects i.e. they can be used in the same way as other objects. Hence, functions can also be assigned to identifiers or stored in a part of a data structure and so on.

As it has already been stated in Section 3.2.3 it is necessary to define a function type for using higher order functions. Then it is possible to define a function that has an argument of such a function type. This argument can then be applied to other objects, yielding the same result as if the function that is the value of the function type argument, was directly called with these objects.

To motivate the introduction of higher order functions into the EVE language, let us look at the following functions:

      IntList == LIST OF INT;
      IncList[l] : IntList[IntList] ==
       IF NULL[l]
         THEN IntList[]
         ELSE { hd : INT     == l.HEAD;
                tl : IntList == l.TAIL
              }
              (hd+1) :: IncList[tl];
      Capitalize[str] : STR[STR] ==
       IF NULL[str]
         THEN ""
         ELSE { hd : INT     == str.HEAD;
                tl : IntList == str.TAIL
              }
              CAP[hd] :: Capitalize[tl];

The function IncList takes a list of integers and increases each of these integers by one. The function Capitalize takes a string, which is per definitionem a list of characters^{13}, and capitalizes this string by capitalizing each of its characters. Just from looking at the syntactic structure of these two functions it is obvious that they perform essentially the same task: They apply a certain function, increasing by one in IncList and capitalizing a character in Capitalize, to a list of the appropriate type. By using a higher order function it is possible to write a more general function that has the same structure as above but that has an additional argument of function type. This is the function that is applied to each of the elements of the list. However, as EVE does not support polymorphism, it is

13See Section 3.2.3 for a discussion of the data type STR.

not possible to write one higher order function that can then be used for both of the two examples above. Instead, one has to write one higher order function for each list type. The higher order function for IncList function above looks as follows:

      IntList   == LIST OF INT;
      IntIntFct == INT[INT];
      IntMap[f,l] : IntList[IntIntFct,IntList] ==
       IF NULL[l]
         THEN IntList[]
         ELSE { hd : INT     == l.HEAD;
                tl : IntList == l.TAIL
              }
              f[hd] :: IntMap[f,tl];
      IncList[l] : IntList[IntList] ==
       { Inc[n] : IntIntFct == n+1 }
       IntMap[Inc,l];

With this ability of working with functions as first order objects, one has a very powerful tool that allows to classify certain kinds of functions that have the same structure.

Note: In [Schreiner, 1990][Page 64] it is stated, that a type conversion for a function is necessary, whose type has a different name than the name of the function type in the function definition of the higher order function. However, as in this version of the language definition structure equivalence instead of name equivalence is used to check whether two objects have the same type, such an explicit type conversion is not necessary any more.

Note: Another difference to the language description cited above is that the prefix versions of the integer, boolean and string operators and of the comparison operators can not be used as first order objects. Hence, if one wants to use the predefined negation function NEG as the argument to the above IntMap function one has to define an own function that does the same as NEG and use this function as argument to the higher order function:

      MyNeg[n] : INT[INT] == NEG[n].

With this function definition the following function call of the higher order function IntMap can be used to negate all integers in the list l:

      IntMap[MyNeg,l];

Some exceptions from the rule that functions are first order objects have to be stated explicitly. In general, none of the predefined functions that are described in Section 3.2.3 is a first order object. This holds for the list functions CONS, HEAD, TAIL and NULL as well as for the error test function FAILED that will be discussed in a later section. Furthermore, all user defined data constructors (for lists, tuples and function types) are not first order.

3.2.10 Strictness Properties

For a definition of the notion of strictness see Chapter 2.1.1. There it is also explained why the strictness properties of a programming language has a strong impact on the runtime behaviour of a program.

The strictness properties in EVE can be described as follows:

• All user defined functions in EVE may be non-strict in all of their arguments. Therefore, the compiler can in general not evaluate the arguments of a function before the function itself. However, it would be possible to detect the strictness of some arguments of user defined functions by performing a strictness analysis on the program. The information of this analysis could then be used to evaluate the strict arguments of a function before the function itself. However, the prototype compiler does not perform any kind of strictness analysis and therefore handles all user defined functions like functions that are non-strict in all arguments.

• All predefined functions and operators are strict in all arguments (some exceptions are given below).

• The application of functions to arguments is strict in the function to be applied.

• The boolean operators & and | (and the corresponding prefix versions AND and OR) are strict in the first argument.

• The list constructor :: (and its prefix version CONS) is non-strict in both arguments. The non-strictness of this function implies that all lists in EVE are non-strict. These non-strict lists are one of the central means to synchronize independent computations that may be performed in parallel by the ADAM machine.

• All list and tuple constructor functions and all union conversion functions are non-strict in all arguments. Therefore, not only lists but also tuples and unions are non-strict data types.

• The conditional construct (if-then-else) is strict in its first argument but nonstrict in the other two arguments.

• The case construct is strict in its first argument but non-strict in all other arguments.

To make this description clearer we give here some EVE functions and analyse the behaviour of these functions with respect to termination. First of all we define the following nonterminating function

Forever[x]: INT[INT] == Forever[x].

If we additionally take the following function definitions

Add0[x]: INT[INT] == 1+0;   -- Not strict in x
AddX[x]: INT[INT] == 1+0*x; -- Strict in x

     Branch[x]: INT[INT] ==      -- Strict in x
       IF x=0                       -- = is strict in x
         THEN 1                     -- THEN-branch not strict
         ELSE Forever[x];           -- ELSE-branch strict in x
     ConsL[x]: IntList[INT] ==   -- Not strict in x
       x :: IntList[0];             -- :: not strict in first arg
     ConsR[x]: IntList[INT] ==   -- Not strict in x
       1 :: IntList[x];             -- :: not strict in secnd arg

the termination behaviour can be expressed by the following set of equations:

      Forever[0]  = ⊥
      Add0[Forever[0]]  = 1
      AddX[Forever[0]]  = ⊥
      Branch[0]  = 1
      Branch[Forever[0]] = ⊥
      ConsL[Forever[0]] = IntList[⊥,0]
      ConsR[Forever[0]] = IntList[1,⊥]

where ⊥ denotes a value that will never be computed. The only difference between Add0 and AddX is the fact that the latter function has to evaluate its argument x before it can return a result^{14}. This is due to the strictness of all arithmetical operations. Hence, if the evaluation of the argument for AddX does not terminate as in the above example, the evaluation of the function call does not terminate either. The function Branch simply shows the non-strictness of the if-then-else construct in its second and third argument. The last two functions show that in non-strict data structures the non-termination of the evaluation of parts of the data structure does not mean that the whole data structure is undefined. Instead, the defined parts of the data structure can be used in further computations.

3.2.11 Error Behaviour

Whenever a runtime error occurs in EVE a special value ERROR is returned, where ERROR is a generic constant i.e. it is part of every data type in EVE . This error value is handled like non-termination, which has been discussed in the previous section. The semantic counterpart of ERROR is therefore ⊥.

Although, both ‘non-termination’ and ‘error’ can be denoted by the semantic object ⊥, these two ways of yielding ⊥ have to be distinguished in two cases:

• In all predefined binary functions and operators the ‘non-terminating ⊥’ has precedence over the ‘error ⊥’. Hence, when the computation of one argument does not terminate and the computation of the other argument yields an error then the computation of the whole construct does not terminate.

14Of course, an optimizing compiler could easily eliminate the 0*x part in the expression. This optimization would then change the semantics of the function AddX, which then would be non-strict in its argument. However, the prototype compiler does not perform any optimization.

Operator  Error  Description
+, -, *, /, ABS Overflow  Result outside the range MININT–MAXINT
/, % Zero divide Division (resp. Remainder) by zero
CHR  No ASCII value Argument outside the range 0–255
HEAD, TAIL  Empty list  Application to an empty list

Table 3.8: Runtime Errors

• The error test function FAILED is strict with respect to the ‘non-terminating ⊥’ but non-strict with respect to the ‘error ⊥’.

Table 3.8 shows all possible runtime errors that may occur. As the error values are treated in the same way as non-termination, the strictness of a function defines if an error will be propagated or not. Hence, if a non-strict argument of a function call yields an error, then the result of the function need not be an error. But if it is a strict argument that yielded an error this error is propagated in any case.

The generic function FAILED is used to test whether the evaluation of an expression yields an error. Like the constant ERROR there is one FAILED function for each EVE data type.

Some examples for using this function are:

      FAILED[ERROR]                    = TRUE
      FAILED[1/0]                      = TRUE
      FAILED[IntList[ERROR]]           = FALSE
      FAILED[HEAD[IntList[ERROR,1]]]   = TRUE
      FAILED[(1>0) | ERROR]            = FALSE
      FAILED[IF ERROR THEN 0 ELSE 1]   = TRUE
      FAILED[IF 1>0 THEN 0 ELSE ERROR] = FALSE

As it has already been discussed in the previous section, all user defined functions are nonstrict in all arguments. Therefore, if we take the following function definition

     f[x,y] :  INT[INT,INT] ==
       IF x>0
         THEN x
         ELSE y;

then the expression FAILED[f[1,ERROR]] yields the value FALSE.

3.2.12 Examples of EVE Programs

To show the syntax and semantics of EVE and to explain the features that have been introduced in the previous sections, some small example programs are shown in this section.

                  Drop[n,list] : IntList[INT,IntList] ==
                  IF ((n=0) | NULL[list])
                    THEN list
                    ELSE Drop[n-1,TAIL[list]] ;

Figure 3.6: EVE Program: Drop

               Filter[p,list] : IntList[PredOfInt,IntList] ==
               IF NULL[list]
                 THEN IntList[]
                 ELSE { hd : INT     == list.HEAD;
                        tl : IntList == list.TAIL }
                      IF p[hd]
                        THEN hd :: Filter[p,tl]
                        ELSE       Filter[p,tl] ;

Figure 3.7: EVE Program: Filter

The examples have been selected in such a way, that they show the characteristic differences to other functional languages best.

For all of these examples the following type definitions are valid:

      IntList    == LIST OF INT;
      PredOfInt  == BOOL[INT];

First Example: The Drop Function

Figure 3.6 shows an implementation of the function Drop that can be found in most functional languages. This function takes an integer value n and a list l as input. The output is the list that remains after dropping the first n elements of the input list l. If l has less than n elements then the result is an empty list.

One characteristic of EVE that can be seen from this example is that most functions that operate on lists first have to explicitly check whether the list is empty or not. This is due to the fact that there is no pattern matching in EVE.

Second Example: The Filter Function

Figure 3.7 shows a higher order function Filter that takes a predicate p and a list list as input and returns a list of all those elements of list, for which p holds, without changing the order of these elements.

Filter is a higher order function since it takes another function (p) as argument. The ELSE branch of the function shows the usage of local definitions, which are enclosed by { and }. Furthermore, it shows that the selector functions for lists (HEAD and TAIL) can be used with the slector syntax of tuples i.e. they can be put after the object itself instead of in front of it as in a function call.

References to Further Examples

In [Schreiner, 1990][Section 3.14] a bigger example of an EVE program can be found. There an algorithm for performing a symbolic differentiation is described. Among others this algorithm shows the usage of unions and of mutually recursive type definitions. However, in the definition of the function VarEq the type conversion is superfluous due to the new definition of the equality of types (structure equivalence instead of name equivalence).

Some further examples of EVE programs that have also been used for benchmarking can be found in Appendix D.

3.2.13 Syntax Definition

This syntax definition is taken from [Schreiner, 1990][Section 3.15] with some minor changes performed on it.

3.2.14 Notation

We define EVE ’s lexical structure and syntax using a form of Extended Backus-Naur Notation:

Terminal Symbols (i.e. the tokens of EVE ) are written in typewriter style.

Nonterminal Symbols (i.e. syntactic entities defined by syntax rules) appear in italic style.

Meta symbols (i.e. symbols that structure syntax rules) appear in simple Roman style:

= separates a nonterminal symbol that is being defined from its definition.

( ) group syntactic items in order to form a larger item^{15}.

[ ] enclose syntactic items that are optional, i.e. that may appear once or not at all

in that place^{16}. { } enclose syntactic items that may be repeated zero or more times in that place^{17}. | separates two alternative syntactic items, one of which must appear in that place. . denotes the end of a syntax rule.

Each nonterminal symbol is defined by a Syntax Rule of the form

Nonterminal = Definition .

15Do not mix up the meta symbols ( ) with the symbols ( ) of the EVE language!

16Do not mix up the meta symbols [ ] with the symbols [ ] of the EVE language!

17Do not mix up the meta symbols { } with the symbols { } of the EVE language!

0  16  32  48  64  80  96  112
0  ⟨NUL⟩  ⟨DLE⟩ ␣       0       @       P       `       p
1  ⟨SOH ⟩ ⟨DC1 ⟩  !       1       A       Q       a       q
2  ⟨STX ⟩ ⟨DC2 ⟩  "       2       B       R       b       r
3  ⟨ETX ⟩ ⟨DC3 ⟩  #       3       C       S       c       s
4  ⟨EOT ⟩ ⟨DC4 ⟩  $       4       D       T       d       t
5  ⟨ENQ⟩ ⟨NAK ⟩ %       5       E       U       e       u
6  ⟨ACK ⟩ ⟨SYN ⟩ &       6       F       V       f       v
7  ⟨BEL⟩  ⟨ETB⟩ '       7       G       W       g       w
8  ⟨BS⟩  ⟨CAN ⟩ (       8       H       X       h       x
9  ⟨HT ⟩  ⟨EM ⟩  )       9       I       Y       i       y
10  ⟨LF ⟩  ⟨SUB⟩  *       :       J       Z       j       z
11  ⟨VT ⟩  ⟨ESC ⟩  +       ;       K       [       k       {
12  ⟨FF ⟩  ⟨FS⟩  ,       <       L       \       l       |
13  ⟨CR⟩  ⟨GS⟩  -       =       M       ]       m       }
14  ⟨SO⟩  ⟨RS⟩  .       >       N       ^n       ~
15  ⟨SI ⟩  ⟨US⟩  /       ?       O       _o       ⟨DEL⟩

Table 3.9: The US-ASCII Character Set

3.2.15 Input File

Input files may consist of all printable characters of the US-ASCII character set (see Table 3.9). In addition, the following invisible ASCII characters (white space) are allowed:

• ␣ (ASCII code 032) is the space character.

• ⟨TAB⟩ (ASCII code 009) is translated to a single space in all places it occurs (even in text constants).

• ⟨LF ⟩ (ASCII code 010) denotes the end of a line.

• ⟨FF ⟩ (ASCII code 012) is translated into the ⟨LF ⟩ character in all places it occurs (also in text constants).

• ⟨CR⟩ (ASCII code 013) is translated into the ⟨LF ⟩ character in all places it occurs (also in text constants).

All other characters are illegal (even within text constants or comments).

3.2.16 Lexical Structure

The following syntax rules define the lexical structure, i.e. the structure of EVE tokens:

3.2. THE EVE PROGRAMMING LANGUAGE 67

Letter  = a | b | ... | z | A | B | ... | Z .
Digit  = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 .
Ident  = Letter { Letter | Digit }.
Integer  = Digit { Digit }.
Number  = Integer .
SpecialChar = Any character of Table 3.4 except \' and \" .

SingleChar = Any printable character of the ASCII set including ␣ except ' and \ |

SpecialChar | " | \' .

StringChar = Any printable character of the ASCII set including ␣ except " and \ |

SpecialChar | ' | \" .

Character = ' SingleChar ' .
String  = " { StringChar } " .

3.2.17 Context-Free Syntax

The following syntax rules define the context-free syntax of the EVE language in four sections:

• Types,

• Definitions,

• Expressions,

• Programs.

Types

IdentList  = Ident { , Ident } .
SimpleType = Ident .
TypeList  = SimpleType { , SimpleType } .
Field  = Ident : SimpleType .
FieldList  = Field { , Field } .
Variant  = Ident == SimpleType .
VariantList = Variant { , Variant } .
ListType  = LIST OF SimpleType .
TupleType  = TUPLE OF FieldList .
UnionType  = UNION OF VariantList .
FunctionType = SimpleType [ TypeList ] .
Type  = SimpleType | ListType | TupleType | UnionType | FunctionType .

Definition Statements

TypeDefinition  = Ident [ == Type ].
ConstantDefinition = Ident : SimpleType [ == Expression ].

FunctionDefinition = Ident [ IdentList ] : ( FunctionType | SimpleType ) [ == Expression

].

Definition  = [TypeDefinition | ConstantDefinition | FunctionDefinition ].
DefinitionSequence = Definition { ; Definition }.

68 CHAPTER 3. ADAM & EVE

Expressions

Expression = ListExpr | { DefinitionSequence } Expression | IF Expression THEN Expression ELSE Expression | CASE Expression { Field } Expression { { Field } Expression } [ ELSE Expression ].

ListExpr  = CompoundExpr { :: Factor }.
CompoundExpr = SimpleExpr [ Relation SimpleExpr ].
Relation  = = | # | < | <= | > | >= .
SimpleExpr  = [ + | - ] Term { AddOp Term }.
AddOp  = + | - | | .
Term  = Factor { MulOp Factor }.
MulOp  = * | / | % | & .

Factor = Number | Character | String | Designator { ArgumentList | Selector } | (

Expression ) | ~ Factor .

Designator  = Ident [ $ Ident ].
ArgumentList = [ [ Expression { , Expression } ] ] .
Selector  = . Ident .

Programs

Program = DefinitionSequence .

3.2.18 Keywords

There are eight keywords that are reserved and must not be redefined:

     CASE  ELSE  IF     LIST
     OF    THEN  TUPLE  UNION

3.3 Predefined Identifiers

Predefined identifiers may be redefined by the user, although it is strongly recommended not to do so. There are three classes of predefined identifiers:

• Types,

• Constants,

• Functions.

3.3.1 Predefined Types

EVE has four predefined types that are shown in Table 3.10.

3.3.2 Predefined Constants

Table 3.11 shows the predefined constants of EVE.

The constant ERROR is a generic constant, i.e. it is element of any type and therefore compatible with every type.

Name Description
INT  Integer Numbers
BOOL Boolean Values
CHAR Characters
STR  Strings

Table 3.10: Predefined Types

Name Type Description
MININT  INT Lower bound of integer numbers
MAXINT  INT Upper bound of integer numbers
TRUE    BOOL Boolean constant
FALSE   BOOL Boolean constant
ERROR Any Error value

Table 3.11: Predefined Constants

3.3.3 Predefined Functions

Functions on Atomic Types Tables 3.12, 3.13 and 3.14 show the predefined functions working on integers, booleans and characters, respectively.

Comparison Functions Table 3.15 describes the functions that can be used to compare objects of an atomic type (i.e. integers, booleans and characters). For each kind of comparison operator, there exist

Name Type  Description

ADD    INT[INT,INT] Addition
SUB    INT[INT,INT] Subtraction
MUL    INT[INT,INT] Multiplication
DIV    INT[INT,INT] Division
MOD    INT[INT,INT] Remainder
NEG    INT[INT]  Negation

ABS    INT[INT]  Absolute Value

ODD    BOOL[INT]  Is number odd?

Table 3.12: Predefined Integer Functions

Name Type  Description

                     AND    BOOL[BOOL,BOOL] Conjunction
                     OR     BOOL[BOOL,BOOL] Disjunction
                     NOT    BOOL[BOOL]  Negation

Table 3.13: Predefined Boolean Functions

Name Type  Description
CAP    CHAR[CHAR] Capitalize a letter
CHR    CHAR[INT] Character from ASCII code
ORD    INT[CHAR] ASCII code of a character

Table 3.14: Predefined Character Functions

three functions, one for each atomic type. In order to get the actual function names, one has to replace the asterisk * in the names by I (INT), B (BOOL), or C (CHAR).

List Functions

Table 3.16 refers to the generic type definition

AnyList == LIST OF Any.

However, all of these functions are not first-order objects and may not be used as arguments of higher-order functions (see Section 3.2.9).

Error Test Function

The generic error test function shown in Table 3.17 can be applied to expressions of any type.

Function Type  Description
*LT      BOOL[Atomic,Atomic] Less Than
*LE      BOOL[Atomic,Atomic] Less or Equal
*EQ      BOOL[Atomic,Atomic] Equal
*NE      BOOL[Atomic,Atomic] Not Equal
*GE      BOOL[Atomic,Atomic] Greater or Equal
*GT      BOOL[Atomic,Atomic] Greater Than

Table 3.15: Predefined Comparison Functions 3.3. PREDEFINED IDENTIFIERS 71

Function Type  Description
CONS  AnyList[Any,AnyList] Construct a list
HEAD  Any[AnyList]  Select the head of a list
TAIL  AnyList[AnyList]  Select the tail of a list
NULL     BOOL[AnyList]  Is a list empty?

Table 3.16: Predefined List Functions

Function Type  Description

         FAILED   BOOL[Any] Test for ERROR

Table 3.17: Error Test Function

However, FAILED is not a first order object and may not be used as an argument of a higher-order function (see Section 3.2.9).

The EVE Compiler

In this chapter the details of the prototype EVE compiler are discussed. The source language of the compilation process (EVE) and the abstract target architecture (ADAM) have been described in the previous chapters.

The prototype compiler can be characterized by the following T-diagram:

Modula-2 EVE  ADAM- Assembler

Source Language: The source language of the compilation process is the functional programming language EVE that has been described in detail in Section 3.2. As EVE is a functional programming language, many techniques that have been developed for a sequential implementation of such languages can also be used here. One such technique is the λ-lifting process that will be discussed in Section 4.3 of this chapter. However, as EVE is a quite small language some more complex topics of the implementation of functional languages are not covered by the compiler. For example, there is no handling of pattern matching required. For a study of the translation of these additional language constructs the interested reader may refer to [Peyton Jones, 1987].

Target Language: The target language of the compilation process is the assembler code of the ADAM abstract dataflow machine. The architecture of this machine has been discussed in Chapter 3.1. However, for the compilation process only the assembler language of the ADAM machine is important. For a short description of the assembler code see Appendix C.

Implementation Language: As implementation language for the prototype EVE compiler Modula-2 has been chosen. With this language it is quite easy to separate the compiler in its different components. Furthermore, this language supports the implementation of data encapsulations where an opaque^{1} type is defined together with all procedures that may operate on this type. As the realization of the type is not visible from outside no direct manipulations of this type can be performed. To be more exact concerning the language, the JPI version of Modula-2 with the TopSpeed^{2} Modula Compiler of Jensen & Partners, International is used for the implementation. This JPI Modula contains some enhancements to standard Modula-2. The whole compiler has been developed under DOS. For a description of Modula-2 in general see [Blaschek et al., 1986] and [Wirth, 1988]. For a description of JPI Modula see [Jensen, 1988] and [King, 1988].

4.1 The Structure of the Compilation

The tasks that must be performed by the EVE compiler are the following:

• Lexical Analysis,

• Syntax Analysis,

• Generation of an abstract syntax tree,

• Transformations on that abstract syntax tree,

• Code Generation.

Note that in this prototype compiler no optimizations are performed. For a discussion of possible sources of optimizations see Chapter 5.

For performing all these tasks the whole compiler is split into three passes. The work that is done in the different passes is the following:

• The first pass performs the lexical analysis, the syntax analysis and creates an abstract syntax tree.

• The second pass then performs some transformations on the abstract syntax tree. The most important transformation is the λ-lifting process. After having performed these transformations, all functions are on top level i.e. there are no nested functions any more. This makes the code generation significantly easier.

• The third pass generates ADAM assembler code from the abstract syntax tree and other intermediate data structures of the compiler. This pass has to create a program file, containing for each EVE function the corresponding ADAM code, and an expression file, containing the code for evaluating the result of the whole EVE program. Furthermore, a data description file has to be produced that describes the structure of the result type of the program. This file can be already generated after the first pass. For a more detailed description of the structure of these files see Appendix C.

Figure 4.1 shows this structure of the whole compilation process.

1An opaque type is a type whose name is declared in the definition module but whose realization is declared in the implementation module, thereby hiding the information about the internal structure of the type.

2TopSpeed is a trademark of Jensen & Partners International.

EVE

First pass “Lexical & Syntax Analysis”

Enriched λ-calculus

Second pass ; “λ-lifting”

Super Combinators

Third pass “Code Generation”

ADAM Assembler Code

Figure 4.1: Structure of the Compilation

In translating an EVE program into ADAM assembler code, the compiler needs a lot of intermediate data structures. As these intermediate data structures are realized as abstract data types, they are implemented via data encapsulations. Such a data encapsulation declares a type that uniquely corresponds to the abstract data type together with some procedures that perform operations on this data structure. However, the internal structure of this type is not visible from the outside and therefore all operations on objects of this data type have to be performed via these procedures. This principle allows to change the implementation of an abstract data type without changing the code that uses this type.

The modules of which the compiler consists can be split into several classes:

• One class of modules consists of the top level module for the whole compiler and the top level modules for each of the tasks that have been described at the beginning of this chapter. The modules of this class can be found in Table 4.1.

• Another class of modules is formed by all data encapsulations for the intermediate data structures of the compiler. Table 4.2 shows the modules that fall into this class.

• Finally, there is one class with some special modules. The modules that handle the I/O belong to this class. They can be found in Table 4.3.

For a more detailed description of the data encapsulations see Appendix E where also the documented definition modules of all modules of the EVE compiler can be found. However, the source code of the compiler (about 20000 lines) is not included in this thesis.

Module Name Description of the Module
EveCompile  Top level module of the whole compiler
EveLexAn  Module for lexical analysis
EveSyntAn  Module for syntax analysis
EveLambdaLift Module for λ-lifting
EveCodeGen  Module for code generation

Table 4.1: Top Level Modules

Module Name  Abstract Data Type
EveAST  Abstract syntax tree
EveCode  Code area
EveEnvironment Environment for code generation
EveEvent  Events for code generation
EveLabel  Labels for code generation
EveList  Lists of numbers
EveListList  Lists of lists of numbers
EveRegister  Registers for code generation

EveStringLib  Strings

EveSym  Symbol table and symbol list
EveType  EVE data types

Table 4.2: Data Encapsulations

Module Name Description
EveCoIO  Handling of result files
EveError  Error handling
EveGetChar Elementary input
EveWrite  Output of enumerations and sets

Table 4.3: Special Modules

4.2 First Pass

The first pass of the EVE compiler does not vary significantly from usual compilers for imperative languages. This is not very surprising since the analyses in this first pass perform just a syntax check and a syntactic transformation of the program. In the first pass the following things have to be done:

• A lexical analysis has to be performed, guaranteeing that all terminal symbols and members of terminal classes of the EVE grammar are correctly built. Among others this includes checks for the size of constants in the program, for the correct structure of identifiers and so on. The top level module for the lexical analysis is EveLexAn.

• A syntax analysis has to be performed to guarantee that the syntactic structure of the given EVE program is correct. For this analysis the principle of the recursive descent has been used. However, before being able to use this method the given EVE grammar of Section 3.2.13 has to be transformed into an equivalent grammar that does not include same terminal starts of different alternatives in a rule and that does not contain left recursion. This transformed grammar can be found in Appendix A. The top level module for the syntax analysis is EveSyntAn.

• An abstract syntax tree has to be built. This abstract syntax tree directly reflects the structure of the program but the tree is easier to handle by other passes of the compiler than the original source code. The handling of this abstract syntax tree is performed by an own data encapsulation in EveAST. This tree will be used through all other passes of the compilation.

• A symbol table and a symbol list have to be created. The difference between these two data structures is the following: The symbol table contains all object of the entire program. After an object has been added to the symbol table this entry will not be deleted until the whole symbol table is not needed any more. The symbol table is needed for the first pass and the second pass, but it can be deleted before the third pass. On the other hand, the symbol list contains only those object that are at certain moment in the scope of the program^{3}. Hence, the symbol list has to be handled in a stack wise fashion. Contrary to the symbol table, the symbol list is only needed in the first pass. Both data structures are put into one data encapsulation, namely in

EveSym.

• The types of the EVE program are handled in an own data encapsulation EveType. There the representations of the types that are defined in the EVE program are built, the equality of types is checked and so on.

In the following sections each of these steps will be discussed in more detail.

4.2.1 Lexical Analysis

3Actually, the symbol list contains only pointers into the symbol table, where the full information of all objects is stored.

Symbol Description  Attributes
char  Character  charVal ASCII value of the character
ident Identifier  spix  Index of identifier in the namelist
number Integer number value  Value of last number
string String saddr Index in the string storage
slen  Length of the string

Table 4.4: Terminal Classes and their Attributes

The goal of this analysis is to combine single characters of the source program to symbols. These symbols may be terminal symbols of the EVE grammar or they may be members of terminal classes. Such a terminal class is a set of strings that are all built according to the generation rule that characterizes that class.

Terminal symbols and terminal classes in EVE are represented by the enumeration Symbol. Table 4.5 shows the representations of all terminal symbols in EVE. Contrary to these terminal symbols, members of terminal classes have attributes. These attributes vary between different terminal classes. Table 4.4 shows the representations of terminal classes and the attributes they need.

In order to avoid creating a procedure that has a variable of type Symbol and all attributes as arguments, although not all attributes are needed normally, there are global variables in the module EveLexAn that always refer to the last symbol in the input file. These global variables are only set in the procedure NewSym, which yields the next symbol of the source program and puts it into the global variable sy. For a description of these global variables see the description of EveLexAn in Appendix E.

Renaming of Identifiers

In EVE it is possible to have two different identifiers with the same name in different blocks. However, during the lexical analysis the variable names are made unique. For that purpose a procedure Rename is used that substitutes a name that has already appeared in a different block by a unique name. Such a renaming has to be initiated by the syntax analyzer when in a definition of an identifier it is detected that this identifier already exists in the symbol list.

At some points the compiler has to create new identifiers that did not appear in the source code. For performing this task there is an own procedure GetNewIdent in the data encapsulation of the symbol table (EveSym) that yields a new and unique identifier.

Internally, the lexical analyzer is split into four different local modules:

• The module EveNames that handles the identifiers in EVE as well as all keywords. For that a special namelist has to be used that contains all the names of the identifiers in the EVE program. The keywords are put at the beginning of that list. For handling the renaming of identifiers correctly an additional list renamings has to be introduced

Symbol  Description
noSy  Dummy symbol
eofSy  End of file reached
caseSy  Keyword CASE
elseSy  Keyword ELSE
ifSy  Keyword IF
listSy  Keyword LIST
ofSy  Keyword OF
thenSy  Keyword THEN
tupleSy  Keyword TUPLE
unionSy  Keyword UNION
divSy  Integer division (/)
minusSy  Integer subtraction (-)
modSy  Integer remainder (%)
plusSy  Integer addition (+)
timesSy  Integer multiplication (*)
andSy  Boolean conjunction (&)
notSy  Boolean negation (~)
orSy  Boolean disjunction (|)
eqSy  Comparison for equal (=)
geSy  Comparison for greater equal (>=)
gtSy  Comparison for greater (>)
leSy  Comparison for less equal (<=)
ltSy  Comparison for less (<)
neSy  Comparison for not equal (# or <>)
lbraceSy  Left brace ({)
lbracketSy  Left bracket ([)
lpar  Left parenthesis (()
rbraceSy  Right brace (})
rbracket  Right bracket (])
rpar  Right parenthesis ())
colonSy  Separation symbol (:)
commaSy  Grouping symbol (,)
conversionSy Conversion symbol for unions ($)
defineSy  Definition symbol for binding (==)
doublecolonSy Infix list constructor (::)
selectionSy  Field selection from a tuple (.)
semicolonSy  Definition separation symbol (;)

Table 4.5: Representation of Terminal Symbols that connects the non-unique names of the identifiers in the EVE source program with the internal unique names. So, this local module can be regarded as a data encapsulation of the list of all names. With the procedures of this module it is now possible to use numbers instead of strings as the names of identifiers. The global variable spix that contains the spelling index of the last identifier in the program is only set in this local module.

• The module EveSkipComment handles the comments in EVE. One comment in EVE always starts with a -- symbol and extends to the end of the line but not further.

• The module EveNumbers handles all integer constants in the EVE program. The maximal value of such a constant that is accepted by this module is the exported constant Maxint. The global variable value that contains the value of the last integer constant in the program is only set in this module.

• The module EveStrings handles all string constants in the EVE program. For that a local string storage is necessary. So, this local module can also be regarded as a data encapsulation of this string storage. In a similar manner as it is done with the identifiers, every string constant is uniquely determined by some global variables. These global variables are: saddr containing the address of the first character of the string in the string storage and slen containing the length of the string. Both global variables are only set in this local module.

For details of the lexical analysis see the comments in the definition module of EveLexAn in Appendix E.

4.2.2 Syntax Analysis

The whole syntax analyzer is based on the principle of the recursive descent. For a description of this principle see [Aho et al., 1986]. However, a grammar that shall be implemented via a recursive descent parser must not contain any

• left-recursion or

• alternatives with same terminal starting symbols.

As the EVE grammar of Section 3.2.13 has alternatives with same terminal starting symbols (e.g. in the definition of the non-terminal symbol Type) it has to be transformed into an equivalent grammar that does not contain alternatives with the same terminal starting symbol any more. This transformation is performed by applying a left-factorization on the critical definition. In this left-factorization the common starting sequences of the alternatives are put in front of a new non-terminal symbol. This new symbol defines the uncaptured rest of the original non-terminal symbol and does not have same terminal starting symbols any more. For a more detailed explanation of the general process of left-factorization see again [Aho et al., 1986][Page 178f]. The new non-terminal symbols that have to be introduced into the EVE grammar are: SimpleOrFctTyp and TCFDefinition. The transformed grammar, which can be implemented by using a recursive descent parser, can be found in Appendix A.

Error Recovery

One important point in the syntax analysis is the way how an error recovery is performed. Many ways how to realize an error recovery have been suggested in the literature. As the EVE compiler is just a prototype compiler, we have chosen a quite simple strategy that is powerful enough to allow an error recovery after a few symbols in most cases.

The error recovery that is used here is based on the principle of the specific stop-symbols^{4}. The idea of this principle is to pass to each procedure that parses a non-terminal symbol, a set of so called stop-symbols. When a syntax error occurs in this procedure all symbols in the source code up to the first stop-symbol are skipped. As this set is different for every procedure this is a very flexible way to handle error recovery. Usually when a procedure is called it gets the set of stop-symbols of the caller and some additional symbols that can be added to this set as we know the context more exactly now. However, one disadvantage of this method is that as each procedure that parses a non-terminal symbol needs an additional parameter, the compilation is slowed down by the passing of the additional parameter a little even when no error occurs.

Although this principle usually yields a quite good error recovery, problems may arise when there are many syntactic and semantic errors mixed in an EVE program. In such a case some internal data structures of the compiler might be not defined (as the corresponding EVE construct yielded an error). Therefore, it is possible that the compiler terminates with a special message in this case.

Output of the Syntax Analysis: Abstract Syntax Tree

One output of the syntax analysis is an abstract syntax tree that represents the EVE source program. The tree structure itself is hidden in the data encapsulation EveAST. Hence, the structure of the tree will be discussed in Appendix E where this data encapsulation is discussed. Furthermore, in this appendix some procedures of the data encapsulation and their usage in the syntax analyzer will be discussed.

For each kind of node in the abstract syntax tree there is a constructor function that takes the components of the subtree that has to be built as arguments and creates the appropriate subtree. The result of each of these abstract syntax tree constructor functions is an abstract syntax tree (the type is therefore ASType) and it is returned as last argument of the procedure. The names of these constructor functions all have the form MakeAST*.

Every non-terminal symbol of the EVE grammar has a synthesized attribute (propagating upward) containing the abstract syntax tree of the EVE code that corresponds to that nonterminal symbol. The synthesized attribute of Program is then the abstract syntax tree of the whole EVE program. This attribute is also the only argument of the syntax analyzer

(SyntAn).

Output of the Syntax Analysis: Symbol Table

For storing the objects of an EVE program together with its attributes there are two data structures required:

4For a discussion of the method of error recovery see [Wirth, 1984].

• A symbol table, which contains at the end of the syntax analysis all objects of the EVE program such that later passes can access these objects.

• A symbol list, which only contains those objects that are in the current scope of the program. Actually, the symbol list contains only pointers to the symbol table, where the actual information about the objects is stored. This data structure is necessary to handle the block oriented semantics of EVE correctly. At the end of the syntax analysis the symbol list is empty and not needed any more.

Both of these data structures are encapsulated by the module EveSym. The most important procedures that operate on these data structures can be divided into several classes:

• Procedures for adding EVE objects to the symbol table and to the symbol list.

• Procedures searching for specific entries in the symbol list.

• Procedures for explicitly setting certain fields of entries in the symbol table.

• Procedures for handling forward definitions.

• Procedures for extracting information about functions from the symbol table.

• Procedures for handling blocks in EVE .

• Procedures performing explicit operations on the constant MAIN.

• Procedures that are needed for λ-lifting.

Output of Syntax Analysis: Types

As EVE is strongly typed, each new identifier has to get an explicit type. However, it is also possible in EVE to apply type constructors to already existing types, thereby getting new types^{5}. For creating such new types and for accessing elements of these types the data encapsulation EveType has been introduced.

Whenever a new compound data type is defined, this type is stored in an object of type EType. This new type is then stored in the type field of the object to which the new type is bound. Contrary to the symbol table there is no own data structure that contains all the types of the EVE program. Moreover, the types are only accessed as fields in the symbol table. As the types of the EVE objects are only needed in the first pass and in the second pass, they can be discarded before the code generation.

4.3 Second Pass

The second pass of the compiler contains several transformations that are performed on the abstract syntax tree that is the result of the first pass. The most important of these transformations is the λ-lifting. Because of its importance we first discuss the general idea of λ-lifting before we describe the λ-lifting algorithm that has been used in the EVE compiler.

5See Section 3.2 for a description of the available type constructors in EVE .

4.3.1 The Principle of λ-lifting

The motivation for performing a λ-lifting is the observation that functions without global variables are very easy to handle. The reason for that is that without global variables no environment that would contain the values of the global variables for a function is needed. The approach of introducing such environments leads for example to the SECD machine that is described in [Henderson, 1980]. In traditional implementations of imperative languages one also has such an environment, which is represented by the stack with the activation records of all active functions. However, the usage of such an environment slows down the computation very much. Therefore, it is worthwhile to transform the given program into a program that has no functions containing global variables any more. This process is then called λ-lifting. After this transformation all function definitions can be put on top level. These functions are then called super combinators. The notions of super combinators and λ-lifting have both been introduced by R. J. M. Hughes in [Hughes, 1984]. The following definition of a super combinator is taken from [Peyton Jones, 1987][Page 223]:

Definition 2 (Super Combinator) A super combinator, S, of arity n is a lambda expression of the form

λx_{1}.λx_{2}…. λx_{n}.E

where E is not a lambda abstraction^{6} such that

1. S has no free variables,

2. any lambda abstraction in E is a supercombinator,

3. n ≥ 0; that is, there need be no lambdas at all.

Another way of using combinators to implement functional programs is to use a fixed set of combinators. This approach has been chosen for example by D. Turner in his implementation of SASL ([Turner, 1979]) and of Miranda^{7}. In [Stoye, 1985] and [Clarke et al., 1980] it is shown how this approach can be used to develop a processor that uses a fixed set of combinators as its instruction set. This possibility of building a processor that implements these combinators in hardware is one of the main advantages of this approach. As it suffices to have the S and the K combinators^{8} from combinator theory such machines are usually called SK reduction machines.

Contrary to this approach of SK combinators we use super combinators in the EVE compiler. Note, that the S and K combinators are just special cases of super combinators. The most important features of super combinators, especially in comparison to SK combinators, can be summarized as follows:

6This just ensures that all the ‘leading lambdas’ are accounted by x_{1} … x_{n}.

7Miranda is a trademark of Research Software Ltd.

8The definitions for these combinators are:

• S f g x = f x (g x),

• K c x = c

• When a super combinator is applied to some expression all formal parameters of the super combinator can be instantiated by these expressions simultaneously instead of instantiating them one by one.

• As for each program a special set of super combinators is produced, there are fewer combinator applications necessary than with the SK approach. Hence, one could say that super combinators have a ‘coarser grain size’ than SK combinators. This offers some further advantages like less complex compilers that yield smaller programs.

• The λ-lifting step, which transforms a functional program into a set of super combinators, can be performed in such a way that the resulting collection of super combinators is fully lazy. This means that when the transformed program is executed, no expression will be evaluated twice. To reach such a behaviour, the λ-lifting has to lift so called maximal free expressions (mfe’s) instead of single global variables of functions. A maximal free expression of an expression E is a free expression that is not a proper subexpression of another free expression of E. In the above mentioned original work of Hughes ([Hughes, 1984]) such a fully lazy λ-lifting was developed. For another discussion of fully lazy λ-lifting see [Peyton Jones, 1987][Chapter 15]. Note that such a fully lazy behaviour of the resulting program is also possible when the given program is compiled to SK combinators.

Apart from the λ-lifting algorithms that are presented in [Hughes, 1984] and in [Johnsson, 1987] there are other algorithms known in the literature that put an emphasis on different aspects of the whole process. For example in [Takeichi, 1988] an algorithm for the so called ‘lambda-hoisting’ is presented. The key idea behind this algorithm is to transform programs into ones with local recursion. Hence, locally defined functions are not lifted on top level as they are by the other λ-lifting algorithms. Furthermore, the lambda-hoisting algorithm is fully lazy. The main advantage of this algorithm is that deeply nested functions get fewer parameters than with the other λ-lifting algorithms. This algorithm has been used to compile code for the ‘fully lazy functional machine’ (FLFM), which is described in [Takeichi, 1986]. A modified version of this machine has been used for the implementation of the functional programming language RUFL (see [Wentworth, 1989b]).

The main new idea that is presented in [Peyton Jones and Lester, 1991] is to separate the process of a fully lazy λ-lifting into two steps:

1. A full laziness transformation and

2. an ordinary λ-lifting step and

The full laziness transformation itself can again be decomposed into two steps:

1. In the first step each maximal free expression e is replaced by the local definition { v == e } v.

2. Afterwards, all local definitions are lifted to the top level.

This approach has several advantages compared to the other approaches discussed so far:

• Compared to the algorithms performing a fully lazy λ-lifting the single steps in this approach are quite simple. This reflects the general software technological idea of splitting big problems into smaller ones.

• For the λ-lifting steps different algorithms can be taken without having to change other parts of this algorithm. Thereby, one can make experiences with already existing λlifting algorithms that do not keep full laziness.

• The full laziness transformation can even be used in compilers that do not perform any λ-lifting at all. Hence, it might be useful to study the advantages of performing such a transformation in its own right.

This λ-lifting algorithm is used in the implementation of the functional programming language Haskell at the University of Glasgow. Therefore, the description of the algorithm itself is given in Haskell, which forms a nice example of how to structure programs in a modern functional programming language.

4.3.2 The λ-lifting Algorithm in the EVE Compiler

The λ-lifting algorithm that is used in the EVE compiler is similar to the algorithm described in [Johnsson, 1987]. As this is no fully lazy λ-lifting algorithm no maximal free expressions have to be detected. Instead, it essentially suffices to transform every global variable of a function into an additional parameter. The resulting functions can then be put on the top level. There are two steps in the algorithm that can be described as follows:

• For each free variable in a local function definition, add an argument to the function.

• Apply the same free variables to each use of the function, substituting fx_{1} … x_{n} for f, where x_{1} … x_{n} are the free variables in the definition of f. The other actual parameters stay at the end of the function call.

To make this strategy clearer let us look at the following example:

     f[x] : INT[INT] ==
       { y    : INT       == 2*x ;
         g[x] : INT[INT]  == x+y }
       g[x*x];

In this example the variable y is a global variable for the function g. Therefore, the definition of g is extended by one argument. This argument stands for the global variable y. In the call of g an additional argument has to be given. It should be obvious that this additional argument must be the global variable in the above example. After these two steps one gets the following lifted version:

     g[x,y] : INT[INT,INT] == x+y;
     f[x] : INT[INT] ==
       { y : INT == 2*x }
       g[x*x,y];

Now, the two functions f and g are super combinators as they don’t have any free variables any more.

From this example one could get the impression that it suffices to determine the set of free variables for each function and then add these free variables to every call of the function and add a formal parameter for each free variable in the function definition. However, when other functions are called in the current function then it might be the case that for these functions variables have to be lifted. These lifted variables must be added to the set of lifted variables of the current function, as long as these variables are not bound inside the current function. Hence, the whole λ-lifting process has to be performed recursively. Again, to make this clearer we given an example:

     f[x] :  INT[INT] ==
       { a : INT == 1 ;
         b : INT == 2 ;
         g[y] : INT[INT] == IF y>a THEN 1 ELSE a+h[y+1] ;
         h[z] : INT[INT] == IF z>b THEN 1 ELSE b*g[z+1] }
       g[0]+h[1];

If we compute the set of free variables for both local functions we get:

freeVars(g) = {a},

freeVars(h) = {b}.

But if we then lift only these free variables, the resulting function definitions again have free variables:

     f[x] : INT[INT] ==
       { a : INT == 1 ;
         b : INT == 2 ;
         g[y,a0] : INT[INT] == IF y>a0 THEN 1 ELSE a0+h[y+1,b] ;
         h[z,b0] : INT[INT] == IF z>b0 THEN 1 ELSE b0*g[z+1,a] }
       g[0,a]+h[1,b];

Since the free variable b was lifted out of the function h it suddenly appears in the call of h in the body of g. Hence, b is now a free variable of g, which was not the case in the original program. The same thing happens with the variable a in the function h. Therefore, the above step has to be repeated with the new sets of free variables. This then indeed yields functions without any free variables that can be put on top level:

     g[y,a0,b0] : INT[INT] == IF y>a0 THEN 1 ELSE a0+h[y+1,b0] ;
     h[z,b0,a0] : INT[INT] == IF z>b0 THEN 1 ELSE b0*g[z+1,a0] ;
     f[x] :  INT[INT] ==
       { a : INT == 1 ;
         b : INT == 2 }
       g[0,a,b]+h[1,b,a];

This example shows, that the set of variables that have to be lifted out of a function is not always equal to the set of free variables of that functions. Moreover, all variables that have to be lifted from functions that are called from inside these functions have to be lifted, too. This yields the following recursive definition of the set of variables that have to be lifted out of a function f:

liftedVars(f) = freeVars(f) ∪ ⋃_{g occurIn f} liftedVars(g),

where g occurIn f iff f contains a function call of g.

If one lifts for each function f the set of variables denoted by liftedVars(f) instead of freeVars(f) then it is not necessary to repeat the lifting several times until there is no function with free variables any more like it was the case in the above example. Therefore, it is far more efficient to first compute liftedVars(f) for each function f and afterwards lift this set in one step.

To make the computation of liftedVars(f) easier we can give a different but equivalent definition of this set:

liftedVars(f) = ⋃_{g occurIn^{∗} f} freeVars(g),

where occurIn^{∗} is the reflexive, transitive closure of the occurIn relation.

The Steps of the λ-lifting Algorithm in the EVE Compiler

After having discussed the general idea of λ-lifting in the previous section, we can now have a closer look on the internal structure of the λ-lifting algorithm in the EVE compiler. The λ-lifting is done in the module EveLambdaLift. However, there are also functions from EveAST and EveSym used in this algorithm. The whole λ-lifting process is separated into five steps:

1. First of all, the whole symbol table is searched for all local functions. For each of these functions, a list of all free variables, a list of those functions that appear in this local function and a list of variables that are defined in this local function has to be created. As all this information is derived from the symbol table, this step is realized by calling the procedure GetLists of the data encapsulation of the symbol table (EveSym).

2. In the next step the transitive closure of the “occurIn” relation, which has been described in the previous section, has to be computed. The result of this step is for each local function a list of those variables that have to be lifted out of the function. The computation of the transitive closure is performed in a local module of the module

     EveLambdaLift.

[figure: ADD[exp_{1}, exp_{2}]; callN; ADD; exp_{1}; exp_{2}; plusN; exp_{1}; exp_{2}]

Figure 4.2: Translation of a Call of a Predefined Function into a Special Node of the Tree

3. The third step of the λ-lifting algorithm again operates on the symbol table. In this step for each local function the variables that have to be lifted are added as parameters to the function definition. Furthermore, all these local functions are put into a list that is then used in step five to bring all local functions on top level. After this step no function contains free variables any more. The procedure that does all this is LiftFcts in the data encapsulation of the symbol table (EveSym).

4. In this step the abstract syntax tree is traversed. For that, the procedure Traverse from the data encapsulation of the abstract syntax tree (EveAST) is used. When traversing the tree there are two things that have to be done:

• The local function definitions have to be cut off the tree. This can be done here as they have been put into a list in step three.

• In every function call the lifted arguments of that function have to be added. Note that for the function definitions these arguments have already been added in step three.

5. As the last step in the λ-lifting algorithm the local functions that have been lifted by now, must be put on top level. As this changes the structure of the abstract syntax tree, this step is realized by calling the procedure AddASTsToDefSeq of the data encapsulation of the abstract syntax tree (EveAST). In the current implementation all the lifted functions are added in front of the whole definition sequence that defines the EVE program.

After having executed these steps, the program consists of a collection of functions definitions that are all on top level (so there are no nested function definitions any more). None of these functions has free variables any more. These characteristics of the program are essential for the last pass in the compiler.

4.3.3 Further Transformations on the Abstract Syntax Tree

Apart from the λ-lifting, which has been described in detail in the previous section, there are also some further transformations performed on the abstract syntax tree that has been produced by the first pass. These transformations can be described as follows:

• One transformation replaces all calls to predefined comparison functions, to arithmetical and boolean operation functions and to the predefined list constructor function by the node of the abstract syntax tree that represents that function. Figure 4.2 shows

CASE E OF  { v_{1} : T_{1} } E_{1}
{ v_{2} : T_{2} } E_{2}
⋮
{ v_{n} : T_{n} } E_{n}
ELSE E_{n+1}

{ x == E ;
h == HEAD(x);
t == T AIL(x) }
IF h = tag(v_{1})  THEN { v_{1} == t } E_{1}
ELSIF h = tag(v_{2}) THEN { v_{2} == t } E_{2}
⋮
ELSIF h = tag(v_{n}) THEN { v_{n} == t } E_{n}
ELSE E_{n+1}

x, h, t are new unique identifiers

tag(v) yields the number of the variant with the name v.

Figure 4.3: Translation of a CASE Expression into an IF-THEN-ELSE expression

how a call of the predefined integer addition function ADD is in the first pass translated into an abstract syntax tree. In the second pass this tree is transformed into a tree without a function call but with a predefined function that also represents integer addition. This transformation process is similar for the following predefined functions:

  – ADD, SUB, MUL, DIV, MOD,
  – NEG, ABS, ODD,
  – AND, OR, NOT,
  – *LT, *LE, *EQ, *NE, *GE, *GT,
  – CONS.

• In another transformation process all case expressions are translated into explicit IF-THEN-ELSE constructs. Figure 4.3 shows this in the form of a rewrite rule on the EVE source code. The basic idea behind this transformation is based on the fact that each object of a union type carries in its first field a tag denoting its actual type. Therefore, a CASE expression can be translated into an IF-THEN-ELSE expression that chooses the right branch by checking for the value in the first field of this object.

After this transformation, the abstract syntax tree does not contain any CASE expressions any more. Therefore, these expressions are not covered in the next section, ADAM Assembler Program Dataflow Graph

EVE Program

Figure 4.4: Description of the Code Generation

where the code generation for the various constructs is described.

• In the last transformation a free variable analysis is performed and as a result of this analysis to each node in the abstract syntax tree the set of free variables in the subtree, starting from that node, is attached. This transformation is done by the procedure AttachFreeVars in the data encapsulation of abstract syntax trees (EveAST). This information about which variables are free in a subtree is needed at each node in the tree for the code generation step as we will see in the next section.

4.4 Third Pass

In this section we give an informal description of the code-generation step in the EVE compiler. However, a complete formal description of this step in the form of a denotational semantics can be found in Appendix B. Hence, the reader may refer to this appendix to look up some details that are omitted in this section in order to concentrate on the main features of the translation.

The central idea in our approach for an automatic parallelization of functional programs is to simulate the dataflow behaviour of a functional program on a control-driven machine that has special constructs for the creation and for the synchronization of parallel threads of control. Therefore, dataflow graphs play a crucial role in the translation process although there is no explicit data structure of a dataflow graph used in the compiler. Moreover, these dataflow graphs show the inherent parallelism that is contained in the EVE language constructs. Therefore, the description of the translation of the various EVE language constructs into ADAM assembler code is split into two parts:

1. First, the translation of the EVE constructs into dataflow graphs will be discussed. This step already shows which parts of the computation may be performed in parallel.

2. Afterwards, it will be shown how these dataflow graphs can be translated into ADAM assembler code that simulates the dataflow behaviour of these graphs.

This idea of using two steps in presenting the code-generation step of the EVE compiler can be also seen from Figure 4.4 where the overall structure of the code generation is presented graphically.

Of course, in the compiler itself there is only one step performed, namely a direct translation of EVE constructs into ADAM assembler code. But in order to understand the motivation for using the translation schemes that will be presented these dataflow graphs are very helpful.

The main advantage of this approach is that one can exploit the inherent parallelism of the dataflow model without losing the efficiency of the controlflow model. As it has been described in Section 3.1 a pure dataflow model needs a lot of copying of result values. However, by using the concept of registers from the controlflow model many of these copying instructions become superfluous. Another advantage of this approach is that some methods from compiler design that have been developed for the optimization of imperative programs can now also be applied to the code that is produced by the EVE compiler.

4.4.1 From EVE Programs to Dataflow Graphs

The structure and the semantics of dataflow graphs have already been discussed in Section 3.1.1. From the description given there one could get the impression that it suffices to have all free variables of an expression as the input of a dataflow graph and the result of evaluating the expression as the only output of the graph. However, when trying to implement such graphs one gets into some problems:

• If there are expressions without any free variables (e.g. constants) the corresponding dataflow graphs should deliver a result whenever it is needed by one of the succeeding nodes. To implement this concept of producing a token “on demand”, an additional input arc, the trigger arc, is introduced. The arrival of a token at this arc indicates that the result of the expression is really needed and should therefore be computed. This trigger arc is also very important for implementing non-strict functions as it allows to start the computation of a function before all arguments have arrived, by sending such a trigger token to the function.

• Another problem stems from the presence of side effect nodes in the dataflow graph. In general, all operations on compound data structures are realized via side effects. Some of these nodes have no actual result (e.g. the node that writes a value into a special field of a tuple). However, it must be possible to tell a succeeding node that such a side-effect node has fired. For that purpose an additional output arc, the signal arc, is

[figure: free variables; trigger; result; signal]

Figure 4.5: The Interface of a Dataflow Graph

introduced. Now, the following invariant holds in the entire dataflow graph: Whenever tokens have appeared on each of the output arcs it is guaranteed that there are no tokens in the dataflow graph left any more. This property is often called self-cleaning property of dataflow graphs.

Figure 4.5 shows the resulting general interface of an arbitrary dataflow graph. In this graph the arcs on which tokens flow that really carry values are drawn thicker than arcs where arriving tokens do not have well defined values. Note that tokens that arrive at the trigger arc as well as tokens that are produced at the signal arc need not carry any values as just the arrival or production of a token on the corresponding arc represents all the information one wants to get from these tokens. For a more detailed discussion of triggers and signals see [Traub, 1986][Chapter 6].

In the following sections for each EVE language construct the corresponding dataflow graph is presented. These graphs are presented in a bottom up fashion. Hence, the simplest graphs are presented at the beginning and the more complex graphs are described at the end of this section.

Constants

One of the simplest EVE constructs, namely a constant, is shown in Figure 4.6. Note that due to the description of the interface of each dataflow graph in Figure 4.5, this dataflow graph has only one input arc, the trigger arc. Whenever a token arrives at that input arc an output token is produced. This output token then has to be copied as every dataflow graph must have two output arcs. The token that arrives at the result output carries the value c. However, the value of the token at the signal output is undefined.

Variable

The dataflow graph of a variable in Figure 4.7 is even simpler than that of a constant. All that has to be done is to forward the token on the input arc that corresponds to the

[figure: constant c; trigger; result; signal]

Figure 4.6: Dataflow Graph of a Constant: c

[figure: v; trigger; result; signal]

Figure 4.7: Dataflow Graph of a Variable: v

[figure: E(E_{1}); E(E_{2}); op; FV(E_{1}) ∪ FV(E_{2}); FV(E_{1}); result_{1}; signal_{1}; result_{2}; signal_{2}; result; signal; FV(E_{2}); trigger]

Figure 4.8: Dataflow Graph of a Binary Operation: E_{1} op E_{2}

variable to the result output. The incoming trigger token can be directly forwarded to the signal output of the dataflow graph. There is no synchronization between the two input arcs necessary as the appearance of a token at the output arc does not directly imply that there are no tokens in the dataflow graph left any more. Moreover, only when at all output arcs tokens have arrived we can conclude that the dataflow graph has been ‘cleaned up’. Therefore, it is very well possible that the signal output arrives even before the actual result has been computed^{9}.

Binary Operations

The dataflow graph in Figure 4.8 shows how a binary operation is performed on two expressions E_{1} and E_{2}. As the incoming trigger token is immediately split into two successor tokens, both expressions are evaluated in parallel. As for any dataflow graph the set of the free variables has to be split into the sets of free variables of the subexpressions. As soon as both results of the subexpressions have been computed the operation is applied on these results. Note that the semantics of dataflow graphs requires both inputs the ‘op’ node to be present before this node can fire. The resulting token is then also the result of the whole binary operation. However, the signal outputs of the subexpressions E_{1} and E_{2} also have to

9This way of defining the semantics of the signal output minimizes the necessary synchronization and therefore maximizes the parallelism in the dataflow graph.

Figure 4.9: Internal Structure of a Signal Tree

[figure: Wait; Wait; Wait; …; Wait; Wait; ⋮; ⋮]

[figure: E_{1}; E_{n}; Invoke f; ⋯; ⋃_{i=1}^{n} FV(E_{i}); trigger; Send Arg.; Send Arg.; signal; result; FV(E_{1}); FV(E_{n}); res; act; n; 1]

Figure 4.10: Dataflow Graph of a Function Call: f[E_{1}, …, E_{n}]

be resolved. This is done by connecting these two arcs with a so called signal tree. Figure 4.9 shows the internal structure of a signal tree. From this figure one can see that a signal tree simply waits for all of its inputs, there may be arbitrary many, and produces an output token when all input tokens have arrived. The value of the output token is undefined. This reflects the fact that a signal tree is only used for the synchronization of the signal tokens, which themselves do not carry well defined values.

Function Call

The function call is a very interesting construct from the parallelism point of view. Obviously, all arguments of the function call may be evaluated in parallel. This exploits the horizontal parallelism, which is also available in strict programming languages. However, as EVE is a non-strict programming language the computation of the function can be started in parallel with the computation of the arguments. This is usually called vertical parallelism. When in the evaluation of the function an argument is needed that has not been computed yet, the evaluation of the function is suspended until that argument is available. This synchronization is realized by using non-strict data structures as they have been described in Section 3.1.

The n + 1 parallel threads in the function call can be seen in Figure 4.10 as the trigger token is split into n+1 successor tokens. The first of these successor tokens starts the invocation of the function, the other n tokens start the evaluation of the n arguments. The ‘invoke’ node in this dataflow graph is of special interest as it has to start the computation of the function itself. This is done by performing a side effect. To be more exact, this node performs the following actions:

• It has to initiate the allocation of an activation record for the called function^{10}.

• It has to initiate the evaluation of the function itself.

Corresponding to these two tasks the ‘invoke’ node has two output arcs:

• The ‘act’ output arc yields a token that carries a pointer to the allocated activation record as its value. This token is needed for sending the arguments to the correct function activation.

• The ‘res’ output arc yields a token that carries the result of the computation of the function. Therefore, this token can be directly forwarded to the result of the dataflow graph.

Due to the non-strict semantics of user defined functions in EVE, the invocation of the function and the sending of the arguments have been split into independent parts. However, for the sending of the arguments the pointer to the activation record of that function must be present. Therefore, the ‘SendArg’ nodes, one node for each argument, have two input tokens and a constant input token. If all tokens are present the node performs a side-effect by sending the argument to the correct function activation. As this side-effect node has no actual result the only output arc is a signal output. All these signal arcs and the signal arcs from the argument expressions are finally synchronized in a signal tree to yield the signal output for the whole dataflow graph.

Conditional Expression

The conditional expression is probably the most complicated expression regarding the translation into a dataflow graph. This can already be seen from the complexity of the resulting dataflow graphs in the Figures 4.11 and 4.12. To make the structure of this dataflow graph clearer it has been split into two parts: a head and a body.

Figure 4.11 shows the dataflow graph of the head of the conditional expression

                            IF E_{1} THEN E_{2} ELSE E_{3}.

There, first the expression E_{1} has to be evaluated. Based on the result of this evaluation, which arrives at the result_{E_{1}} arc, the free variables of the two body expressions, the trigger input and the signal of this expression are either forwarded to the THEN branch (E_{1}) or to the ELSE branch (E_{2}). Therefore, it is guaranteed that all input tokens of the switch node are forwarded to the same branch. After having evaluated the appropriate branch the results of both branches have to be merged together as it is not possible to decide at compile time which branch has been chosen. However, it is guaranteed that only from one of the two branches a result is returned. The same merging has to be done with the signal outputs of the two branches.

As the THEN and the ELSE branch are essentially the same, Figure 4.12 shows a dataflow graph that is parameterized with the number i, where i = 2 stands for the THEN branch and i = 3 stands for the ELSE branch. In this dataflow graph two items have to be emphasized:

10The actual allocation has to be done by the memory management unit.

4.4. THIRD PASS 97

[figure: E_{1}; \overline{E_{2}}; \overline{E_{3}}; FV(E_{1}) ∪ FV(E_{2}) ∪ FV(E_{3}); trigger; T; F; result; signal; FV(E_{1}); result_{E_{1}}; signal_{E_{1}}; FV(E_{2}) ; ∪ ; FV(E_{3})]

Figure 4.11: Dataflow Graph of the Head of a Conditional Expression

[figure: E_{i}; FV(E_{2}) ∪ FV(E_{3}); trigger; signal_{E_{1}}; FV(E_{i}); {FV(E_{i})- \atop FV(E_{i′})}; result; signal]

Figure 4.12: Dataflow Graph of the Body of a Conditional Expression

• Not all free variables that are the inputs of this dataflow graph are needed in the expression. Those free variables that are only needed in the other branch have to bypass the expression and can be directly forwarded to the signal tree.

• The trigger input of this dataflow graph must be the trigger of the whole conditional expression. It is not possible to take the signal of the expression E_{1} as the trigger as E_{1} might not terminate but nevertheless yield a result. In this case the whole conditional expression would not terminate if we used ‘signal_{E_{1}}’ as the trigger for the expressions E_{2} and E_{3}. But this would violate the semantics of the conditional expression. As ‘signalE_{1}’ can’t be used as a trigger it must be directly forwarded to the signal tree in both branches.

The central insight from the parallelism point of view is here, that the result of the expression E_{1} must have been computed before the computation in the appropriate branch can be started. This is one of the central synchronization points in an EVE program. Therefore, one should avoid conditional expressions in which the evaluation of the head takes most of the time. Wherever possible the expensive computations should be pushed into the two branches and only cheap computations should be performed in the head. This is especially important when we have a recursive function where a conditional expression is necessary to detect the base case of the recursion. If the head contains expensive computations, there won’t be many overlapping computations between the different levels in the recursion. However, if the the computation of the head is quite cheap many different levels of recursion can be evaluated in parallel. For a more detailed description of this behaviour see Section 5.2.

[figure: E; E_{1}; E_{n}; FV(E) ∪ ⋃_{i=1}^{n} FV(E_{i}); trigger; result; signal; v_{1}; v_{n}; FV(E_{1}); FV(E_{n}); FV(E); …; …]

Figure 4.13: Dataflow Graph of a Local Definition: {v_{1} == E_{1}, …, v_{n} == E_{n}}E

Local Definitions

Figure 4.13 shows the dataflow graph of an expression, where the objects v_{1}, …, v_{n} are defined locally to that expression^{11}. Examining the splitting of the trigger token we see, that all locally defined objects and the main expression itself are evaluated in parallel^{12}. As the object v_{i} may refer to objects v_{j} for j < i it would be wrong to just forward the object to the main expression. Instead, it has to be put into the stream of free variables that enters the dataflow graph. The result of the main expression can be directly forwarded to the result of the whole expression. The signals of all expressions have to be synchronized in a signal tree.

As this dataflow graph shows, all local objects will be evaluated in parallel even if the some of the objects are not needed at all. It might even happen that the evaluation of some local object does not terminate although a result is returned by the main expression. In this

11Note that we need not consider local function definitions as they have already been lifted on top level by the second pass.

12As in the function call, the computation of the main expression is automatically suspended when an object is needed that has not been evaluated yet.

[figure: Allocate; n; E_{1}; E_{n}; Write; 1; Write; n; …; …; trigger; ⋃_{i=1}^{n} FV(E_{i}); result; signal; FV(E_{1}); FV(E_{n})]

Figure 4.14: Dataflow Graph of a Tuple Construction: [E_{1}, …, E_{n}]

case a token carrying the result leaves the dataflow graph but a signal output will never be produced.

Tuple Construction

The dataflow graph of the tuple construction in Figure 4.14 is quite similar to the dataflow graph of the function call. The main difference is the fact that the allocation of a tuple is done sequentially before the expressions of the various components are evaluated. However, as it has been described in Section 3.1 the allocation of some memory is a split-phase instruction and therefore tolerates long memory latencies. Therefore, this part has been implemented sequentially. The ‘Allocate’ node performs a side-effect, as it causes the memory unit to allocate some memory. The result of this node is a token carrying a pointer to the allocated memory. As in EVE data structures are non-strict, this token is immediately forwarded to the result of the whole dataflow graph. Hence, another instruction may use this data structure even before all of the arguments have been computed. This is possible since the data structures are implemented in such a way that with each field of the data structure a full/empty bit is associated. This corresponds to the idea of I-structures that are described in [Traub, 1986] and [Arvind et al., 1987]. For a more detailed discussion of this concept see Section 3.1.

In the other n parallel threads the values of the expressions are evaluated. As soon as one value is available it is written into the correct field of the tuple. This is done in the node

[figure: E; Read; i; FV(E); trigger; result; signal]

Figure 4.15: Dataflow Graph of a Tuple Selection: π_{i} E

‘Write’. This node actually performs a side effect^{13} and has as result just a signal output arc. To keep the self-cleaning property all these signal arcs and the signal arcs of the expressions have to be synchronized in a signal tree.

Tuple Selection

In Figure 4.15 a dataflow graph is presented that allows to select a special field of a tuple. In the EVE language this is described by using a selector of the form Tuple.Field. However, in the abstract syntax tree this has been transformed into a selection of the i-th field of that tuple. In the denotational semantics this is described by the construct π_{i} E.

The interesting part in this dataflow graph is the ‘Read’ node that takes an integer value i and a pointer to a tuple T and initiates the selection of the i-th field of T by the memory management unit. This side-effect node then produces a token that carries the value of the i-th field of T.

Function Application

After having discussed the translation of the basic EVE expression constructs into dataflow graphs in the previous sections, we will now cover the translation of two advanced constructs that deal with higher order functions.

In Section 4.4.1 only the case of first order functions was covered. However, as in EVE higher order functions are allowed it is possible to apply an expression rather than a simple name of a function to some arguments. The translation of such a function application into a dataflow graph is shown in Figure 4.16.

13All reading and writing operations of data structures are performed by the memory management unit. The corresponding nodes in the dataflow graph just initiate the operation of the memory management unit.

Figure 4.16: Dataflow Graph of a Function Application: E[E_{1}, …, E_{n}]

[figure: E_{1}; E_{n}; E; ⋯; FV(E) ∪ ⋃_{i=1}^{n} FV(E_{i}); trigger; Send Arg.; Send Arg.; signal; result; FV(E_{1}); FV(E_{n}); Apply; act; 1; n]

[figure: Allocate; n + 1; v_{1}; v_{n}; Write; 1; Write; n; …; …; trigger; result; signal; Fct.Adr.; Write; 0; f; v_{1}; v_{n}]

Figure 4.17: Dataflow Graph of a Curried Expression: < f, v_{1}, …, v_{n} >

The overall structure is similar to that of the function call. However, in this case the function is the result of an expression E. The ‘Apply’ node has the same tasks as the ‘Invoke’ node but it takes the function that has to be applied as an input.

Currying

Although an explicit currying of functions is not possible in EVE, the abstract syntax tree as an intermediate language offers the possibility to represent curried expressions. In the current version of the compiler this is only exploited for higher order functions (e.g. functions that are parameters to other functions) that are represented as curried expressions with 0 curried variables.

The dataflow graph in Figure 4.17 is actually just a special case of a tuple construction. However, as it is a special case some parts can be implemented more efficiently than in a general tuple construction. For example no general expressions have to be evaluated as the components of the tuple that is created are just the curried variables. In the first component of the tuple the address of the curried function and the number of the curried variables are encoded. This encoding is performed by the ‘Fct.Adr.’ node.

[figure: E; FV(E); trigger; result; signal]

Figure 4.18: Dataflow Graph of a Simple Definition: v == E

Simple Definitions

After having discussed the translation of EVE expressions into dataflow graphs it now remains to describe the translation of EVE definitions into dataflow graphs.

As it can be seen from Figure 4.18, a simple definition has the same interface as the expression that defines a new object. Therefore, in the dataflow graph the input arcs are directly forwarded to the input arcs of the expression and the output arcs of the expression are directly forwarded to the output arcs of the simple definition. The only reason to introduce such a simple expression is the ability of giving a name to some expression. This directly reflects the semantics of a variable binding in functional languages (see Section 2.1).

Function Definitions

Contrary to simple definitions, function definitions have no explicit input or output arcs. Moreover, when the function is called dynamic links to this dataflow graph are established (this is done in the nodes ‘Invoke’ and ‘SendArg’ that have been introduced in Section 4.4.1). And when the result has been computed a dynamic link to the corresponding ‘Invoke’ node is established such that this node can emit a token carrying the result of the function. To denote this notion of dynamic links the arcs for the trigger token and for the formal parameters are drawn from the left and are not entering the dataflow graph but they have their origin in the border of the graph. The same principle has been used to describe the result of the function.

The two interesting nodes in this construct are:

• The ‘Return’ node that establishes the dynamic link from this function definition to the ‘Invoke’ node that called this function. Thereby, the resulting value is transmitted to the caller of the function.

• The ‘Terminate’ node destroys the incoming token. As a side-effect it tells the memory management unit that the activation record for this function activation can be discarded.

[figure: E; Return; Terminate; trigger; v_{1}, …, v_{n}; result]

Figure 4.19: Dataflow Graph of a Function Definition: f[v_{1}, …, v_{n}] == E

D_{1}  ⋯  D_{n}

Figure 4.20: Dataflow Graph of a Definition Sequence (Top Level): D_{1}, …, D_{n}

Definition Sequences

Here one has to distinguish between definition sequences on top level, where only functions and the constant MAIN are defined and nested definition sequences that contain only simple definitions. The latter case has already been handled in the discussion of local definitions^{14}. In the former case we just have a collection of unconnected function definitions as they are described in the previous section. Therefore, the dataflow graph looks like in Figure 4.20.

Program

As one can see from the syntax definition of EVE in Section 3.2.13 a program is just a special definition sequence. Therefore, the dataflow graph for a definition sequence can also be used here. However, the only constant on top level, namely MAIN, has to be handled in a special way. The compiler does this by extracting the definition for MAIN out of the program. The rest of the program can then be translated as it is described for definition sequences of function definitions. This approach is useful as the compiler has to produce two different files anyway:

• A program file containing a collection of function definitions. This file will be produced from the program without the constant MAIN.

• An expression file containing the code for the top level expression. This file will be produced by applying the usual translation of an expression on the body of the constant

     MAIN.

4.4.2 From Dataflow Graphs to ADAM Assembler Code

As our approach to the automatic parallelization of functional programming languages does not rely on special hardware support but is based on conventional hardware, the dataflow graphs have to be translated into the assembler code of an abstract machine, namely the ADAM machine. A description of the ADAM machine has been given in Section 3.1. A summary of the ADAM instruction set with a discussion of the various instructions can be found in Appendix C.

As it has already been mentioned, the ADAM assembler code is essentially a conventional two address code with extensions for creating and synchronizing parallel threads of control.

14The reader should be aware that local definitions are the only possibility where nested definition sequences might occur.

Therefore, in this step we have to describe the translation of the dataflow model into the controlflow model. As this is obviously more complex than a translation that remains within one model we will first discuss the general correspondencies of the entities in the dataflow and the controlflow model. Afterwards, we can, based on these correspondencies, give the translations of the dataflow graphs of Section 4.4.1 into ADAM assembler code.

In the description of this translation it turns out that the handling of the free variables forms a quite big part without reflecting any crucial concepts of a parallel implementation. In order to concentrate on the important parts of the translation this handling of free variables is usually omitted in the presentation of the ADAM assembler code. However, this is only true for the description in this chapter. In Appendix B a denotational semantics of the whole code generation process in the EVE compiler is given. There all details of the translation are covered.

A Comparison between the Dataflow Model and the Controlflow Model

The translation of dataflow graphs into ADAM assembler code requires a study of the correspondencies between the central entities in the dataflow model and in the controlflow model. By this it is then possible to simulate a dataflow behaviour on a control driven machine.

Now, we will discuss for each of the important entities of the dataflow model its correspondence with entities in the controlflow model:

• As we want to get a parallel assembler program the constructs dealing with parallelism are most important for us. The creation of parallelism, that is realized by copying a token (especially the trigger token), into several successor tokens, is done in the controlflow model with an instruction that splits the thread of control into two independent threads (the SPLIT instruction in the ADAM code). The synchronization of parallelism, that is realized by waiting for two input tokens in order to produce one output token in the dataflow graph, is done in the controlflow model by a waiting instruction (WAIT in ADAM code) that merges two threads of control into one thread.

• The connections between the operations, that are realized by the arcs between nodes in the dataflow graph, are established in the controlflow model by explicit jumps. ADAM instructions usually have a field that states the relative address of the next instruction to be performed. This reflects the situation in the dataflow model. The straightforward translation of a dataflow graph into a dataflow program that is presented in Section 3.1.1 also shows the typical structure of the instructions containing the address of the successor.

• Due to this concept of ‘translating arcs into jumps’ the following correspondencies become obvious:

– The names of the arcs in the dataflow graph become labels in the controlflow program.

– The nodes in the dataflow graph become (sequences of) instructions in the controlflow program.

• In the previous section it has been stated that some nodes in the dataflow graph perform side effects. The corresponding ADAM assembler instructions are characterized by the fact that the result of the side-effect instruction will appear at the next instruction in the program. Therefore, this instruction will usually be a RECEIVE that puts the value it gets in the register that is the first argument of this instruction.

Abstract Data Types for the Code Generation

Before we can discuss the code generation itself, the abstract data types that are needed for the creation of ADAM assembler code have to be described. The following data types are needed in the code generation:

• Variables. This data type contains all possible variables in an ADAM assembler program. The set of all variables can be expressed as a language of the grammar given in Section 3.2.13 with Ident as starting symbol. The only operation that must be performed on these objects is the test for equality.

• Flags for checking compound objects. This data type is needed to decide whether an ADAM object is an integer value or a pointer to a compound data structure. Such a distinction is necessary to guarantee that the garbage collection operates in the intended way.

• Registers. The register data type represents registers of the ADAM machine. However, they need not directly correspond to the registers of the underlying hardware. In the ADAM machine arbitrarily many registers are available. Therefore, there is no explicit procedure for “dropping” a once fetched register. Moreover, all registers are local to a function and therefore all registers of a function can be dropped at the end of the function. The most important operation that is performed on this data type is to get a unique register out of the pool of available registers. In the following, registers will usually be denoted by r, r_{1}, . . ..

• Labels. The objects of this data type are needed for the jump instructions. As with the registers, an operation is needed that gets a unique label out of the pool of available labels. There are arbitrarily many labels, which are local within a function. Labels will usually be denoted by l, l_{1}, . . ..

• Events. Events are needed by WAIT instructions to synchronize parallel threads of control. When the first WAIT on an event is applied the thread of control is suspended. As soon as a second WAIT instruction is performed on the same event one thread continues with the successor instruction. By this construction two threads of control are joined into one thread. As with registers and labels the most important operation on this data type is the selection of a unique event. There are arbitrarily many events, which are local within a function. Events will usually be denoted by e, e_{1}, . . ..

• ADAM Assembler Code. See Appendix C for a description of the ADAM instruction set.

• Environment. The environment is carried through the whole compilation process. At the beginning of the compilation it is empty. For each of the variables that are currently in scope, the environment contains

trigger : CONST r, +2  ; Get constant into register r
c′  ; Value of the constant
SPLIT result, signal ; 2 output labels required

Figure 4.21: ADAM Assembler Code of a Constant: c

– the register that carries the value of that variable,

– the label that announces the availability of this register,

– a flag that indicates whether this variable contains an integer value or a pointer to a compound data structure.

The operations that are performed on an environment are

– the binding of a variable to a triple of register, label and flag,

– the binding of a variable to a new label,

– a selection operation for each of the three components of an environment.

• State. The overall state consists of

– the state of the registers,

– the state of the labels,

– the state of the events.

The states of registers, labels and events contain the information that guarantees that every new register, label or event is unique. Therefore, this state must also be carried through the whole compilation process.

A formal description of these abstract data types and of the properties that must hold for these structures can be found in the denotational semantics of the code generation in Appendix B.

Constants

As for all EVE constructs, the input and the output of this construct are:

• Input: trigger.

• Output: result, signal.

Figure 4.21 shows the assembler code that has to be created for a constant. The code sequence is entered by a jump to the label trigger. This reflects to the dataflow point of view where the arrival of a trigger token starts the computation of the expression. Depending on the type of the constant, c′ gets one of the following values:

trigger : SPLIT l_{E_{1}}, l_{E_{2}}  ; 2 parallel pathes
C_{E_{1}}  ; evaluate 1^{st} subexpression
C_{E_{2}}  ; evaluate 2^{nd} subexpression
result_{E_{1}} : WAIT e_{1}, +2  ; synchronize the results
result_{E_{2}} : WAIT e_{1}, +1  ; of both subexpressions
COPYI r, r_{E_{1}}, +1  ; r will contain the result
op  r, r_{E_{2}}, result ; perform binary operation
signal_{E_{1}} : WAIT e_{2}, signal  ; synchronize the signals
signal_{E_{2}} : WAIT e_{2}, signal  ; of both subexpressions

Figure 4.22: ADAM Assembler Code of a Binary Operation: E_{1} op E_{2}

• If the constant is of the form numberN c (i.e. it is an integer constant) then the value of c′ is c.

• If the constant is of the form charN c (i.e. it is a character constant) then the value of c′ is the ASCII value of c denoted by ORD(c).

• If the constant is TRUE (i.e. it is a boolean constant) then the value of c′ is 1.

• If the constant is FALSE (i.e. it is a boolean constant) then the value of c′ is 0.

The CONST instruction takes the value of the next line and puts it into the register r. As it can be seen from the dataflow graph in Figure 4.6 the result of this operation has to be split into two threads (two tokens from the dataflow point of view). These two threads are the output threads of this construct.

Variables

As the dataflow graph in Figure 4.7 already shows there need not be any code generated for a variable at all. However, somehow it must be stated where to get the value of this variable from (this means that the correct arc in the dataflow graph has to be chosen as input arc). This is done by carrying an environment through the whole compilation, in which the variables are bound to a triple consisting of a register, a label and a flag.

Binary Operations

The first line in Figure 4.22 already shows that the two expressions of the binary operation are evaluated in parallel. The interface of the subexpressions C_{E_{i}} (where i = 1, 2) is described by the following input/output definition:

• Input: l_{E_{i}}.

C_{split,trigger}  ; Feed trigger to fct and

; to each argument

invoke : FASTCALL f, +1  ; Invoke function
act :  RECEIVE ract , send ; Fct activation arrives
res :  RECEIVE r, result  ; Result of fct arrives
C_{split,send}  ; One send for

; each argument

C_{E_{i}}  ; i = 1, …, n
result_{E_{i}} : WAIT  e_{i}, +2  ; Synchronize result of
send_{E_{i}} : WAIT  e_{i}, +1  ; arg. with fct. invocation
SEND  r_{E_{i}}, ract , i ; send ith arg. to fct.
JUMP  arg_{E_{i}}

CSignalTree

Figure 4.23: ADAM Assembler Code of a Function Call: f[E_{1}, …, E_{n}]

• Output: result_{E_{i}}, signal_{E_{i}}.

After the computation of the values of the subexpressions the two results have to be synchronized with two WAIT instruction that refer to the same event e_{1}. As soon as both results are available, the computation is continued with a COPYI instruction. This instruction is necessary as all binary arithmetical and boolean instructions in the ADAM instruction set put the result of the operation in the first argument thereby altering the value of the argument. The result value of this operation is immediately made available to succeeding parts of the program by performing a jump to result. The last two lines in the code represent the signal tree with two inputs. From now on the code for the signal trees will be abbreviated by a special code block.

One important item of this instruction sequence is that the binary operation has to be ‘guarded’ with a WAIT instruction. In the dataflow model this was incorporated in the dataflow semantics of the firing rules. But in the controlflow model all these synchronizations must be expressed explicitly.

Function Call

Due to the non-strict semantics of user defined functions in EVE, the evaluation of the function can be started in parallel with the evaluation of the function itself. Therefore, the code sequence in Figure 4.23 starts with a splitting of the controlflow into n + 1 parallel threads. This is done in the code sequence C_{split,trigger} , which has the following interface:

• Input: trigger.

• Output: invoke, trigger_{E_{1}}, …, trigger_{E_{n}}.

In the first parallel thread, namely in the invoke thread, the given function is immediately called. As this calling of a function is performed via a side effect the answer will be received in the next line. There the pointer to the activation record will be stored in the register ract . However, as the corresponding dataflow graph shows, the invocation of a function has two results. The second result, namely the actual result of evaluating the function, is stored in register r and is immediately made available by the jump to result.

As the pointer to the activation record of the function is needed for the sending of the arguments, the thread from the first result of the FASTCALL instruction (denoted by send) is split into n parallel threads (one for each argument). This splitting is performed in the code sequence C_{split,send} , which has the following interface:

• Input: send.

• Output: send_{E_{1}}, …, send_{E_{n}}.

Each thread is synchronized with the result of the corresponding argument.

The interface for the argument expressions that are denoted by C_{E_{i}} (where i = 1, …, n) looks as follows:

• Input: trigger_{E_{i}}.

• Output: result_{E_{i}}, signal_{E_{i}}.

After the synchronization of the result of the i-th argument expression with one of threads for sending arguments, this result is sent as the i-th argument to the function that is denoted by the activation record in ract . As the SEND instruction performs a side effect its result arrives at the next line. However, this result is just a signal output. All the unresolved signal outputs in the code sequence are resolved in the signal tree that is denoted by CSignalTree. The interface of this signal tree is:

• Input: signal_{E_{i}}, arg_{E_{i}}, i = 1, …, n.

• Output: signal.

Note that like the binary operation in the previous section the SEND instruction has to be guarded by a WAIT instruction to achieve a correct synchronization.

Conditional Expressions

Figure 4.24 shows the assembler code of the conditional expression:

IF E_{1} THEN Etrue ELSE Efalse.

trigger :  SPLIT  trigger_{E_{1}}, trigger_{E_{23}} ; Split one path for body
C_{E_{1}}  ; 1 path for head
Csplit  ; 1 split for each free var in E_{2,3}
trigger_{E_{23}} : WAIT  e, +2  ; Synchronize trigger with
trigger_{0} :  WAIT  e, +1  ; result of E_{1}
SWITCH  r_{E_{1}}, l_{true,0}, l_{false,0} ; Forward it to right branch
COPYI  r, r_{E_{1}}, result  ; error ⇒ return error value
ly_{i} :  WAIT  e_{i}, +2  ; i = 0, …, n
trigger_{i} :  WAIT  e_{i}, +1  ; Sync. as above
SWITCH  r_{E_{1}}, l_{true,i}, l_{false,i} ; Forward var to right branch
JUMP  l_{error ,i}  ; error ⇒ jump to signal tree
C_{Etrue}  ; True branch
C_{SignalTree_{Etrue}}  ; Vars not used in this branch

C_{Efalse}  ; True branch
C_{SignalTree_{Efalse}}  ; Vars not used in this branch

C_{SignalTreeerror}  ; For the error case
result_{Etrue} : COPYI  r, r_{Etrue} , +1  ; r gets the result
JUMP  result  ; Leave code sequence
result_{Efalse} : COPYI  r, r_{Efalse} , +1  ; r gets the result
JUMP  result  ; Leave code sequence
signal′_{Etrue} : JUMP  signal  ; Merge the signals
signal′_{Efalse} : JUMP  signal  ; of both branches

Figure 4.24: ADAM Assembler Code of a Conditional Expression As it can be seen from the dataflow graph for a conditional expression in Figures 4.11 and 4.12, the generated code for the whole expression can be split into four parts:

1. The computation of the head of the conditional expression. For this part the trigger thread first has to be split into one thread that starts the computation of the head expression and into another thread that can be used by the body of the conditional expression. In the code sequence denoted by C_{E_{1}} the value of the head of the conditional expression is computed. The interface for this part looks as follows:

• Input: trigger_{E_{1}}.

• Output: result_{E_{1}}, ly_{0}.

As the result of this expression is used to guide the free variables of the body to the correct side, it has to be immediately split into n + 2 parallel threads where n is the number of free variables in E_{2} and E_{3}. Therefore, the interface of this splitting in Csplit can be described as follows:

• Input: result_{E_{1}}.

• Output: trigger_{i}, i = 0, …, n + 1.

2. The next bigger part is the switching of all free variables of the body of the conditional expression to the correct branch. For that we assume that the free variables of E_{2} and E_{3} are v_{1}, …, v_{n} with the labels ly_{1}, …, ly_{n}. The switching of these variables, the trigger and the signal of E_{1} is done by n + 2 guarded SWITCH constructs. In the guards, the free variables have to be synchronized with the splitted result of the expression E_{1}. Note that the SWITCH instruction has three exits depending on the value in the register that is the first argument of the instruction:

• The second argument is the exit if this value is TRUE,

• the third argument is the exit if the value is FALSE,

• the next line is the exit if the value is ERROR.

If the evaluation of E_{1} returns an error this error value is also the result of the conditional expression (this can be seen from the switching block of the trigger thread, where a COPYI instruction is performed in the error case).

3. The third part of the conditional expression contains the code sequences for the evaluation of both branches of the body. Each branch also has an own signal tree, where those variables that are only needed in the other branch and the signal of E_{1} are synchronized. The interface of the expressions C_{E_{j}} (where j = true, false) looks as follows:

• Input: l_{j}, l_{j,i} for those i where v_{i} is a free variable of E_{j}.

• Output: result_{E_{j}}, signal_{E_{j}}.

The signal trees C_{SignalTree_{E_{j}}} that synchronize the superfluous variables and the

signals have the following interface:

trigger : SPLIT  trigger_{D}, trigger_{E} ; Evaluate def. and expr. in parallel
C_{D}  ; Definition Sequence
C_{E}  ; Expression that yields value of construct
CSignalTree  ; Synchronize all signals

Figure 4.25: ADAM Assembler Code of a Local Definition: {D}E

• Input: signal_{E_{j}}, l_{j,i} for those i where v_{i} is not a free variable of E_{j}.

• Output: signal′_{E_{j}}.

The code sequence C_{SignalTreeerror} for synchronizing all free variables in the error case has the following interface:

• Input: l_{error ,k}, k = 0, …, n.

• Output: signal.

4. The last part in the code of Figure 4.24 realizes the merging of the result and the signal outputs of both branches into the result and signal output of the conditional expression. Again we can specify the behaviour of this part by describing the input and the output of this merging:

Merging of the results of both branches:

• Input: result_{Etrue} , result_{Efalse} .

• Output: result.

Merging of the signals of both branches:

• Input: signal′_{Etrue} , signal′_{Efalse} .

• Output: signal.

Furthermore, in the merging of the results the actual result value is copied to the correct register r.

Local Definitions

The assembler code of a local definition consists of the code sequence for the definition sequence (C_{D}) and the code sequence for the expression (C_{E}). Furthermore, the signals have to be synchronized in the signal tree CSignalTree. Due to the SPLIT instruction in the first line of Figure 4.25 these two parts will be evaluated in parallel. The interface for the different parts looks as follows:

C_{D}:

• Input: trigger_{D}.

• Output: signal_{D}.

trigger : ALLOC  n, +1  ; Allocate memory for tuple
RECEIVE r, +1  ; Get pointer to memory in r
SPLIT  result, split ; Return pointer and

; give it to next instructions

Csplit  ; Splittings for parallel eval.

; of tuple fields

C_{E_{i}}  ; i = 1, …, n
result_{E_{i}} : WRITE  r_{i}, r, i − 1 ; Write value in tuple field
JUMP  end_{i}  ; Signal output of Write
CSignalTree  ; Synchronize signals and ends

Figure 4.26: ADAM Assembler Code of a Tuple Construction: [E_{1}, …, E_{n}]

C_{E}:

• Input: trigger_{E}.

• Output: result, signal_{E}.

CSignalTree:

• Input: signal_{D}, signal_{E}.

• Output: signal.

Tuple Construction

Figure 4.26 shows the assembler code that constructs a tuple consisting of n arguments that are evaluated in the code sequences C_{E_{i}} (where i = 1, …, n). This code bears a strong resemblance to the code for a function call. However, before the controlflow is split into parallel threads, memory must be allocated for the tuple. As this is done via a side-effect a pointer to the allocated memory arrives in the next line and is stored in the register r. As in EVE data structures are non-strict the pointer can be immediately made available to other parts of the program by performing a jump to result.

After this first step the controlflow has to be split into n parallel threads, one for each argument of the tuple construction. This splitting is done in the code sequence Csplit , which has the following interface:

• Input: split.

• Output: trigger_{E_{1}}, …, trigger_{E_{n}}.

The argument expressions are evaluated in the code sequences C_{E_{i}} (where i = 1, …, n). These have the following interface:

C_{E}  ; Body of the tuple
result_{E} : READ  i, r_{E}, +1 ; Select ith component
RECEIVE r, result ; Receive result in register r

Figure 4.27: ADAM Assembler Code of a Tuple Selection: π_{i} E

• Input: trigger_{E_{i}}.

• Output: result_{E_{i}}, signal_{E_{i}}.

With the WRITE instruction^{15} the result of the i-th argument expression is written into the (i − 1)-st field of the tuple^{16}. The signal of this side-effect instruction arrives at the next line and is immediately forwarded to the signal tree, where all unresolved labels are synchronized. The interface for CSignalTree is therefore:

• Input: signal_{E_{i}}, end_{i}, where i = 1, …, n.

• Output: signal.

Note that contrary to the code in the function call, here is no explicit synchronization before the WRITE instruction necessary. The reason for this is that the tuple is allocated before the triggers of the argument expressions are split. Therefore, it is guaranteed that the memory for the tuple is available when an expression has computed a result.

Tuple Selection

In the code for a tuple selection of Figure 4.27 the only instruction that has to be applied to the expression that is represented by the code sequence C_{E} is a READ instruction of the i-th element of the tuple. The result of this operation is received in register r.

The interface of C_{E} is:

• Input: trigger.

• Output: result_{E}, signal.

Function Application

The code in Figure 4.28 looks very similar to the code of a function call. As a matter of fact, the only difference to the code in Figure 4.23 is the part of the function invocation. In the function call only first order functions were possible but here the function is in general the result of a curried expression represented by C_{E}. The structure of such a curried expression will be discussed in the next section. The interface of this expression is:

15This WRITE is a WRITEI instruction if an integer value is written to the tuple field or a WRITEP instruction if a pointer is written to the tuple field.

16The counting of the fields of a tuple always starts with 0 instead of 1!

C_{split,trigger}  ; Parallel pathes: Eval. fct-expr.

; and eval. of all args.

C_{E}  ; Fct.-expr.; yields a curried expr.
result_{E} : READ  0, r_{E}, +1  ; Extract address of fct.
APPLY  r_{1}, +1  ; Allocate activation record in ract
SUBMIT  r_{E}, ract , send ; Submit curried arguments to fct.
RECEIVE r, result  ; Receive result in register r
C_{split,send}  ; One split for each arg. expr.

; to allow the sending of args.

C_{E_{i}}  ; i = 1, …, n
result_{E_{i}} : WAIT  e_{i}, +2  ; Synchronize result with
send_{E_{i}} : WAIT  e_{i}, +1  ; activation record
SEND  r_{E_{i}}, ract , i  ; Send arg. to fct.
JUMP  arg_{E_{i}}  ; Signal output of sending
CSignalTree  ; Synchronize signals and args

Figure 4.28: ADAM Assembler Code of a Function Application: E[E_{1}, …, E_{n}]

• Input: trigger_{E}.

• Output: result_{E}, signal_{E}.

To get the actual address of the function the first field of the tuple that is the result of C_{E} has to be selected. With the APPLY instruction, which gets this address as it is the next instruction after the READ, an activation record is allocated^{17}. The pointer to the allocated activation record is stored in ract , as this register appears in the next line. The SUBMIT instruction sends the curried arguments that might be contained in the tuple that is stored in r_{E} to this function activation. In the next line the final result of the computation is received in the register r.

The interfaces for the two code sequences that perform splittings are:

C_{split,trigger} :

• Input: trigger.

• Output: trigger_{E}, trigger_{E_{1}}, …, trigger_{E_{n}}.

C_{split,send} :

• Input: send.

• Output: send_{E_{1}}, …, send_{E_{n}}.

17This corresponds to the FASTCALL instruction in a function call.

trigger : ALLOC  n + 1, +1 ; Allocate memory for tuple
RECEIVE r, +1  ; Receive pointer to tuple in r
SPLIT  result, split ; Return pointer and

; give it to next instructions

Csplit  ; Splittings for parallel eval.

; of tuple fields

trigger_{0} : CONST r_{0}, +2 ; Fct. descriptor in r_{0}
< f, i >  ; Evaluate fct. descriptor
WRITE r_{0}, r, 0 ; Write fct. descr. in first component
JUMP  end
C_{v_{i}}  ; i = 1, …, n
result_{i} : WRITE  r_{i}, r, i − 1 ; Write curried arg. in
JUMP  end_{i}  ; ith component
CSignalTree  ; Synchronize all signals and ends

Figure 4.29: ADAM Assembler Code of a Curried Expression: < f, v_{1}, …, v_{n} >

Curried Expressions

The code sequence of Figure 4.29 has essentially the same structure as the code for a tuple construction that is presented in Figure 4.26. Therefore, here only the differences to this above code sequence are discussed.

• The size of the tuple is n + 1, since not only the values of the arguments but also the address of the function has to be stored in that tuple.

• After the allocation of the tuple the controlflow is split into n + 1 parallel threads in Csplit , one for each field of the tuple. Therefore, Csplit now has the following interface:

– Input: split.

– Output: trigger_{0}, trigger_{1}, …, trigger_{n}.

• The code block that is labelled with trigger_{0} writes a value that encodes the address of the function and the parameter number of the first of the n fixed arguments into the first field of the tuple. The encoding of this value is done by the assembler when it finds the expression < f, i >.

• In a curried expression the parts of the expression must be simple variables rather than general expressions. This is indicated by using C_{v_{i}} instead of C_{E_{i}}. The interface of C_{v_{i}} (i = 1, …, n) is:

– Input: trigger_{i}.

C_{E}  ; Evaluate body expression
result_{E} : COPYI r, r_{E}, +1 ; Bring result in register r
JUMP result  ; Return result

Figure 4.30: ADAM Assembler Code of a Simple Definition: v == E

– Output: result_{i}, signal_{i}.

As in EVE no explicit currying is allowed this construct will normally be used with n = 0, thus only containing the encoded value for the function address^{18}. The final signal tree (CSignalTree) has the following interface:

• Input: signal_{i}, end_{i}, where i = 1, …, n.

• Output: signal.

Simple Definitions

In the assembler code of a simple definition the result output of the body of the definition can be directly forwarded to the result output of the whole definition. Analogously, the result register of the body of the definition can be taken as the result register of the whole definition.

The reason for using an explicit JUMP statement instead of performing an indirect jump is the fact that indirect jumps may only lead forward but not backward. However, for circular definitions a backward jump is necessary.

One important fact that can’t be seen from the assembler code is that the variable that is defined in this simple definition is bound to the register r and the label result. This binding is stored in an environment that will be accessed when another expression has to refer to this variable.

Function Definitions

In Figure 4.31 the assembler code of a function definition is shown. In every such function definition the trigger starts the computation of the function by a jump to the first line. The n arguments of the function are received in the next n lines.

The interface of the body expression (C_{E}) of that function is:

• Input: trigger_{E}.

• Output: result_{E}, signal_{E}.

18As n = 0 the index i of the first fixed argument is meaningless

trigger : JUMP  trigger_{E} ; Arrival of trigger and args. (below)
RECEIVE  r_{i}, l_{i}  ; i = 1, …, n
C_{E}  ; Evaluate body expression
result_{E} : RETURN  r_{E}, +1 ; Return result value to caller
signal_{E} : WAIT e, +1 ; Synchronize with signal of E
TERMINATE  ; Deallocate activation record

Figure 4.31: ADAM Assembler Code of a Function Definition: f[v_{1}, …, v_{n}] == E

Csplit  ; One trigger for

; each definition

C_{D_{i}}  ; i = 1, …, n

CSignalTree

Figure 4.32: ADAM Assembler Code of a Definition Sequence: D_{1}, …, D_{n}

As soon as the result of the body expression has been computed it can be returned to the caller. It has already been noted that from the dataflow point of view a function definition is connected with its caller via dynamic links. Therefore, this construct has no explicit output label. Instead, a TERMINATE instruction is performed after having synchronized all threads in the construct. This instruction causes the activation record of the current function to be discarded.

Definition Sequences

The assembler code for this construct in Figure 4.32 immediately splits the controlflow in n parallel threads. This is done in Csplit , which has the following interface:

• Input: trigger.

• Output: trigger_{D_{1}}, …, trigger_{D_{n}}.

Each thread initiates the computation of one definition. The interface of these definitions (C_{D_{i}}) is:

• Input: trigger_{D_{i}}, where i = 1, …, n.

• Output: signal_{D_{i}}.

All the signals of the different definitions D_{i} (i = 1, …, n) are synchronized in the last signal tree (CSignalTree) that has the following interface:

• Input: signal_{D_{i}}, where i = 1, …, n.

• Output: signal.

Program

As by the definition of the EVE language a program is nothing else but a definition sequence, the generated code will look like the code in Figure 4.32. However, a program is a special case of a definition sequence. First of all it contains exactly one constant (MAIN). This definition must not be added to the program file that is generated by the compiler. Instead, the generated assembler code of the body of this MAIN constant will be put into an own expression file. All the other definitions in a program are function definitions, which don’t have an explicit output label. Therefore, there is no signal tree for synchronization necessary.

Experiments

In this chapter we will present some experiments that we have made with simple EVE programs that have been compiled with the prototype EVE compiler. These experiments include some benchmarks that have been made with standard functional programs on a single transputer and on a network of sixteen transputers. Furthermore, in this chapter the efficiency of the generated ADAM assembler code is discussed. Some possible sources of optimization are discussed in an own section.

5.1 Benchmarks

In order to estimate the quality of the code generated by the EVE compiler, some typical simple functional programs have been implemented in EVE, compiled with the EVE compiler and executed on an ADAM machine with one and with sixteen transputers.

5.1.1 Benchmark Programs in EVE

In this section we discuss the programs that have been used for the benchmarks with the EVE compiler on the ADAM machine. The documented EVE source code of all benchmark programs can be found in Appendix D.

Fibonacci Numbers

In Fib.eve a straightforward program for computing a Fibonacci number is given. As this algorithm uses the straightforward definition of Fibonacci numbers it has exponential complexity.

Quicksort

An EVE algorithm for sorting a list is given in the file QuickSort.eve. The presented algorithm uses the principle of recursively partitioning the list into a part that is smaller and into a part that is larger than a certain ‘pivot’ element.

Program  Parameter 1 Processor 16 Processors Speed Up
Fibonacci  18  40.4 s  5.0 s  8.1
Quick-Sort [50,49 … 1]  44.7 s  6.7 s  6.7
N-Queens  6  130.2 s  11.9 s  10.9
Primes  16000  930.3 s  215.7 s  4.3

Table 5.1: Timings of the Benchmark Programs

N-Queens Problem

In Queens.eve an algorithm for solving the N-Queens Problem is presented. This algorithm yields all positions of N queens on a chessboard of the size N×N such that no queen checks another queen on the chessboard. As this is one of the most used benchmark programs in functional programming but as it is not as trivial to realize as the two previous programs there exist several versions of this algorithms that differ in some details. However, we have chosen to use a first order program (i.e. it contains no higher order functions) to implement this algorithm. This algorithm also shows the implementation of some basic list operation functions. In the benchmarks this program was executed for a chessboard of the size 6×6.

Prime Number Generator

In Prime.eve a program is given, that generates a list of prime numbers up to a given limit n. The presented algorithm is a first order algorithm. Contrary to this algorithm, in CircPrime two circular prime number generator algorithms are presented. For a discussion of the latter algorithms and their runtime behaviours see Section 5.2. As an upper limit for the list of prime numbers the value 16000 has been chosen in the benchmarks.

5.1.2 Timings

In this section we present the timings that have been achieved by executing the above programs on an ADAM machine with only one processor (‘sequential case’) and on an ADAM machine with sixteen processors (‘parallel case’). The runtimes for the sequential and for the parallel case are given in Table 5.1. The second column in this table denotes the value of the parameter with which the benchmark program was called. Furthermore, the last column denotes the factor by which the parallel case was faster than the sequential case. All timings are given in seconds.

The results in Table 5.1 show that with a problem that contains much inherent parallelism a speed-up of almost 11 on a network of sixteen processors can be achieved (N-Queens). Although, these speed ups might be not as impressing as speed ups that could be achieved with explicitly parallel programs, it has to be emphasized that these results were achieved by only using the implicit parallelism of the functional program without giving the compiler or the abstract machine additional help in generating code or executing the program^{1}.

1Giving the programmer the additional possibility of submitting such information to the compiler would be one method to potentially increase the efficiency of the generated parallel programs.

Program  1 Processor 16 Processors Speed Up
Wentworth’s algorithm  2.3 s  1.1 s  2.1
Schreiner’s algorithm  1.2 s  0.4 s  3.0

Table 5.2: Timings of Two Circular Prime Number Generators

Moreover, the same object code that is generated by the EVE compiler can be used for a sequential and for a parallel execution by just using abstract machines with a different number of processors.

When one compares the absolute times of these tests with the runtimes achieved by handwritten ADAM assembler programs one notices that the latter programs require about 50 to 70 % of the time needed for the execution of a compiled EVE program. For example a hand-written version of the Fibonacci number algorithm with parameter 18 takes 27.9 seconds in the sequential case. That are about 70 % of the time needed by the compiled EVE program as it can seen from Table 5.1. The main reason for this quite low efficiency is the fact that the prototype EVE compiler does not perform any optimizations.

5.2 Comparison of Two Circular Prime Number Generators

In Appendix D in CircPrime.eve two versions of a circular prime number generator are presented^{2}. One version, which will be called Wentworth’s algorithm from now on, is taken from [Wentworth, 1989a] and the other algorithm, which will be called Schreiner’s algorithm, is taken from [Schreiner, 1990]. Generally, such a circular prime number generator is more efficient than the straightforward algorithm in Prime.eve as the circular algorithm can access previous elements of the list that is being constructed. Hence, for the test whether a number n is prime it is divided by the prime numbers that are smaller than or equal to \sqrt{n}. These prime numbers are taken from the list that is the result of the whole computation and that is therefore just being constructed. This is of course more efficient than dividing a number n by all odd numbers smaller than \sqrt{n} as it is done by the function isPrime in

Prime.eve.

Both of the presented algorithms are based on this principle. However, the implementation shows a subtle difference in the parallel runtime behaviour of the programs that has strong influence on the speedup that can be achieved with these algorithms. Table 5.2 shows the execution times of both algorithms in the sequential and in the parallel case as well as the speed up, which is very small in this case as only a list of all prime numbers up to 70 has been computed.

To understand this difference we have to take a closer look at the data dependencies inside both algorithms, which determine their runtime behaviours. At first glance both algorithms have the same global structure as they both create a bidirectional pipeline of ‘processes’. In the case of Wentworth’s algorithm these processes are those calls to the function oddPrimesFromTo for which n<=limit holds. The first call of this function with n>limit

2For a discussion of the notion of ‘circularity’ see Section 2.1.

[figure: …; primes; primes; primes; primes]

Figure 5.1: Global Structure of Both Prime Number Generators

[figure: isPrime; :]

Figure 5.2: Structure of a Process in Wentworth’s Prime Number Generator

builds the right end of the pipeline where the list of primes that has been built is pushed back. In the case of Schreiner’s algorithm these processes consist of one call to sieve and one call to cons. Again the condition n>limit determines the end of the pipeline. This global structure of both algorithms can be seen in Figure 5.1.

However, when we examine the interior of the processes in the two algorithms we can see the significant difference between them. Figure 5.2 shows the structure of a process in Wentworth’s algorithm where n<=limit. The characteristic feature of such a process is the fact that when the input arrives from the left the function isPrime has to be computed before the input can be forwarded to the next process, which is nothing but the recursive call inside oddPrimesFromTo. However, the computation of isPrime is the most time consuming part of the whole function, which is denoted in the figure by the thick box around isPrime. Hence, the whole computation is sequentialized by the data dependency between the head of the if-expression and the branches in the if-expression, that forms the body of the function

oddPrimesFromTo.

Contrary to this, in Schreiner’s algorithm one process consists essentially of function calls to sieve and to cons (again n<=limit in these function calls). The structure of such a process can be seen in Figure 5.3. The crucial difference to the process in Figure 5.2 is the fact that cons is the time consuming function that has to test whether the actual n is prime or not. When we examine the definition of sieve we see that there immediately after checking the termination condition of the pipeline a recursive call can be performed. Therefore, the

[figure: {cons \atop n}; sieve]

Figure 5.3: Structure of a Process in Schreiner’s Prime Number Generator

input can be immediately forwarded to the next process in the pipeline, thereby creating a large number of parallel processes. On the way back from the right end of the pipeline in the cons function the time consuming computations are performed in parallel in all created processes. In the source code of the program this behaviour can be seen as the test for primality is put from the head of the if-expression (in the case of Wentworth’s algorithm) into the else-branch of the algorithm where it does not block the recursive call of sieve^{3}.

From this case study one can see that it would be naive to believe that with a parallelizing compiler like the one presented in this thesis one can automatically get efficient parallel programs. In this approach of automatic parallelization the parallelism is restricted by the data dependencies in a program. Therefore, one can say that with functional languages the problem of parallelization is reduced to the problem of eliminating or weakening the data dependencies in a program. However, it should be noted that the notion of data dependency is still a notion of the functional specification of the program. Hence, the elimination of data dependencies can be treated in a similar way as other transformations on the functional program without leaving this model of computation. As it is far easier to apply mathematical transformations to functional programs than to imperative programs, one has a better chance to increase the parallel efficiency of functional programs by performing mathematical transformations on these programs within an optimizing compiler (e.g. by using some transformations that weaken strong data dependencies). Summarizing we can say that the data dependencies in a functional program build the interface to the operational behaviour of the program with respect to parallelism.

5.3 Sources of Optimization

It has already been mentioned that the prototype EVE compiler does not perform any optimizations on the generated ADAM assembler code. However, by looking at the generated code some inefficiencies become obvious. In this section we will discuss some ideas for performing optimizations on the generated code. However, there are many optimization techniques for sequential languages that could be performed on this code. But we are here

3Remember that the definitions in a local definition are evaluated in parallel with the body of the construct.

only interested in those optimizations that directly affect the parallel behaviour of the program. As our approach is based on the dataflow model this mainly means that we will try to group blocks of code together to get bigger sequential parts that can then be executed in parallel with each other. This is essentially the same idea that has also lead to the research in the area of ‘macro-dataflow’ (see [Sarkar and Hennessy, 1986]).

To explain the major inefficiencies in the code produced by the EVE compiler we show in Figure 5.4 the ADAM assembler code that is generated by the EVE compiler for the expression

     { a : INT == 1 ;
       double[x] : INT[INT] == 2*x }
     a + double[5];

Note that the function double has been lifted out of the local definition and put on top level. Figure 5.5 shows the code that is generated for this function.

From this code several inefficiencies become obvious. The most obvious inefficiency in the code is the large number of SPLIT and WAIT instructions in the code. Sometimes one of the threads of control that is created with a SPLIT instruction is immediately synchronized without performing some useful computation inbetween. The reason for these ‘empty’ threads in which no computation is performed is the translation rule for simple variables. As it can be seen from the dataflow graph that corresponds to a simple variable (see Figure 4.7 in Section 4.4.1) no operation has to be performed in the two independent threads. However, in each construct the parallel threads have to be synchronized in such a way that the construct has exactly two output arcs (from the dataflow point of view). For example, whenever a binary operation is applied on two variables, two parallel threads are created only to see that in these parallel threads no computation has to be performed. Hence, the parallel threads are immediately synchronized with each other. As this behaviour essentially holds for every appearance of a variable a large number of superfluous SPLIT and WAIT instructions is introduced. The major task of an optimization must be to eliminate these superfluous synchronization statements. This can be achieved in several different ways:

• As all arithmetic/boolean operations are strict in all of their arguments, it is safe to evaluate these arguments before the operation itself is performed. From the dataflow point of view this means that the trigger input can be directly forwarded to the signal output and the free variables can be directly fed to the operations. From the controlflow point of view that means that there is only one thread of control necessary. Figure 5.6 shows a dataflow graph as it would be derived from the EVE expression x*(y+z) via the rules in Section 4.4.1. When one examines the splitting of the trigger arcs it becomes obvious that this trigger is entirely superfluous here, as after each splitting it is immediately synchronized in a signal tree. Therefore, one can optimize this dataflow graph yielding the graph that is shown in Figure 5.7. In this graph the trigger is directly forwarded to the signal output.

• The above transformation can eliminate many superfluous synchronization statements but it is restricted to purely arithmetic/boolean expressions. As soon as a user defined function appears in an expression this step can not be used in this straightforward way any more.

5.3. SOURCES OF OPTIMIZATION 129

       SPLIT     l3,   l4
;
; New Definition -----------
;
l3:    CONST     4,    2
       1
       SPLIT     l7,   l8
;
l7:    COPYI     3,    4,     1
       JUMP      l6
;
; End of Definition -------------
;
; Binary %%%%%%%%%%%%%
;
l4:    SPLIT     l9,   l10
;
; CALL %%%%%%%%%%%%%
;
l10:   SPLIT     l17,  l18
l18:   FASTCALL  double,      1
       COPYI     6,    6,     l14
       COPYI     7,    7,     l15
;
l17:   CONST     8,    2
       5
       SPLIT     l19,  l20
;
l19:   WAIT      4,    2
l14:   WAIT      4,    1
       SEND      8,    6,     1
       JUMP      l13
l13:   WAIT      5,    l16
l20:   WAIT      5,    l16
;
; end of CALL $$$$$$$$$$$$$
;
l6:    WAIT      1,    2
l15:   WAIT      1,    1
       COPYI     5,    3,     1
       ADD       5,    7,     l11
l9:    WAIT      2,    l12
l16:   WAIT      2,    l12
;
; end of Binary $$$$$$$$$$$$$
;
l11:   COPYI     1,    5,     1
       RETURN    1,    1
l8:    WAIT      7,    1
l12:   WAIT      6,    1

Figure 5.4: ADAM Assembler Code for a Local Definition in EVE 130 CHAPTER 5. EXPERIMENTS

FUNCTION double(5,5)
       JUMP      l6
       COPYI     2,    2,     l8
;
; Binary %%%%%%%%%%%%%
;
;
l6:    SPLIT     l9,   l10
;
l9:    CONST     4,    2
       2
       SPLIT     l13,  l14
;
l13:   WAIT      1,    2
l8:    WAIT      1,    1
       COPYI     3,    4,     1
       MUL       3,    2,     l11
l14:   WAIT      2,    l12
l10:   WAIT      2,    l12
;
; end of Binary $$$$$$$$$$$$$
;
l11:   RETURN    3,    1
l12:   WAIT      4,    1
       TERMINATE
END double

Figure 5.5: ADAM Assembler Code for an EVE Function

[figure: *; +; x; trigger; y; z; result; signal]

Figure 5.6: Dataflow Graph of x*(y+z)

[figure: *; *; x; y; z; trigger; result; signal]

Figure 5.7: Optimized Dataflow Graph of x*(y+z)

However, one can perform a strictness analysis on all user defined functions. This strictness analysis yields the information in which parameters the function is strict. This information can then be used in a function call to evaluate the strict arguments of the function sequentially before the function itself is started. Thereby, the parallelism in a program is reduced and the grain size of the sequential blocks is increased. For a discussion of the notion of strictness analysis and its applicability for the compilation of functional programming languages see [Burn, 1991].

• Another way to eliminate ‘empty’ threads in the generated code would be to use a peephole optimizer. The idea with this optimizer is to scan the generated code with a window and to search in this window for patterns of instructions that are known to be inefficient. These patterns can then be replaced by more efficient instructions. The advantage of such an optimizer is that it is quite simple to implement. The major disadvantage is obviously that only local optimizations can be performed. However, as it is shown in e.g. [Augustsson, 1987] even such a simple optimizer can increase the efficiency of a program significantly.

If one wants to use such a peephole optimizer for the optimization of parallel code one has to extend its concept a little bit. In the sequential case it suffices to use only one window as one has the additional information that there is only one thread of control in the program. However, as there are several threads of control in a parallel program there will be also several windows necessary to perform sensible optimizations. Especially, for eliminating ‘empty’ threads in a program such an optimizer can be very useful as for this elimination only local information about the program is necessary.

• If one wants to build bigger, sequential blocks in a program one could try to combine expressions like the branches of a conditional expression into sequential blocks. However, in order to get blocks of an appropriate size one must be able to estimate the execution time that is needed for every statement. The compile time analysis that performs such an estimation is called weight analysis. This process helps to build sequential blocks of an optimal size to get an efficient program. However, as it is especially difficult to analyze the costs for executing recursive functions, the value of the results of this analysis is quite limited.

Conclusion

The main goal of the ADAM & EVE project, of which this thesis is a part, was to study the behaviour of functional languages that have been compiled to the assembler code of an abstract dataflow machine ADAM. As this thesis shows, dataflow graphs can be used to get controlflow programs that make the implicit parallelism of a functional language explicit. This transformation into explicit parallelism that is expressed in a language similar to that of a von-Neumann processor is an important step to achieve an efficient parallelization. The main reasons why the resulting parallel programs that have been presented in Chapter 5 are nevertheless rather inefficient are that

• the prototype EVE compiler does not perform any optimizations and that

• the resulting ADAM code has to be interpreted.

However, as it has been described in Section 5.3 there are many promising possibilities to improve the efficiency of the generated ADAM assembler code. Furthermore, functional programming languages offer the possibility to perform transformations on the intermediate code in a quite straightforward way. In the EVE compiler this is only used in the λ-lifting transformation. However, there could also be some transformations used that directly affect the parallel behaviour of the generated program^{1}. The second inefficiency, namely the usage of an interpreter as an implementation of the abstract machine, has been accepted as the main goal of the project was not efficiency but to show the principle applicability of the dataflow model as an abstract machine. However, there is currently some work done at RISC that aims at improving the efficiency of this approach by using a library of C functions instead of an abstract machine to simulate dataflow behaviour (see [Schreiner, 1991]). Thus one could say that here a development from interpreted to compiled dataflow code takes place.

As another important outcome of this project it was described how the dataflow model can be used to understand the parallel behaviour of functional programs. Section 5.2 shows that the dataflow model can visualize the data dependencies in a functional program and thereby also the parallel behaviour of the program. Therefore, the dataflow model is not only a good model for the parallel implementation of functional programming languages, but it is also

1Note that the λ-lifting transformation is mainly intended for sequential implementations of functional languages and does not improve the parallel behaviour of the generated program.

a good model for understanding the operational behaviour of functional programs. This is of course very important for the programmer who has to write parallel functional programs as he can with this knowledge “parallelize” his functional program by weakening the data dependencies in the program.

The main insight from the compiler design point of view is the fact that dataflow graphs form a useful intermediate language in the parallelization of functional languages. Although it is very intuitive to use this intermediate language when the source language of the compilation process is a functional language, this approach need not be restricted only to functional languages.

Summarizing, it can be said that the dataflow model proves useful in almost all steps of the parallel implementation of functional programs.

Appendix A

Attribute Grammar of the First Pass

This appendix gives a formal description of the first pass in the compilation process. For this formal description an attribute grammar is used. This way of describing a compilation process was introduced by D. E. Knuth in [Knuth, 1968]. One advantage of this approach is that an imperative program can be quite easily derived from an attribute grammar.

The main idea of an attribute grammar is to extend the given grammar of the language by semantic actions that are executed interleavingly with the procedures for parsing nonterminal symbols of the grammar. In order to establish a communication between the nonterminal symbols and the semantic procedures attributes are added to these non-terminal symbols. With this communication it is possible to build up tables and other data structures that are needed by the next passes in the compilation. The actual output of the first pass consists of

• an abstract syntax tree that represents the source program,

• a symbol table that contains all objects of the source program,

• a collection of types.

The whole formal description of the first pass consists of four parts:

1. The context-free syntax of EVE, with which a parser that is based on the principle of the recursive descent can be constructed. Hence, the presented grammar has no left recursion and no alternatives with the same terminal starting symbols. This grammar is obtained from the grammar in 3.2.17 by performing a left factorization on the alternatives of Type that start with the terminal symbol Ident. To perform this left factorization one has to introduce the new non-terminal symbols SimpleOrFctType and TCFDefinition. Furthermore, one has to change the definitions of the non-terminal symbols FunctionType, Type, TypeDefinition, ConstantDefinition, FunctionDefinition and Definition.

2. The declaration of the objects that are used in the attribute grammar. This includes the type declarations of all important data structures except those structures that are implemented as abstract data types ^{1}. Especially, the declarations of all the attributes that are used in the attribute grammar are given here.

3. The description of the semantic procedures that are used in the attribute grammar. Again, the semantic procedures that operate on abstract data types are not described here. To get a description about them see Appendix E.

4. The attribute grammar. This part can already be read like an imperative program. Note that there are no ‘meta-keywords’ sem and endsem as they are often used to separate semantic actions from the actual grammar. Instead, syntactic and semantic parts are distinguished by indentation. All parts referring to the syntax of EVE start in the first column. All semantic actions and context assertions are indented.

In the attribute grammar at some points the meta-keyword CC with a succeeding boolean expression can be found. This marks a context condition that must be true at that point. If it is not true, the compiler will yield a semantic error.

A.1 Altered Context-Free Syntax of EVE

Types

IdentList  = Ident { , Ident } .
SimpleType  = Ident .
TypeList  = SimpleType { , SimpleType } .
Field  = Ident : SimpleType .
FieldList  = Field { , Field } .
Variant  = Ident == SimpleType .
VariantList  = Variant { , Variant } .
ListType  = LIST OF SimpleType .
TupleType  = TUPLE OF FieldList .
UnionType  = UNION OF VariantList .
FunctionType  = [ TypeList ] .
SimpleOrFctType = SimpleType [ FunctionType ] .
Type  = SimpleOrFctType | ListType | TupleType | UnionType .

Definition Statements

TypeDefinition  = [ == Type ] .
ConstantDefinition = : SimpleType [ == Expression ] .
FunctionDefinition = [ IdentList ] : SimpleOrFctType [ == Expression ] .
TCFDefinition  = TypeDefinition | ConstantDefinition | FunctionDefinition .
Definition  = [ Ident TCFDefinition ] .
DefinitionSequence = Definition { ; Definition } .

1For a specification of these abstract data types and of the procedures that operate on them see Appendix E.

Expressions

Expression = ListExpr | { DefinitionSequence } Expression | IF Expression THEN Expression ELSE Expression | CASE Expression { Field } Expression { { Field } Expression } [ ELSE Expression ].

ListExpr  = CompoundExpr { :: Factor } .
CompoundExpr = SimpleExpr [ Relation SimpleExpr ] .
Relation  = = | # | < | <= | > | >= | <> .
SimpleExpr  = [ + | - ] Term { AddOp Term } .
AddOp  = + | - | | .
Term  = Factor { MulOp Factor } .
MulOp  = * | / | % | & .

Factor = Number | Character | String | Designator { ArgumentList |

Selector } | ( Expression ) | ~ Factor .

Designator  = Ident [ $ Ident ] .
ArgumentList = [ [ Expression { , Expression } ] ] .
Selector  = . Ident .

Programs

Program = DefinitionSequence .

A.2 Declarations of Objects

A.2.1 Constants

maxIdentListLen  = 16
maxSTypeListLen  = 16
maxIdTypeListLen  = 16
maxNameTypeListLen  = 16
maxTypeListLen  = 16
maxIdASTypeListLen  = 16
maxNumIdASTypeListLen = 16
maxFieldListLen  = 16
maxVariantListLen  = 16
maxASTypeListLen  = 16
maxFlagListLen  = 16

A.2.2 Types

IType = RECORD — ident with corresponding name, type : CARDINAL; name of type END;

Ntype = RECORD — ident with corresponding type name : CARDINAL; type : EType; END;

IdASType = RECORD name : CARDINAL; ast : ASType; END;

NumIdASType = RECORD — for the bodies of the branches number, name : CARDINAL; of a CASE Expression ast : ASType; END;

OptionalASType = RECORD — AST of the optional ELSE CASE exist : BOOLEAN OF branch in a CASE Expression TRUE : ast : ASType; END; END;

FwType = RECORD — the type of a forward definition CASE isForward : BOOLEAN OF TRUE : type : EType; END; END;

Flags = (pointer, int, none);

RelationType = (eqRelTy, ltRelTy, leRelTy, gtRelTy, geRelTy, neRelTy);

ArithmType = (minusArTy, plusArTy, orArTy, timesArTy, divArTy, modArTy, andArTy);

AtomType = (charAtom, idAtom, numberAtom);

IdList  = ARRAY [1..maxIdentListLen] OF CARDINAL;
STypeList  = ARRAY [1..maxSTypeListLen] OF CARDINAL;
IdTypeList  = ARRAY [1..maxIdTypeListLen] OF IType;
NameTypeList  = ARRAY [1..maxNameTypeListLen] OF NType;
ETypeList  = ARRAY [1..maxTypeListLen] OF EType;
IdASTypeList  = ARRAY [1..maxIdASTypeListLen] OF IdASType;
NumIdASTypeList = ARRAY [1..maxNumIdASTypeListLen] OF NumIdASType;
ASTypeList  = ARRAY [1..maxASTypeListLen] OF ASType;
FlagList  = ARRAY [1..maxFlagListLen] OF Flags;

A.2.3 Attributes

name, nameTy, len, len1, nr  : CARDINAL;
idList  : IdList;
sTypeList  : STypeList;
idType  : IType;
idTypeList  : IdTypeList;

eType, eTypeExpr, eTypeIn, eTypeOut, eType0, eType1, eType2, eTypeU,

eTypeHead, eTypeThen, eTypeElse  : EType;
typeList  : ETypeList;
x, ast, ast0, ast1, ast2, astHead, astThen, astElse : ASType;
fw  : FwType;
rel  : RelationType;
add, mul  : ArithmType;
c  : CHAR;
isUnionFct, isHigherOrder  : BOOLEAN;

A.2.4 Further Variables

name1, i, j, lenT  : CARDINAL;
eTypeRet, listType, eTypeVar  : EType;
defTypeList  : ETypeList;
nameType  : NType;
nameTypeList  : NameTypeList;
x0, nrAST, astLambda  : ASType;
def, cells, arg, asts  : ASTypeList;
elseBody  : OptionalASType;
body  : IdASTypeList;
obj  : Object;
op  : Operand;
flagList  : FlagList;
fwd, renamed, sign, loopEntered : BOOLEAN

A.3 Semantic Procedures

Join (↓ idList,↓ typeList,↓ len,↑ nameTypeList);

— Combine two lists of equal length to a list of pairs (the zip function of usual functional languages).

BEGIN FOR i:=1 TO len DO nameTypeList[i].name:=idList[i]; nameTypeList[i].type:=typeList[i]; END; END Join; StringToList (↓ saddr, ↓ slen, ↑ x);

— Convert a string into the abstract syntax tree that represents this string.

VAR str : LongString; ast : ASTypeList;

BEGIN FetchString (↓ saddr,↓ slen,↑ str); FOR i := 0 TO slen–1 DO

MakeASTAtom (↓ charAtom,↓ str[i],↓ noCardVal,↑ ast[i + 1]); END; MakeASTList (↓ ast,↓ slen,↑ x); END StringToList;

forwarded (↓ obj,↓ op) ↑ BOOLEAN

— Test whether there exists a forward definition for the given object on the same level.

BEGIN RETURN (obj<>noObject AND op.level=CurrentLevel()) END forwarded;

CreateDummyIdList (↓ len,↑ idList)

— Create a list of empty identifieres of length len (this is needed for forward definitions of functions).

BEGIN FOR i := 1 TO len DO

idList[i] := dummyName; END; END CreateDummyIdList;

CheckTypes (↓ idList,↓ typeList,↓ len,↓ name,↓ eType,↓ eTypeRet);

— Check whether the names and types given in the lists idList and typeList (name and eType are the name and the type of of the return value) are the same as the names and types that are derived from the function type eTypeRet.

VAR idlistOk, typeListOk : BOOLEAN; nameRet : CARDINAL; eTypeRetOrig : EType; typeList1 : ETypeList; idList1 : IdList;

BEGIN GetParTypeList(↓ eType,↑ idList1,↑ typeList1,↑ len1) GetReturnType(↓ eType,↑ eTypeRetOrig)

GetReturnName(↓ eType,↑ nameRet) TestIdList(↓ idList,↓ len,↓ idList1,↓ len1,↑ idListOk) TestTypes(↓ typeList,↓ len,↓ typeList1,↓ len1,↑ typeListOk) CC: (idListOk AND typeListOk AND (name=nameRet) AND

equalType(eTypeRet,eTypeRetOrig)) END CheckTypes;

flag (↓ eType) ↑ Flags

— Determine whether the type eType denotes a compound data structure, which is represented by a pointer in the ADAM machine, or whether it denotes an atomic data structure, which is represented as an integer value.

BEGIN IF isListType(eType) OR isTupleType(eType) OR isFctType(eType)

THEN RETURN pointer ELSE RETURN int

END; END flag;

TypesToFlags (↓ typeList,↓ len,↑ flagList)

— Apply the flag function on the list typeList yielding flagList.

BEGIN FOR i := 1 TO len DO

flagList[i] := flag(typeList[i]); END; END TypesToFlags;

A.4 Attribute Grammar of the First Pass

Types

IdentList_{↑idList↑len} =

len := 0

Ident_{↑name} INC(len); idList[len] := name

{ ,

CC: len < maxIdentListLen

Ident_{↑name} INC(len); idList[len] := name

}

SimpleType_{↑name} = Ident_{↑name}

TypeList_{↑sTypeList↑len} =

len := 0

SimpleType_{↑name} INC(len); sTypeList[len] := name

{ ,

CC: len < maxSTypeListLen

SimpleType_{↑name} INC(len); sTypeList[len] := name

}

Field_{↑idType} = Ident_{↑name}

idType.name := name

DisableRenaming

: SimpleType_{↑name} EnableRenaming idType.sType := name

FieldList_{↑idTypeList↑len} =

len := 0

Field_{↑idType} INC(len); idTypeList[len] := idType;

{ ,

CC: len < maxFieldListLen

Field_{↑idType} INC(len); idTypeList[len] := idType

}

Variant_{↑idType} = Ident_{↑name}

idType.name := name

== SimpleType_{↑name} idType.type := name

VariantList_{↑idTypeList↑len} =

len := 0

Variant_{↑idType} INC(len); idTypeList[len] := idType;

{ ,

CC: len < maxVariantListLen

Variant_{↑idType} INC(len); idTypeList[len] := idType

}

ListType_{↓fw↑eType} = LIST OF SimpleType_{↑name}

GetType(↓ name,↑ eType1) MakeListType(↑ eType,↓ eType1,↓ fw)

TupleType_{↓fw↑eType} = TUPLE OF FieldList_{↑idTypeList↑len}

FOR i := 1 TO len DO SearchType(↓ idTypeList[i].type, ↑ nameTypeList[i].type) nameTypeList[i].name := idTypeList[i].name

END MakeTupleType(↑ eType, ↓ nameTypeList, ↓ len, ↓ fw)

UnionType_{↓fw↑eType} = UNION OF VariantList_{↑idTypeList↑len}

FOR i := 1 TO len DO SearchType(↓ idTypeList[i].type,↑ nameTypeList[i].type) nameTypeList[i].name := idTypeList[i].name

END MakeUnionType(↑ eType, ↓ nameTypeList,↓ len, ↓ fw)

SimpleOrFunctionType_{↑eType↑typeList↑len} = SimpleType_{↑name}

FindObject(↓ name,↑ obj,↑ op) CC: obj<>noObject len := 0 eType := op.type

( ε

IF op.kind=fctKind THEN CC: isFct(op.type) ExtractParTypes(↓ name,↑ typeList,↑ len)

END

| FunctionType_{↑typeList↑len} )

FunctionType_{↑typeList↑len} = [ TypeList_{↑sTypeList↑len}

FOR i := 1 TO len DO

SearchType(↓ sTypeList[i],↑ typeList[i]) END

]

Type_{↓fw↑eType} = SimpleOrFctType_{↑eType↑typeList↑len}

IF len>0 THEN eTypeRet := eType; CreateDummyIdList(↓ len,↑ idList) Join(↓ idList,↓ typeList,↓ len,↑ nameTypeList) MakeFctType(↑ eType,↓ eTypeRet,↓ dummyName,

↓ nameTypeList,↓ len,↓ fw)

END

|ListType_{↓fw↑eType} |TupleType_{↓fw↑eType} |UnionType_{↓fw↑eType}

Definition Statements

TypeDefinition_{↓name↑eType} = FindObject(↓ name,↑ obj,↑ op) CC: obj=noObject OR

(obj<>noObject AND op.level<CurrentLevel()) OR (obj<>noObject AND op.forward AND op.kind=typeKind AND equalType(op.Type,anyType));

IF existInSymbolTable(name) THEN

Rename(↓ name,↓ CurrentLevel()) END — if necessary rename new type fw.isForward := forwarded(↓ obj,↓ op) IF fw.isForward THEN

fw.type := op.type ELSE

NewType(↓ name) END — name in symbollist (currLevel)

( == Type_{↓fw↑eType}

IF fw.isForward THEN

ResolveForwardType(↓ name) ELSE

AssignType(↓ name,↓ eType) END

|ε CreateType(↑ eType) MakeForwardType(↓ name,↓ eType)

)

ConstantDefinition_{↓name↑eType↑x} = x := emptyAST IF existInSymbolTable(↓ name) THEN

Rename(↓ name,↓ CurrentLevel()) END — if necessary rename new const.

: SimpleType_{↑nameType} SearchType(↓ nameType,↑ eType) FindObject(↓ name,↑ obj,↑ op) IF name=nameOfMain THEN

obj := objOfMain op := opOfMain AssignType (↓ name,↓ eType)

END CC: obj=noObject OR

(obj<>noObject AND op.level<CurrentLevel()) OR (obj<>noObject AND op.forward AND op.kind=constKind AND equalType(op.Type,eType));

fwd := forwarded(↓ obj,↓ op) IF NOT fwd THEN

NewConst(↓ name,↓ eType) END — name in symbollist (currLevel)

( == Expression_{↑ast1↑eType1} CC: equalType(↓ eType,↓ eType1) MakeASTDef(↓ name,↓ ast1,↓ flag(eType),↑ x) IF fwd THEN

ResolveForwardConstant(↓ name,↓ x) ELSE

AssignAST(↓ name,↓ x) END

|ε

MakeForwardConstant(↓ name)

)

FunctionDefinition_{↓name↑eType↑x} = x := emptyAST FindObject(↓ name,↑ obj,↑ op) fwd := forwarded(↓ obj,↓ op) CC: obj=noObject OR

(obj<>noObject AND op.level<CurrentLevel()) OR (obj<>noObject AND op.forward AND op.kind=fctKind)

IF name=nameOfMain THEN obj := objOfMain op := opOfMain

END IF existInSymbolTable(name) AND name<>nameOfMain THEN

Rename(↓ name,↓ CurrentLevel()) END — if necessary rename new const.

[ IdentList_{↑idList↑len} ] :

CC: len>0

SimpleOrFctType_{↑eType↑typeList↑len1}

IF len1>0 THEN — Explicit FctType CC: len=len1 — eType is return type eTypeRet := eType Join (↓ idList,↓ typeList,↓ len,↑ nameTypeList); IF NOT fwd THEN — i.e. new fct

MakeFctType(↑ eType,↓ eTypeRet,↓ name,

↓ nameTypeList,↓ len,↓ noForward)

NewFct(↓ name,↓ eType) ELSE — i.e. prev. forward

IF name=nameOfMain THEN

MakeFctType(↑ eType,↓ eTypeRet,↓ name,

↓ nameTypeList,↓ len,↓ noForward)

SetMAINFctType (↓ eType, ↓ objOfMain, ↓ opOfMain) ELSE

eType := op.Type

CheckTypes (↓ idList,↓ typeList,↓ len,

↓ name,↓ eType,↓ eTypeRet)

END END

ELSE — eType=type of fct SetParNames(↓ name,↓ idList,↓ len,↓ eType) GetParTypeList(↓ eType, ↑ idList,↑ typeList,↑ lenT) GetReturnType(↓ eType,↑ eTypeRet) Join (↓ idList,↓ typeList,↓ len,↑ nameTypeList) IF NOT fwd THEN — i.e. new fct

NewFct(↓ name,↓ eType) ELSE — i.e. prev. forward

IF name=nameOfMain THEN

SetMAINFctType (↓ eType, ↓ objOfMain,↓ opOfMain) ELSE

CheckTypes (↓ idList,↓ typeList,↓ len,

↓ name,↓ op.type,↓ eTypeRet)

END END

END — name in symbollist (currLevel)

( ==

BeginFct(↓ name) FOR i := 1 TO len DO

IF existInSymbolTable(idList[i]) THEN Rename(↓ idList[i],↓ CurrentLevel()) idList[i] := GetVirtualName(↓ idList[i])

END END — rename parameters SetParNames(↓ name,↓ idList,↓ len,↓ eType) NewParList(↓ name,↓ idList,↓ typeList,↓ len)

Expression_{↑ast↑eTypeExpr} CC: equalType(↓ eTypeRet,↓ eTypeExpr) UnRenameLevel(↓ CurrentLevel()) EndFct(↓ name) TypesToFlags (↓ typeList, ↓ len,↑ flagList) MakeASTLambda(↓ idList,↓ flagList,↓ len,

↓ ast,↓ flag(eTypeExpr),↑ astLambda)

MakeASTDef(↓ name,↓ astLambda,↓ flag(eTypeExpr),↑ x) IF fwd THEN — fct was forwarded

ResolveForwardFct(↓ name,↓ ast) ELSE

AssignAST (↓ name,↓ ast) END

|ε

MakeForwardFct(↓ name)

)

TCFDefinition_{↓name↑eType↑x} =

x := emptyAST

TypeDefinition_{↓name↑eType} | ConstantDefinition_{↓name↑eType↑x} | FunctionDefinition_{↓name↑eType↑x}

Definition_{↑eType↑x} = [ Ident_{↑name} TCFDefinition_{↓name↑eType↑x} ]

DefinitionSequence_{↑x} = ast := emptyAST i := 1

Definition_{↑eType↑ast}

IF ast<>emptyAST THEN def[i] := ast INC(i)

END

{ ;

ast := emptyAST

Definition_{↑eType↑ast} CC: i<maxASTypeListLen IF ast<>emptyAST THEN

def[i] := ast

INC(i) END

}

IF i>1 THEN

MakeASTDefSeq(↓ def,↓ i − 1,↑ x) END

Expression

Expression_{↑x↑eType} = eType := anyTy x := emptyAST

ListExpr_{↑x↑eType}

|{

BeginBlock

DefinitionSequence_{↑ast1} } Expression_{↑ast2↑eType}

MakeASTLet(↓ ast1,↓ ast2,↓ flag(eType),↑ x) UnRenameLevel(↓ CurrentLevel()) EndBlock()

|IF

Expression_{↑astHead↑eTypeHead}

CC: equalType(↓ eTypeHead,↓ boolType)

THEN Expression_{↑astThen↑eTypeThen} ELSE Expression_{↑astElse↑eTypeElse}

CC: equalType(↓ eTypeThen,↓ eTypeElse) MaxType(↓ eTypeThen,↓ eTypeElse,↑ eType) MakeASTIf(↓ astHead,↓ astThen,↓ astElse,↓ flag(eType),↑ x)

|CASE

elseBody.exist := FALSE

Expression_{↑astHead↑eTypeHead}

CC: isUnionType(↓ eTypeHead)

{ Field_{↑idType}

IF existInSymbolTable(idType.name) THEN Rename(↓ idType.name,↓ CurrentLevel()) renamed := TRUE

END — if necessary rename new const. GetVariantType(↓ eTypeHead,↓ idType.type,↑ eType1,↑ nr) NewConst (↓ idType.name,↓ eType1)

} Expression_{↑ast↑eType} body[1].number := nr body[1].name := idType.name body[1].ast := ast i := 2 DeleteVar(↓ idType.name) IF renamed THEN

UnRename(↓ idType.name)

renamed := FALSE END

{ { Field_{↑idType}

IF existInSymbolTable(idType.name) THEN Rename(↓ idType.name,↓ CurrentLevel()) renamed := TRUE

END — if necessary rename new const. GetVariantType(↓ eTypeHead,↓ idType.type,↑ eType1,↑ nr) NewConst (↓ idType.name,↓ eType1)

} Expression_{↑ast↑eType0} CC: equalType(↓ eType0,↓ eType) MaxType(↓ eType,↓ eType0,↑ eType) body[i].number := nr body[i].name := idType.name body[i].ast := ast INC(i) DeleteVar(↓ idType.name) IF renamed THEN

UnRename(↓ idType.name)

renamed := FALSE END

} [ ELSE Expression_{↑ast↑eType0}

CC: equalType(↓ eType0,↓ eType) MaxType(↓ eType,↓ eType0,↑ eType) elseBody.exist := TRUE elseBody.ast := ast

] MakeASTCase(↓ astHead,↓ body,↓ i − 1,↓ elseBody,↓ flag(eType),↑ x)

ListExpr_{↑x↑eType} = CompoundExpr_{↑x↑eType}

cells[1] := x typeList[1] := eType i := 2

{ :: Factor_{↑ast↑eType} CC: i<maxASTypeListLen ∧ i<maxTypeListLen cells[i] := ast typeList[i] := eType INC(i) CC: ∀1 ≤ j ≤ i − 3 :

equalType(typeList[j], typeList[j + 1])∧ isListOf(typeList[i − 1], typeList[1])

}

MakeASTConsList (↓ cells,↓ i − 2,↓ cells[i − 1],↑ x)

CompoundExpr_{↑x↑eType} = SimpleExpr_{↑ast1↑eType}

x := ast1

[ Relation_{↑rel}

CC: equalType(↓ eType,↓ boolType) OR equalType(↓ eType,↓ charType) OR equalType(↓ eType,↓ intType)

SimpleExpr_{↑ast2↑eType2} CC: equalType(↓ eType,↓ eType2) MakeASTRelation(↓ rel,↓ ast1,↓ ast2,↑ x)

] eType := boolType

Relation_{↑rel} =

=

rel := eqRelTy

|#

rel := neRelTy

|<

rel := ltRelTy

|<=

rel := leRelTy

|>

rel := gtRelTy

|>=

rel := geRelTy

SimpleExpr_{↑x↑eType} =

sign := FALSE

[ +

|-

sign := TRUE

] Term_{↑ast↑eType} CC: NOT sign OR equalType(↓ eType,↓ intType) IF sign THEN

MakeASTSign(↓ ast,↑ x) ELSE

x := ast END

{AddOp_{↑add} Term_{↑ast↑eType2}

CC: CASE add OF plusArTy, minusArTy : equalType(↓ eType2,↓ intType)

orArTy : equalType(↓ boolType) END

CC: equalType(↓ eType,↓ eType2)

MakeASTArithm(↓ add,↓ x,↓ ast,↓ flag(eType),↑ ast0)

x := ast0

}

AddOp_{↑add} = +

add := plusArTy

|-

add := minusArTy

||

add := orArTy

Term_{↑x↑eType} = Factor_{↑x↑eType} {MulOp_{↑mul} Factor_{↑ast1↑eType2}

CC: CASE mul OF timesArTy, divArTy, modArTy : equalType(↓ intType) andArTy : equalType(↓ boolType)

END CC: equalType(↓ eType2,↓ eType) MakeASTArithm(↓ mul,↓ x,↓ ast1,↑ ast0) x := ast0

}

MulOp_{↑mul} =

*

mul := timesArTy

|/

mul := divArTy

|%

mul := modArTy

|&

mul := andArTy

Factor_{↑x↑eType} = |Number_{↑nr}

MakeASTAtom(↓ numberAtom,↓ noCharVal,↓ value,↓ int,↑ x) eType := intType

|Character_{↑c} MakeASTAtom(↓ charAtom,↓ c,↓ noCardVal,↓ int,↑ x)

eType := charType

|String_{↑saddr↑slen} StringToList(↓ saddr,↓ slen,↑ x) eType := strType

|( Expression_{↑x↑eType} ) |~ Factor_{↑ast↑eType}

MakeASTNot(↓ ast,↑ x)

|Designator_{↑name↑nr↑eType↑eTypeU} x := emptyAST isUnionFct := isUnionType(↓ eTypeU) AND isFctType(↓ eType) IF isFctType(↓ eType) AND NOT isUnionFct THEN

MakeASTAtom(↓ idAtom,↓ noCharVal,↓ name,↓ flag(eType),↑ x) END isHigherOrder := isParOrConst(name) AND isFct(eType)

{ eType0 := eType ast := x

( ArgumentList_{↓ast↓eType0↓isUnionFct↓isHigherOrder↑x}

IF isFctType(↓ eType) AND NOT isUnionFct THEN eType0 := eType GetReturnType(↓ eType0,↑ eType)

ELSIF isUnionType(eTypeU) THEN MakeASTAtom (↓ numberAtom,↓ noCharVal,↓ nr,↓ int,↑ nrAST) asts[1] := nrAST asts[2] := x MakeASTTuple (↓ asts,↓ 2,↑ x)

END isUnionFct := FALSE isHigherOrder := TRUE

|

IF x=emptyAST THEN

MakeASTAtom(↓ idAtom,↓ noCharVal,↓ name,↓ flag(eType), ↑ x) END

Selector_{↓ast↓eType0↑x↑eType} isUnionFct := FALSE isHigherOrder := TRUE

)} |ε

IF x=emptyAST THEN

MakeASTAtom(↓ idAtom,↓ noCharVal,↓ name,↓ flag(eType), ↑ x) END IF isUnionType(↓ eTypeU) THEN

eType := eTypeU END IF isFctType(eType) AND NOT isParOrConst(↓ name) THEN

x0 := x

MakeASTCurry(↓ x0,↑ x) END Designator_{↑name↑nr↑eType↑eTypeU} = Ident_{↑name}

TestAdd(↓ name) FindObject(↓ name,↑ obj,↑ op) CC: obj<>noObject eType := op.type eTypeU := op.type

[ $

CC: isUnion(↓ eType) DisableRenaming

Ident_{↑nameTy} EnableRenaming eType0 := eType GetVariantType(↓ eType0,↓ nameTy,↑ eType,↑ nr)

]

ArgumentList_{↓ast↓eTypeIn↓isUnionFct↓isHigherOrder↑x} = len := 1 i := 1

[ [

CC: NOT isUnionType(↓ eTypeIn) IF isFctType(↓ eTypeIn) AND NOT isUnionFct THEN

GetParTypeList(↓ eTypeIn,↓ idList,↑ defTypeList,↑ len) ELSIF isTupleType(↓ eTypeIn) THEN

GetTupleTypeList(↓ eTypeIn,↑ defTypeList,↑ len) ELSIF isListType(↓ eTypeIn) THEN

GetListType(↓ etypeIn,↑ listType) END

Expression_{↑ast1↑eType1}

CC: IF isListType(↓ eTypeIn) THEN

equalType(↓ listType,↓ eType1) ELSIF isTupleType(↓ eTypeIn) OR

(isFctType(↓ eTypeIn) AND NOT isUnionFct) THEN len>0 AND equalType(↓ defTypeList[1],↓ eType1)

ELSE

equalType(↓ eTypeIn,↓ eType1) END

arg[i] := ast1 typeList[i] := eType1 INC(i)

{ , Expression_{↑ast2↑eType2}

CC: IF isListType(↓ eTypeIn) THEN

equalType(↓ listType,↓ eType2) ELSIF isTupleType(↓ eTypeIn) OR

(isFctType(↓ eTypeIn) AND NOT isUnionFct) THEN

len≥i AND equalType(↓ defTypeList[i],↓ eType2) END

arg[i] := ast2 typeList[i] := eType2 INC(i)

} ] ] CC: isListType(↓ eTypeIn) OR len=i-1 OR (isUnionFct AND (i=2)))) IF isFctType(↓ eTypeIn) AND NOT isUnionFct THEN

GetReturnType (↓ eTypeIn,↑ eTypeOut)

IF isHigherOrder THEN

MakeASTApplication(↓ ast,↓ arg,↓ i − 1,↓ flag(eTypeOut),↑ x) ELSE

MakeASTCall(↓ ast,↓ arg,↓ i − 1,↓ flag(eTypeOut),↑ x) END

ELSIF isTupleType(↓ eTypeIn) THEN

MakeASTTuple(↓ arg,↓ i − 1,↑ x) ELSIF isListType(↓ eTypeIn) THEN

MakeASTList(↓ arg,↓ i − 1,↑ x) ELSIF isUnionFct THEN

x := ast1 ELSE

x := ast1 END

Selector_{↓ast↓eTypeIn↑x↑eTypeOut} =

DisableRenaming

. Ident_{↑name} EnableRenaming CC: isTupleType(↓ eTypeIn) OR

(isListType(↓ eTypeIn) AND

(name=nameOfHEAD OR name=nameOfTAIL) ) IF (name=nameOfHEAD OR name=nameOfTAIL) THEN

IF name=nameOfHEAD THEN

GetListType (↓ eTypeIn,↑ eTypeOut) ELSE

eTypeOut := eTypeIn END MakeASTAtom (↓ idAtom,↓ noCharVal,↓ name,↓ flag(eTypeOut),↑ ast1) arg[1] := ast MakeASTCall (↓ ast1,↓ arg,↓ 1,↓ flag(eTypeOut),↑ x)

ELSE GetFieldType(↓ eTypeIn, ↓ name,↑ nr,↑ eTypeOut) MakeASTSelection(↓ nr,↓ ast,↓ flag(eTypeOut), ↑ x)

END A.4. ATTRIBUTE GRAMMAR OF THE FIRST PASS 155

Program

Program_{↑x} = DefinitionSequence_{↑x} MakeASTProgram (↓ x,↑ x)

Appendix B

Denotational Semantics of the Code Generation

This appendix gives a formal description of the code generation in the EVE compiler. For this description a denotational semantics is used. Due to the description in [Schmidt, 1986], a denotational semantics consists of three parts:

• An abstract syntax definition of the source language. Section B.1 gives a defintion of the abstract syntax of the EVE language. This abstract syntax is in the compiler represented by an abstract syntax tree (AST). This abstract syntax tree is an abstract data type with EveAST as its data encapsulation. The separation of the language constructs into the three syntax classes Definitions, Expressions and the Program is kept in the abstract syntax and it will also be kept in the other parts of the denotational semantics.

• The semantic algebras. All the relevant semantic algebras are described in Section B.2. The most important of these algebras cover the abstract data types of registers, labels, events and environments. For each semantic algebra the necessary procedures and their properties are specified.

• The valuation function. This function maps a program in the abstract syntax to ADAM assembler code. Actually, a valuation function for each of the three syntax classes will be given. This is also useful as each syntax class requires a different input and output for the definition of this valuation function. The names of these valuation functions are:

– D for Definitions,

– E for Expressions,

– P for the Program.

Note that these valuation functions can already be read as a functional program that implements the code generation of the EVE compiler.

Section B.3, which describes the valuation functions, is split into several subsections. Each of these subsections covers the translation of one construct of the abstract syntax into assembler code. However, it does not suffice to describe just the generated assembler code. To keep the information about all semantic algebras up to date one must also perform some operations on these algebras. In general, the description of the translation of a construct is split into the following parts:

– The names of the input tuple and of the result tuple of the valuation function. Note that the variables in the input tuple are universally quantified whereas the variables in the result tuple are defined in the where expression. From this part already the structure of the resulting tuple can be seen.

– The actual ADAM assembler code. This part will always be the first component in the local definitions of the where construct. However, the assembler code does not only contain single assembler statements but also whole code sequences. Such ADAM assembler sequences are usually named C_{x}, where x denotes the syntax construct that is evaluated.

– The next part in the description contains some initializations. Especially, new registers, labels and events have to be fetched via calls to the corresponding procedures of the semantic algebras.

– If the language construct contains subexpressions with free variables, the free variables that are the input for the whole expression must be forked such that they are available for the subexpressions. Hence, for each subexpression it must be decided which free variables are also used in other parts of the whole expression. If a variable is not only used in that subexpression a SPLIT has to be performed to make the variable available to both usages. This part is called Forking common free variables in the description of the valuation function.

– The next important part contains, if necessary, recursive calls to the valuation function in order to get code sequences for the subexpressions of the current expression. As these calls state the input/output behaviour of the subexpressions they are essential for the understanding of the generated ADAM assembler program. To emphasis the importance of this part these recursive calls have been put into frameboxes .

– The last part in the description of the evaluation function covers the splitting into parallel threads of control (e.g. the splitting of the trigger thread that must often be done at the beginning of a code block) and the synchronization of parallel threads. The former is done via calls to the functions Split, SplitP and \overline{Split}, the latter is done via calls to SignalTree. This part is called Splitting and Synchronization in the description of the valuation function.

Additionally, to the abstract syntax definition, the description of the semantic algebras and of the valuation function, which already define a denotational semantics, we also describe in Section B.4 the code that is generated for the predefined functions in the EVE language. For all these predefined functions inline code instead of function calls is generated.

Note that in this formal description of the code generation process some aspects will be discussed that have not been covered in Section 4.4.2. These aspects are:

• The forking of common free variables.

• The different handling of integer and pointer values in the following cases:

158 APPENDIX B. DENOTATIONAL SEMANTICS OF THE CODE GENERATION

– In the splitting of common free variables and

– in the writing of these values into tuple fields.

• In the ADAM assembler code there is no RECEIVE instruction available as it was used in the description of the code generation in Section 4.4.2. However, one can simulate the semantics of the statement

RECEIVE r, next

by using the statement

COPYI r, r, next.

In the description of the valuation functions an abbreviation for a sequence of applications of the same functions is used. Hence, the expression

x_{1}, …, x_{n}, RegState′ = NewReg(RegState)^{n}

is nothing but a shorthand for

(x_{1}, RegState_{1}) = NewReg(RegState) (x_{2}, RegState_{2}) = NewReg(RegState_{1})

⋮

(x_{n}, RegState′) = NewReg(RegState_{n−1}).

B.1 Abstract Syntax

Definition Statements

ConstantDefinition = defN Ident [ Expression ] .

FunctionDefinition = defN Ident [ (lambdaN (lheadN Ident { Ident }) (lbodyN

Expression)) ] .

Definition  = ConstantDefinition | FunctionDefinition .
DefinitionSequence = defseqN Definition { Definition } .

Expressions

Expression = ListExpr | letN DefinitionSequence Expression | ifN Expression Expression Expression | ArithBoolExpr | CurriedExpr | BracketExpr | Atom .

ListExpr  = composeN Expression Expression | nilN .

ArithBoolExpr = Relation Expression Expression | UnaryOp Expression | Bi-

nOp Expression Expression .

Relation  = eqN | neN | ltN | leN | gtN | geN .
UnaryOp  = notN | minusSignN .
BinOp  = plusN | minusN | timesN | divN | modN | orN | andN .

CurriedExpr = curryN Ident { Ident } .

BracketExpr = applyN BracketExpr { Expression } | callN Ident { Expres-

sion } | selectN Number BracketExpr | Ident .

Atom  = Ident | Number | Char .
Ident  = idN identifier .
Number  = numberN number .
Char  = charN character .

Program

Program = programN DefinitionSequence .

B.2 Semantic Algebras

B.2.1 Variables (atomic domain)

• Domain: Variable.

B.2.2 Flags for checking compound objects (atomic domain)

• Domain: Flags = {int, pointer, none}

• Properties: int ≠ pointer ≠ none

B.2.3 Handling of Registers

• Domains: StateOfRegisters, Register.

• Operations:

InitReg : StateOfRegisters

NewReg : StateOfRegisters → (Register, StateOfRegisters)

• Properties:

Typed Variables: RegState, RegState′, rs_{0} ∈ StateOfRegisters r, r′, r_{0} ∈ Register

Distinct(r_{0}, rs_{0}), where (r_{0}, rs_{0}) = NewReg(InitReg), Distinct(r, RegState) :⇔ r ≠ r′ ∧ Distinct(r, RegState′) ∧ Distinct(r′, RegState′), where (r′, RegState′) = NewReg(RegState).

B.2.4 Handling of Labels

• Domains: StateOfLabels, Label.

• Operations:

StartLabel, EmptyLabel : Label

InitLabel : StateOfLabels

NewLabel : StateOfLabels → (Label, StateOfLabels)

• Properties:

Typed Variables: LabelState, LabelState′, ls_{0} ∈ StateOfLabels l, l′, l_{0} ∈ Label

Distinct(l_{0}, ls_{0}), Distinct(StartLabel, InitLabel), Distinct(EmptyLabel, InitLabel), StartLabel ≠ EmptyLabel, where (l_{0}, ls_{0}) = NewLabel(InitLabel),

Distinct(l, LabelState) :⇔ l ≠ l′ ∧ Distinct(l, LabelState′) ∧ Distinct(l′, LabelState′), where (l′, LabelState′) = NewLabel(LabelState)

B.2.5 Handling of Events

• Domains: StateOfEvents, Event.

• Operations:

InitEvent : StateOfEvents

NewEvent : StateOfEvents → (Event, StateOfEvents)

• Properties:

Typed Variables:

EventState, EventState′, es_{0} ∈ StateOfEvents

e, e′, e_{0} ∈ Event

Distinct(e_{0}, es_{0}),

where

(e_{0}, es_{0}) = NewEvent(InitEvent),

Distinct(e, EventState) :⇔

e ≠ e′ ∧ Distinct(e, EventState′) ∧ Distinct(e′, EventState′), where (e′, EventState′) = NewEvent(EventState)

B.2.6 Generated Code

• Domain: Code (ADAM Assembler Code).

B.2.7 Binding Registers, Labels and Flags to Variables

• Domain:

Environment : Variable → (Register, Label, Flags)

• Operations: The usual operations on functions that are based on set theory. Further-

more:

AddToEnv : Environment → Variable → Register → Label →

Flags → Environment

BindVar : Variable → Label → Environment → Environment lookupRegister : Environment → Variable → Register lookupLabel : Environment → Variable → Label lookupFlag : Environment → Variable → Flags

• Properties:

Typed Variables: l ∈ Label, env ∈ Environment, r ∈ Register, v ∈ Variable, f ∈ Flags

∀l, env, v, f : lookupRegister (AddToEnv env v r l f) v = r lookupRegister (AddToEnv env v′ r l f) v = lookupRegister env v , if v ≠ v′ lookupLabel (AddToEnv env v r l f) v = l lookupLabel (AddToEnv env v′ r l f) v = lookupLabel env v , if v ≠ v′ lookupFlag (AddToEnv env v r l f) v = f lookupFlag (AddToEnv env v′ r l f) v = lookupFlag env v , if v ≠ v′

∀newLab, env, v : lookupLabel (BindVar v newLab env) v = newLab lookupLabel (BindVar v′ newLab env) v = lookupLabel env v , if v ≠ v′ 162 APPENDIX B. DENOTATIONAL SEMANTICS OF THE CODE GENERATION

B.2.8 State of the resources that must be handled

• Domain:

State : (StateOfRegisters, StateOfLabels, StateOfEvents)

• Operations: The usual operations on tuples (like fst for selecting the first component).

B.2.9 The result of the valuation function

• Domain:

Result : Code DefResult : (Code, Label, State, Environment)

ExprResult : (Code, Register, (Label × Label), State)

• Operations: The usual operations on tuples.

B.3 Valuation Function

Some useful auxiliary functions

Splitting one thread in various other threads (no pointer token)

Split : Label → Int → StateOfLabels → (Code, [Label], StateOfLabels)

Split l n LabelState = (C, [l_{1}, …, l_{n}], LabelState′′′)

where

IF n = 1
THEN l_{1} = l

LabelState′′′ = LabelState ELSE

C = ( \begin{matrix}l : & SPLIT & l_{1}, l_{2} \\ & C_{split,l_{1}} \\ & C_{split,l_{2}} \end{matrix} )

l_{1}, l_{2}, LabelState′ = NewLabel(LabelState)^{2} (C_{split,l_{1}}, [l_{1}, …, l_{(n DIV 2)}], LabelState′′) = Split(l_{1}, (n DIV 2), LabelState′) (C_{split,l_{2}}, [l_{(n DIV 2)+1}, …, l_{n}], LabelState′′′) = Split(l_{2}, n − (n DIV 2), LabelState′′)

END

Splitting one thread in various other threads (with pointer token)

SplitP : Label → Register → Int → StateOfLabels → (Code, [Label], StateOfLabels)

SplitP l r n LabelState = (C, [l_{1}, …, l_{n}], LabelState′′′)

where

IF n = 1

THEN l_{1} = l

LabelState′′′ = LabelState ELSE

C = ( \begin{matrix}l : & COPYP & r, r, +1 \\ & SPLIT & l_{1}, l_{2} \\ & C_{split,l_{1}} \\ & C_{split,l_{2}} \end{matrix} )

l_{1}, l_{2}, LabelState′ = NewLabel(LabelState)^{2} (C_{split,l_{1}}, [l_{1}, …, l_{(n DIV 2)}], LabelState′′) = SplitP(l_{1}, (n DIV 2), LabelState′) (C_{split,l_{2}}, [l_{(n DIV 2)+1}, …, l_{n}], LabelState′′′) = SplitP(l_{2}, n − (n DIV 2), LabelState′′)

END Splitting one thread in various other threads

\overline{Split} : Label → Register → Int → Flags → StateOfLabels → (Code, [Label], StateOfLabels)

\overline{Split} l r n f LabelState = SplitP l r n LabelState , if f = pointer
Split l n LabelState  , otherwise

Merging various threads into one thread

SignalTree : [ Label ] → Int →

StateOfLabels → StateOfEvents →

(Code, StateOfLabels, StateOfEvents, Label)

SignalTree [l_{1}, …, l_{n}] n LabelState EventState = (C, LabelState′′, EventState′′, l)

where

IF n = 1
THEN l = l_{1}

EventState′′ = EventState LabelState′′ = LabelState

ELSE

C = ( l_{1} :  WAIT  e_{1}, l′_{1} l_{2} :  WAIT  e_{1}, l′_{1} ⋮ l_{2k−1} : WAIT  e_{k}, l′_{k} l_{2k} : WAIT  e_{k}, l′_{k} ⋮ l_{n} :  WAIT  e_{(n DIV 2)+1}, l′_{(n DIV 2)+1} CSignalTree )

e_{1}, …, e_{(n DIV 2)+1}, EventState′ = NewEvent(EventState)^{(n DIV 2)+1} l_{1}, …, l_{(n DIV 2)+1}, LabelState′ = NewLabel(LabelState)^{(n DIV 2)+1}

(CSignalTree, LabelState′′, EventState′′, l) =

SignalTree [l′_{1}, …, l_{(n DIV 2)+1}] (n DIV 2) + 1 LabelState′ EventState′

END

B.3.1 Valuation Function of Expression

E : Expression → Label → State → Environment → ExprResult

Constant

                  const ∈ { numberN c, charN c, TRUE, FALSE }

E [const] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState′, LabelState′, EventState)

where

C = ( \begin{matrix}trigger : & CONST & r, +2 \\ & c′ \\ & SPLIT & result, signal \end{matrix} )

c′ = \{ \begin{matrix}c & , if const = numberN c \\ ORD(c) & , if const = charN c \\ 1 & , if const = TRUE \\ 0 & , if const = FALSE \end{matrix} \}

result, signal, LabelState′ = NewLabel(LabelState)^{2} (r, RegState′) = NewReg(RegState)

Simple Variable

E [idN v] trigger (RegState, LabelState, EventState) env = (C, r, (l, trigger), (RegState, LabelState′, EventState′)

where

C = ()

r = lookupRegister(env, v) l = lookupLabel(env, v)

166 APPENDIX B. DENOTATIONAL SEMANTICS OF THE CODE GENERATION Unary Operations

E [op E] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState_{E}, LabelState_{E}, EventState_{E})) where C = \binom{C_{E}}{result_{E} : op r, r_{E}, result}

result, LabelStateSignal′ = NewLabel(LabelState_{m}) (r, RegState′) = NewReg(RegState) (C_{E}, r_{E}, (result_{E}, signal), (RegState_{E}, LabelState_{E}, EventState_{E})) = E [E] trigger (RegState′, LabelStateSignal′, EventState) env

Compound Arithmetical and Logical Expressions E [op E_{1} E_{2}] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState_{E_{2}}, LabelState_{E_{2}}, EventState_{E_{2}})) where

C = ( trigger : SPLIT l_{E_{1}}, l_{E_{2}} C_{1} ⋮ C_{k} C_{E_{1}} C_{E_{2}} result_{E_{1}} : WAIT e_{1}, +2 result_{E_{2}} : WAIT e_{1}, +1 COPYI r, r_{E_{1}}, +1 op r, r_{E_{2}}, result signal_{E_{1}} WAIT e_{2}, signal signal_{E_{2}} WAIT e_{2}, signal )

B.3. VALUATION FUNCTION 167 fV_{1} = freeVars(E_{1})

flag_{1} = getFlag(E_{1})
fV_{2} = freeVars(E_{2})
flag_{2} = getFlag(E_{2})
l_{E_{1}}, l_{E_{2}}, LabelState_{m} = NewLabel(LabelState)^{2}

result, signal, LabelStateSignal = NewLabel(LabelState_{m})^{2} (r, RegStateR) = NewReg(RegState) e_{1}, e_{2}, e_{3}, EventState_{e_{3}} = NewEvent(EventState)^{3} — Forking common free variables —

[z_{1}, …, z_{k}] = fV_{1} ∩ fV_{2} (r_{1}, l_{1}, f_{1}) = (lookupRegister env z_{1} , lookupLabel env z_{1} , lookupFlag env z_{1})

⋮

(r_{k}, l_{k}, f_{k}) = (lookupRegister env z_{k} , lookupLabel env z_{k} , lookupFlag env z_{k}) (C_{1}, [l_{1,1}, l_{1,2}], LabelState_{1}) = \overline{Split} l_{1} 2 LabelStateSignal

⋮

(C_{k}, [l_{k,1}, l_{k,2}], LabelState_{k}) = \overline{Split} l_{k} 2 LabelState_{k} env_{E_{1}} = BindVar(z_{1}, l_{1,1}) ∘ ⋯ ∘ BindVar(z_{k}, l_{k,1}) env

env_{E_{2}} = BindVar(z_{1}, l_{1,2}) ∘ ⋯ ∘ BindVar(z_{k}, l_{k,2}) env

— End of forking common free variables —

(C_{E_{1}}, r_{E_{1}}, (result_{E_{1}}, signal_{E_{1}}), (RegState_{E_{1}}, LabelState_{E_{1}}, EventState_{E_{1}})) = E [E_{1}] l_{E_{1}} (RegStateR, LabelStateSignal, EventState_{e_{2}}) env_{E_{1}} (C_{E_{2}}, r_{E_{2}}, (result_{E_{2}}, signal_{E_{2}}), (RegState_{E_{2}}, LabelState_{E_{2}}, EventState_{E_{2}})) = E [E_{2}] l_{E_{2}} (RegState_{E_{1}}, LabelState_{E_{1}}, EventState_{E_{1}}) env_{E_{2}} 168 APPENDIX B. DENOTATIONAL SEMANTICS OF THE CODE GENERATION Function Calls

E [ callN (idN f) E_{1}, …, E_{n} ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState_{E_{n}}, LabelState′, EventState′))

where

C = ( Csplit lsplit_{0} :  FASTCALL f, +1 COPYI  ract , ract , send COPYI  r, r, result Csend C_{1,1} ⋮ C_{1,m_{1}} C_{E_{1}} result_{E_{1}} : WAIT  e_{1}, +2 send_{1} :  WAIT  e_{1}, +1 SEND  r_{E_{1}}, ract , 1 JUMP  arg_{1} ⋮ C_{n−1,1} ⋮ C_{n−1,m_{n−1}} C_{E_{n−1}} result_{E_{n−1}} : WAIT  e_{n−1}, +2 send_{n−1} : WAIT  e_{n−1}, +1 SEND  r_{E_{n−1}}, ract , n − 1 JUMP  arg_{n−1} C_{E_{n}} result_{E_{n}} : WAIT  e_{n}, +2 send_{n} :  WAIT  e_{n}, +1 SEND  r_{E_{n}}, ract , n JUMP  arg_{n} CST )

B.3. VALUATION FUNCTION 169

fV_{1} = freeVars(E_{1})

⋮

fV_{n} = freeVars(E_{n})

ract , r, RegStateresult = NewReg(RegState)^{2} send, result, signal, arg_{1}, …, arg_{n}, LabelStatesignal = NewLabel(LabelState)^{n+3}

e_{1}, …, e_{n}, EventState′ = NewEvent(EventState)^{n} — Forking common free variables — [y_{1,1}, …, y_{1,m_{1}}] = fV_{1} ∩ (fV_{2} ∪ ⋯ ∪ fV_{n}) (ry_{1,1}, ly_{1,1}, fy_{1,1}) = (lookupRegister env y_{1,1} , lookupLabel env y_{1,1} , lookupFlag env y_{1,1})

⋮

(ry_{1,m_{1}}, ly_{1,m_{1}}, fy_{1,m_{1}}) = (lookupRegister env y_{1,m_{1}} , lookupLabel env y_{1,m_{1}} , lookupFlag env y_{1,m_{1}})

(C_{1,1}, [l_{1,1}, l′_{1,1}], LabelState_{1,1}) = \overline{Split}(ly_{1,1}, ry_{1,1}, 2, fy_{1,1}, LabelStatesignal )

⋮

(C_{1,m_{1}}, [l_{1,m_{1}}, l′_{1,m_{1}}], LabelState_{1,m_{1}}) = \overline{Split}(ly_{1,m_{1}}, ry_{1,m_{1}}, 2, fy_{1,m_{1}}, LabelState_{1,m_{1}−1})

env_{E_{1}} = BindVar (y_{1,1} l_{1,1}) ∘ ⋯ ∘ (y_{1,m_{1}} l_{1,m_{1}}) env env′_{E_{1}} = BindVar (y_{1,1} l′_{1,1}) ∘ ⋯ ∘ (y_{1,m_{1}} l′_{1,m_{1}}) env

⋮

[y_{n−1,1}, …, y_{n−1,m_{n−1}}] = fV_{n−1} ∩ fV_{n} (ry_{n−1,1}, ly_{n−1,1}, fly_{n−1,1}) = (lookupRegister env′_{E_{n−2}} y_{n−1,1} , lookupLabel env′_{E_{n−2}} y_{n−1,1} , lookupFlag env′_{E_{n−2}} y_{n−1,1})

⋮

(ry_{n−1,m_{n−1}}, ly_{n−1,m_{n−1}}, fy_{n−1,m_{n−1}}) = (lookupRegister env′_{E_{n−2}} y_{n−1,m_{n−1}} , lookupLabel env′_{E_{n−2}} y_{n−1,m_{n−1}} , lookupFlag env′_{E_{n−2}} y_{n−1,m_{n−1}})

(C_{n−1,1}, [l_{n−1,1}, l′_{n−1,1}], LabelState_{n−1,1}) = \overline{Split}(ly_{n−1,1}, ry_{n−1,1}, 2, fy_{n−1,1}, LabelState_{n−2,m_{n−2}})

⋮

(C_{n−1,m_{n−1}}, [l_{n−1,m_{n−1}}, l′_{n−1,m_{n−1}}], LabelState_{n−1,m_{n−1}}) = \overline{Split}(ly_{n−1,m_{n−1}}, ry_{n−1,m_{n−1}}, 2, fy_{n−1,m_{n−1}}, LabelState_{n−1,m_{n−1}−1})

env_{E_{n−1}} = BindVar (y_{n−1,1} l_{n−1,1}) ∘ ⋯ ∘ (y_{n−1,m_{n−1}} l_{n−1,m_{n−1}}) env′_{E_{n−2}} env′_{E_{n−1}} = BindVar (y_{n−1,1} l′_{n−1,1}) ∘ ⋯ ∘ (y_{n−1,m_{n−1}} l′_{n−1,m_{n−1}}) env′_{E_{n−2}} (C_{E_{1}}, r_{E_{1}}, (result_{E_{1}}, signal_{E_{1}}), (RegState_{E_{1}}, LabelState_{E_{1}}, EventState_{E_{1}})) = E [E_{1}] l_{split_{1}} (RegStateresult , LabelStatesignal , EventState_{e_{n}}) env_{E_{1}}

(C_{E_{n−1}}, r_{E_{n−1}}, (result_{E_{n−1}}, signal_{E_{n−1}}), (RegState_{E_{n−1}}, LabelState_{E_{n−1}}, EventState_{E_{n−1}})) = E [E_{n−1}] l_{split_{n−1}} (RegState_{E_{n−2}}, LabelState_{E_{n−2}}, EventState_{E_{n−2}}) env_{E_{n−1}}

(C_{E_{n}}, r_{E_{n}}, (result_{E_{n}}, signal_{E_{n}}), (RegState_{E_{n}}, LabelState_{E_{n}}, EventState_{E_{n}})) = E [E_{n}] l_{split_{n}} (RegState_{E_{n−1}}, LabelState_{E_{n−1}}, EventState_{E_{n−1}}) env_{E_{n}}

— Splitting and Synchronizing —

(Csplit , [l_{split_{0}}, …, l_{split_{n}}], LabelStatesplit ) = Split(trigger, n + 1, LabelState_{E_{n}})

(Csend , [send_{1}, …, send_{n}], LabelStatesend ) = Split(send, n, LabelStatesplit )

(CST , LabelState′, EventState′, signal) =

SignalTree([arg_{1}, …, arg_{n}, signal_{E_{1}}, …, signal_{E_{n}}], 2n, LabelStatesend , EventState_{E_{n}})

— End of Splitting and Synchronizing —

Conditional Expression

E [ ifN E_{1} E_{2} E_{3} ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState-r_{E_{3}}, LabelState_{ST error} , EventState_{ST error} ))

C = ( trigger :  SPLIT  trigger_{E_{1}}, trigger_{E_{23}} C_{1,1} ⋮ C_{1,m} C_{E_{1}} Csplit signal_{E_{1}} : WAIT  e, +2 head :  WAIT  e, +1 SWITCH r_{E_{1}}, lST true, lST false JUMP  l_{error ,0} trigger_{E_{23}} : WAIT  e_{0}, +2 trigger_{0} : WAIT  e_{0}, +1 SWITCH r_{E_{1}}, ltrue, lfalse COPYI r, r_{E_{1}}, result ly_{1} :  WAIT  e_{1}, +2 trigger_{1} : WAIT  e_{1}, +1 SWITCH r_{E_{1}}, l_{true,1}, l_{false,1} JUMP  l_{error ,1} ⋮ ly_{n} :  WAIT  e_{n}, +2 trigger_{n} : WAIT  e_{n}, +1 SWITCH r_{E_{n}}, l_{true,n}, l_{false,n} JUMP  l_{error ,n} C_{E_{2}} C_{ST_{E_{2}}} C_{E_{3}} C_{ST_{E_{3}}} C_{ST error} result_{E_{2}} : COPYI r, r_{E_{2}}, +1 JUMP  result result_{E_{3}} : COPYI r, r_{E_{3}}, +1 JUMP  result signal′_{E_{2}} : JUMP  signal signal′_{E_{3}} : JUMP  signal )

B.3. VALUATION FUNCTION 173 where

fV_{1} = freeVars(E_{1})
fV_{2} = freeVars(E_{2})
fV_{3} = freeVars(E_{3})

trigger_{E_{1}}, trigger_{E_{23}}, head, trigger_{1}, …, trigger_{n}, lST true, ltrue, l_{true,1}, …, l_{true,n}, lST false, lfalse, l_{false,1}, …, l_{false,n}, l_{error ,0}, l_{error ,1}, …, l_{error ,n}, signal′_{E_{2}}, signal′_{E_{3}}, result, signal, LabelStateLFalse_{n} = NewLabel(LabelState)^{3n+11}

e, e_{0}, e_{1}, …, e_{n}, EventStateE_{n} = NewEvent(EventStateE)^{n+2}

(r, RegStateR) = NewReg(RegState)

— Forking common free variables —

[x_{1}, …, x_{m}] = fV_{1} ∩ (fV_{2} ∪ fV_{3}) (rx_{1}, lx_{1}, fx_{1}) = (lookupRegister env x_{1} , lookupLabel env x_{1} , lookupFlag env x_{1})

⋮

(rx_{m}, lx_{m}, fx_{m}) = (lookupRegister env x_{m} , lookupLabel env x_{m} , lookupFlag env x_{m}) (C_{1,1}, [l_{1}, l′_{1}], LabelState_{1,1}) = \overline{Split}(lx_{1}, rx_{1}, 2, fx_{1}, LabelStateLFalse_{n})

⋮

(C_{1,m}, [l_{m}, l′_{m}], LabelState_{1,m}) = \overline{Split}(lx_{m}, rx_{m}, 2, fx_{m}, LabelState_{1,m−1}) env_{E_{1}} = BindVar (x_{1} l_{1}) ∘ ⋯ ∘ (x_{m} l_{m}) env env′_{E_{1}} = BindVar (x_{1} l′_{1}) ∘ ⋯ ∘ (x_{m} l′_{m}) env [y_{1}, …, y_{n}] = fV_{2} ∪ fV_{3}

ly_{1} = lookupLabel env y_{1}

⋮

ly_{n} = lookupLabel env y_{n}

I_{2} = {j | y_{j} ∈ fV_{2}} I′_{2} = {1, …, n} − I_{2} I_{3} = {j | y_{j} ∈ fV_{3}} I′_{3} = {1, …, n} − I_{3}

env_{E_{2}} = ({◯ \atop j ∈ I_{2}} BindVar (y_{j} l_{true,j})) env′_{E_{1}} st_{E_{2}} = [ l_{true,j} | j ∈ I′_{2} ]

env_{E_{3}} = ({◯ \atop j ∈ I_{3}} BindVar (y_{j} l_{false,j})) env′_{E_{1}} st_{E_{3}} = [ l_{false,j} | j ∈ I′_{3} ]

(C_{E_{1}}, r_{E_{1}}, (result_{E_{1}}, signal_{E_{1}}), (RegState′, LabelState′, EventState′)) = E [E_{1}] trigger_{E_{1}} (RegStateR, LabelStateL_{False,n}, EventState_{e_{n}}) env_{E_{1}}

(C_{E_{2}}, r_{E_{2}}, (result_{E_{2}}, signal_{E_{2}}), (RegState′′, LabelState′′, EventState′′)) = E [E_{2}] ltrue (RegState′, LabelState′, EventState′) env_{E_{2}}

(C_{E_{3}}, r_{E_{3}}, (result_{E_{3}}, signal_{E_{3}}), (RegState′′′, LabelState′′′, EventState′′′)) = E [E_{3}] lfalse (RegState′′, LabelState′′, EventState′′) env_{E_{3}}

— Splitting and Synchronizing —

(Csplit , [head, trigger_{0}, trigger_{1}, …, trigger_{n}], LabelState′′′′) = Split(result_{E_{1}}, n + 2, LabelState_{ST_{E_{3}}} )

(C_{ST_{E_{2}}} , LabelState_{ST_{E_{2}}} , EventState_{ST_{E_{2}}} , signal′_{E_{2}}) = SignalTree(lST true : signal_{E_{2}} : st_{E_{2}}, |I_{2}| + 2, LabelState′′, EventState′′)

(C_{ST_{E_{3}}} , LabelState_{ST_{E_{3}}} , EventState_{ST_{E_{3}}} , signal′_{E_{3}}) = SignalTree(lST false : signal_{E_{3}} : st_{E_{3}}, |I_{3}| + 2, LabelState′′′, EventState′′′) (C_{ST error} , LabelState_{ST error} , EventState_{ST error} , signal) = SignalTree([l_{error ,0}, l_{error ,1}, …, l_{error ,n}], n + 1, LabelState′′′′, EventState_{ST_{E_{3}}} )

Local Definitions

E [ letN D E ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState_{E}, LabelState′, EventState′))

where

C = ( trigger : SPLIT trigger_{D}, trigger_{E} C_{1} ⋮ C_{m} C_{D} C_{E} CST )

trigger_{D}, trigger_{E}, send, LabelState_{0} = NewLabel(LabelState)^{3}

fV_{1} = freeVars(D) fV_{2} = freeVars(E)

— Forking common free variables —

[x_{1}, …, x_{m}] = fV_{1} ∩ fV_{2}

(r_{1}, l_{1}, f_{1}) = (lookupRegister env x_{1} , lookupLabel env x_{1} , lookupFlag env x_{1})

⋮

(r_{m}, l_{m}, f_{m}) = (lookupRegister env x_{m} , lookupLabel env x_{m} , lookupFlag env x_{m})

(C_{1}, [l_{1,D}, l_{1,E}], LabelState_{1}) = \overline{Split}(l_{1}, r_{1}, 2, f_{1}, LabelState_{0})

⋮

(C_{m}, [l_{m,D}, l_{m,E}], LabelState_{m}) = \overline{Split}(l_{m}, r_{m}, 2, f_{m}, LabelState_{m−1})

env_{D} = BindVar(x_{1} l_{1,D}) ∘ ⋯ ∘ BindVar(x_{m} l_{m,D}) env

env_{E} = BindVar(x_{1} l_{1,E}) ∘ ⋯ ∘ BindVar(x_{m} l_{m,E}) env′_{D}

— End of forking common free variables —

(C_{D}, signal_{D}, (RegisterState_{D}, LabelState_{D}, EventState_{D}), env′_{D}) = D [D] trigger_{D} (RegisterState′, LabelState_{m}, EventState) fV_{2} env_{D}

(C_{E}, r, (result, signal), (RegisterState′, LabelState_{E}, EventState_{E})) = E [E] trigger_{E} (RegisterState_{D}, LabelState_{D}, EventState_{D}) env_{E}

— Splitting and Synchronizing —

(CST , LabelState′, EventState′, signal) =

SignalTree([signal_{D}, signal_{E}], 2, LabelState_{E}, EventState_{E})

— End of Splitting and Synchronizing —

Empty Tuple

E [ nilN ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState_{E_{n}}, LabelState′, EventState′))

where

C = ( \begin{matrix}trigger : & CONST & r, +2 \\ & 0 \\ & JUMP & result, signal \end{matrix} )

(r, RegState′) = NewReg(RegState)

result, signal, LabelState′ = NewLabel(LabelState)^{2}

Tuple Construction

E [ composeN E_{1} … E_{n} ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState_{E_{n}}, LabelState′, EventState′))

C = ( trigger :  ALLOC n, +1 COPYP r, r, +1 SPLIT  result, lsplit C_{splitP} C_{1,1} ⋮ C_{1,m_{1}} l_{split_{E_{1}}} :  COPYP r, r, +1 SPLIT  trigger_{E_{1}}, gc_{E_{1}} C_{E_{1}} lsplit_{E_{1}}^{′} :  WAIT  e_{E_{1}}, +2 result_{E_{1}} : WAIT  e_{E_{1}}, +1 gc_{E_{1}} :  WAIT  e′_{E_{1}}, result′_{E_{1}} C′_{E_{1}} JUMP  end_{1} ⋮ C_{n−1,1} ⋮ C_{n−1,m_{n−1}} l_{split_{E_{n−1}}} : COPYP r, r, +1 SPLIT  trigger_{E_{n−1}}, gc_{E_{n−1}} C_{E_{n−1}} lsplit_{E_{n−1}}^{′} : WAIT  e_{E_{n−1}}, +2 result_{E_{n−1}} : WAIT  e_{E_{n−1}}, +1 gc_{E_{n−1}} :  WAIT  e′_{E_{n−1}}, result′_{E_{n−1}} C′_{E_{n−1}} JUMP  end_{n−1} C_{E_{n}} lsplit_{En}^{′} :  WAIT  e_{E_{n}}, +2 result_{E_{n}} : WAIT  e_{E_{n}}, +1 gc_{E_{n}} :  WAIT  e′_{E_{n}}, result′_{E_{n}} C′_{E_{n}} JUMP  end_{n} CST )

C′_{E_{1}} = \{ \begin{matrix}( result′_{E_{1}} : WRITEI r_{1}, r, 0 ) & if flag_{1} = int \\ \binom{result′_{E_{1}} : COPYP r_{1}, r_{1}, +1}{WRITEP r_{1}, r, 0} & otherwise \end{matrix}

⋮

C′_{E_{n−1}} = \{ \begin{matrix}( result′_{E_{n−1}} : WRITEI r_{n−1}, r, n − 2 ) & if flag_{n−1} = int \\ \binom{result′_{E_{n−1}} : COPYP r_{n−1}, r_{n−1}, +1}{WRITEP r_{n−1}, r, n − 2} & otherwise \end{matrix}

C′_{E_{n}} = \{ \begin{matrix}( result′_{E_{n}} : WRITEI r_{n}, r, n − 1 ) & if flag_{n} = int \\ \binom{result′_{E_{n}} : COPYP r_{n}, r_{n}, +1}{WRITEP r_{n}, r, n − 1} & otherwise \end{matrix}

fV_{1} = freeVars(E_{1})
⋮
fV_{n} = freeVars(E_{n})
flag_{1} = getFlag(E_{1})
⋮
flag_{n} = getFlag(E_{n})

(r, RegStateR) = NewReg(RegState) result, signal, LabelStateRS = NewLabel(LabelState)^{2} lsplit , result′_{E_{1}}, …, result′_{E_{n}}, gc_{E_{1}}, …, gc_{E_{1}}, trigger_{E_{1}}, …, trigger_{E_{n−1}}, end_{1}, …, end_{n}, LabelStateTrigger = NewLabel(LabelStateRS)^{4n} e_{E_{1}}, …, e_{E_{n}}, e′_{E_{1}}, …, e′_{E_{n}}, EventState′ = NewEvent(EventState)^{2n} [y_{1,1}, …, y_{1,m_{1}}] = fV_{1} ∩ (fV_{2} ∪ ⋯ ∪ fV_{n}) (ry_{1,1}, ly_{1,1}, fy_{1,1}) = (lookupRegister env y_{1,1} , lookupLabel env y_{1,1} , lookupFlag env y_{1,1})

⋮

(ry_{1,m_{1}}, ly_{1,m_{1}}, fy_{1,m_{1}}) = (lookupRegister env y_{1,m_{1}} , lookupLabel env y_{1,m_{1}} , lookupFlag env y_{1,m_{1}})

(C_{1,1}, [l_{1,1}, l′_{1,1}], 2, LabelState_{1,1}) = \overline{Split}(ly_{1,1}, ry_{1,1}, 2, fy_{1,1}, LabelStatesignal )

⋮

(C_{1,m_{1}}, [l_{1,m_{1}}, l′_{1,m_{1}}], 2, LabelState_{1,m_{1}}) = \overline{Split}(ly_{1,m_{1}}, ry_{1,m_{1}}, 2, fy_{1,m_{1}}, LabelState_{1,m_{1}−1})

env_{E_{1}} = BindVar (y_{1,1} l_{1,1}) ∘ ⋯ ∘ (y_{1,m_{1}} l_{1,m_{1}}) env env′_{E_{1}} = BindVar (y_{1,1} l′_{1,1}) ∘ ⋯ ∘ (y_{1,m_{1}} l′_{1,m_{1}}) env

⋮

[y_{n−1,1}, …, y_{n−1,m_{n−1}}] = fV_{n−1} ∩ fV_{n} (ry_{n−1,1}, ly_{n−1,1}, fy_{n−1,1}) = (lookupRegister env′_{E_{n−2}} y_{n−1,1} , lookupLabel env′_{E_{n−2}} y_{n−1,1} , lookupFlag env′_{E_{n−2}} y_{n−1,1})

⋮

(ry_{n−1,m_{n−1}}, ly_{n−1,m_{n−1}}, ly_{n−1,m_{n−1}}) = (lookupRegister env′_{E_{n−2}} y_{n−1,m_{n−1}} , lookupLabel env′_{E_{n−2}} y_{n−1,m_{n−1}} , lookupFlag env′_{E_{n−2}} y_{n−1,m_{n−1}})

(C_{n−1,1}, [l_{n−1,1}, l′_{n−1,1}], LabelState_{n−1,1}) = \overline{Split}(ly_{n−1,1}, ry_{n−1,1}, 2, fy_{n−1,1}, LabelState_{n−2,m_{n−2}})

⋮

(C_{n−1,m_{n−1}}, [l_{n−1,m_{n−1}}, l′_{n−1,m_{n−1}}], LabelState_{n−1,m_{n−1}}) = \overline{Split}(ly_{n−1,m_{n−1}}, ry_{n−1,m_{n−1}}, 2, fy_{n−1,m_{n−1}}, LabelState_{n−1,m_{n−1}−1})

env_{E_{n−1}} = BindVar (y_{n−1,1} l_{n−1,1}) ∘ ⋯ ∘ (y_{n−1,m_{n−1}} l_{n−1,m_{n−1}}) env′_{E_{n−2}} env′_{E_{n−1}} = BindVar (y_{n−1,1} l′_{n−1,1}) ∘ ⋯ ∘ (y_{n−1,m_{n−1}} l′_{n−1,m_{n−1}}) env′_{E_{n−2}} 180 APPENDIX B. DENOTATIONAL SEMANTICS OF THE CODE GENERATION

(C_{E_{1}}, r_{E_{1}}, (result_{E_{1}}, signal_{E_{1}}), (RegState_{E_{1}}, LabelState_{E_{1}}, EventState_{E_{1}})) = E [E_{1}] l_{trigger_{E_{1}}} (RegStateresult , LabelStatesignal , EventState_{e_{n}}) env_{E_{1}}

⋮

(C_{E_{n−1}}, r_{E_{n−1}}, (result_{E_{n−1}}, signal_{E_{n−1}}), (RegState_{E_{n−1}}, LabelState_{E_{n−1}}, EventState_{E_{n−1}})) = E [E_{n−1}] l_{split_{E_{n−1}}} (RegState_{E_{n−2}}, LabelState_{E_{n−2}}, EventState_{E_{n−2}}) env_{E_{n−1}} (C_{E_{n}}, r_{E_{n}}, (result_{E_{n}}, signal_{E_{n}}), (RegState_{E_{n}}, LabelState_{E_{n}}, EventState_{E_{n}})) = E [E_{n}] l_{split_{En}} (RegState_{E_{n−1}}, LabelState_{E_{n−1}}, EventState_{E_{n−1}}) env′_{E_{n−1}}

— Splitting and Synchronizing —

(Csplit , LabelState′′, EventState′′, lsplit ) = Split([l_{split_{E_{1}}} , …, l_{split_{En}} , lsplit_{E_{1}}^{′} , …, lsplit_{En}^{′}]2n, LabelState_{l_{n}}, EventState_{E_{n}}) (CST , LabelState′, EventState′, signal) = SignalTree([signal_{E_{1}}, …, signal_{E_{n}}, end_{1}, …, end_{n}], 2n, LabelState′′, EventState′′)

— End of Splitting and Synchronizing —

Selections

E [ selectN (numberN n) E ] trigger (RegState, LabelState, EventState) env =

(C, r, (result, signal), (RegState′, LabelState′, EventState′)) where C = ( \begin{matrix}& C_{E} \\ result_{E} : & READ & n, r_{E}, +1 \\ & COPYI & r, r, result \end{matrix} )

IF getF lag(E) ≠ pointer THEN Error END (r, RegStateR) = NewReg(RegState) (e, EventStateE) = NewEvent(EventState) result, signal, LabelStateSignal = NewLabel(LabelState_{m})^{2}

(C_{E}, r_{E}, (result_{E}, signal), (RegState′, LabelState′, EventState′)) = E [E] trigger (RegStateR, LabelStateSignal, EventStateE) env

Advanced Constructs

Higher Order Functions (Function Call)

E [ applyN E E_{1} … E_{n} ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState_{E_{n}}, LabelState′, EventState′))

where

C = ( Csplit C_{0,1} ⋮ C_{0,m_{0}} C_{E} result_{E} :  COPYP r_{E}, r_{E}, +1 READ  0, r_{E}, +1 APPLY r_{1}, +1 SUBMIT r_{E}, ract , send COPYI r, r, result Csend C_{1,1} ⋮ C_{1,m_{1}} C_{E_{1}} result_{E_{1}} : WAIT  e_{1}, +2 send_{1} :  WAIT  e_{1}, +1 SEND  r_{E_{1}}, ract , 1 JUMP  arg_{1} ⋮ C_{n−1,1} ⋮ C_{n−1,m_{n−1}} C_{E_{n−1}} result_{E_{n−1}} : WAIT  e_{n−1}, +2 send_{n−1} : WAIT  e_{n−1}, +1 SEND  r_{E_{n−1}}, ract , n − 1 JUMP  arg_{n−1} C_{E_{n}} result_{E_{n}} : WAIT  e_{n}, +2 send_{n} :  WAIT  e_{n}, +1 SEND  r_{E_{n}}, ract , n JUMP  arg_{n} CST )

B.3. VALUATION FUNCTION 183 fV = freeVars(E) fV_{1} = freeVars(E_{1})

⋮

fV_{n} = freeVars(E_{n}) r_{1}, ract , r, RegStateresult = NewReg(RegState)^{3}

arg_{1}, …, arg_{n}, send, result, signal, LabelStatesignal = NewLabel(LabelState)^{n+3}

e_{1}, …, e_{n}, EventStateE = NewEvent(EventState)^{n} — Forking common free variables —

[y_{0,1}, …, y_{0,m_{0}}] = fV ∩ (fV_{1} ∪ ⋯ ∪ fV_{n}) (ry_{0,1}, ly_{0,1}, fy_{0,1}) = (lookupRegister env y_{0,1} , lookupLabel env y_{0,1} , lookupFlag env y_{0,1})

⋮

(ry_{0,m_{0}}, ly_{0,m_{0}}, fy_{0,m_{0}}) = (lookupRegister env y_{0,m_{0}} , lookupLabel env y_{0,m_{0}} , lookupFlag env y_{0,m_{0}}) (C_{0,1}, [l_{0,1}, l′_{0,1}], LabelState_{0,1}) = \overline{Split}(ly_{0,1}, ry_{0,1}, 2, fy_{0,1}, LabelStatesignal )

⋮

(C_{0,m_{0}}, [l_{0,m_{0}}, l′_{0,m_{0}}], LabelState_{0,m_{0}}) = \overline{Split}(ly_{0,m_{0}}, ry_{0,m_{0}}, 2, fy_{0,m_{0}}, LabelState_{0,m_{0}−1}) env_{E} = BindVar (y_{0,1} l_{0,1}) ∘ ⋯ ∘ (y_{0,m_{0}} l_{0,m_{0}}) env env′_{E} = BindVar (y_{0,1} l′_{0,1}) ∘ ⋯ ∘ (y_{0,m_{0}} l′_{0,m_{0}}) env [y_{1,1}, …, y_{1,m_{1}}] = fV_{1} ∩ (fV_{2} ∪ ⋯ ∪ fV_{n}) (ry_{1,1}, ly_{1,1}, fy_{1,1}) = (lookupRegister env y_{1,1} , lookupLabel env y_{1,1} , lookupFlag env y_{1,1})

⋮

(ry_{1,m_{1}}, ly_{1,m_{1}}, fy_{1,m_{1}}) = (lookupRegister env y_{1,m_{1}} , lookupLabel env y_{1,m_{1}} , lookupFlag env y_{1,m_{1}})

(C_{1,1}, [l_{1,1}, l′_{1,1}], LabelState_{1,1}) = \overline{Split}(ly_{1,1}, ry_{1,1}, 2, fy_{1,1}, LabelStatesignal )

⋮

(C_{1,m_{1}}, [l_{1,m_{1}}, l′_{1,m_{1}}], LabelState_{1,m_{1}}) = \overline{Split}(ly_{1,m_{1}}, ry_{1,m_{1}}, 2, fy_{1,m_{1}}, LabelState_{1,m_{1}−1})

env_{E_{1}} = BindVar (y_{1,1} l_{1,1}) ∘ ⋯ ∘ (y_{1,m_{1}} l_{1,m_{1}}) env′_{E} env′_{E_{1}} = BindVar (y_{1,1} l′_{1,1}) ∘ ⋯ ∘ (y_{1,m_{1}} l′_{1,m_{1}}) env′_{E}

⋮

[y_{n−1,1}, …, y_{n−1,m_{n−1}}] = fV_{n−1} ∩ fV_{n} (ry_{n−1,1}, ly_{n−1,1}, fy_{n−1,1}) = (lookupRegister env′_{E_{n−2}} y_{n−1,1} , lookupLabel env′_{E_{n−2}} y_{n−1,1} , lookupFlag env′_{E_{n−2}} y_{n−1,1})

⋮

(ry_{n−1,m_{n−1}}, ly_{n−1,m_{n−1}}, fy_{n−1,m_{n−1}}) = (lookupRegister env′_{E_{n−2}} y_{n−1,m_{n−1}} , lookupLabel env′_{E_{n−2}} y_{n−1,m_{n−1}} , lookupFlag env′_{E_{n−2}} y_{n−1,m_{n−1}})

(C_{n−1,1}, [l_{n−1,1}, l′_{n−1,1}], LabelState_{n−1,1}) = \overline{Split}(ly_{n−1,1}, ry_{n−1,1}, 2, fy_{n−1,1}, LabelState_{n−2,m_{n−2}})

⋮

(C_{n−1,m_{n−1}}, [l_{n−1,m_{n−1}}, l′_{n−1,m_{n−1}}], LabelState_{n−1,m_{n−1}}) = \overline{Split}(ly_{n−1,m_{n−1}}, ry_{n−1,m_{n−1}}, 2, fy_{n−1,m_{n−1}}, LabelState_{n−1,m_{n−1}−1})

env_{E_{n−1}} = BindVar (y_{n−1,1} l_{n−1,1}) ∘ ⋯ ∘ (y_{n−1,m_{n−1}} l_{n−1,m_{n−1}}) env′_{E_{n−2}} env′_{E_{n−1}} = BindVar (y_{n−1,1} l′_{n−1,1}) ∘ ⋯ ∘ (y_{n−1,m_{n−1}} l′_{n−1,m_{n−1}}) env′_{E_{n−2}} (C_{E}, r_{E}, (result_{E}, signal_{E}), (RegState_{E}, LabelState_{E}, EventState_{E})) = E [E] l_{split_{0}} (RegStateresult , LabelStatesignal , EventStateE) env_{E}

(C_{E_{1}}, r_{E_{1}}, (result_{E_{1}}, signal_{E_{1}}), (RegState_{E_{1}}, LabelState_{E_{1}}, EventState_{E_{1}})) = E [E_{1}] l_{split_{1}} (RegStateresult , LabelStatesignal , EventState_{e_{n}}) env_{E_{1}}

⋮

(C_{E_{n−1}}, r_{E_{n−1}}, (result_{E_{n−1}}, signal_{E_{n−1}}), (RegState_{E_{n−1}}, LabelState_{E_{n−1}}, EventState_{E_{n−1}})) = E [E_{n−1}] l_{split_{n−1}} (RegState_{E_{n−2}}, LabelState_{E_{n−2}}, EventState_{E_{n−2}}) env_{E_{n−1}}

(C_{E_{n}}, r_{E_{n}}, (result_{E_{n}}, signal_{E_{n}}), (RegState_{E_{n}}, LabelState_{E_{n}}, EventState_{E_{n}})) = E [E_{n}] l_{split_{n}} (RegState_{E_{n−1}}, LabelState_{E_{n−1}}, EventState_{E_{n−1}}) env′_{E_{n−1}}

— Splitting and Synchronizing —

(Csplit , [l_{split_{0}}, …, l_{split_{n}}], LabelStatesplit ) = Split(trigger, n + 1, LabelState_{E_{n}})

(Csend , [send_{1}, …, send_{n}], LabelStatesend ) = SplitP(send, ract , n, LabelStatesplit )

(CST , LabelState′, EventState′, signal) =

SignalTree([arg_{1}, …, arg_{n}, signal_{E}, signal_{E_{1}}, …, signal_{E_{n}}], 2n + 1, LabelStatesend , EventState_{E_{n}})

— End of Splitting and Synchronizing —

Curried Expressions

E [ curryN (idN f) (idN v_{1}) … (idN v_{n}) ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState′, LabelState′, EventState′))

C = ( trigger : ALLOC n + 1, +1 COPYP r, r, +1 SPLIT result, split′ C_{splitP} l_{split_{0}} : CONST r_{0}, +2 < f, i > WRITEI r_{0}, r, 0 JUMP  end l_{split_{1}} : COPYP r, r, trigger_{1} C_{v_{1}} Cv_{1}^{′} JUMP  end_{1} ⋮ l_{split_{n−1}} : COPYP r, r, trigger_{n−1} C_{v_{n−1}} Cv_{n−1}^{′} JUMP  end_{n−1} C_{v_{n}} Cv_{n}^{′} JUMP  end_{n} CST )

C′_{v_{1}} = \{ \begin{matrix}( result_{1} : WRITEI r_{1}, r, 1 ) & if flag_{1} = int \\ \binom{result_{1} : COPYP r_{1}, r_{1}, +1}{WRITEP r_{1}, r, 1} & otherwise \end{matrix}

⋮

C′_{v_{n−1}} = \{ \begin{matrix}( result_{n−1} : WRITEI r_{n−1}, r, n − 1 ) & if flag_{n−1} = int \\ \binom{result_{n−1} : COPYP r_{n−1}, r_{n−1}, +1}{WRITEP r_{n−1}, r, n − 1} & otherwise \end{matrix}

C′_{v_{n}} = \{ \begin{matrix}( result_{n} : WRITEI r_{n}, r, n ) & if flag_{n} = int \\ \binom{result_{n} : COPYP r_{n}, r_{n}, +1}{WRITEP r_{n}, r, n} & otherwise \end{matrix}

flag_{1} = flag(v_{1})
⋮
flag_{n} = flag(v_{n})

r, r_{0}, RegState_{r} = NewReg(RegState)^{2}

split′, result, signal, trigger_{1}, …, trigger_{n−1}, end, end_{1}, …, end_{n}, LabelStatesignal = NewLabel(LabelState)^{2n+3}

i = arity(f) − (n − 1)

(C_{v_{1}}, r_{v_{1}}, (result_{v_{1}}, signal_{v_{1}}), (RegState_{v_{1}}, LabelState_{v_{1}}, EventState_{v_{1}})) = E [v_{1}] trigger_{1} (RegStateE , LabelStateE , EventState_{E}) env

⋮

(C_{v_{n−1}}, r_{v_{n−1}}, (result_{v_{n−1}}, signal_{v_{n−1}}), (RegState_{v_{n−1}}, LabelState_{v_{n−1}}, EventState_{v_{n−1}})) = E [v_{n−1}] trigger_{n−1} (RegState_{v_{n−2}}, LabelState_{v_{n−2}}, EventState_{v_{n−2}}) env

(C_{v_{n}}, r_{v_{n}}, (result_{v_{n}}, signal_{v_{n}}), (RegState_{v_{n}}, LabelState_{v_{n}}, EventState_{v_{n}})) = E [v_{n}] l_{split_{n}} (RegState_{v_{n−1}}, LabelState_{v_{n−1}}, EventState_{v_{n−1}}) env

— Splitting and Synchronizing —

(CsplitP , [l_{split_{0}}, l_{split_{1}}, …, l_{split_{n}}], LabelState_{trigger_{0}}) = SplitP(split′, r, n + 1, LabelState_{v_{n}})

(CST , LabelState′, EventState′, signal) =

SignalTree([signal_{v_{1}}, …, signal_{v_{n}}, end_{1}, …, end_{n}, end], 2n + 1, LabelState_{split_{n}}, EventState_{v_{n}})

B.3.2 Valuation Function of Definition

D : Definition → Label → State → [Variable] → Environment → DefResult

Simple Definitions

D [ defN (idN x) E ] trigger

(RegState, LabelState, EventState) vl env =

(C, signal, (RegState′, LabelState′, EventState′), env′)

where

C = \binom{C_{E}}{result_{E} : JUMP result}

f = flag(E)

result, signal, LabelStatesignal = NewLabel(LabelState)^{2}

env′ = env ∪ {x → (r_{E}, result, f)}

(C_{E}, r_{E}, (result_{E}, signal), (RegState′, LabelState′, EventState′)) = E [E] trigger (RegState_{r}, LabelStatesignal , EventState) env′

Function Definitions

    D [ defN (idN x) (lambdaN (lheadN (idN x_{1}) . . . (idN x_{n}) ) (lbodyN E)) ] trigger

(RegState, LabelState, EventState) vl env =

(C, signal, (RegState′, LabelStateE, EventStateE), env′)

where

C = ( trigger : JUMP  start COPYI  r_{1}, r_{1}, l_{1} ⋮ COPYI  r_{n}, r_{n}, l_{n} C_{E} result_{E} : RETURN  r, +1 signal_{E} : WAIT  e, +1 TERMINATE )

f_{1} = flag(x_{1})
⋮
f_{n} = flag(x_{n})
f = flag(E)

start, l_{1}, …, l_{n}, result, LabelStateLn = NewLabel(LabelState)^{n+2} r, r_{1}, …, r_{n}, RegStateRn = NewReg(RegState)^{n+1} e, EventStateE = NewEvent(EventState)

envEin = env ∪ {x_{1} → (r_{1}, l_{1}, f_{1}), …, x_{n} → (r_{n}, l_{n}, f_{n})}

∪ {x → (r, result, f)}

env′ = env ∪ {x → (r, result, f)} signal = EmptyLabel

(C_{E}, r, (result_{E}, signal_{E}), (RegState_{E}, LabelState_{E}, EventState_{E})) = E [E] start (RegStateRn, LabelStateLn, EventStateE) envEin

Definition Sequences

D [ defseqN D_{1}, …, D_{n} ] trigger (RegState, LabelState, EventState) fV env = (C, signal, (RegState′, LabelState′, EventState′), env_{n})

C = ( Csplit C_{1,1} ⋮ C_{1,m_{1}} C_{D_{1}} ⋮ C_{n−1,1} ⋮ C_{n−1,m_{n−1}} C_{D_{n−1}} C_{D_{n}} CST )

fV_{1} = freeVars(D_{1})

⋮

fV_{n} = freeVars(D_{n})

[y_{1,1}, …, y_{1,m_{1}}] = fV_{1} ∩ (fV_{2} ∪ ⋯ ∪ fV_{n} ∪ fV ) (ry_{1,1}, ly_{1,1}, fy_{1,1}) = (lookupRegister env y_{1,1} , lookupLabel env y_{1,1} , lookupFlag env y_{1,1})

⋮

(ry_{1,m_{1}}, ly_{1,m_{1}}, fy_{1,m_{1}}) = (lookupRegister env y_{1,m_{1}} , lookupLabel env y_{1,m_{1}} , lookupFlag env y_{1,m_{1}})

(C_{1,1}, [l_{1,1}, l′_{1,1}], LabelState_{1,1}) = \overline{Split}(ly_{1,1}, ry_{1,1}, 2, fy_{1,1}, LabelStatesignal )

⋮

(C_{1,m_{1}}, [l_{1,m_{1}}, l′_{1,m_{1}}], LabelState_{1,m_{1}}) = \overline{Split}(ly_{1,m_{1}}, ry_{1,m_{1}}, 2, fy_{1,m_{1}}, LabelState_{1,m_{1}−1})

env_{D_{1}} = BindVar (y_{1,1} l_{1,1}) ∘ ⋯ ∘ (y_{1,m_{1}} l_{1,m_{1}}) env

env′′_{D_{1}} = BindVar (y_{1,1} l′_{1,1}) ∘ ⋯ ∘ (y_{1,m_{1}} l′_{1,m_{1}}) env′_{D_{1}}

⋮

[y_{n−1,1}, …, y_{n−1,m_{n−1}}] = fV_{n−1} ∩ (fV_{n} ∪ fV ) (ry_{n−1,1}, ly_{n−1,1}, fy_{n−1,1}) = (lookupRegister env′_{E_{n−2}} y_{n−1,1} , lookupLabel env′_{E_{n−2}} y_{n−1,1} , lookupFlag env′_{E_{n−2}} y_{n−1,1})

⋮

(ry_{n−1,m_{n−1}}, ly_{n−1,m_{n−1}}, fy_{n−1,m_{n−1}}) = (lookupRegister env′_{E_{n−2}} y_{n−1,m_{n−1}} , lookupLabel env′_{E_{n−2}} y_{n−1,m_{n−1}} , lookupFlag env′_{E_{n−2}} y_{n−1,m_{n−1}})

(C_{n−1,1}, [l_{n−1,1}, l′_{n−1,1}], LabelState_{n−1,1}) = \overline{Split}(ly_{n−1,1}, ry_{n−1,1}, 2, fy_{n−1,1}, LabelState_{n−2,m_{n−2}})

⋮

(C_{n−1,m_{n−1}}, [l_{n−1,m_{n−1}}, l′_{n−1,m_{n−1}}], LabelState_{n−1,m_{n−1}}) = \overline{Split}(ly_{n−1,m_{n−1}}, ry_{n−1,m_{n−1}}, 2, fy_{n−1,m_{n−1}}, LabelState_{n−1,m_{n−1}−1})

env_{D_{n−1}} = BindVar (y_{n−1,1} l_{n−1,1}) ∘ ⋯ ∘ (y_{n−1,m_{n−1}} l_{n−1,m_{n−1}}) env′′_{D_{n−2}}

env′′_{D_{n−1}} = BindVar (y_{n−1,1} l′_{n−1,1}) ∘ ⋯ ∘ (y_{n−1,m_{n−1}} l′_{n−1,m_{n−1}}) env′_{D_{n−1}} [y_{n,1}, …, y_{n,m_{n}}] = fV_{n} ∩ fV (ry_{n,1}, ly_{n,1}, fy_{n,1}) = (lookupRegister env′_{E_{n−2}} y_{n,1} , lookupLabel env′_{E_{n−2}} y_{n,1} , lookupFlag env′_{E_{n−2}} y_{n,1})

⋮

(ry_{n,m_{n}}, ly_{n,m_{n}}, fy_{n,m_{n}}) = (lookupRegister env′_{E_{n−2}} y_{n,m_{n}} , lookupLabel env′_{E_{n−2}} y_{n,m_{n}} , lookupFlag env′_{E_{n−2}} y_{n,m_{n}})

(C_{n,1}, [l_{n,1}, l′_{n,1}], LabelState_{n,1}) = \overline{Split}(ly_{n,1}, ry_{n,1}, 2, fy_{n,1}, LabelState_{n−2,m_{n−2}})

⋮

(C_{n,m_{n}}, [l_{n,m_{n}}, l′_{n,m_{n}}], LabelState_{n,m_{n}}) = \overline{Split}(ly_{n,m_{n}}, ry_{n,m_{n}}, 2, fy_{n,m_{n}}, LabelState_{n,m_{n}−1})

env_{D_{n}} = BindVar (y_{n,1} l_{n,1}) ∘ ⋯ ∘ (y_{n,m_{n}} l_{n,m_{n}}) env′′_{D_{n−2}}

— End of forking common free variables —

(C_{D_{1}}, signal_{1}, (RegState_{1}, LabelState_{1}, EventState_{1}), env′_{D_{1}}) = D [D_{1}] l_{split_{1}} (RegStateresult , LabelStateresult , EventState) env_{D_{1}}

⋮

(C_{D_{n−1}}, signal_{n−1}, (RegState_{n−1}, LabelState_{n−1}, EventState_{n−1}), env′_{D_{n−1}}) = D [D_{n−1}] l_{split_{n−1}} (RegState_{n−2}, LabelState_{n−2}, EventState_{n−2}) env_{D_{n−1}}

(C_{D_{n}}, signal, (RegState′, LabelState_{n}, EventState_{n}), env_{n}) = D [D_{n}] l_{split_{n}} (RegState_{n−1}, LabelState_{n−1}, EventState_{n−1}) env_{D_{n}}

— Splitting and Synchronizing —

(Csplit , [l_{split_{1}}, …, l_{split_{n}}], LabelState′) = Split(trigger, n, LabelState_{n})

IFsignal_{1} ≠ EmptyLabel

THEN (CST , LabelState′, EventState′, signal) =

SignalTree([signal_{1}, …, signal_{n}], n, LabelState_{n}, EventState_{n}) ELSE (CST , LabelState′, EventState′, signal) =

((), LabelState_{n}, EventState_{n}, EmptyLabel)

END

B.3.3 Valuation Function of Program

P : Program → Result

                           P [ programN DS ] = C

where

(C, signal′, (RegState′, LabelState′, EventState′), env′) = D [DS] StartLabel (InitReg, InitLabel, InitEvent) InitEnv

B.4 Inline Code for Predefined Functions

Absolute Value

E [ callN (idN ABS) E ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState′, LabelState′, EventState′))

where

C = ( \begin{matrix}& C_{E} \\ result_{E} : & COPYI & r, r_{E}, +1 \\ & LESSC & 0, r_{E}, +1 \\ & SWITCH & r_{E}, ltrue, lfalse \\ & COPYI & r, r_{E}, lfalse \\ ltrue : & NEG & r, +1 \\ lfalse : & SPLIT & result, end \\ & CST \end{matrix} )

r, RegStateR = NewReg(RegState)

result, signal, end, ltrue, lfalse, LabelStateL = NewLabel(LabelState)^{5}

(C_{E}, r_{E}, (result_{E}, signal_{E}), (RegState′, LabelState_{E}, EventState_{E})) = E [E] trigger (RegStateR, LabelStateL, EventState) env Test Whether a Number Is Odd

E [ callN (idN ODD) E ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState′, LabelState′, EventState′))

where

C = ( \begin{matrix}& C_{E} \\ result_{E} : & COPYI & r, r_{E}, +1 \\ & MODC & 2, r, +1 \\ & EQUALC & 0, r, +1 \\ & NOT & r, +1 \\ & SPLIT & result, end \\ & CST \end{matrix} )

r, RegStateR = NewReg(RegState)

result, signal, end, LabelStateL = NewLabel(LabelState)^{3}

(C_{E}, r_{E}, (result_{E}, signal_{E}), (RegState′, LabelState_{E}, EventState_{E})) = E [E] trigger (RegStateR, LabelStateL, EventState) env

(CST , LabelState′, EventState′, signal) =

SignalTree([signal_{E}, end], 2, LabelState_{E}, EventState_{E})

Extract Head of a List

E [ callN (idN HEAD) E ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState′, LabelState′, EventState′))

where

C = ( \begin{matrix}& C_{E} \\ result_{E} : & COPYI & r, r_{E}, +1 \\ & READ & 0, r, +1 \\ & COPYI & r, r, +1 \\ & SPLIT & result, end \\ & CST \end{matrix} )

r, RegStateR = NewReg(RegState)

result, signal, end, LabelStateL = NewLabel(LabelState)^{3}

(C_{E}, r_{E}, (result_{E}, signal_{E}), (RegState′, LabelState_{E}, EventState_{E})) = E [E] trigger (RegStateR, LabelStateL, EventState) env

(CST , LabelState′, EventState′, signal) =

SignalTree([signal_{E}, end], 2, LabelState_{E}, EventState_{E})

Extract Tail of a List

E [ callN (idN TAIL) E ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState′, LabelState′, EventState′))

where

C = ( \begin{matrix}& C_{E} \\ result_{E} : & COPYI & r, r_{E}, +1 \\ & READ & 1, r, +1 \\ & COPYI & r, r, +1 \\ & SPLIT & result, end \\ & CST \end{matrix} )

r, RegStateR = NewReg(RegState)

result, signal, end, LabelStateL = NewLabel(LabelState)^{3}

(C_{E}, r_{E}, (result_{E}, signal_{E}), (RegState′, LabelState_{E}, EventState_{E})) = E [E] trigger (RegStateR, LabelStateL, EventState) env Capitalize Letter

E [ callN (idN CAP) E ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState′, LabelState′, EventState′))

where

C = ( \begin{matrix}& C_{E} \\ result_{E} : & COPYI & r, r_{E}, +1 \\ & COPYI & r_{1}, r_{E}, +1 \\ & LESSRC & 64, r_{1}, +1 \\ & COPYI & r_{2}, r_{E}, +1 \\ & LESSC & 123, r_{2}, +1 \\ & AND & r_{1}, r_{2}, +1 \\ & SWITCH & r_{1}, ltrue, lfalse \\ & COPYI & r, r_{1}, lfalse \\ ltrue : & SUBC & 32, r, +1 \\ lfalse : & SPLIT & result, end \\ & CST \end{matrix} )

r, r_{1}, r_{2}, RegStateR = NewReg(RegState)^{3}

result, signal, end, ltrue, lfalse, LabelStateL = NewLabel(LabelState)^{5}

(C_{E}, r_{E}, (result_{E}, signal_{E}), (RegState′, LabelState_{E}, EventState_{E})) = E [E] trigger (RegStateR, LabelStateL, EventState) env

(CST , LabelState′, EventState′, signal) =

SignalTree([signal_{E}, end], 2, LabelState_{E}, EventState_{E})

Test Whether List is Empty

E [ callN (idN NULL) E ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState′, LabelState′, EventState′))

C = ( \begin{matrix}& C_{E} \\ result_{E} : & COPYI & r, r_{E}, +1 \\ & EQUALC & 0, r, +1 \\ & SPLIT & result, end \\ & CST \end{matrix} )

r, RegStateR = NewReg(RegState)

result, signal, end, LabelStateL = NewLabel(LabelState)^{3}

(C_{E}, r_{E}, (result_{E}, signal_{E}), (RegState′, LabelState_{E}, EventState_{E})) = E [E] trigger (RegStateR, LabelStateL, EventState) env

(CST , LabelState′, EventState′, signal) =

SignalTree([signal_{E}, end], 2, LabelState_{E}, EventState_{E})

Test Whether Some Computation Has Failed

E [ callN (idN FAILED) E ] trigger (RegState, LabelState, EventState) env = (C, r, (result, signal), (RegState′, LabelState′, EventState′))

where

C = ( \begin{matrix}& C_{E} \\ result_{E} : & COPYI & r, r_{E}, +1 \\ & FAILED & r, +1 \\ & SPLIT & result, end \\ & CST \end{matrix} )

r, RegStateR = NewReg(RegState)

result, signal, end, LabelStateL = NewLabel(LabelState)^{3}

(C_{E}, r_{E}, (result_{E}, signal_{E}), (RegState′, LabelState_{E}, EventState_{E})) = E [E] trigger (RegStateR, LabelStateL, EventState) env

Appendix C

Description of the ADAM Instruction Set

This appendix describes the instruction set of the ADAM machine which has itself been described in Section 3. This description is split into two parts:

1. First the syntax of ADAM assembler code and of the files that are created by the EVE compiler are described and then

2. an informal description of the semantics of the ADAM instructions is given.

However, the instruction set will only be discussed in as much detail as it is necessary to understand the code that is generated by the EVE compiler. For a more detailed discussion of the ADAM instruction set than it is given in this appendix and its implementation see [Schreiner, 1990][Appendix B].

Figure C.1 shows a summary of the ADAM instruction set^{1}. This picture is taken from the above mentioned diploma thesis.

C.1 Syntax

In the syntax description, the distinction of ADAM objects into registers, labels, events and functions was already given by using different non-terminal symbols for their description. However, in this part it will only be described which kind of object may appear at which position of an instruction. The operations have been grouped to several classes according to their type i.e. according to the kinds of arguments they need. This grouping of all ADAM instructions is described in Section C.1. Based on this classification, in Section C.1 the general structure of one line of ADAM assembler code is described. In general, ADAM assembler code is then just a sequence of such lines (see Section C.1).

After the general description of ADAM assembler code, in the next sections the structure of the output files that are generated by the compiler are described. These files are:

1Note that the names of the types in Figure C.1 correspond to the names of the non-terminal symbols of the syntax description in Section C.1.

C.1. SYNTAX 199

Name  Type  Description
ADD(C)  OpReReIn Integer Addition (with Constant)
SUB((R)C)  OpReReIn Integer Subtraction ((Reverse) with Constant)
MUL(C)  OpReReIn Integer Multiplication (with Constant)
DIV((R)C)  OpReReIn Integer Division ((Reverse) with Constant)
MOD((R)C)  OpReReIn Integer Remainder ((Reverse) with Constant)
AND  OpReReIn Boolean Conjunction
OR  OpReReIn Boolean Disjunction
EQUAL(C)  OpReReIn Test for Integer Equality (with Constant)
LESS((R)C)  OpReReIn Test for Integer Strict Order ((Reverse) with Constant)
LESSEQ((R)C) OpReReIn Test for Integer Reflexive Order ((Reverse) with Constant)
NEG  OpReReIn Integer Negation
NOT  OpReReIn Boolean Negation
FAILED  OpReReIn Test for Error Value
COPYI  OpReReIn Duplicate an Integer Value
COPYP  OpReReIn Duplicate a Tuple Pointer
DELP  OpReIn  Delete a Pointer Value
SPLIT  OpInIn  Split Control Flow Into Parallel Paths
SWITCH  OpReInIn Switch Control Flow
JUMP  OpJu  Route Control Flow
WAIT  OpEvIn  Wait for Synchronization Event
CONST  OpReIn  Generate Long Constant
FASTCALL  OpFuIn  Request Standard Function Activation
TAILCALL  OpFuIn  Request Tail-Recursive Function Activation
CONTEXT  OpReIn  Submit Current Result Address to Callee
APPLY  OpReIn  Request Activation of a Non-Fixed Function
SEND(C)  OpReReCo Send (Constant) Argument to a Function Activation
SUBMIT  OpReReIn Request Submission of Curried Function Arguments
RETURN(C) OpReIn Send Result Value back to Caller
TERMINATE Op  Terminate Actual Activation
ALLOC  OpCoIn  Allocate Tuple
READ  OpCoReIn Read Contents of a Tuple Field
WRITEI  OpReReCo Write Integer into a Tuple Field
WRITEP  OpReReCo Write Pointer into a Tuple Field
WRITEC  OpCoReCo Write Integer Constant into a Tuple Field

Figure C.1: The ADAM Instruction Set (Summary)

• A program file (with extension “.asm”), which contains for each function of the EVE program the corresponding ADAM assembler code. However, in this file the code for a function has to be embedded in other statements containing the name of the function and the necessary number of registers and events. The whole program file must also contain a list of those functions that can be called from outside this file (‘export’ list). The structure of this file is described in Section C.1.

• An expression file (with extension “.ase”), which contains the code that is generated from the body of the definition of the EVE constant MAIN. This code is embedded in statements that announce the ADAM functions that may be called from inside this expression. The structure of this file is described in Section C.1.

• A type description file (with extension “.adt”), which contains a description of the type of the EVE constant MAIN and therefore of the result of the whole program. The structure of this file is described in Section C.1. The type tree has been transformed into this file by converting each node in the tree to a line with a leading number as its identification. The atomic EVE types are represented by single letters:

– I stands for the integer type,

– B stands for the boolean type and

– C stands for the character type.

Each compound data type is represented by a single letter and a list of numbers that represent the types of the components of this compound type:

– L x stands for a list where the type of the components is represented by the expression in the line x,

– T x_{1} … x_{n} stands for a tuple where the types of the components are represented by the expressions in the lines x_{1}, …, x_{n}.

– U x_{1} … x_{n} stands for a union where the types of the components are represented by the expressions in the lines x_{1}, …, x_{n}.

For example the EVE type L0 that is defined by the following EVE definitions:

              T0 == TUPLE OF
                     i1 : INT,
                     i2 : INT,
                     b  : BOOL;
              L0 == LIST OF T0;

will be transformed into the following type description file:

              #0:  1           ;Root of type is in line 1
              1:   L  2        ;List. Comps described in line 2
              2:   T  3  3  4  ;Tuple. Comps described in 3, 4
              3:   I           ;Integer
              4:   B           ;Boolean

Lexical Structure

Letter  = a | … | z | A | … | Z .
Digit  = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 .
Constant  = Digit { Digit } .
SignedConst = [ + | - ] Constant .
Ident  = Letter { Letter | Digit } .
Register  = Ident .
Label  = Ident .
Event  = Ident .
Function  = Ident .
Indir  = Label | SignedConst .

Types of Operations

OpReReIn = ADD | SUB | MUL | DIV | MOD | AND | OR | EQUAL | LESS | LESSEQ | NEG | NOT | FAILED | COPYI | COPYP | SUBMIT .

OpCoReIn = ADDC | SUBC | SUBRC | MULC | DIVC | DIVRC | MODC | MODRC | EQUALC | LESSC | LESSRC | LESSEQC | LESSEQRC | READ .

OpReReCo = SEND | WRITEI | WRITEP .
OpCoReCo = SENDC | WRITEC .
OpReInIn = SWITCH .
OpReIn  = DELP | CONST | CONTEXT | APPLY | RETURN .
OpInIn  = SPLIT .
OpCoIn  = RETURNC | ALLOC .
OpFuIn  = FASTCALL | TAILCALL .
OpEvIn  = WAIT .
OpLa  = JUMP .
Op  = TERMINATE .

Structure of a Code Line

Line  = [ Label ] CommandWithArgs .

CommandWithArgs = OpReReIn Register Register Indir | OpCoReIn Constant Register Indir | OpReReCo Register Register Constant | OpCoReCo Register Register Constant | OpReInIn Register Indir Indir | OpReIn Register Indir | OpInIn Indir Indir | OpCoIn Constant Indir | OpFuIn Function Indir | OpEvIn Event Indir | OpLa Label | Op .

Code

Code = { Line } .

Function Code

FctCode =  FUNCTION Function ( Constant , Constant )

Code

END Function .

Program File

PrgFile =  PROGRAM Ident

{ EXPORT Function { , Function } } { FctCode } END Ident .

Expression File

ExprFile =  EXPRESSION Ident

{ IMPORT Function { , Function } } BEGIN ( Constant , Constant ) Code END Ident .

Type Description File

Atom  = I | B | C .
List  = L Constant .
Tuple  = T Constant { Constant } .
Union  = U Constant { Constant } .
Node  = Atom | List | Tuple | Union .
TypeLine = Constant : Node .
TypeFile = #0: 1 TypeLine { TypeLine } .

C.2 Semantics

This section gives an informal description of the semantics of the various ADAM instructions. Most of the ADAM assembler instructions are usual two address instructions as the ADAM machine is essentially a controlflow machine. However, there are additional instructions that introduce explicit or implicit parallelism. These instructions are specially emphasized in this description.

The ADAM instructions can be divided into four classes:

1. Arithmetical-Logical Instructions,

2. Control Instructions,

3. Function Call Instructions,

4. Storage Instructions.

As the instructions of different classes have slightly different characteristics, the presentation of the semantics is split according to the above classes. However, to keep a general structure of the description of each instruction for all ADAM instructions the following scheme of an semantic description is used:

• Instruction:

• Description:

• Successor Instruction:

In this section the following typed variables are used:

• c, c′ are Constants,

• r, r′ are Registers,

• l, l′, l_{1}, l_{2} are Indirs,

• ev is an Event.

C.2.1 Arithmetical-Logical Instructions

Integer Addition • Instruction: l: ADD r, r′, l′

• Description: r ← r + r′

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Integer Addition with a Constant • Instruction: l: ADDC c, r, l′

• Description: r ← r + c

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Integer Subtraction • Instruction: l: SUB r, r′, l′

• Description: r ← r − r′

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix} 204 APPENDIX C. DESCRIPTION OF THE ADAM INSTRUCTION SET

Integer Subtraction with a Constant • Instruction: l: SUBC c, r, l′

• Description: r ← r − c

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Reverse Integer Subtraction with a Constant • Instruction: l: SUBRC c, r, l′

• Description: r ← c − r

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Integer Multiplication • Instruction: l: MUL r, r′, l′

• Description: r ← r ∗ r′

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Integer Multiplication with a Constant • Instruction: l: MULC c, r, l′

• Description: r ← r ∗ c

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Integer Division • Instruction: l: DIV r, r′, l′

• Description: r ← r/r′

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix} C.2. SEMANTICS 205

Integer Division with a Constant • Instruction: l: DIVC c, r, l′

• Description: r ← r/c

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Reverse Integer Division with a Constant • Instruction: l: DIVRC c, r, l′

• Description: r ← c/r

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Integer Remainder • Instruction: l: MOD r, r′, l′

• Description: r ← r%r′ (% … remainder of a division)

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Integer Remainder with a Constant • Instruction: l: MODC c, r, l′

• Description: r ← r%c (% … remainder of a division)

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Reverse Integer Remainder with a Constant • Instruction: l: MODRC c, r, l′

• Description: r ← c%r (% … remainder of a division)

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix} 206 APPENDIX C. DESCRIPTION OF THE ADAM INSTRUCTION SET

Boolean Conjunction • Instruction: l: AND r, r′, l′

• Description: r ← r&r′ (& … boolean conjunction operation)

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Boolean Disjunction • Instruction: l: OR r, r′, l′

• Description: r ← r | r′ (| … boolean disjunction operation)

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Test for Integer Equality • Instruction: l: EQUAL r, r′, l′

• Description: r ← r = r′

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Test for Integer Equality with a Constant • Instruction: l: EQUALC c, r, l′

• Description: r ← r = c

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Test for Integer Strict Order • Instruction: l: LESS r, r′, l′

• Description: r ← r < r′

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix} C.2. SEMANTICS 207

Test for Integer Strict Order with a Constant • Instruction: l: LESSC c, r, l′

• Description: r ← r < c

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Test for Reverse Integer Strict Order with a Constant • Instruction: l: LESSRC c, r, l′

• Description: r ← c < r

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Test for Integer Reflexive Order • Instruction: l: LESSEQ r, r′, l′

• Description: r ← r <= r′

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Test for Integer Reflexive Order with a Constant • Instruction: l: LESSEQC c, r, l′

• Description: r ← r <= c

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Test for Reverse Integer Reflexive Order with a Constant • Instruction: l: LESSEQRC c, r, l′

• Description: r ← c <= r

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix} 208 APPENDIX C. DESCRIPTION OF THE ADAM INSTRUCTION SET

Integer Negation • Instruction: l: NEG r, r′, l′

• Description: r ← −r′

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Boolean Negation • Instruction: l: NOT r, r′, l′

• Description: r ← ¬r′ (¬ … boolean negation operation)

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Test for Error Value • Instruction: l: FAILED r, r′, l′

• Description: r ← \{ \begin{matrix}TRUE & if in r′ the error flag is set, \\ FALSE & otherwise \end{matrix}

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

C.2.2 Control Instructions

Duplicate an Integer Value • Instruction: l: COPYI r, r′, l′

• Description: r ← r′ Copy the value of register r′ to register r.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Duplicate an Tuple Pointer • Instruction: l: COPYP r, r′, l′

• Description: r ← r′ As r′ is a tuple pointer in this case its reference count is decreased by this instruction.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix} Delete a Pointer Value

• Instruction: l: DELP r, l′

• Description: This instruction deletes the reference r to some tuple. This instruction is needed to handle the garbage collection which is done via reference counting.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Split Control Flow into Parallel Pathes • Instruction: l: SPLIT l_{1}, l_{2}

• Description: This instruction creates the two parallel threads of control l_{1} and l_{2} from the incoming thread l. This instruction and the function call are the only possibilities to create parallelism in the ADAM assembler language.

• Successor Instruction:

\{ \begin{matrix}l_{1} & if l_{1} ∈ Label \\ l + l_{1} & if l_{1} ∈ SignedConst \end{matrix}

and

\{ \begin{matrix}l_{2} & if l_{2} ∈ Label \\ l + l_{2} & if l_{2} ∈ SignedConst \end{matrix}

Switch Control Flow • Instruction: l: SWITCH r, l_{1}, l_{2}

• Description: This instruction only switches the controlflow to l_{1} if r contains the code for TRUE, to l_{2} if r contains the code for FALSE and to the next line if r contains an error value. No register values are changed.

• Successor Instruction: \{ \begin{matrix}l_{1} & if r = TRUE \\ l_{2} & if r = FALSE \\ l + 1 & if r = ERROR \end{matrix}

Route Control Flow • Instruction: l: JUMP l′

• Description: Perform an explicit jump to the label l′. Note: For backward jumps only this instruction can be used (i.e. no indirect backward jumps are possible).

• Successor Instruction: l′ Wait for a Synchronization Event

• Instruction: l: WAIT ev, l′

• Description: This instruction is used to synchronize parallel threads of control. The event ev can be viewed as a toggle that indicates if a WAIT on this event has already been performed. If yes the toggle is reset and the computation continues at line l′, thereby joining two threads into one. If no the toggle is set and the controlflow is suspended until another thread reaches a WAIT instruction on this event.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Generate a Long Constant

• Instruction: l: CONST r, l′

• Description: This instruction takes the constant that stands in the next line and puts it into the register r.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

C.2.3 Function Call Instructions

Request Standard Function Activation

• Instruction: l: FASTCALL f, l′

• Description: This instruction initiates the computation of the function f. First an activation record will be allocated and a pointer to it will arrive at the line l + 1. The result of the computation of the function will arrive at the line l + 2.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Request Tail-Recursive Function Activation

• Instruction: l: TAILCALL f, l′

• Description: This instruction initiates a tail recursive computation of the function f. The own context of the current function has to be sent to the function activation of f such that the result can be directly sent to the caller of the current function. This is done with the CONTEXT instruction. A pointer to the activation record will appear at the line l + 1.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix} Submit the Current Result Address to Callee

• Instruction: l: CONTEXT r, l′

• Description: This instruction submits the result address, which is stored inside the current function activation, to the newly created activation to which r points. Together with TAILCALL this is used to shortcut the returning process for tail recursive functions.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Request activation of a non-fixed function

• Instruction: l: APPLY r, l′

• Description: This instruction takes a function address in r and initiates the computation of the function. First an activation record will be allocated and a pointer to it is returned in the line l + 1. Hence, APPLY does essentially the same as FASTCALL, but it also operates on higher order functions.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Send Argument to a Function Activation

• Instruction: l: SEND r, r′, i

• Description: Send the register r as the i-th argument to the function activation to which r′ points. This r′ must be the result of a FASTCALL or of an APPLY instruction.

• Successor Instruction: l + 1

Send Constant Argument to a Function Activation

• Instruction: l: SENDC c, r′, i

• Description: Send the constant c as the i-th argument to the function activation to which r′ points. This r′ must be the result of a FASTCALL or of an APPLY instruction.

• Successor Instruction: l + 1

Request Submission of Curried Function Arguments

• Instruction: l: SUBMIT r, r′, l′

• Description: This instruction delivers the curried arguments that are stored in the curried expression to which r points, to the function activation to which r′ points.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Send Result Value back to Caller • Instruction: l: RETURN r, l′

• Description: This instruction returns the value in the register r as the result of the evaluation of the current function.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Send Constant as Result Value back to Caller • Instruction: l: RETURNC c, l′

• Description: This instruction returns the constant c as the result of the evaluation of the current function.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Terminate Actual Activation • Instruction: l: TERMINATE

• Description: This instruction terminates the activation of the current function and frees the memory that has been allocated by it.

• Successor Instruction: None

C.2.4 Storage Instructions

Allocate Tuple • Instruction: l: ALLOC c, l′

• Description: This instruction allocates a tuple of the size c. A pointer to this tuple will arrive at the line l + 1. This instruction performs a side-effect.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix} Read Contents of a Tuple Field

• Instruction: l: READ c, r, l′

• Description: This instruction reads the value of the c-th component of the tuple to which r points (counting starts from 0). The value of this component will arrive at the line l + 1. This instruction performs a side-effect.

• Successor Instruction: \{ \begin{matrix}l′ & if l′ ∈ Label \\ l + l′ & if l′ ∈ SignedConst \end{matrix}

Write Integer into Tuple Field

• Instruction: l: WRITEI r, r′, c

• Description: This instruction writes the integer value of register r into the c-th field of the tuple to which r′ points. A signal output of this instruction will arrive at the line l + 1. This instruction performs a side-effect.

• Successor Instruction: l + 1

Write Pointer into Tuple Field

• Instruction: l: WRITEP r, r′, c

• Description: This instruction writes the pointer value of register r into the c-th field of the tuple to which r′ points. The difference to the WRITEI instruction is that here the reference counter of the tuple to which r points is decreased. This only affects the garbage collection. A signal output of this instruction will arrive at the line l +1. This instruction performs a side-effect.

• Successor Instruction: l + 1

Write Integer Constant into Tuple Field

• Instruction: l: WRITEC c′, r′, c

• Description: This instruction writes the integer constant value c′ into the c-th field of the tuple to which r′ points. A signal output of this instruction will arrive at the line l + 1. This instruction performs a side-effect.

• Successor Instruction: l + 1

Appendix D

EVE Test Programs

This appendix contains some EVE programs that have been used to make benchmarks. However, the circular prime number generators that are presented in CircPrime.eve are not used for benchmarking but for explaining in more detail the differences of the parallel behaviour of two similar programs that only differ in the data dependencies inside the programs. The algorithms themselves and their runtime behaviour as well as the behaviour of the two circular prime number generators are discussed in Chapter 5. Table D.1 shows a list of those EVE program files that are included in this appendix.

Program Name Description of the Program  Page
Fib.eve Fibonacci Number Program 211
QuickSort.eve Quick Sort Program  212
Queens.eve Program for Solving N-Queens Problem 214
Prime.eve  Straightforward Prime Number Generator 217
CircPrime.eve Two Circular Prime Number Generators  219

Table D.1: EVE programs

Fib.eve                                                               Page:   1
 1  -- Fib.eve;
 2  --
 3  -- -------------------------------------------------------------------------
 4  --
 5  -- Simple Fibonacci Function.
 6  --
 7  -- Problem Specification:
 8  --  Given: n, n ... integer.
 9  --  Find:  fib(n) s.t.
10  --                fib(0) = 1,
11  --                fib(1) = 1,
12  --                fib(n) = fib(n-2) + fib(n-1).
13  --
14  -- -------------------------------------------------------------------------
15
16  Fib[n] : INT[INT] ==
17    IF n<=1
18      THEN 1
19      ELSE Fib[n-1]+Fib[n-2] ;
20
21  -- -------------------------------------------------------------------------
22  -- Result expression
23  -- -------------------------------------------------------------------------
24
25  MAIN : INT == Fib[18];

216 APPENDIX D. EVE TEST PROGRAMS

QuickSort.eve                                                         Page:   1
 1  -- QuickSort.eve;
 2  --
 3  -- ------------------------------------------------------------------------
 4  --
 5  -- Eve Program for performing the Quicksort algorithm.
 6  --
 7  -- Problem Specification:
 8  --  Given: l, l ... list of integers of the form [l_1, ..., l_n].
 9  --  Find:  l', l' ... list of integers of the form [l_1, ..., l_n] s.t.
10  --             for all 1<=i<n: l'_i <= l'_{i+1},
11  --             exists f : [1..n] -> [1..n] : l_i = l'_{f(i)},
12  --                                           f is bijective.
13  --
14  -- ------------------------------------------------------------------------
15
16  -- -------------------------------------------------------------------------
17  -- Type Definitions
18  -- -------------------------------------------------------------------------
19
20  IntList == LIST OF INT ;
21
22  -- -------------------------------------------------------------------------
23  -- Basic list operations
24  -- -------------------------------------------------------------------------
25
26  Append[l1, l2] : IntList[IntList, IntList] ==
27   IF NULL[l1]
28     THEN l2
29     ELSE { hd : INT     == l1.HEAD ;
30            tl : IntList == l1.TAIL }
31          hd :: Append[tl,l2] ;
32
33  -- -------------------------------------------------------------------------
34  -- FromToStep[a,b,limit] creates the same list as the list comprehension
35  -- [a,b..limit], namely  [a,b,b+(b-a),...,limit].
36  -- -------------------------------------------------------------------------
37
38  FromToStep[a,b,limit] : IntList[INT,INT,INT] ==
39   { inc  : INT == b-a ;
40     test[x,y] : BOOL[INT,INT] == IF a<b
41                                    THEN IGT[x,y]
42                                    ELSE ILT[x,y] ;
43     fs[a] : IntList[INT] == IF test[a,limit]
44                               THEN IntList[]
45                               ELSE a :: fs[a+inc] }
46   fs[a] ;
47
48  -- -------------------------------------------------------------------------
49  -- Functions
50  -- -------------------------------------------------------------------------

QuickSort.eve                                                         Page:   2
51
52  QSort[l] : IntList[IntList] ==
53    IF NULL[l]
54      THEN IntList[]
55      ELSE {
56            -- --------------------------------------------------------------
57            -- Result of splitting a list with the `Part' function.
58            -- --------------------------------------------------------------
59
60            PTuple == TUPLE OF
61                        less            : IntList,
62                        greater         : IntList,
63                        pivot           : INT;
64
65            -- --------------------------------------------------------------
66            -- Splitting a list `l' in a list with all elements < `x'
67            -- and a list with all elements > `x'.
68            -- --------------------------------------------------------------
69
70            Part [l,x] : PTuple[IntList, INT] ==
71              IF NULL[l]
72                THEN PTuple[IntList[], IntList[], x]
73                ELSE { hd : INT       == l.HEAD ;
74                       tl : IntList   == l.TAIL ;
75                       p  : PTuple    == Part[tl,x];
76                       pl : IntList   == p.less ;
77                       pg : IntList   == p.greater }
78                     IF hd <= x
79                       THEN PTuple[hd :: pl, pg, x]
80                       ELSE PTuple[pl, hd :: pg, x] ;
81
82            -- --------------------------------------------------------------
83            -- Local variables
84            -- --------------------------------------------------------------
85
86            HD : INT       == l.HEAD ;
87            TL : IntList   == l.TAIL ;
88            p  : PTuple    == Part[TL,HD] ;
89            l1 : IntList   == QSort[p.less] ;
90            l2 : IntList   == QSort[p.greater]                             }
91           Append[l1,Append[IntList[HD],l2]];
92
93  -- -------------------------------------------------------------------------
94  -- Result expression
95  -- -------------------------------------------------------------------------
96
97  MAIN : IntList ==
98   { l  : IntList == FromToStep[50,49,1]  }
99   QSort[l];

Queens.eve                                                            Page:   1
 1  -- Queens.eve;
 2  --
 3  -- -------------------------------------------------------------------------
 4  --
 5  -- Program for solving the 8-Queens problem.
 6  -- First order version (i.e. this program doesn't use higher order fcts)
 7  --
 8  -- Problem Specification:
 9  --  Given: n, n ... integer
10  --  Find:  b, b ... list of boards of the form [b_1,...,b_m]  s.t.
11  --              for all 1<=i<=m : the board b_i contains n queens,
12  --                                no queen checks another queen on board b_i
13  --
14  -- -------------------------------------------------------------------------
15
16  -- -------------------------------------------------------------------------
17  -- Type Definitions
18  -- -------------------------------------------------------------------------
19
20  Board       == LIST OF INT;
21  BoardList   == LIST OF Board;
22
23  IntList     == LIST OF INT;
24
25  -- -------------------------------------------------------------------------
26  -- Basic list operations
27  -- -------------------------------------------------------------------------
28
29  Append[l1, l2] : IntList[IntList, IntList] ==
30   IF NULL[l1]
31     THEN l2
32     ELSE { hd : INT    == l1.HEAD ;
33            tl : IntList  == l1.TAIL }
34          hd :: Append[tl,l2] ;
35
36  Append0[l1, l2] : BoardList[BoardList, BoardList] ==
37   IF NULL[l1]
38     THEN l2
39     ELSE { hd : Board      == l1.HEAD ;
40            tl : BoardList  == l1.TAIL }
41          hd :: Append0[tl,l2] ;
42
43  Length[l] : INT[IntList] ==
44   IF NULL[l]
45     THEN 0
46     ELSE 1+Length[TAIL[l]] ;
47
48  -- -------------------------------------------------------------------------
49  -- Functions
50  -- -------------------------------------------------------------------------

Queens.eve                                                            Page:   2
51
52  Queens[size] : BoardList[INT] ==
53   {
54    -- -----------------------------------------------------------------------
55    -- Test whether it is safe to put a queen on the position `row'/`col',
56    -- where `bs' is a board on which queens have been placed only on the
57    -- columns 1,..,`col'-1.
58    -- -----------------------------------------------------------------------
59
60    Allsafe[col,row,bs] : BOOL[INT,INT,Board] ==
61     IF NULL[bs]
62       THEN TRUE
63       ELSE {
64             hd : INT   == HEAD[bs];
65             tl : Board == TAIL[bs];
66
67             dx : INT  == Length[bs];
68             dy : INT  == row-hd;
69            }
70            (hd#row) & (ABS[dx]#ABS[dy]) & Allsafe[col,row,tl] ;
71
72    -- -----------------------------------------------------------------------
73    -- This function returns a list of that valid boards with queens on the
74    -- columns 1,...,`col' that can be derived from the board `b' (with
75    -- queens on the columns 1,...,`col'-1).
76    -- -----------------------------------------------------------------------
77
78    Add1[col,row,b] : BoardList[INT,INT,Board] ==
79     IF row>=size+1
80       THEN BoardList[]
81       ELSE { rest : BoardList  == Add1[col,row+1,b] }
82            IF Allsafe[col,row,b]
83              THEN Append[b,Board[row]] :: rest
84              ELSE rest ;
85
86    -- -----------------------------------------------------------------------
87    -- This function takes a list of all valid boards with queens on the
88    -- columns 1,...,`col' where `bss' is a list of all valid boards with
89    -- queens on the columns 1,...,`col'-1.
90    -- -----------------------------------------------------------------------
91
92    Add[col,bss] : BoardList[INT,BoardList] ==
93     IF NULL[bss]
94       THEN BoardList[]
95       ELSE {
96             b    : Board      == HEAD[bss];
97             bs   : BoardList  == TAIL[bss];
98            }
99            Append0[ Add1[col,1,b] , Add[col,bs] ];

Queens.eve                                                            Page:   3
101    -- -----------------------------------------------------------------------
102    -- This function computes a list of all valid boards with queens on the
103    -- columns 1,...,`col'.
104    -- -----------------------------------------------------------------------
105
106    QueensCol[col] : BoardList[INT] ==
107     IF col=0
108       THEN BoardList[Board[]]
109       ELSE { boards : BoardList == QueensCol[col-1] }
110            Add[col,boards];
111   }
112   QueensCol[size];
113
114  -- -------------------------------------------------------------------------
115  -- Result expression
116  -- -------------------------------------------------------------------------
117
118  MAIN : BoardList ==  Queens[6];

Prime.eve                                                             Page:   1
 1  -- Prime.eve;
 2  --
 3  -- -------------------------------------------------------------------------
 4  --
 5  -- Program for generating a list of prime numbers up to a given number.
 6  --
 7  -- Problem Specification:
 8  --  Given: n, n ... integer > 0
 9  --  Find:  l, l ... list of the form [l_1, ..., l_m]   s.t.
10  --            for all 1<=i<=m:  l_i is prime, l_i < l_{i+1},
11  --            for all p: p is prime, p < n ==>
12  --                       exists j, 1<=j<=m: l_j = p.
13
14  -- -------------------------------------------------------------------------
15  -- Type Definitions
16  -- -------------------------------------------------------------------------
17
18  IntList   == LIST OF INT;
19  PredOfInt == BOOL[INT];
20
21  -- -------------------------------------------------------------------------
22  -- Functions
23  -- -------------------------------------------------------------------------
24
25  -- -------------------------------------------------------------------------
26  -- IsPrime[x]:
27  --  Test whether the integer `x' is prime by dividing by 2 and by all odd
28  --  numbers up to the square root of `x'.
29  -- -------------------------------------------------------------------------
30
31  IsPrime[x] : PredOfInt ==
32   IF (x=2) | (x=3)
33     THEN TRUE
34     ELSE { test[x,n] : BOOL[INT,INT] ==
35              IF n*n > x
36                THEN TRUE
37                ELSE IF x % n = 0
38                       THEN FALSE
39                       ELSE test[x,n+2]
40            }
41            (x%2#0) & test[x,3];
42
43  -- -------------------------------------------------------------------------
44  -- PrimesTo[n]:
45  --  Generate a list of all prime numbers up to the number `n'.
46  --  First order version (no higher order functions used).
47  -- -------------------------------------------------------------------------
48
49  PrimesTo[n] : IntList[INT] ==
50   { Aux[m] : IntList[INT] ==

Prime.eve                                                             Page:   2
51       IF m>n
52         THEN IntList[]
53         ELSE IF IsPrime[m]
54                THEN m :: Aux[m+2]
55                ELSE      Aux[m+2] ;
56   }
57   IF n<2
58     THEN IntList[]
59     ELSE 2 :: Aux[3] ;
60
61  -- -------------------------------------------------------------------------
62  -- Result expression
63  -- -------------------------------------------------------------------------
64
65  MAIN : IntList == PrimesTo[170];

CircPrime.eve                                                         Page:   1
 1  -- CircPrime.eve;
 2  --
 3  -- --------------------------------------------------------------------------
 4  --
 5  -- Circular Algorithms for computing the list of all prime numbers.
 6  -- This file contains implementations of 2 different, circular prime
 7  -- number algorithms:
 8  --
 9  --   1)     "Introduction to Functional Programming using RUFL",
10  --                        E. P. Wentworth, p.87
11  --
12  --   2)   "Adam & Eve - An Abstract Dataflow Machine and its Programming
13  --                               Language",
14  --                            Master's Thesis,
15  --                              W. Schreiner
16  --
17  -- --------------------------------------------------------------------------
18  --  Data Structures
19  -- --------------------------------------------------------------------------
20
21  IntList   == LIST OF INT;
22  PredOfInt == BOOL[INT];
23
24  -- --------------------------------------------------------------------------
25  -- Algorithm of Peter Wentworth
26  -- --------------------------------------------------------------------------
27
28  -- --------------------------------------------------------------------------
29  -- Test whether a number `n' is prime by dividing it by all prime numbers
30  -- less than the sqare root of `n'. These primes are taken from the
31  -- list of all prime numbers in `primes'.
32  -- --------------------------------------------------------------------------
33
34  isPrime[n,primes] : BOOL[INT,IntList] ==
35   { p  : INT     == HEAD[primes];
36     ps : IntList == TAIL[primes] }
37   IF n<(p*p)
38     THEN TRUE
39     ELSE IF (n % p) = 0
40            THEN FALSE
41            ELSE isPrime[n,ps]  ;
42
43  -- --------------------------------------------------------------------------
44  -- Compute a list of all odd prime numbers from `n' to `limit'.
45  -- `primes' contains a list of all prime numbers.
46  -- --------------------------------------------------------------------------
47
48  oddPrimesFromTo[n,limit,primes] : IntList[INT,INT,IntList] ==
49   IF n>limit
50     THEN IntList[]

CircPrime.eve                                                         Page:   2
51     ELSE IF isPrime[n,primes]
52            THEN n :: oddPrimesFromTo[n+2,limit,primes]
53            ELSE      oddPrimesFromTo[n+2,limit,primes] ;
54
55
56  -- --------------------------------------------------------------------------
57  -- Algorithm of Wolfgang Schreiner
58  -- --------------------------------------------------------------------------
59
60  -- --------------------------------------------------------------------------
61  -- Test whether `n' is prime and add it to `r' iff it is prime.
62  -- --------------------------------------------------------------------------
63
64  cons[n,r,primes] : IntList[INT,IntList,IntList] ==
65   { p  : INT     == HEAD[primes];
66     ps : IntList == TAIL[primes]
67   }
68   IF n < p*p
69     THEN n :: r   ELSE
70   IF n % p = 0
71     THEN r
72   ELSE cons[n,r,ps];
73
74  -- --------------------------------------------------------------------------
75  -- Compute a list of all odd prime numbers from `n' to `limit'.
76  -- `primes' contains a list of all prime numbers.
77  -- --------------------------------------------------------------------------
78
79  sieve[n,limit,primes] : IntList[INT,INT,IntList] ==
80    IF n>limit
81      THEN IntList[]
82      ELSE { r : IntList == sieve[n+2,limit,primes] }
83           cons[n,r,primes] ;
84
85  -- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
86
87  -- --------------------------------------------------------------------------
88  -- MAIN part of Alg. of P. Wentworth
89  -- --------------------------------------------------------------------------
90
91  -- MAIN : IntList == 2 :: oddPrimesFromTo[3,70,MAIN] ;
92
93  -- --------------------------------------------------------------------------
94  -- MAIN part of Alg. of W. Schreiner
95  -- --------------------------------------------------------------------------
96
97  MAIN : IntList == 2 :: sieve[3,70,MAIN];

Appendix E

Definition Modules of the EVE Compiler

This appendix contains the documented definition modules of all parts of the prototype EVE compiler. These definition modules also form the documentation of the data types and procedures that are exported by the data encapsulations for the abstract data types that are needed by the compiler. Some of these procedures are used in Appendix A, describing the syntax analysis and in Appendix B, describing the code generation.

As it has already been described in Chapter 4 all the modules of the EVE compiler are separated into three classes:

1. The first class describes the top level module of the whole compiler and the top level modules of the several tasks of the compiler. Table E.1 shows the modules contained in this class.

2. The second class describes the data encapsulations that are needed by the compiler. Table E.2 shows the modules contained in this class.

3. The third class describes some special modules especially for I/O. Table E.3 shows the modules contained in this class.

The above mentioned figures also show the modules in the same order as they appear in the following sections.

Module Name Description of the Module  Page
EveCompile  Top level module of the whole compiler 223
EveLexAn  Module for lexical analysis  224
EveSyntAn  Module for syntax analysis  230
EveLambdaLift Module for λ-lifting  231
EveCodeGen  Module for code generation  232

Table E.1: Top Level Modules

Module Name  Abstract Data Type  Page
EveAST  Abstract syntax tree  234
EveCode  Code area  247
EveEnvironment Environment for code generation 254
EveEvent  Events for code generation  257
EveLabel  Labels for code generation  260
EveList  Lists of numbers  263
EveListList  Lists of lists of numbers  268
EveRegister  Registers for code generation  272

EveStringLib  Strings  275

EveSym  Symbol table and symbol list  277
EveType  EVE data types  287

Table E.2: Data Encapsulations

Module Name Description  Page
EveCoIO  Handling of result files  295
EveError  Error handling  298
EveGetChar Elementary input  299
EveWrite  Output of enumerations and sets 300

Table E.3: Special Modules

E.1. TOP LEVEL MODULES 227

E.1 Top Level Modules

EveCompile                                                            Page:   1
 1  DEFINITION MODULE EveCompile;     (* Top Level module of the Eve Compiler
 2  =========================================================================
 3   This module contains as its only procedure the top level procedure for
 4   the whole compilation process. A call to this procedure is the only
 5   thing that is done in the module `Eve.MOD'.
 6   ------------------------------------------------------------------------  *)
 7
 8
 9  TYPE
10      errorType = (none, CommandLineError, LexicalError, SyntaxError,
11                   LambdaLiftError, SemanticError);
12
13  VAR
14      success : BOOLEAN;   (* states if the parsing was successful *)
15      error   : errorType; (* states which error has occured *)
16
17  PROCEDURE Compile;       (* Top Level procedure; called in `Eve.MOD' *)
18
19  END EveCompile.

228 APPENDIX E. DEFINITION MODULES OF THE EVE COMPILER

EveLexAn                                                              Page:   1
 1  DEFINITION MODULE EveLexAn;                            (* Lexical Analyzer
 2  ==========================================================================
 3   Top Level module of the lexical analysis (part of 1st pass).
 4   All objects from the lexical analyzer that are used by the syntax
 5   analyzer are described here. The procedures that are exported by
 6   the lexical analyzer can be split into several categories:
 7
 8     - One routine gets the next symbol from the program.
 9
10     - One class of procedures performs the task of renaming variables.
11
12     - Direct access routines fetch the real name of a variable that is
13       represented by a CARDINAL number.
14
15     - A routine for checking wether an object is predefined or not is
16       also needed.
17
18     - Several routines for explicit memory deallocation allow it to
19       free some memory after the first pass of the compiler.
20
21     - Several routines for printing information about a variable,
22       string or such things are mainly used for testing.
23
24   ------------------------------------------------------------------------- *)
25
26  TYPE Symbol = (
27   (* special *)  noSy, eofSy,
28   (* keywords *) caseSy, elseSy,
29                  ifSy, listSy, ofSy,
30                  thenSy, tupleSy, unionSy,
31   (* classes *)  char, ident, number, string,
32   (* arithm. *)  divSy, minusSy, modSy, plusSy, timesSy,
33   (* boolean *)  andSy, notSy, orSy,
34   (* rel.ops *)  eqSy, geSy, gtSy, leSy, ltSy, neSy,
35   (* brackets*)  lbraceSy, lbracketSy, lparSy,
36                  rbraceSy, rbracketSy, rparSy,
37   (* others  *)  colonSy, commaSy, conversionSy, defineSy, doublecolonSy,
38                  selectionSy, semicolonSy);
39
40  CONST SymbolX =                            (* Needed for printing a symbol *)
41    "noSy,eofSy,caseSy,elseSy,"+
42    "ifSy,listSy,ofSy,"+
43    "thenSy,tupleSy,unionSy,"+
44    "char,ident,number,string,divSy,minusSy,modSy,plusSy,timesSy,"+
45    "andSy,notSy,orSy,eqSy,geSy,gtSy,leSy,ltSy,neSy,"+
46    "lbraceSy,lbracketSy,lparSy,rbraceSy,rbracketSy,rparSy,"+
47    "colonSy,commaSy,conversionSy,defineSy,doublecolonSy,"+
48    "selectionSy,semicolonSy ";
49
50  (* ------------------------------------------------------------------------*)

EveLexAn                                                              Page:   2
51
52  CONST
53        maxNrTransFcts = 29;         (* For translation of fctcalls to       *)
54                                     (* explicit nodes in the AST (2nd pass).*)
55        MaxInt = MAX(INTEGER) DIV 2; (* Max value of a constant of           *)
56                                     (* type INT.                            *)
57
58  TYPE
59       TransFctNames = ARRAY [1..maxNrTransFcts] OF CARDINAL;
60                       (* Names of fcts that have to be translated to        *)
61                       (* explicit nodes in the AST (2nd pass).              *)
62
63                                 (* The following variables are READ-ONLY !! *)
64
65  VAR sy     : Symbol;    (* the kind of the symbol detected by the last     *)
66                          (* recent call of NewSym                           *)
67      col    : CARDINAL;  (* the column number, where the symbol `sy' starts *)
68                          (* in the printed output                           *)
69      spix   : CARDINAL;  (* `spelling index' of the last recently detected  *)
70                          (* identifier                                      *)
71      value  : CARDINAL;  (* the value of the last recently detected number  *)
72      saddr  : CARDINAL;  (* the start address of the last recently detected *)
73                          (* string in the string list                       *)
74      slen   : CARDINAL;  (* the length of the last recently detected string *)
75      charVal: CHAR;      (* the character value of last recently detected   *)
76                          (* character; after having detected a char. also   *)
77                          (* saddr points to the place where charVal has been*)
78                          (* entered to the string storage; slen=1           *)
79
80        (* The following names have to be known outside the lexical analyzer *)
81        (* too. Hence, they are explicitly exported.                         *)
82
83      nameOfHEAD, nameOfTAIL,
84      nameOfABS, nameOfODD, nameOfCAP,
85      nameOfNULL, nameOfFAILED, nameOfORD, nameOfMAIN,
86      nameOfTRUE, nameOfFALSE,
87      nameOfMAXINT, nameOfMININT,
88      nameOfERROR
89                        : CARDINAL;    (* spelling indices of the predefined *)
90                                       (* fcts that stay present in the AST. *)
91                                       (* READ ONLY!                         *)
92
93      noName            : CARDINAL;    (* READ ONLY! The value               *)
94                                       (* is set in a local module *)
95
96      transFctNames     : TransFctNames;
97                             (* A list of fct-names that must be translated  *)
98                             (* into nodes of the AST by the procedure:      *)
99                             (* EveAST.Translate                             *)

EveLexAn                                                              Page:   3
101  (* ----------------------------------------------------------------------- *)
102
103  PROCEDURE NewSym;  (* Central procedure in the lexical analysis.
104                        It fetches the next symbol from the EVE source
105                        program and sets the following global variables
106                        in this module:
107                         sy, col, spix, value, saddr, slen, charVal.         *)
108
109  (* ----------------------------------------------------------------------- *)
110  (*                                                                         *)
111  (*    Procedures for handling the renaming of variables in the program.    *)
112  (*                                                                         *)
113  (* ----------------------------------------------------------------------- *)
114
115  PROCEDURE Rename (VAR name : CARDINAL; level : CARDINAL);
116   (*
117     Input:  name  ... The name of a variable in the EVE program.
118             level ... The level where this variable was defined.
119     Output: name ... A new, unique name that represents the same object
120                      from now on.
121
122     By this procedure, a variable is renamed if there already exists a
123     different object of the same name in the current scope. The renaming
124     ranges over the whole `level' and has to be explicitly undone with a
125     call to `UnRename'.                                                     *)
126
127  PROCEDURE UnRename (name : CARDINAL);
128   (*
129     Input: name ... The name of a variable whose renaming has to be undone.
130
131     This procedure deletes the last renaming if it affected `name'. If the
132     last renaming was not applied to `name' an error occurs.                *)
133
134  PROCEDURE UnRenameLevel (level : CARDINAL);
135   (*
136     Input: level ... The static nesting level for which all renamings have to
137                      be undone.
138
139     This procedure deletes all renamings for objects in the nesting
140     evel `level'. This is usually needed at the end of a function or
141     of a block.                                                             *)
142
143  PROCEDURE DisableRenaming;
144   (*
145     Turns the renaming mechanism off. This is needed for the correct
146     handling of selections from tuples and conversions of tuples as
147     in the following case obviously the field x and not the variable x is
148     meant in the expression of the local definition:
149
150                { Alpha == TUPLE OF

EveLexAn                                                              Page:   4
151                             x : INT,
152                             y : BOOL;
153                  {
154                    x : BOOL  == TRUE ;
155                    a : Alpha == Alpha[1,TRUE]
156                  }
157                  a.x
158                }                                                            *)
159
160  PROCEDURE EnableRenaming;
161   (*
162     Turns the renaming mechanism on. This is the default status of the
163     renaming. Typically, the renaming is just turned off for one or two
164     statements, then it is turned on again.                                 *)
165
166  PROCEDURE GetVirtualName (name : CARDINAL) : CARDINAL;
167   (*
168     Input:  name ... The name of a variable.
169     Output: name ... The ambigous name that was renamed and yielded the
170                      value that was the input to this procedure.
171
172     This procedure inverses the renaming process for `name' without
173     undoing the renaming.                                                   *)
174
175  (* ----------------------------------------------------------------------- *)
176  (*                                                                         *)
177  (*  Procedures for accessing the local namelist from outside               *)
178  (*  the lexical analyzer.                                                  *)
179  (*                                                                         *)
180  (* ----------------------------------------------------------------------- *)
181
182  PROCEDURE FetchNameStr (spix : CARDINAL; VAR name : ARRAY OF CHAR);
183   (*
184     Input:  spix ... An Eve object, represented by a spelling index of
185                      the local namelist.
186     Output: name ... The name of the object as a string like it appears
187                      in the Eve code.
188
189     This routine is mainly needed for testing.                              *)
190
191  PROCEDURE FetchName(VAR spix : CARDINAL; name : ARRAY OF CHAR);
192   (*
193     Input:  name ... The name of the object as a string like it appears
194                      in the Eve code.
195     Output: spix ... An Eve object, represented by a spelling index of
196                      the local namelist.
197
198     The other direction compared to `FetchNameStr'.                         *)
199
200  (* ----------------------------------------------------------------------- *)

EveLexAn                                                              Page:   5
201  (*                                                                         *)
202  (*           Some operations on the names in an Eve program                *)
203  (*                                                                         *)
204  (* ----------------------------------------------------------------------- *)
205
206  PROCEDURE isPredefined (spix : CARDINAL) : BOOLEAN;
207   (*
208     Input:  spix  ... The name of an Eve object.
209     Output: TRUE  ... if spix is a predefined object (like CONS, CAP, ... )
210             FALSE ... if spix is not a predefined object (usually).         *)
211
212  PROCEDURE GetNewIdent (VAR name : CARDINAL);
213   (*
214     Output: name ... A new, unique name for an Eve object.
215
216     This procedure is only needed in `EveSym.NewIdent' where a new, anonymous
217     identifier has to be added to the symbol table.                         *)
218
219  (* ----------------------------------------------------------------------- *)
220  (*                                                                         *)
221  (*         Procedures for explicitly discarding local objects.             *)
222  (*                                                                         *)
223  (* ----------------------------------------------------------------------- *)
224
225  PROCEDURE DisposeNames;
226   (*
227     This procedure explicitly frees the local namelist that is not used after
228     the 1st pass (lexical & syntax analysis) of the compiler any more.      *)
229
230  PROCEDURE DisposeStringStorage;
231   (*
232     This procedure explicitly frees the local stringstorage that is not used
233     after the 1st pass (lexical & syntax analysis) of the compiler any more *)
234
235  (* ----------------------------------------------------------------------- *)
236  (*                                                                         *)
237  (*                 Procedures for testing the compiler                     *)
238  (*                                                                         *)
239  (* ----------------------------------------------------------------------- *)
240
241  PROCEDURE PrintName(spix : CARDINAL);
242   (*
243     Print the real name (string) of the identifier that is denoted
244     by `spix'.                                                              *)
245
246  PROCEDURE PrintNameList;
247   (*
248     Print the real names (strings) of all identifiers that have occured
249     in the EVE program so far.                                              *)

EveLexAn                                                              Page:   6
251  PROCEDURE PrintSymbol;
252   (*
253     Print the current contents of the global variables:
254       sy, col, spix, value, saddr, slen.
255     If the symbol represents an identifier its real name is printed.
256     If the symbol represents a string its contents is printed.              *)
257
258  PROCEDURE FetchString(saddr, slen : CARDINAL; VAR str : ARRAY OF CHAR);
259   (*
260     Copy the EVE string that is denoted by the values of `saddr', `slen'
261     into `str'.                                                             *)
262
263  PROCEDURE PrintStringList;
264   (*
265     Print a list of all strings that have occured in the EVE program so far.*)
266
267  END EveLexAn.

EveSyntAn                                                             Page:   1
 1  DEFINITION MODULE EveSyntAn;                  (*   Pass 1: Syntax Analyzer
 2  ==========================================================================
 3   This module contains the syntax analyzer of the Eve compiler with the
 4   top level procedure. The modules that are needed by the syntax analyzer are:
 5
 6     EveAST, EveSym, EveType, EveLexAn,
 7     EveIO, EveCompile, EveWrite, EveStringLib, EveError.
 8   ------------------------------------------------------------------------- *)
 9
10  FROM EveAST IMPORT ASType;
11
12  PROCEDURE SyntAn (VAR x : ASType);
13   (*
14     Top level procedure for the syntax analysis.
15     When this procedure is called the given program will be checked for
16     lexical (EveLexAn) and syntactical (EveSyntAn) correctness.
17     The main result of this step is `x', and abstract syntax tree that
18     represents the given program. Furthermore, some encapsulated information
19     will be produced as output of this step:
20
21      - In a symbol table (`EveSym') all objects in the source program with
22        their attributes are stored.
23
24      - In the data encapsulation `EveTypes' all user defined types from the
25        source program are stored. These types are only referred to by
26        an attribute in the element of the symbol table.                     *)
27
28  END EveSyntAn.

EveLambdaLift                                                         Page:   1
 1  DEFINITION MODULE EveLambdaLift;       (*  Lambda Lifting (part of Pass 2)
 2  ==========================================================================
 3   Top level module for the lambda lifting transformation on the AST
 4   that is the output of the syntax analysis. The modules that are
 5   needed in this module are:
 6
 7     EveAST, EveList, EveListList, EveSym, EveIO.
 8
 9   Furthermore, this module contains a local module for performing the
10   computation of a transitive closure.
11   ------------------------------------------------------------------------- *)
12
13  FROM EveAST      IMPORT ASType;
14  FROM EveList     IMPORT List;
15  FROM EveListList IMPORT ListList;
16
17  PROCEDURE LambdaLift (ast : ASType);
18   (*
19     Procedure performing a lambda lifting on the abstract syntax tree `ast'.
20     In this transformation all local function definitions are brought
21     on the top level. Global variables in these function definitions
22     are turned into additional parameters of the function. The result
23     is a set of function definitions on top level that contain no
24     global variables and have no local function definitions
25     (they are usually called ``super combinators'').
26     The liftes functions will be afterwards at the beginning of the
27     definition sequence that represents the program (in the order of their
28     apearance in the given program).                                        *)
29
30  END EveLambdaLift.

EveCodeGen                                                            Page:   1
 1  DEFINITION MODULE EveCodeGen;                  (*  Pass 3: Code Generation
 2  ==========================================================================
 3   Top level module for the code generation (3rd pass) in the Eve compiler.
 4   The actual code generation is performed by a call to the procedure
 5   `CodeGen'. The other procedure is used for collecting statistical
 6   information about the generated code. Both procedures are called
 7   from `EveCompile'. The modules that are needed in this module are:
 8
 9     EveAST, EveCode, EveEnvironment, EveEvent, EveLabel, EveRegister,
10     EveList, EveSym, EveLexAn, EveError, EveIO.
11   ------------------------------------------------------------------------- *)
12
13  FROM EveAST  IMPORT ASType;
14  FROM EveList IMPORT List;
15
16  PROCEDURE CodeGen (ast : ASType; boundVars : List);
17   (*
18     This procedure takes an abstract syntax tree `ast' and a list `boundVars'
19     of all top level functions in this tree. It produces Adam assembler
20     code. This code is encapsulated by the data encapsulation `EveCode'.
21     This procedure actually produces code for a program file (`.asm'
22     extension) and for an expression file (`.ase' extension).
23     The former contains the code for all functions in the given program.
24     The names of all these functions are exported from the program file.
25     The latter contains just the code for the expression that is bound
26     to `MAIN' in the given program. The value of this expression is then
27     the result of the whole program.
28     See also the comments in `EveCoIO on the produced files.                *)
29
30
31  PROCEDURE StatisticsOfCodeGen (VAR heapBeginning, heapDispose, heapBefCodeGen,
32                                 heapAftCodeGen, usedHeap : LONGCARD;
33                                 VAR usedHeapInPercent : CARDINAL);
34   (*
35     This procedure collects some statistical information about the
36     generated Adam assembler code. The meaning of the parameters, which
37     are all output parameters, is:
38
39      heapBeginning ... Available heap at the beginning of the procedure
40                        `CodeGen'.
41      heapDispose ... Available heap after having discarded data structures
42                      of previous passes that are not needed any more.
43      heapBefCodeGen ...Available heap immediately before calling the
44                        procedure that traverses the AST and created code.
45                        The difference to `heapDispose' is that in the
46                        meantime the code area has been initialized the
47                        head and the export list have been entered into the
48                        code area and two lists that contained the
49                        export list and their arities have been discarded.
50      heapAftCodeGen ...Available heap after calling this

EveCodeGen                                                            Page:   2
51                        AST traversing procedure and after splitting the
52                        code into program and an expression part.
53      usedHeap          ... heapCodeGen-heapAftCodeGen.
54      usedHeapInPercent ... 100*usedHeap / heapBefCodeGen.                   *)
55
56  END EveCodeGen.

E.2 Data Encapsulations

EveAST                                                                Page:   1
 1  DEFINITION MODULE EveAST;   (*  Data Encapsulation of Abstract Syntax Tree
 2  ==========================================================================
 3   This module contains the opaque data type of an abstract syntax tree
 4   and all available procedures that work on this data type.
 5   ------------------------------------------------------------------------- *)
 6
 7  FROM EveList     IMPORT List;
 8  FROM EveListList IMPORT ListList;
 9  FROM EveType     IMPORT EType;
10
11
12  TYPE
13        ASType;         (* Encapsulated data type of an abstract syntax tree *)
14
15  CONST
16        NodeKindX = "programN,defseqN,defN,applyN,callN,curryN,lambdaN,"+
17                    "letN,ifN,caseN,composeN,selectN,"+
18                    "eqN,ltN,leN,gtN,geN,neN,"+
19                    "minusN,plusN,orN,timesN,divN,modN,andN,notN,"+
20                    "minusSignN,nilN,bodyN,elseN,lheadN,lbodyN,"+
21                    "charN,idN,numberN ";
22
23  TYPE
24        NodeKind = (programN,defseqN, defN,
25                    applyN, callN, curryN, lambdaN,
26                    letN, ifN, caseN, composeN, selectN,
27                  (* Relations *)
28                    eqN, ltN, leN, gtN, geN, neN,
29                  (* Basic Operations *)
30                    minusN, plusN, orN, timesN, divN, modN, andN, notN,
31                  (* Special *)
32                     minusSignN, nilN,
33                     bodyN, elseN, lheadN, lbodyN,
34                  (* Atomar *)
35                    charN, idN, numberN);
36
37        Flags = (pointer, int, none);    (* Information if value of subtree  *)
38                                         (* is integer or pointer or ubknown *)
39                                         (* Flags must be defined in the     *)
40                                         (* definition module (components    *)
41                                         (* are used in EveSyntAn) but       *)
42  CONST
43        FlagsX = "pointer,int,none";     (* Needed for printing              *)
44
45        noFlag = none;
46
47        maxArrayOfASTLen = 64;          (* Maximal length of a list of ASTs  *)

E.2. DATA ENCAPSULATIONS 239

EveAST                                                                Page:   2
48        maxArrayFlagLen  = 64;          (* Maximal length of a list of Flags *)
49
50        noCharVal = ' ';
51        noCardVal = 0;
52
53  TYPE
54        ArrayOfASType = ARRAY [1..maxArrayOfASTLen] OF ASType;
55        ArrayOfFlags  = ARRAY [1..maxArrayFlagLen] OF Flags;
56
57
58        RelationType = (eqRelTy, ltRelTy, leRelTy, gtRelTy, geRelTy, neRelTy);
59                        (* This enumeration is used to select which relation *)
60                        (* should be built in MakeASTRelation.               *)
61
62        ArithmType = (minusArTy, plusArTy, orArTy,
63                      timesArTy, divArTy, modArTy, andArTy);
64                        (* This enumeration is used to select which arith.   *)
65                        (* operation should be built in MakeASTArithm.       *)
66
67        AtomType = (charAtom, idAtom, numberAtom);
68                        (* This enumeration is used to select which leave of *)
69                        (* the AST should be built in MakeASTAtom.           *)
70
71        NumIdASType = RECORD                        (* Used for branches in  *)
72                       number, name : CARDINAL;     (* Case expr.:           *)
73                       ast          : ASType;       (*  number: nr of variant*)
74                      END;                          (*  name: id of variant  *)
75                                                    (*  ast: ast of body     *)
76        OptionalASType = RECORD                    (* Used for else branch   *)
77                          CASE exist : BOOLEAN OF  (* in a case expr         *)
78                            TRUE : ast : ASType;   (* optional               *)
79                          END;
80                         END;
81
82
83  VAR
84        emptyAST    : ASType;
85
86  (* ----------------------------------------------------------------------- *)
87  (*                                                                         *)
88  (*           Procedures for creating abstract syntax trees                 *)
89  (*                   (only used in `EveSyntAn')                            *)
90  (*                                                                         *)
91  (* ----------------------------------------------------------------------- *)
92
93  PROCEDURE MakeASTDefSeq (asts : ARRAY OF ASType; len : CARDINAL;
94                           VAR x : ASType);
95   (*
96     Build an AST representing a DefinitionSequence out of the `asts[0..len-1]'
97     by making them to subtrees of the definition sequence.

EveAST                                                                Page:   3
 98
 99     Input: asts_0, ..., asts_(len-1)
100     Output: x where
101             x = defseqN
102                   |
103                asts_0  -  ...  -  asts_(len-1)                              *)
104
105
106  PROCEDURE MakeASTDef    (idName : CARDINAL; ast : ASType;
107                           flag : Flags; VAR x : ASType);
108   (*
109     Build an AST for a variable binding (ast may be a lambda expr -> fct def)
110
111     Input: idName, ast
112     Output: x where
113             x = defN
114                   |
115                  idN       -  ast
116                   (idName)                                                  *)
117
118
119  PROCEDURE MakeASTLambda (idNames  : ARRAY OF CARDINAL;
120                           flagList : ARRAY OF Flags;   len: CARDINAL;
121                           ast : ASType; flag : Flags;
122                           VAR x : ASType);
123   (*
124     Build an AST for a lambda abstraction (i.e. body of a fct def).
125     To make the structure of a lambda asbtraction clearer, it is split
126     into a lambda-head (`lheadN') containing the variables and a
127     lambda-body (`lbodyN') containing the actual expression. The param
128     `flag' expresses if the expression evaluates to an integer value or
129     to a pointer.
130
131     Input: idNames_0, ..., idNames_(len-1), ast
132     Output: x where
133             x = lambdaN
134                   |
135                 lheadN          ----------------------        lbodyN
136                   |                                             |
137                  idN     -     ...  -  idN                     ast
138                   (idNames_0)           (idNames_(len-1))                   *)
139
140  PROCEDURE MakeASTLet    (ast1, ast2 : ASType; flag : Flags;
141                           VAR x : ASType);
142   (*
143     Build an AST representing a local definition.
144     `ast1' must contain a DefinitionSequence and `ast2' must contain an
145     expression. Again, `flag' indicates wether the whole construct
146     evaluates to an integer value or to a pointer.

EveAST                                                                Page:   4
148     Input: ast1, ast2
149     Output: x where
150             x = letN
151                   |
152                  ast1  -  ast2                                              *)
153
154  PROCEDURE MakeASTIf     (head, thenBody, elseBody : ASType;
155                           flag : Flags; VAR x : ASType);
156   (*
157     Build an AST representing a conditional expression. The input asts
158     are the subexpressions for head, then-branch and else-branch.
159
160     Input: head, thenBody, elseBody
161     Output: x where
162             x =  ifN
163                   |
164                  head  -  thenBody  -  elseBody                             *)
165
166  PROCEDURE MakeASTCase   (head : ASType;
167                           bodies : ARRAY OF NumIdASType; len : CARDINAL;
168                           else : OptionalASType;
169                           flag : Flags; VAR x : ASType);
170   (*
171     Build an AST representing a Case Expr. Here the array `bodies'
172     contains for each part all necessay computations. The AST might
173     have an else branch, too.
174
175     Input: head, bodies_0, ..., bodies_(len-1), else
176     Output: x where
177             x = caseN
178                   |
179                 head  - ... -  bodyN  ------   ...   ------     [  elseN   ]
180                                  |                              [    |     ]
181                              numberN  -  idN   -  bodies_i.ast  [ else.ast ]
182                        (bodies_1.number) (bodies_i.name)
183
184                where                i=0..len-1                              *)
185
186  PROCEDURE MakeASTList   (cells : ARRAY OF ASType; len : CARDINAL;
187                           VAR x : ASType);
188   (*
189     Build an AST representing a list. `cells' contains the `len' components
190     of the list. The whole AST ends with a `composeN' that has the special
191     node `nilN' as the second component.
192     As all elements of the list must be given to this procedure via
193     the parameter `cells' this procedure is only used for list constants
194     (compare `MakeASTConsList').
195
196     Input: cells_0, ..., cells_(len-1)
197     Output: x where

EveAST                                                                Page:   5
198             x =   composeN
199                      |
200                   cells_0  -  composeN
201                                  |
202                               cells_1  - .
203                                          .
204                                          . -  composeN
205                                                  |
206                                             cells_(len-1) - nilN
207
208      Takes ASTs and combines them to a list.
209      NOTICE: The last element of a list is the atomic object `nilN',
210              which represents the NIL pointer on the level of the AST.      *)
211
212  PROCEDURE MakeASTConsList   (cells : ARRAY OF ASType; len : CARDINAL;
213                               ast : ASType;
214                               VAR x : ASType);
215   (*
216     Build an AST representing a list. Differently to `MakeASTList' here
217     is also a `ast' given that should form the end of the list. This
218     `ast' will itself represent a list. Therefore, this procedure
219     is used for the CONS operation.
220
221     Input: cells_0, ..., cells_(len-1), ast (list!)
222     Output: x where
223             x =   composeN
224                      |
225                   cells_0  -  composeN
226                                  |
227                               cells_1  - .
228                                          .
229                                          . -  composeN
230                                                  |
231                                             cells_(len-1) - ast             *)
232
233  PROCEDURE MakeASTRelation (rel : RelationType; ast1, ast2 : ASType;
234                             VAR x : ASType);
235   (*
236     Build an AST representing a relation (comparison) between two expressions.
237
238     Input: rel, ast1, ast2      rel IN {eqRelTy,ltRelTy,leRelTy,gtRelTy,
239                                         geRelTy,neRelTy}
240     Output: x where
241             x = rel
242                  |
243                 ast1  -  ast2                                               *)
244
245  PROCEDURE MakeASTArithm (operator : ArithmType; ast1, ast2 : ASType;
246                           VAR x : ASType);
247   (*

EveAST                                                                Page:   6
248     Build an AST representing an arithmetical operation on two expressions.
249     This includes  binary operations on INTs and BOOLs.
250
251     Input: operator, ast1, ast2   operator IN {minusArTy,plusArTy,orArTy,
252                                                timesArTy,divArTy,modArTy,
253                                                andArTy}
254     Output: x where
255             x = operator
256                    |
257                   ast1  -  ast2                                             *)
258
259  PROCEDURE MakeASTSign   (ast : ASType;
260                           VAR x : ASType);
261   (*
262     Build an AST representing a negative INT value. Hence, `ast' must
263     represent an INT value itself.
264
265     Input: ast
266     Output: x where
267             x = minusSignN
268                     |
269                    ast                                                      *)
270
271  PROCEDURE MakeASTNot    (ast : ASType;
272                           VAR x : ASType);
273   (*
274     Build an AST representing the negation of a BOOL value. Hence,
275     `ast' must itself represent a BOOL value.
276
277     Input: ast
278     Output: x where
279             x = notN
280                  |
281                 ast                                                         *)
282
283  PROCEDURE MakeASTApplication (ast : ASType;
284                                args : ARRAY OF ASType; len : CARDINAL;
285                                flag : Flags; VAR x : ASType);
286   (*
287     Build an AST representing the application of a higher order fct to
288     some argument expressions. These `len' argument expressions are
289     represented by the array `args'.
290
291     Input: ast, args_0, ..., args_(len-1)
292     Output: x where
293             x = applyN
294                   |
295                  ast  -  args_0  -  ...  -  args_(len-1)                    *)
296
297  PROCEDURE MakeASTCall (ast : ASType;

EveAST                                                                Page:   7
298                        args : ARRAY OF ASType; len : CARDINAL;
299                        flag : Flags; VAR x : ASType);
300   (*
301     Build an AST representing a call of a first order function with the
302     `len' arguments that are represented by the array `args'. Note that
303     since no currying is allowed in Eve `len' must be equal to the number
304     of formal parameters in the function definition of the called function.
305
306     Input: ast, args_0, ..., args_(len-1)
307     Output: x where
308             x = callN
309                   |
310                  idN  -  args_0  -  ...  -  args_(len-1)                    *)
311
312  PROCEDURE MakeASTTuple  (elems : ARRAY OF ASType; len : CARDINAL;
313                           VAR x : ASType);
314   (*
315     Build an AST representing a tuple in Eve. The `len' fields of the
316     tuple will be filled by evaluating the expressions in the array `elems'.
317
318     Input: elems_0, ..., elems_(len-1)
319     Output: x where
320             x = composeN
321                    |
322                 elems_0  -  ...  -  elems_(len-1)                           *)
323
324  PROCEDURE MakeASTSelection (nr : CARDINAL; ast : ASType;
325                              flag : Flags; VAR x : ASType);
326   (*
327     Build an AST representing the selection of one field with number `nr'
328     from a tuple that is represented by `ast'.
329
330     Input: nr, ast
331     Output: x where
332             x = selectN
333                    |
334                 numberN  -  ast
335                    (nr)                                                     *)
336
337  PROCEDURE MakeASTCurry  (ast : ASType; VAR x : ASType);
338   (*
339     Build an AST representing a curried expression. As there are no
340     explicit curried expression allowed in Eve it suffices to have only
341     one argument, namely the `ast' that is transformed into a curried
342     expression. However, after the lambda-lifting step there might be
343     also some other variables added to the this implicit curried
344     expression.
345
346     Input: ast
347     Output: x where

EveAST                                                                Page:   8
348             x = curryN
349                   |
350                  ast
351
352     Remark: This procedure is needed to handle functions as first order
353             objects. If a fct is used as a parameter or stored in a
354             data structure `curryN' is put at the root of `ast' to
355             denote that `ast' is a higher order fct.                        *)
356
357  PROCEDURE MakeASTProgram  (ast : ASType; VAR x : ASType);
358   (*
359     Build an AST representing the whole program. As argument it
360     takes the `ast' that have been delivered from DefinitionSequence.
361     The result of this procedure is then a well defined abstract syntac tree.
362
363     Input: ast
364     Output: x where
365             x = programN
366                   |
367                  ast                                                        *)
368
369
370  PROCEDURE MakeASTAtom   (atom : AtomType;
371                           charVal : CHAR; cardVal : CARDINAL;
372                           flag : Flags;
373                           VAR x : ASType);
374   (*
375     Build an AST representing a leaf in the AST. The parameter `arg' tells
376     which kind of leaf will be built (possible options are: a character
377     constant, an integer constant and an identifier).
378     If a leaf with a character value should be built `charVal' contains
379     the ASCII code of that value.
380     If a leaf with an integer constant should be built `cardVal' contains
381     the actual value of the object.
382     If a leaf with an identifier should be built `cardVal' contains the
383     name of this identifier.
384     The argument flag may only be `pointer' for integer values (namely
385     if it is a pointer to some compound data structure.
386
387     Input: atom, charVal, cardVal   atom IN {charAtom, idAtom, numberAtom}
388     Output: x where
389             x = charN                   , if atom=charAtom
390                  (charVal)
391             x = idN                     , if atom=idAtom
392                  (cardVal)
393             x = numberN                 , if atom=numberAtom
394                  (cardVal)                                                  *)
395
396  (* ----------------------------------------------------------------------- *)
397  (*                                                                         *)

EveAST                                                                Page:   9
398  (*       Procedures that extract some information out of the AST           *)
399  (*                                                                         *)
400  (* ----------------------------------------------------------------------- *)
401
402  PROCEDURE GetIdNameAST (ast : ASType; VAR name : CARDINAL);
403   (*
404     This routine takes as input the AST `ast'. It checks whether this
405     AST is an identifier leaf. If not an error occurs. If yes then
406     the name of this identifier is extracted from the node and
407     returned as output parameter `name'                                     *)
408
409  (* ----------------------------------------------------------------------- *)
410  (*                                                                         *)
411  (*       Procedures for handling the auxiliar data structure of a          *)
412  (*                         ``case type table''                             *)
413  (*                                                                         *)
414  (* ----------------------------------------------------------------------- *)
415
416  PROCEDURE SetCaseType (ast : ASType; eType : EType);
417   (*
418     This routine checks whether `ast' is a case expression. If not an error
419     occurs. If yes then this `ast' and the corresponding type `eType'
420     are entered in table. This table is only needed for the translation
421     of case expressions into if-then-else expressions. This translation
422     is done in `Translation'.                                               *)
423
424  PROCEDURE GetCaseType (ast : ASType; VAR eType : EType);
425   (*
426     This procedure takes `ast' as an input and searches in the case type table
427     for the corresponding `eType'. This auxiliar data structure of a case type
428     table is used to speed up the translation of case expressions into
429     if-then-else expressions.                                               *)
430
431  PROCEDURE DisposeCaseTypeTable;
432   (*
433     Discard the memory that has been allocated for the case type table.
434     This table may be discarded after the execution of the translation
435     in `Translate'.                                                         *)
436
437  (* ----------------------------------------------------------------------- *)
438  (*                                                                         *)
439  (*      Procedures for getting statistical information about the AST       *)
440  (*                                                                         *)
441  (* ----------------------------------------------------------------------- *)
442
443  PROCEDURE sizeOfAST (x : ASType) : CARDINAL;
444   (*
445     This procedure computes the size of the AST `x' in terms of nodes in
446     the AST (i.e. each node has the same weight).                           *)

EveAST                                                                Page:  10
448
449  PROCEDURE GetSizeOfAST (x : ASType) : LONGCARD;
450   (*
451     This procedure computes the size of the AST `x' in terms of bytes.
452     For that the builtin function SIZE is used to compute the size of
453     a single node in the tree.                                              *)
454
455  (* ----------------------------------------------------------------------- *)
456  (*                                                                         *)
457  (*        Procedures performing some transformations on the AST            *)
458  (*                                                                         *)
459  (* ----------------------------------------------------------------------- *)
460
461  PROCEDURE Translate (x : ASType);
462   (*
463     This procedure actually performs two transformations on the AST `x':
464      - All calls to predefined functions (like CONS, ADD, ...) are translated
465        into the corresponding, explicit nodes (like composeN, addN, ...).
466      - All case expressions are replaced by equivalent if-then-else
467        expression in which the tag of the object is tested. Upon the result
468        of this test the correct branch for the further computation will be
469        chosen.                                                              *)
470
471  PROCEDURE AttachBoundVars (ast : ASType; VAR boundVars, freeVars : List);
472   (*
473     This procedure attaches to each node of the AST the set of free variables
474     that occur in that subtree. Furthermore, it returns a list of
475     variables that are bound in `ast' and whose binding scopes over the
476     next subtree in the AST. This is for example necessary for removing
477     all variables that are bound in the DefinitionSequence part of
478     a local definition from the free variables of the body expression.
479     The resulting set is then the set of free variables. The local
480     definition itself need not export any bound variables.
481     As the top level node in the AST is a defSeqN this routine has to be
482     used for attaching free variables to the nodes in the tree. A
483     similar procedure `AttachFreeVars' has the same semantics as this
484     procedure but it does not return any bound variables (this is
485     possible for all expressions).                                          *)
486
487  PROCEDURE Traverse (x : ASType; addedParFctsL : List; newParLL : ListList);
488   (*
489     The input of this procedure is the whole AST of the program (`x'),
490     a list of all functions where parameters had to be added in its
491     definition (`addedParFctsL') and a list of those parameter-lists that
492     had to be added to the corresponding function. So, the i-th
493     element of `newParLL' contains a list of parameters that have been
494     added to the function definition of the i-th element in the list
495     addedParFctsL. This procedure now makes the following:
496
497      - Each nested function definition is pruned from the AST. This can be

EveAST                                                                Page:  11
498        done as the nested function definitions have already been put
499        into a list by `EveSym.LiftFcts'.
500      - In each function call of a function that is an element of
501        `addedParFctsL' the new parameters that can be fetched from
502        `newParLL' have to be added (for that `AddParam' is used).           *)
503
504  (* ----------------------------------------------------------------------- *)
505  (*                                                                         *)
506  (*                  Procedures for lambda lifting                          *)
507  (*                                                                         *)
508  (* ----------------------------------------------------------------------- *)
509
510  PROCEDURE AddASTsToDefSeq (asts : ARRAY OF ASType; len : CARDINAL;
511                             VAR ast : ASType);
512   (*
513     The input for this procedure is a list of lifted function definitions
514     of length `len' in `asts'. The AST `ast' represents the original program
515     where all local function definitions have been cut off. In this step
516     all elements of `asts' are put in the top level definition sequence
517     before the definition of `ast'. The resulting `ast' is then the lifted
518     version of the given program.                                           *)
519
520  PROCEDURE AddParam (ast : ASType; freeVarsList : List;
521                      VAR newParList : List);
522   (*
523     Here, `ast' must be a definition of a function (otherwise an error occurs).
524     `freeVarsList' contains all variables that must be parameters of
525     that function definition (it contains parameters of the function and
526     free variables that must be changed into additional parameters of the
527     function. After this procedure the function definition does not contain
528     any free variables any more and can be lifted on top level.             *)
529
530  PROCEDURE GetLocalVarsFromAST (ast : ASType; VAR localVars : List);
531   (*
532     The input (`ast') must be a fcunction definition. This procedure
533     extracts all local variables of the function and returns them
534     in `localVars'. In this case local variables can only be variables
535     that are defined in the DefinitionSequence of a local definition that
536     is the top level construct of the `ast'. If the body of the function
537     that is defined in `ast' is no local definition `localVars' will be
538     the empty list.
539     This procedure is only needed for the lambda lifting, namely
540     in `EveSym.GetLists'.                                                   *)
541
542  (* ----------------------------------------------------------------------- *)
543  (*                                                                         *)
544  (*                  Procedures for code generation                         *)
545  (*                                                                         *)
546  (* ----------------------------------------------------------------------- *)

EveAST                                                                Page:  12
548  PROCEDURE getFreeVars (ast : ASType) : List;
549   (*
550     This function extracts the list of free variables that is associated
551     to the AST `ast'.                                                       *)
552
553  PROCEDURE getFlag (ast : ASType) : Flags;
554   (*
555     This function extracts the flag (`pointer' or `int') that is associated
556     to the AST `ast'.                                                       *)
557
558  PROCEDURE DeComposeAST (ast : ASType;
559                          VAR nodeKind : NodeKind;
560                          VAR cardVal : CARDINAL; VAR charVal : CHAR;
561                          VAR sons : ARRAY OF ASType; VAR len : CARDINAL);
562   (*
563     The input for this procedure is an AST `ast'.
564     This AST is split into all of its components, which are:
565      - The kind of the node itself (e.g. defN, letN, ...).
566      - If the node is an identifier (idN) or an integer value (numberN)
567        `cardVal' contains the name of the identifier or the value of
568        the integer.
569      - If the node is a character (charN) value then `charVal' contains
570        the ASCII code of this character.
571      - All the subtrees of the given tree `ast' are returned in the list
572        `sons' that contains `len' entries.
573     This decompostion is extensively used in code generation step
574     (`EveCodeGen') as the AST has to be traversed there.                    *)
575
576  PROCEDURE GetASTFctNames (VAR fctNames, arities : List; ast : ASType);
577   (*
578     This procedure extracts from the AST `ast' a list of all functions
579     that are defined in that AST (`fctNames') and a list of the
580     corresponding arities (`arities'). This information will be kept in
581     a table locally in `EveCode'. When code for a curried expression
582     has to be generated this information is needed.                         *)
583
584  (* ----------------------------------------------------------------------- *)
585  (*                                                                         *)
586  (*               Miscellaneous functions and procedures                    *)
587  (*                                                                         *)
588  (* ----------------------------------------------------------------------- *)
589
590  PROCEDURE flag (eType : EType) : Flags;
591   (*
592     This function decides upon the structure of the given type if it is
593     a compund structure (-->`pointer') or if it is an atomic structure
594     (-->`int').                                                             *)
595
596  PROCEDURE PrintAST (x : ASType; nr : CARDINAL);
597   (*

EveAST                                                                Page:  13
598     This procedure is mainly intended for testing.
599     It prints the whole AST `x' to standard output. Different levels
600     in the tree are denoted by indentation and level numbers (`nr' is
601     the level number of the current AST.                                    *)
602
603  END EveAST.

EveCode                                                               Page:   1
 1  DEFINITION MODULE EveCode;   (* Data Encapsulation of Code Area Management
 2  ==========================================================================
 3   This module contains the opaque data type of the Code Area and
 4   all procedures that are necessary to write Adam assembler code into
 5   that area.
 6   ------------------------------------------------------------------------- *)
 7
 8  FROM FIO            IMPORT File;
 9  FROM EveRegister    IMPORT Register;
10  FROM EveLabel       IMPORT Label;
11  FROM EveEvent       IMPORT Event;
12  FROM EveStringLib   IMPORT LongString, String;
13  FROM EveList        IMPORT List;
14  FROM EveAST         IMPORT ASType;
15
16
17  TYPE  CodeArea;                     (* Encapsulated date type of code area *)
18
19
20  CONST                                                 (* For printing and  *)
21        CommandX = "ADD,ADDC,SUB,SUBC,SUBRC,MUL,MULC,"+ (* writing to files  *)
22                   "DIV,DIVC,DIVRC,MOD,MODC,MODRC,AND,OR,"+
23                   "EQUAL,EQUALC,LESS,LESSC,LESSRC,LESSEQ,LESSEQC,LESSEQRC,"+
24                   "NEG,NOT,FAILED,"+
25                   "COPYI,COPYP,DELP,SPLIT,SWITCH,JUMP,WAIT,CONST,"+
26                   "FASTCALL,TAILCALL,CONTEXT,APPLY,SEND,SENDC,"+
27                   "SUBMIT,RETURN,RETURNC,TERMINATE,"+
28                   "ALLOC,READ,WRITEI,WRITEP,WRITEC,NOP,STRING,SHORT,COMMENT  ";
29
30        ArgKindX = "register,event,label,indir,fct,constant,ref,no  ";
31
32  TYPE
33        Command = (add,addC,sub,subC,subRC,mul,mulC,
34                   div,divC,divRC,mod,modC,modRC,and,or,
35                   equal,equalC,less,lessC,lessRC,lesseq,lesseqC,lesseqRC,
36                   neg,not,failed,
37                   copyI,copyP,delP,split,switch,jump,wait,const,
38                   fastcall,tailcall,context, apply, send, sendC,
39                   submit,return,returnC,terminate,
40                   alloc,read,writeI,writeP,writeC,
41                   nop,string,short,comment);
42              (* ---------------------------------------------------------- *)
43              (* This enumeration contains all Adam assembler instructions. *)
44              (* Furthermore, it contains tags for characterizing lines     *)
45              (* where only text should be generated in the resulting       *)
46              (* Adam assembler file. These tags are:                       *)
47              (*  nop ... creates an empty line in the output               *)
48              (*  comment ... creates a line ";  " in the output            *)
49              (*  short ... writes a short string (max 40 chars) to output  *)
50              (*  string ... writes a long string (max 80 chars) to output  *)

EveCode                                                               Page:   2
51              (* ---------------------------------------------------------- *)
52
53        ArgumentKind = (register,event,label,indir,fct,constant,ref,no);
54                     (* --------------------------------------------------- *)
55                     (* The objects to which arguments of Adam instructions *)
56                     (* may refer. Note:  ref is label or indir             *)
57                     (* --------------------------------------------------- *)
58
59        ArgumentKindSet = SET OF ArgumentKind;
60
61        Argument = RECORD
62                     CASE kind : ArgumentKind OF
63                        register : regVal   : Register;
64                      | event    : eventVal : Event;
65                      | label    : labVal   : Label;
66                      ELSE         value    : CARDINAL;
67                     END;
68                   END;
69
70  VAR   Code                    : CodeArea;   (* The generated code is here *)
71        const0, const1,const2,        (* Constant values (.kind = constant) *)
72
73        next, next2,                  (* Indirect jumps  (.kind = indir)    *)
74        noArgument              : Argument;            (* Special arguments *)
75        nrOfStretchedJumps,
76        nrOfUnresolvedLabels    : CARDINAL;      (* Statistical information *)
77
78  (* ---------------------------------------------------------------------- *)
79  (*                                                                        *)
80  (*                          Initializations                               *)
81  (*                                                                        *)
82  (* ---------------------------------------------------------------------- *)
83
84  PROCEDURE InitCode ;                       (* Initialization of Code Area *)
85
86  PROCEDURE GetFctNames (ast : ASType; VAR fctNames, arities : List);
87   (*
88     This procedure takes the whole AST as input and creates a list of all
89     functions and a list of the corresponding arities. This is needed
90     to store the function names (cardinals), arities together with the
91     real names of the functions (strings) in a table (local data structure
92     in EveCode). Hence, the symbol table can be discarded after the 2nd
93     pass.
94     This procedure is only called in `EveCodeGen.CodeGen'.
95     It is the only procedure that calls `EveAST.GetASTFctNames'.            *)
96
97
98  (* ---------------------------------------------------------------------- *)
99  (*                                                                        *)
100  (*             Type transformations into `Argument' type                  *)

EveCode                                                               Page:   3
101  (*                                                                        *)
102  (* ---------------------------------------------------------------------- *)
103
104  PROCEDURE RegArg (reg : Register) : Argument;
105   (*
106     This procedure takes a register and returns an argument of register
107     type that has the value `reg'. Necessary, because the Emit* procedures
108     require parameters of `Argument' type.                                  *)
109
110  PROCEDURE LabArg (lab : Label) : Argument;
111   (*
112     This procedure takes a label and returns an argument of label
113     type that has the value `lab'. Necessary, because the Emit* procedures
114     require parameters of `Argument' type.                                  *)
115
116  PROCEDURE EvArg (ev : Event) : Argument;
117   (*
118     This procedure takes an event and returns an argument of event
119     type that has the value `ev'. Necessary, because the Emit* procedures
120     require parameters of `Argument' type.                                  *)
121
122  (* ---------------------------------------------------------------------- *)
123  (*                                                                        *)
124  (*               Procedures writing into the code area                    *)
125  (*                                                                        *)
126  (* ---------------------------------------------------------------------- *)
127
128  PROCEDURE Emit (label : Label; command : Command;
129                  arg1, arg2, arg3 : Argument);
130   (*
131     Write one line of code into the code area (at next free place). Each
132     line is split into its components, which are the input parameters.
133     If a line has no label, `label' must have the special value `noLabel'.
134     If a command has fewer than 3 arguments, the superflous arguments must
135     have the special value `noArgument'. When written into a file a line
136     is produced with the components in the same order as in the parameter
137     list with <TAB> characters separating the components, a `:' after the
138     label and the arguments separated by `,'.                               *)
139
140  PROCEDURE EmitCall (lab : Label; whichCall : Command;
141                      name : CARDINAL; arg : Argument);
142   (*
143     Write a line containing a fucntion call to the code area. `whichCal' may
144     only have the values `fastcall' or `tailcall'. In this procedure the
145     table, containing function names (cardinals) and their real names
146     (strings) is accessed to get the string that has to be written as
147     first argument of the `whichCall' command to the code area. Further
148     procedures that needs this local table are:
149      - `EveCodeGen.EmitExport',
150      - `EveCodeGen.EmitFunction',

EveCode                                                               Page:   4
151      - `EveCodeGen.EmitEnd'.
152     The `arg' parameter must specify the successor instruction.             *)
153
154  PROCEDURE EmitConstant (val : CARDINAL);
155   (*
156     Write a constant to the code area. `val' contains a valid constant value.
157     The label field gets the value `noLabel', all arguments get the value
158     `noArgument'.                                                           *)
159
160  PROCEDURE EmitString (str : String);
161   (*
162     Write a string to the code area. `str' contains a string
163     (with maximal length: `EveStringLib.stringLength').
164     The label field gets the value `noLabel', all arguments get the value
165     `noArgument'.                                                           *)
166
167  PROCEDURE EmitProgram;
168   (*
169     Write the string "PROGRAM Program"to the code area.
170     The label field gets the value `noLabel', all arguments get the value
171     `noArgument'.                                                           *)
172
173  PROCEDURE EmitEndProgram;
174   (*
175     Write the string "END Program"to the code area.
176     The label field gets the value `noLabel', all arguments get the value
177     `noArgument'.                                                           *)
178
179  PROCEDURE EmitExport (fctNames : List);
180   (*
181     Write an export list containing all functions in `fctNames' to the
182     code area. Each line of the export list starts with "EXPORT " and then
183     contains the names of functions in `fctNames'. Each line has max.
184     `EveCodeGen.maxExportLen' characters. For getting the real names (strings)
185     of the functions in `fctNames' the local table in `EveCodeGen' has to
186     be accessed. The label field gets the value `noLabel', all arguments
187     get the value `noArgument'.                                             *)
188
189  PROCEDURE EmitFunction (name : CARDINAL);
190   (*
191     Write the head of a function definition to the code area. This head
192     consists of the string "FUNCTION " and the real name of the function
193     `name' (access to local table of functions and their names).
194     The label field gets the value `noLabel', all remaining arguments get the
195     value `noArgument'.                                                     *)
196
197  PROCEDURE EmitEnd (name : CARDINAL);
198   (*
199     Write the last line of a function definition to the code area. This line
200     consists of the string "END " and the real name of the function

EveCode                                                               Page:   5
201     `name' (access to local table of functions and their names).
202     The label field gets the value `noLabel', all arguments get the
203     value `noArgument'.                                                     *)
204
205  PROCEDURE EmitFctTuple (name : CARDINAL; i : CARDINAL);
206   (*
207     Write a line of the form "<f,k>" to the code area. Here, `f' is the
208     real name of the function `name' and `k'=arity(`name')+1-i. Hence,
209     `i' is the number of curried arguments of the function and `k' is
210     the index of the first of these curried arguments. The translation
211     from "<f,k>" to a number that contains the address of the fct and `k'
212     is done by the assembler.
213     The label field gets the value `noLabel', all arguments get the value
214     `noArgument'.                                                           *)
215
216  PROCEDURE EmitComment (emptyLine : BOOLEAN);
217   (*
218     Write a comment line the code area. This comment line may be:
219      - an empty line (denoted by the pseudo command `nop'),
220      - a line containing the comment symbol ";" and a comment string
221        (denoted by the pseudo comment `comment').
222     The label field gets the value `noLabel', all arguments get the value
223     `noArgument'.                                                           *)
224
225  (* ---------------------------------------------------------------------- *)
226  (*                                                                        *)
227  (*    Handling of the number of events, registers and labels that are     *)
228  (*                       used in a function.                              *)
229  (*                                                                        *)
230  (* ---------------------------------------------------------------------- *)
231
232  PROCEDURE StoreEventRegisterLabelNr (x, maxEventNr, maxRegisterNr,
233                                          maxLabelNr               : CARDINAL);
234   (*
235     Store the numbers `maxEventNr', `maxRegisterNr' and `maxLabelNr' as the
236     current number of events, registers and labels, respectively, that are
237     used by the the funtion `x'. These values are stored together with
238     `x' and the real name of `x' in a local data structure.                 *)
239
240  PROCEDURE GetEventRegisterLabelNr (x : CARDINAL;
241                                     VAR maxEventNr, maxRegisterNr,
242                                         maxLabelNr                : CARDINAL);
243   (*
244     Fetches the number of events, registers and labels that have been used
245     by the function `x' in the output parameters `maxEventNr', `maxRegisterNr'
246     and  `maxLabelNr'. These values must have been stored with a call to
247     `StoreEventRegisterLabelNr' before. This procedure is needed in
248     `AdjustCode' where for each function e.g. unresolved labels have to be
249     resolved. For that the number of needed events, registers and labels
250     of that function must be restored.                                      *)

EveCode                                                               Page:   6
251
252  PROCEDURE SetEventRegisterNr (maxEventNr, maxRegisterNr : CARDINAL);
253   (*
254     This procedure adds a string of the form "(m,n)" to the line where
255     the head of the current fct is defined. There, `m' stands for the
256     maximal number of events and `n' for the maximal number of registers
257     that are needed in that function. As these numbers are known after
258     the whole code for this function has been generated they can't be
259     directly set in `EmitFunction'.                                         *)
260
261  (* ---------------------------------------------------------------------- *)
262  (*                                                                        *)
263  (*              Marking the beginning and the end of MAIN                 *)
264  (*                                                                        *)
265  (* ---------------------------------------------------------------------- *)
266
267  PROCEDURE SetMAINStart;
268   (*
269     This procedure marks the start of the definition of MAIN in the
270     code area and remembers the index of that start in a global variable.
271     This is needed to easily extract the definition of MAIN into an
272     explicit file.                                                          *)
273
274  PROCEDURE SetMAINEnd;
275   (*
276     This procedure marks the end of the definition of MAIN in the
277     code area and remembers the index of that end in a global variable.
278     This is needed to easily extract the definition of MAIN into an
279     explicit file.                                                          *)
280
281  (* ---------------------------------------------------------------------- *)
282  (*                                                                        *)
283  (*                Transformations on the generated code                   *)
284  (*                                                                        *)
285  (* ---------------------------------------------------------------------- *)
286
287  PROCEDURE AdjustCode ;
288   (*
289     Here some transformations of the already generated code are performed.
290     It is only called once from `EveCodeGen.CodeGen' after the code
291     for the program has been created. This procedure does the following:
292      - It deletes all the SPLIT commands before the first function
293        definition in the program (they are only necessary for nested
294        Definition Sequences but not on top level).
295      - It deletes the label at the first line of a function. This is an
296        unresolved label and not needed as a function is called and not
297        jumped to.
298      - It resolves unresolved labels. After the code generation there may
299        be unresolved labels (e.g. local objects that are not used in the
300        body of the definition). These labels are resolved by inserting

EveCode                                                               Page:   7
301        a WAIT instruction as the last line of the function, where the
302        unresolved label jumps to. This creates a final signal tree.
303        The number of such labels that have to be explicitly resolved
304        is counted.
305      - It stretches short jumps. All indirect jumps (i.e. all jumps
306        except those with "JUMP label") can at most jump over
307        `EveCodeGen.maxShortJump' lines. If an indirect jump is longer
308        an explicit "JUMP" command has to be inserted at the next line with
309        the label for the indirect jump that is too long. The indirect
310        jump itself is transformed to `+1' (e.g. the following transformation
311        is necessary if the label `l1' is more than `maxShortJump' lines away:
312
313                      ADD r,r1,l1   ---->   ADD   r,r1,+1
314                                            JUMP  l1
315
316        The number of such stretched jumps is counted.                       *)
317
318  (* ---------------------------------------------------------------------- *)
319  (*                                                                        *)
320  (*         Create output and fetch information for more output            *)
321  (*                                                                        *)
322  (* ---------------------------------------------------------------------- *)
323
324  PROCEDURE WriteToFile;
325   (*
326     This procedure writes the code area into the files "<filename>.asm" and
327     "<filename>.ase". The former contains all function definitions and the
328     latter contains the body of MAIN as the result expression. <filename> is
329     the name of the input file with which the compiler was called.
330     Here the global variables for the start and the end of MAIN are needed.
331     The pseudo commands are used here to determine the format of special
332     lines in the output files (e.g. `nop' for creating an empty line).      *)
333
334  PROCEDURE StatisticsOfCode (VAR nrOfLines, nrOfExportedFcts : CARDINAL;
335                              VAR codeSize : LONGCARD);
336   (*
337     This procedure fetches some statistical information of the code
338     generation. This contains:
339      nrOfLines ... the total number of lines (including MAIN and comment
340                    lines) in the generated code.
341      nrOfExportedFcts ... the number of functions in the `.asm' file that are
342                           included in the export list.
343      codeSize ... the size of the generated code in bytes.                  *)
344
345
346  END EveCode.

EveEnvironment                                                        Page:   1
 1  DEFINITION MODULE EveEnvironment;    (*  Data Encapsulation of Environment
 2  ==========================================================================
 3   This module contains the opaque data type of an Environment and all
 4   procedures that are necessary to add something to an environment,
 5   delete something out of an environment or extract information out of an
 6   environment.
 7   ------------------------------------------------------------------------- *)
 8
 9  FROM EveAST      IMPORT Flags;
10  FROM EveRegister IMPORT Register;
11  FROM EveLabel    IMPORT Label;
12
13
14  TYPE Environment;                    (* Hidden data type of an environment *)
15
16  CONST
17       maxArrayEnvLen = 16;
18
19  TYPE
20       ArrayOfEnv = ARRAY [1..maxArrayEnvLen] OF Environment;
21
22  VAR
23       initEnv : Environment;    (* Empty environment (at begin of code gen. *)
24
25  (* ----------------------------------------------------------------------- *)
26  (*                                                                         *)
27  (*           Creation and destruction of an environment                    *)
28  (*                                                                         *)
29  (* ----------------------------------------------------------------------- *)
30
31  PROCEDURE InitEnv (VAR env : Environment);
32   (*
33     Initialize an environment. Necessary, since `Environment' is an
34     opaque data type.                                                       *)
35
36  PROCEDURE DisposeEnv (VAR env : Environment);
37   (*
38     Discard the whole environment and free the allocated memory for
39     the environment and for the environment descriptor (i.e. afterwards
40     `env' is no environment any more).                                      *)
41
42  PROCEDURE DeleteEnv (env : Environment);
43   (*
44     Discard the whole environment and free the allocated memory for the
45     environment. As the environment descriptor is reset but valid
46     `env' is afterwards the empty environment.                              *)
47
48  (* ----------------------------------------------------------------------- *)
49  (*                                                                         *)
50  (*                Adding information to an environment                     *)

EveEnvironment                                                        Page:   2
51  (*                                                                         *)
52  (* ----------------------------------------------------------------------- *)
53
54  PROCEDURE AddToEnv (VAR env : Environment; var : CARDINAL;
55                      register : Register; label : Label;
56                      flag : Flags);
57   (*
58     This procedure changes the given environment `env' by binding the
59     variable `var' to a triple of `register', `label' and `flag'.
60     This is done by adding one entry, containing these four values,
61     to the environment `env'.                                               *)
62
63
64  PROCEDURE BindVar (env : Environment; var : CARDINAL; label : Label);
65   (*
66     This procedure changes the environment `env' by binding the variable
67     `var' to the label `label'. An entry for `var' must already exist in
68     `env'. In this entry the label field is replaced by `label'.            *)
69
70  (* ----------------------------------------------------------------------- *)
71  (*                                                                         *)
72  (*           Extracting information from an environment                    *)
73  (*                                                                         *)
74  (* ----------------------------------------------------------------------- *)
75
76  PROCEDURE lookupRegister (env : Environment; var : CARDINAL) : Register;
77   (*
78     Return the register to which the variable `var' is bound in the
79     environment `env'. If `var' doesn't exist in `env' an error occurs.     *)
80
81  PROCEDURE lookupLabel (env : Environment; var : CARDINAL) : Label;
82   (*
83     Return the label to which the variable `var' is bound in the
84     environment `env'. If `var' doesn't exist in `env' an error occurs.     *)
85
86  PROCEDURE lookupFlag (env : Environment; var : CARDINAL) : Flags;
87   (*
88     Return the flag to which the variable `var' is bound in the
89     environment `env'. If `var' doesn't exist in `env' an error occurs.     *)
90
91  (* ----------------------------------------------------------------------- *)
92  (*                                                                         *)
93  (*                     Duplication of an environment                       *)
94  (*                                                                         *)
95  (* ----------------------------------------------------------------------- *)
96
97  PROCEDURE CopyEnv (env1 : Environment; VAR env2 : Environment);
98   (*
99     Copies the whole environment, with all of its entries, from `env1' to
100     `env2'. This copying automatically initializes `env1' and therefore

EveEnvironment                                                        Page:   3
101     there is no explicit `InitEnv(env2)' necessary.                         *)
102
103  PROCEDURE FlatCopyEnv (env1 : Environment; VAR env2 : Environment);
104   (*
105     Creates a second reference to `env1' in `env2'. This means that afterwards
106     `env1' and `env2' are ALIASES, so every change in `env1' also affects
107     `env2' and vice versa. Hence, this procedure should only be used when
108     only one of these two environments will be changed afterwards.          *)
109
110  (* ----------------------------------------------------------------------- *)
111  (*                                                                         *)
112  (*               Print an environment (only for testing)                   *)
113  (*                                                                         *)
114  (* ----------------------------------------------------------------------- *)
115
116  PROCEDURE PrintEnv (env : Environment);
117   (*
118     This procedure prints the whole environment. Each entry will have the
119     form "(variable,register,label,flag)" in the output.
120     This is only used for testing.                                          *)
121
122  END EveEnvironment.

EveEvent                                                              Page:   1
 1  DEFINITION MODULE EveEvent;        (*  Data Encapsulation of Event Handling
 2  ===========================================================================
 3   This module contains the opaque data type of an Event and procedures for
 4   performing all necessary operations on that data structure (for creation,
 5   modification, destruction, ...  of events). It has the same structure as
 6   `EveLabel' and `EveRegister'.
 7   ------------------------------------------------------------------------- *)
 8
 9  FROM EveStringLib  IMPORT LongString;
10
11  TYPE  Event;                               (* Hidden data type of an Event *)
12
13  CONST
14        maxArrayEventLen = 64;
15
16  TYPE
17        ArrayOfEvent = ARRAY [1..maxArrayEventLen] OF Event;
18
19  VAR
20        noEvent : Event;                            (* Special, unique event *)
21
22  (* ----------------------------------------------------------------------- *)
23  (*                                                                         *)
24  (*                Creation and destruction of an Event                     *)
25  (*                                                                         *)
26  (* ----------------------------------------------------------------------- *)
27
28  PROCEDURE NewEvent() : Event;
29   (*
30     This procedure creates a new, unique event, thereby allocating
31     memory for the event. Such an explicit procedure
32     call is necessary as Event is an opaque data type.                      *)
33
34  PROCEDURE DropEvent (event : Event);
35   (*
36     This procedure discards the event `event' and frees the memory that
37     has been allocated for it. Afterwards `event' is no valid event
38     any more (i.e. no operations of this module may be performed on it).    *)
39
40  (* ----------------------------------------------------------------------- *)
41  (*                                                                         *)
42  (*                         Operations on Events                            *)
43  (*                                                                         *)
44  (* ----------------------------------------------------------------------- *)
45
46  PROCEDURE ConvertEvent (event : Event) : LongString;
47   (*
48     This procedure converts the event `event' into a unique string
49     that then appears in the output files. Usually, such a string begins
50     with the letter "e" and then has a unique number (unique

EveEvent                                                              Page:   2
51     with respect to the function in which it is used).                      *)
52
53  PROCEDURE AssignEvent (VAR newEvent : Event; oldEvent : Event);
54   (*
55     This procedure copies the value of the event `oldEvent' to `newEvent'.
56     The event `newEvent' need not be initialized as it is automatically
57     initialized in this procedure.                                          *)
58
59  PROCEDURE equalEvents (ev1, ev2 : Event) : BOOLEAN;
60   (*
61     This function tests whether the two events `ev1' and `ev2' are equal.
62     As `NewEvent' creates unique events, two events that have been created
63     by different calls to `NewEvent' can never be equal. They are only equal
64     if they have been derived via 0 or more calls to `AssignEvent' from the
65     same call to `NewEvent'. Both events must already be initialized,
66     otherwise an error occurs.                                              *)
67
68  (* ----------------------------------------------------------------------- *)
69  (*                                                                         *)
70  (*  Get the state of event creation and manipulate the creation of events  *)
71  (*                                                                         *)
72  (* ----------------------------------------------------------------------- *)
73
74  PROCEDURE getMaxEventNr () : CARDINAL;
75   (*
76     This function returns the number of events that have been created
77     since the last `ResetEvents'. Hence, this call delivers the last number
78     that is already occupied by an event. This can be viewed as the state
79     of the events. This function is needed to get the state of the events
80     in an Eve function. This state must be stored at the end of the
81     code generation of a function and it must be fetched again in
82     `AdjustCode' as eventually new events have to be created there.         *)
83
84  PROCEDURE ResetEvents;
85   (*
86     Reset the number of already created, unique events to 1. This is done
87     at the start of a function as the events only have to be unique within
88     one function. Hence, in each function the creation of new events starts
89     with the same value.                                                    *)
90
91  PROCEDURE SetEvent (nr : CARDINAL);
92   (*
93     This procedure sets the number of created events to a special value.
94     This is only needed in `AdjustCode' to restore the number of
95     created events at the end of the code generation for a specific Eve
96     function. The parameter for this procedure can therefore only be
97     got by a call to `EveCode.GetEventRegisterLabelNr'.                     *)
98
99  (* ----------------------------------------------------------------------- *)
100  (*                                                                         *)

EveEvent                                                              Page:   3
101  (*                  Print one event (only for testing)                     *)
102  (*                                                                         *)
103  (* ----------------------------------------------------------------------- *)
104
105  PROCEDURE PrintEvent (event : Event);
106   (*
107     This procedure prints one event in the same form as it will be written
108     to a file (it uses `ConvertEvent').
109     This procedure is only needed for testing.                              *)
110
111  END EveEvent.

EveLabel                                                              Page:   1
 1  DEFINITION MODULE EveLabel;       (*  Data Encapsulation of Label Handling
 2  ==========================================================================
 3   This module contains the opaque data type of a Label and procedures for
 4   performing all necessary operations on that data structure (for creation,
 5   modification, destruction, ...  of labels). It has the same structure as
 6   `EveEvent' and `EveRegister'.
 7   ------------------------------------------------------------------------- *)
 8
 9  FROM EveStringLib  IMPORT LongString;
10
11  TYPE  Label;                                (* Hidden data type of a Label *)
12
13  CONST
14        maxArrayLabelLen = 64;
15
16  TYPE
17        ArrayOfLabel = ARRAY [1..maxArrayLabelLen] OF Label;
18
19  VAR
20        noLabel, startLabel : Label;  (* `noLabel' creates no output in the  *)
21                                      (* output file. `startLabel' is the    *)
22                                      (* input label for the code generation *)
23                                      (* of the whole program (it is only    *)
24                                      (* used in `EveCodeGen.P').            *)
25
26  (* ----------------------------------------------------------------------- *)
27  (*                                                                         *)
28  (*                Creation and destruction of a Label                      *)
29  (*                                                                         *)
30  (* ----------------------------------------------------------------------- *)
31
32  PROCEDURE NewLabel() : Label;
33   (*
34     This procedure creates a new, unique label, thereby allocating
35     memory for the label. Such an explicit procedure
36     call is necessary as Label is an opaque data type.                      *)
37
38  PROCEDURE DropLabel (label : Label);
39   (*
40     This procedure discards the label `label' and frees the memory that
41     has been allocated for it. Afterwards `label' is no valid label
42     any more (i.e. no operations of this module may be performed on it).    *)
43
44  (* ----------------------------------------------------------------------- *)
45  (*                                                                         *)
46  (*                         Operations on Labels                            *)
47  (*                                                                         *)
48  (* ----------------------------------------------------------------------- *)
49
50  PROCEDURE ConvertLabel (label : Label) : LongString;

EveLabel                                                              Page:   2
51   (*
52     This procedure converts the label `label' into a unique string
53     that then appears in the output files. Usually, such a string begins
54     with the letter "l" and then has a unique number (unique
55     with respect to the function in which it is used).                      *)
56
57  PROCEDURE AssignLabel (VAR newLab : Label; oldLab : Label);
58   (*
59     This procedure copies the value of the label `oldLab' to `newLab'.
60     The label `newLab' need not be initialized as it is automatically
61     initialized in this procedure.                                          *)
62
63  PROCEDURE equalLabels (lab1, lab2 : Label) : BOOLEAN;
64   (*
65     This function tests whether the two labels `lab1' and `lab2' are equal.
66     As `NewLabel' creates unique labels, two labels that have been created
67     by different calls to `NewLabel' can never be equal. They are only equal
68     if they have been derived via 0 or more calls to `AssignLabel' from the
69     same call to `NewLabel'. Both labels must already be initialized,
70     otherwise an error occurs.                                              *)
71
72  (* ----------------------------------------------------------------------- *)
73  (*                                                                         *)
74  (*  Get the state of label creation and manipulate the creation of labels  *)
75  (*                                                                         *)
76  (* ----------------------------------------------------------------------- *)
77
78  PROCEDURE getMaxLabelNr () : CARDINAL;
79   (*
80     This function returns the number of labels that have been created
81     since the last `ResetLabels'. Hence, this call delivers the last number
82     that is already occupied by a label. This can be viewed as the state
83     of the labels. This function is needed to get the state of the labels
84     in an Eve function. This state must be stored at the end of the
85     code generation of a function and it must be fetched again in
86     `AdjustCode' as eventually new labels have to be created there.         *)
87
88  PROCEDURE ResetLabels;
89   (*
90     Reset the number of already created, unique labels to 1. This is done
91     at the start of a function as the labels only have to be unique within
92     one function. Hence, in each function the creation of new labels starts
93     with the same value.                                                    *)
94
95  PROCEDURE SetLabel (nr : CARDINAL);
96   (*
97     This procedure sets the number of created labels to a special value.
98     This is only needed in `AdjustCode' to restore the number of
99     created labels at the end of the code generation for a specific Eve
100     function. The parameter for this procedure can therefore only be

EveLabel                                                              Page:   3
101     got by a call to `EveCode.GetEventRegisterLabelNr'.                     *)
102
103  (* ----------------------------------------------------------------------- *)
104  (*                                                                         *)
105  (*                  Print one label (only for testing)                     *)
106  (*                                                                         *)
107  (* ----------------------------------------------------------------------- *)
108
109  PROCEDURE PrintLabel (label : Label);
110   (*
111     This procedure prints one label in the same form as it will be written
112     to a file (it uses `ConvertLabel').
113     This procedure is only needed for testing.                              *)
114
115  END EveLabel.

EveList                                                               Page:   1
 1  DEFINITION MODULE EveList;                (*  Data Encapsulation of a List
 2  ==========================================================================
 3   This module contains the opaque data type of a list of CARDINAL numbers
 4   and all procedures performing operations on this data type.
 5   ------------------------------------------------------------------------- *)
 6
 7  FROM EveStringLib    IMPORT LongString;
 8
 9  CONST
10       maxListLen = 256;
11       noValue    = 0;
12
13  TYPE
14       ListIndex = [0..maxListLen];
15
16       List;                      (* Hidden data type of a List of CARDINALs *)
17
18  CONST
19        maxArrayListLen = 256;
20
21  TYPE
22        ArrayOfList = ARRAY [1..maxArrayListLen] OF List;
23
24
25  VAR   emptylist : List;               (* Special, unique list. Only needed *)
26                                        (* in `EveCodeGen.P'.                *)
27
28  (* ----------------------------------------------------------------------- *)
29  (*                                                                         *)
30  (*                         Test functions on lists                         *)
31  (*                                                                         *)
32  (* ----------------------------------------------------------------------- *)
33
34  PROCEDURE existInList (x : CARDINAL; l : List) : BOOLEAN;
35   (*
36     Test whether the number `x' is an element of the list `l'.
37     As with all functions the list must be initialized otherwise an
38     error occurs.                                                           *)
39
40  PROCEDURE emptyList (l : List) : BOOLEAN;
41   (*
42     Check whether the given list `l' is empty. Immediately after
43     initialization a list is empty.                                         *)
44
45  PROCEDURE lengthOfList (l : List) : ListIndex;
46   (*
47     This function returns the length of the list `l'. If the length is 0
48     `emptyList' with that list must be TRUE.                                *)
49
50  (* ----------------------------------------------------------------------- *)

EveList                                                               Page:   2
51  (*                                                                         *)
52  (*                   Creation and destruction of lists                     *)
53  (*                                                                         *)
54  (* ----------------------------------------------------------------------- *)
55
56  PROCEDURE InitList (VAR l : List);
57   (*
58     This procedure initializes the list `l'. Every list must be initialized
59     before it is used. This is necessary as `List' is an opaque data type.
60     If an uninitialized list is fed as parameter to any of the other
61     procedures an error occurs.                                             *)
62
63  PROCEDURE MakeList (elems : ARRAY OF CARDINAL; len : CARDINAL;
64                      VAR l : List);
65   (*
66     This procedure takes an array of the length `len' of CARDINAL values,
67     namely `elems', and creates a list `l' out of it that contains the
68     same elements as `elems' in the same order. If `l' already contains
69     elements they are overwritten by the new ones.                          *)
70
71  PROCEDURE DisposeList (VAR l : List);
72   (*
73     This procedure discards the whole list `l' and frees the memory that was
74     allocated for the list cells and for the list descriptor. As also
75     the list descriptor is discarded `l' is no list any more after a call
76     of this procedure. Therefore, no operations of this module (except
77     `InitList') may be applied to it.                                       *)
78
79  PROCEDURE DeleteList (l : List);
80   (*
81     This procedure discards the whole list and frees the memory that was
82     allocated for the list cells. However, contrary to `DisposeList'
83     this procedure keeps and updates the list descriptor such that `l' is
84     an empty list after a call of this procedure.                           *)
85
86  (* ----------------------------------------------------------------------- *)
87  (*                                                                         *)
88  (*                          Operations on lists                            *)
89  (*                                                                         *)
90  (* ----------------------------------------------------------------------- *)
91
92  PROCEDURE AddToList (x : CARDINAL; VAR l : List);
93   (*
94     This procedure first checks whether `x' already exists in the list `l'.
95     If yes the `l' is unchanged. Otherwise the number `x' is added as
96     the last element to the list `l'.                                       *)
97
98  PROCEDURE AddToListWithoutCheckForDoubleEntry (x : CARDINAL; VAR l : List);
99   (*
100     This procedure adds the number `x' as the last element to the list `l'.

EveList                                                               Page:   3
101     Contrary to the previous function no check whether the number `x'
102     already exists in `l' is performed.                                     *)
103
104  PROCEDURE GetListElement (l : List; i : ListIndex) : CARDINAL;
105   (*
106     This function returns the `i'-th element of the list `l'. As this
107     module only handles lists of CARDINALs the result value is a
108     CARDINAL. If `l' has fewer than `i' elements an error occurs.           *)
109
110  PROCEDURE AssignList (VAR lhs : List; rhs : List);
111   (*
112     This procedure copies all list elements of `rhs' to `lhs'.
113     The list `lhs' must already be initialized. If `lhs' contains
114     elements before this operation these elements are overwritten by
115     the elements of `rhs'.                                                  *)
116
117  PROCEDURE ConcatenateLists (VAR l1, l2 : List);
118   (*
119     This procedure combines the lists `l1' and `l2' to one list that
120     begins with the elements `l1' and then contains the elements of `l2'.
121     The resulting list is `l1'. This is done by redirecting the pointer
122     of the last element of `l1' to the first element of `l2'.
123     NOTE: This procedure is destructive. After a call to this procedure
124           `l2' and the new `l1' are ALIASES i.e. the new part of `l1'
125           and `l2' are identical. Therefore, this procedure should only be
126           used when only `l1' but not `l2' is used afterwards!              *)
127
128  PROCEDURE AppendLists (VAR l1 : List; l2 : List);
129   (*
130     This procedure combines the lists `l1' and `l2' to one list that
131     begins with the elements `l1' and then contains the elements of `l2'.
132     The resulting list is `l1'. Contrary to the previous procedure,
133     this procedure is not destructive. Instead, it copies all the
134     elements of `l2' and adds them one by one to `l1'. Furthermore,
135     only those elements of `l2' are added to `l1' that are not
136     already contained in `l1'. Hence, if `l1' does not contain multiple
137     entries of a value before it won't contain multiple entries of a value
138     afterwards.                                                             *)
139
140  PROCEDURE IntersectLists (VAR l1 : List; l2, l3 : List);
141   (*
142     This procedure performs a ``set intersection'' on the lists `l2' and
143     `l3' yielding the list `l1' as a result. As this operation is only
144     used on lists without multiple entries the notion from set theory
145     can be applied here. Hence, `l1' contains those elements that occur
146     in `l2' and `l3'. The elements in `l1' are in the same order as the
147     elements in `l2'.
148     NOTE: `l1' need not be initialized before this procedure is called. It
149           is automatically initialized within this procedure.               *)

EveList                                                               Page:   4
151  PROCEDURE DifferenceLists (VAR l1 : List; l2, l3 : List);
152   (*
153     This procedure performs a ``set difference'' on the lists `l2' and
154     `l3' yielding the list `l1' as a result. As this operation is only
155     used on lists without multiple entries the notion from set theory
156     can be applied here. Hence, `l1' contains those elements that occur
157     in `l2' but not in `l3'. The elements in `l1' are in the same order
158     as the elements in `l2'.
159     NOTE: `l1' need not be initialized before this procedure is called. It
160           is automatically initialized within this procedure.               *)
161
162  PROCEDURE GetListIndex (l : List; name : CARDINAL) : ListIndex;
163   (*
164     This function searches in the list `l' for the value `name'. If
165     `name' does not occur in `l' an error occurs. Otherwise, the function
166     returns the position at which `name' occurs in `l' (counting starts
167     with 1). Hence, the return value must be between 1 and
168     lengthOfList(l).                                                        *)
169
170  PROCEDURE SetListElement (VAR l : List; i : ListIndex; x : CARDINAL);
171   (*
172     This procedure stores the number `x' as the `i'-th element of the
173     list `l'. The value that was previously stored at that place is
174     overwritten. The index `i' must be between 1 and lengthOfList(l)
175     otherwise an error occurs.                                              *)
176
177  PROCEDURE RemoveFromList (VAR l1 : List; l2 : List);
178   (*
179     This procedure removes all numbers that are elements of `l2' from `l1'.
180     The only difference to `DifferenceLists' is that the changes are
181     directly performed in `l1' and no new list has to be allocated.
182     As this procedure directly operates on the internal structure of a list,
183     this procedure is more efficient than `DifferenceLists'.                *)
184
185  (* ----------------------------------------------------------------------- *)
186  (*                                                                         *)
187  (*                Get statistical information about lists                  *)
188  (*                                                                         *)
189  (* ----------------------------------------------------------------------- *)
190
191  PROCEDURE GetSizeOfList (l : List) : LONGCARD;
192   (*
193     This function returns the size of the list `l' in bytes.
194     As lists might become very long the type of the returned size is
195     LONGCARD instead of CARDINAL.                                           *)
196
197  (* ----------------------------------------------------------------------- *)
198  (*                                                                         *)
199  (*            Procedures for testing the structure of a list               *)
200  (*                                                                         *)

EveList                                                               Page:   5
201  (* ----------------------------------------------------------------------- *)
202
203  PROCEDURE PrintList (l : List);
204   (*
205     This procedure prints the whole list `l'. The output has the form:
206                 [l_1, l_2, ... , l_lengthOfList(l)]
207     where l_1, l_2, ..., l_lengthOfList(l) are the elements of that list in
208     the same order as they are stored in `l'.
209     This procedure is only used for testing.                                *)
210
211  PROCEDURE CheckListConsistency (l : List);
212   (*
213     This procedure checks if the information that is stored in the list
214     descriptor is consistent with the actual structure of the list itself.
215     This procedure is only used for testing.                                *)
216
217  END EveList.

EveListList                                                           Page:   1
 1  DEFINITION MODULE EveListList;   (*  Data Encapsulation of a List of Lists
 2  ==========================================================================
 3   This module contains the opaque data type of a list of lists of CARDINAL
 4   numbers (in the following called `ListList') and all procedures performing
 5   operations on this data type. The structure of this module is essentially
 6   the same as that of `EveList'. The operations in this module have
 7   counterparts in `EveList' with the only difference that they operate on
 8   lists of CARDINALs instead of CARDINALs themselves.
 9   ------------------------------------------------------------------------- *)
10
11  FROM EveStringLib    IMPORT LongString;
12  FROM EveList         IMPORT List;
13
14  CONST
15       maxListListLen = 256;
16
17  TYPE
18       ListListIndex = [0..maxListListLen];
19
20       ListList;         (* Hidden data type of a List of Lists of CARDINALs *)
21
22  CONST
23        maxArrayListListLen = 256;
24
25  TYPE
26        ArrayOfListList = ARRAY [1..maxArrayListListLen] OF List;
27
28
29  VAR
30        emptylistlist : ListList;                (* Special, unique ListList *)
31
32
33  (* ----------------------------------------------------------------------- *)
34  (*                                                                         *)
35  (*                      Test functions on ListLists                        *)
36  (*                                                                         *)
37  (* ----------------------------------------------------------------------- *)
38
39  PROCEDURE existInListList (x : List; ll : ListList) : BOOLEAN;
40   (*
41     Test whether the list `x' is an element of the ListList `ll'.
42     As with all functions the ListList must be initialized otherwise an
43     error occurs.                                                           *)
44
45  PROCEDURE emptyListList (ll : ListList) : BOOLEAN;
46   (*
47     Check whether the given ListList `ll' is empty. Immediately after
48     initialization a ListList is empty.                                     *)
49
50  PROCEDURE lengthOfListList (ll : ListList) : ListListIndex;

EveListList                                                           Page:   2
51   (*
52     This function returns the length of the ListList `ll'. If the length is 0
53     `emptyListList' with that ListList must be TRUE.                        *)
54
55  (* ----------------------------------------------------------------------- *)
56  (*                                                                         *)
57  (*                 Creation and destruction of ListLists                   *)
58  (*                                                                         *)
59  (* ----------------------------------------------------------------------- *)
60
61  PROCEDURE InitListList (VAR ll : ListList);
62   (*
63     This procedure initializes the ListList `ll'. Every ListList must be
64     initialized before it is used. This is necessary as `ListList' is an
65     opaque data type. If an uninitialized ListList is fed as parameter to
66     any of the other procedures an error occurs.                            *)
67
68  PROCEDURE MakeListList (elems : ARRAY OF List; len : CARDINAL;
69                          VAR ll : ListList);
70   (*
71     This procedure takes an array of the length `len' of Lists,
72     namely `elems', and creates a ListList `ll' out of it that contains the
73     same elements as `elems' in the same order. If `ll' already contains
74     elements they are overwritten by the new ones.                          *)
75
76  PROCEDURE DisposeListList (VAR ll : ListList);
77   (*
78     This procedure discards the whole ListList `ll' and frees the memory
79     that was allocated for the ListList cells and for the ListList
80     descriptor. As also the ListList descriptor is discarded `ll' is no
81     ListList any more after a call of this procedure. Therefore, no
82     operations of this module (except `InitListList') may be applied to it. *)
83
84  PROCEDURE DeleteListList (ll : ListList);
85   (*
86     This procedure discards the whole ListList `ll' and frees the memory that
87     was allocated for the ListList cells. However, contrary to
88     `DisposeListList' this procedure keeps and updates the ListList
89     descriptor such that `ll' is an empty ListList after a call of this
90     procedure.                                                              *)
91
92  (* ----------------------------------------------------------------------- *)
93  (*                                                                         *)
94  (*                        Operations on ListLists                          *)
95  (*                                                                         *)
96  (* ----------------------------------------------------------------------- *)
97
98  PROCEDURE AddToListList (x : List; VAR ll : ListList);
99   (*
100     This procedure first checks whether `x' already exists in the ListList

EveListList                                                           Page:   3
101     `ll'. If yes the ListList `ll' is unchanged. Otherwise the list `x' is
102     added as the last element to the ListList `ll'.                         *)
103
104  PROCEDURE GetListListElement (ll : ListList; i : ListListIndex) : List;
105   (*
106     This function returns the `i'-th element of the ListList `ll'. As this
107     module only handles Lists of Lists of CARDINALs the result value is a
108     List of CARDINALs. If `ll' has fewer than `i' elements an error occurs. *)
109
110  PROCEDURE AssignListList (VAR lhs : ListList; rhs : ListList);
111   (*
112     This procedure copies all ListList elements of `rhs' to `lhs'.
113     The ListList `lhs' must already be initialized. If `lhs' contains
114     elements before this operation these elements are overwritten by
115     the elements of `rhs'.                                                  *)
116
117  PROCEDURE ConcatenateListLists (VAR ll1 : ListList; ll2 : ListList);
118   (*
119     This procedure combines the ListLists `ll1' and `ll2' to one ListList
120     that begins with the elements `ll1' and then contains the elements of
121     `ll2'. The resulting ListList is `ll1'. This is done by redirecting the
122     pointer of the last element of `ll1' to the first element of `ll2'.
123     NOTE: This procedure is destructive. After a call to this procedure
124           `ll2' and the new `ll1' are ALIASES i.e. the new part of `ll1'
125           and `ll2' are identical. Therefore, this procedure should only be
126           used when only `ll1' but not `ll2' is used afterwards!            *)
127
128  PROCEDURE AppendListLists (VAR ll1 : ListList; ll2 : ListList);
129   (*
130     This procedure combines the ListLists `ll1' and `ll2' to one ListList that
131     begins with the elements `ll1' and then contains the elements of `ll2'.
132     The resulting ListList is `ll1'. Contrary to the previous procedure,
133     this procedure is not destructive. Instead, it copies all the
134     elements of `ll2' and adds them one by one to `ll1'. Furthermore,
135     only those elements of `ll2' are added to `ll1' that are not
136     already contained in `ll1'. Hence, if `ll1' does not contain multiple
137     entries of a value before it won't contain multiple entries of a value
138     afterwards.                                                             *)
139
140  PROCEDURE GetListListIndex (ll : ListList; x : List) : ListListIndex;
141   (*
142     This function searches in the ListList `ll' for the list `x'. If
143     `x' does not occur in `ll' an error occurs. Otherwise, the function
144     returns the position at which `x' occurs in `ll' (counting starts
145     with 1). Hence, the return value must be between 1 and
146     lengthOfListList(ll).                                                   *)
147
148  PROCEDURE SetListListElement(VAR ll : ListList; i : ListListIndex; x : List);
149   (*
150     This procedure stores the list `x' as the `i'-th element of the

EveListList                                                           Page:   4
151     ListList `ll'. The value that was previously stored at that place is
152     overwritten. The index `i' must be between 1 and lengthOfListList(ll)
153     otherwise an error occurs.                                              *)
154
155  (* ----------------------------------------------------------------------- *)
156  (*                                                                         *)
157  (*        Procedure for printing a List of Lists (only for testing)        *)
158  (*                                                                         *)
159  (* ----------------------------------------------------------------------- *)
160
161  PROCEDURE PrintListList (ll : ListList);
162   (*
163     This procedure prints the whole ListList `ll'. The output has the form:
164                 [ll_1, ll_2, ... , ll_lengthOfListList(ll)]
165     where ll_1, ll_2, ..., ll_lengthOfListList(ll) are the elements of that
166     ListList in the same order as they are stored in `ll'.
167     This procedure is only used for testing.                                *)
168
169  END EveListList.

EveRegister                                                           Page:   1
 1  DEFINITION MODULE EveRegister;  (*  Data Encapsulation of Register Handling
 2  ===========================================================================
 3   This module contains the opaque data type of a Register and procedures for
 4   performing all necessary operations on that data structure (for creation,
 5   modification, destruction, ...  of registers). It has the same structure
 6   as `EveEvent' and `EveLabel'.
 7   ------------------------------------------------------------------------- *)
 8
 9  FROM EveStringLib  IMPORT LongString;
10
11  TYPE  Register;                          (* Hidden data type of a Register *)
12
13  CONST
14        maxArrayRegisterLen = 64;
15
16  TYPE
17        ArrayOfRegister = ARRAY [1..maxArrayRegisterLen] OF Register;
18
19  VAR
20        noRegister : Register;                   (* Special, unique register *)
21
22  (* ----------------------------------------------------------------------- *)
23  (*                                                                         *)
24  (*                Creation and destruction of a Register                   *)
25  (*                                                                         *)
26  (* ----------------------------------------------------------------------- *)
27
28  PROCEDURE NewRegister() : Register;
29   (*
30     This procedure creates a new, unique register, thereby allocating
31     memory for the register. Such an explicit procedure
32     call is necessary as Register is an opaque data type.                   *)
33
34  PROCEDURE DropRegister (register : Register);
35   (*
36     This procedure discards the register `register' and frees the memory that
37     has been allocated for it. Afterwards `register' is no valid register
38     any more (i.e. no operations of this module may be performed on it).    *)
39
40  (* ----------------------------------------------------------------------- *)
41  (*                                                                         *)
42  (*                         Operations on Registers                         *)
43  (*                                                                         *)
44  (* ----------------------------------------------------------------------- *)
45
46  PROCEDURE ConvertRegister (reg : Register) : LongString;
47   (*
48     This procedure converts the register `reg' into a unique string
49     that then appears in the output files. Usually, such a string begins
50     with the letter "r" and then has a unique number (unique

EveRegister                                                           Page:   2
51     with respect to the function in which it is used).                      *)
52
53  PROCEDURE AssignRegister (VAR newReg : Register; oldReg : Register);
54   (*
55     This procedure copies the value of the register `oldReg' to `newReg'.
56     The register `newReg' need not be initialized as it is automatically
57     initialized in this procedure.                                          *)
58
59  PROCEDURE equalRegisters (reg1, reg2 : Register) : BOOLEAN;
60   (*
61     This function tests whether the two registers `reg1' and `reg2' are equal.
62     As `NewRegister' creates unique registers, two registers that have been
63     created by different calls to `NewRegister' can never be equal. They are
64     only equal if they have been derived via 0 or more calls to
65     `AssignRegister' from the same call to `NewRegister'. Both registers must
66     already be initialized, otherwise an error occurs.                      *)
67
68  (* ----------------------------------------------------------------------- *)
69  (*                                                                         *)
70  (*               Get the state of register creation and                    *)
71  (*                manipulate the creation of registers                     *)
72  (*                                                                         *)
73  (* ----------------------------------------------------------------------- *)
74
75  PROCEDURE getMaxRegisterNr () : CARDINAL;
76   (*
77     This function returns the number of registers that have been created
78     since the last `ResetRegisters'. Hence, this call delivers the last number
79     that is already occupied by an register. This can be viewed as the state
80     of the registers. This function is needed to get the state of the
81     registers in an Eve function. This state must be stored at the end of the
82     code generation of a function and it must be fetched again in
83     `AdjustCode' as eventually new registers have to be created there.      *)
84
85  PROCEDURE ResetRegisters;
86   (*
87     Reset the number of already created, unique registers to 1. This is done
88     at the start of a function as the registers only have to be unique within
89     one function. Hence, in each function the creation of new registers starts
90     with the same value.                                                    *)
91
92  PROCEDURE SetRegister (nr : CARDINAL);
93   (*
94     This procedure sets the number of created registers to a special value.
95     This is only needed in `AdjustCode' to restore the number of
96     created registers at the end of the code generation for a specific Eve
97     function. The parameter for this procedure can therefore only be
98     got by a call to `EveCode.GetRegisterRegisterLabelNr'.                  *)
99
100  (* ----------------------------------------------------------------------- *)

EveRegister                                                           Page:   3
101  (*                                                                         *)
102  (*                  Print one register (only for testing)                  *)
103  (*                                                                         *)
104  (* ----------------------------------------------------------------------- *)
105
106  PROCEDURE PrintRegister (register : Register);
107   (*
108     This procedure prints one register in the same form as it will be written
109     to a file (it uses `ConvertRegister').
110     This procedure is only needed for testing.                              *)
111
112  END EveRegister.

EveStringLib                                                          Page:   1
 1  DEFINITION MODULE EveStringLib;           (* Encapsulating the type String
 2  ==========================================================================
 3   This module contains the data type of a string (in versions with different
 4   lengths) and all procedures for performing operations on this data type.
 5   ------------------------------------------------------------------------- *)
 6
 7  CONST
 8        stringEnd        = 0C;                    (* End of string character *)
 9
10        stringLength       = 40;            (* Length of a normal string     *)
11        longStringLength   = 80;            (* Length of a long string       *)
12        xlongStringLength  = 900;           (* Length of a extra long string *)
13
14  TYPE
15      String       = ARRAY [0..stringLength-1]       OF CHAR; (*Normal String*)
16      LongString   = ARRAY [0..longStringLength-1]   OF CHAR; (*Long String  *)
17      XLongString  = ARRAY [0..xlongStringLength-1]  OF CHAR; (*XLong String *)
18
19  (* ----------------------------------------------------------------------- *)
20  (*                                                                         *)
21  (*              Some important operations on strings                       *)
22  (*                                                                         *)
23  (* ----------------------------------------------------------------------- *)
24
25  PROCEDURE length (line : ARRAY OF CHAR) : CARDINAL;
26   (*
27     Determine the actual length of a string.                                *)
28
29  PROCEDURE Slice (VAR result : ARRAY OF CHAR;
30                   VAR source : ARRAY OF CHAR;
31                       position, length : CARDINAL);
32   (*
33     Get the substring of `source' that starts at the index `position' and
34     is `length' characters long. The resulting string is stored in `result'.
35     If the string `source' is not long enough `reult' is truncated
36     appropriately.                                                          *)
37
38  PROCEDURE equalStr (str1, str2 : ARRAY OF CHAR) : BOOLEAN;
39   (*
40     Test whether the two strings `str1' and `str2' contain the same
41     characters in the same order.                                           *)
42
43  PROCEDURE CardToStr (    card : CARDINAL;
44                       VAR str  : ARRAY OF CHAR;
45                       VAR ok   : BOOLEAN);
46   (*
47     Convert a CARDINAL number, namely `card', to a string `str' that
48     represents the value `str'. Iff the conversion was successful `ok'
49     returns TRUE.                                                           *)

EveStringLib                                                          Page:   2
51  PROCEDURE Append (VAR str1 : ARRAY OF CHAR; str2 : ARRAY OF CHAR);
52   (*
53     Add the characters of `str2' at the end of `str1'.                      *)
54
55  PROCEDURE AddChar (ch : CHAR; VAR str : ARRAY OF CHAR);
56   (*
57     Add one character, namely `ch', at the end of `str'.                    *)
58
59  PROCEDURE DeleteLastChar (ch : CHAR; VAR str : ARRAY OF CHAR;
60                            VAR ok : BOOLEAN);
61   (*
62     This procedure gets the last character of the string `str' that is not
63     a blank. If this character is not `ch' then `ok' is set to FALSE and no
64     deletion of a character takes place. Otherwise, this character is
65     deleted by replacing it by a 0C character and `ok' is set to TRUE.      *)
66
67  PROCEDURE LongStringToString (longStr : LongString) : String;
68   (*
69     This procedure converts a LongString to a String. If the actual
70     length of `longStr' is bigger than `stringLength' then the rest of
71     the LongString is truncated.                                            *)
72
73  PROCEDURE CutOffFromChar (ch : CHAR; VAR str : ARRAY OF CHAR);
74   (*
75     This procedure deletes all characters after (and including) the first
76     `ch' character in the String `str'. This is done by replacing the
77     first `ch' character by a 0C character.                                 *)
78
79  END EveStringLib.

EveSym                                                                Page:   1
 1  DEFINITION MODULE EveSym; (* Data Encapsulation of Symbol Table & Symbol List
 2  =============================================================================
 3   This module contains the two opaque data types of a symbol table and of a
 4   symbol list, where the later is essentially a list of pointers to the
 5   former. Hence, the important information of all objects in an Eve program
 6   is stored in the symbol table. The symbol list is only used to handle
 7   the scoping of the Eve objects correctly (Symbol list is organized in a
 8   stack like fashion. New entries are added to the list when  a new block
 9   in the Eve program is entered and the local elements of that block are
10   deleted from the symbol list at the end of that block).
11   Furthermore, this module contains all procedures that operate on these
12   two data types.
13   ------------------------------------------------------------------------- *)
14
15  FROM EveType     IMPORT EType;
16  FROM EveAST      IMPORT ASType;
17  FROM EveList     IMPORT List;
18  FROM EveListList IMPORT ListList;
19
20  CONST
21       noObject = 0;                                (* Special unique object *)
22       OpKindX = "parKind,fctKind,constKind,typeKind,errorKind";
23                                                    (* Only for printing     *)
24  TYPE
25       Object = CARDINAL;                          (* Index in symbol table  *)
26       OpType = EType;                             (* Operand Types is EType *)
27       OpKind = (parKind, fctKind, constKind, typeKind, errorKind);
28
29       Operand = RECORD                           (* Operand gives the       *)
30                  name    : CARDINAL;             (* description of any      *)
31                  CASE  kind : OpKind  OF         (* object in Eve (function *)
32                    fctKind :   freeVars  : List; (* variable, parameter,..) *)
33                                occurIn   : List;
34                  END;
35                  type    : OpType;               (* Type of the object      *)
36                  level   : CARDINAL;             (* Static nesting level    *)
37                  forward : BOOLEAN;              (* Object defined forward? *)
38                  ast     : ASType;               (* AST for eval. object    *)
39                 END;
40
41  VAR
42        objOfMAIN           : Object;       (* Index of MAIN in symbol table *)
43        opOfMAIN            : Operand;      (* Operand description of MAIN   *)
44        nrPredef            : CARDINAL;     (* Nr. of predef objects         *)
45
46  (* ---------------------------------------------------------------------- *)
47  (*                                                                        *)
48  (*      Procedures for adding Eve objects to the symbol table             *)
49  (*                                                                        *)
50  (* ---------------------------------------------------------------------- *)

EveSym                                                                Page:   2
51
52   (*
53     The following procedures add a new entry to the symbol table and add the
54     index of this new entry to the symbol list. If there is already an
55     entry on the same level that has `name' in the name field of the operand
56     then an error occurs. The procedure below only differ in the value
57     that is written to the `kind' field of the operand.
58     The default entries for the other fields are:
59       name   ... `name' (parameter of the procedure)
60       type   ... `eType' (parameter of the procedure; this field may be
61                  manipulated by the following procedures:
62                    AssignType  ... explicitly setting the type of an object
63                    MakeForward*, ResolveForward* ... if there is a forward
64                       definition of that onject that only defines its
65                       type but not its value.
66       level  ... CurrentLevel() (the static nesting level of the current
67                  block in the Eve program)
68       forward ... FALSE (only changed by MakeForward*, ResolveForward* )
69       ast     ... emptyAST (this field may later be filled by a call to
70                   `AssignAST').                                             *)
71
72  PROCEDURE NewConst (name : CARDINAL; eType : EType);
73   (*
74     Add constant operand to symbol table and symbol list
75     (i.e. .kind := constKind).                                              *)
76
77  PROCEDURE NewType (name : CARDINAL);
78   (*
79     Add type operand to symbol table and symbol list
80     (i.e. .kind := typeKind). The `.type' field will later be explicitly
81     set via a call to `AssignType'.                                         *)
82
83  PROCEDURE NewPar (name : CARDINAL; eType : EType);
84   (*
85     Add parameter operand to symbol table and symbol list
86     (i.e. .kind := parKind).                                                *)
87
88  PROCEDURE NewFct (name : CARDINAL; eType : EType);
89   (*
90     Add function operand to symbol table and symbol list
91     (i.e. .kind := fctKind).  The fields `.freeVars', containing all free
92     variables that occur in the body of the function definition, and
93     `.occurIn', containing all functions that are called in the body
94     of the function definition, are initialized in this procedure.          *)
95
96  PROCEDURE NewParList (name : CARDINAL; idList : ARRAY OF CARDINAL;
97                        typeList : ARRAY OF EType; len : CARDINAL  );
98   (*
99     Add `len' parameter operands to the symbol table and symbol list
100     (i.e. `.kind' := parKind). If `name' does not exist or is not

EveSym                                                                Page:   3
101     a function an error occurs. In this procedure `NewPar' is called
102     with `idList[i]' and `typeList[i]' for each 1<=i<=len.                  *)
103
104  PROCEDURE NewIdent (VAR name : CARDINAL; eType : EType; ast : ASType);
105   (*
106     Add a new constant operand to the symbol table but NOT to the symbol
107     list. This procedure gets a new, unique identifier by a call to
108     `EveLexAn.GetNewIdent' and uses it as the `.name' field of the new
109     entry in the symbol table. This procedure is needed in
110     `EveAST.EliminateCASE' to get variables where the tag and the body
111     of a union can be bound to.                                             *)
112
113  (* ----------------------------------------------------------------------- *)
114  (*                                                                         *)
115  (*       Searching for entries in the symbol list and extracting           *)
116  (*     information from the corresponding entry in the symbol table        *)
117  (*                                                                         *)
118  (* ----------------------------------------------------------------------- *)
119
120  PROCEDURE SearchType (name : CARDINAL; VAR eType : EType);
121   (*
122     Search in the symbol list for an operand of `typeKind' with the `name'
123     in the `.name' field. If such an operand exists `eType' gets the
124     type of that operand. Otherwise a semantic error occurs.                *)
125
126  PROCEDURE GetType (name : CARDINAL; VAR eType : EType);
127   (*
128     Same as `SearchType' but if no appropriate operand is found in the
129     symbol list a compiler error occurs.                                    *)
130
131  PROCEDURE FindObject (name : CARDINAL; VAR obj : CARDINAL; VAR op : Operand);
132   (*
133     Search in the symbol list for an operand that has `name' in the `.name'
134     field. If such an operand is found, `obj' gets the index of this
135     operand in the symbol table and `op' gets the contents of the
136     operand itself. If it is not found then `obj' gets the special value
137     `noObject' and `op' is undefined in this case.                          *)
138
139  (* ----------------------------------------------------------------------- *)
140  (*                                                                         *)
141  (*            Explicit setting of fields of certain operands               *)
142  (*                                                                         *)
143  (* ----------------------------------------------------------------------- *)
144
145  PROCEDURE AssignType (name : CARDINAL; eType : EType);
146   (*
147     Search in the symbol list for an operand that has `name' in the `.name'
148     field. If such an operand is found, and its type is `errorType'
149     then this `.type' field is set to `eType'. Otherwise an error occurs.   *)

EveSym                                                                Page:   4
151  PROCEDURE AssignAST  (name : CARDINAL; ast : ASType);
152   (*
153     Search in the symbol list for an operand that has `name' in the `.name'
154     field. If such an operand is found, and its AST is `emptyAST'
155     then this `.ast' field is set to `ast'. Otherwise an error occurs.      *)
156
157  (* ----------------------------------------------------------------------- *)
158  (*                                                                         *)
159  (*                   Handling of forward definitions                       *)
160  (*                                                                         *)
161  (* ----------------------------------------------------------------------- *)
162
163   (* ------------------------------------------------------------------------
164     All the MakeForward* procedures search for an operand with `name' in
165     the `.name' field (error if not found). Hence, even in a forward
166     definition the Eve object first has to be entered to the symbol list
167     with one of the `New*' functions before a `MakeForward*' can be called.
168     In the found operand the `.forward' field is set to TRUE and the
169     `.type' field is set to `eType' (parameter of the function).
170     In the following cases errors occur:
171      - `name' isn't found in the symbol list.
172      - The level of the found operand is different from the current level
173        (i.e. there is no operand with the name `name' on this level).
174      - The found operand has a wrong `.kind field (the `.kind' field
175        in e.g. `MakeForwardType' must have the value `typeKind').
176     A compiler restriction occurs if:
177      - The same forward definition of an Eve object occurs twice on the
178        same level (if different forward definitions of the same Eve object
179        occur on the same level a semantic error will be produced).
180     ----------------------------------------------------------------------- *)
181
182  PROCEDURE MakeForwardType (name : CARDINAL; eType : EType);
183   (*
184     Changes the type operand of `name' into a forward operand and sets its
185     `.type' field to `eType'. Checks see above.                             *)
186
187  PROCEDURE MakeForwardConstant (name : CARDINAL);
188   (*
189     Changes the constant operand of `name' into a forward operand.
190     Checks see above.                                                       *)
191
192  PROCEDURE MakeForwardFct (name : CARDINAL);
193   (*
194     Changes the function operand of `name' into a forward operand.
195     Checks see above.                                                       *)
196
197   (* ------------------------------------------------------------------------
198     All the ResolveForward* procedures search for an operand with `name' in
199     the `.name' field (error if not found). In the found operand the
200     `.forward' field is set to FALSE. If an `ast' parameter is submitted

EveSym                                                                Page:   5
201     to the procedure the `.ast' field of the found operand is set to this
202     `ast'.
203     In the following cases errors occur:
204      - `name' isn't found in the symbol list.
205      - The `.forward' field of the found operand is FALSE.
206      - The found operand has a wrong `.kind field (the `.kind' field
207        in e.g. `ResolveForwardType' must have the value `typeKind').
208      - An `ast' is submitted and the `.ast' field of the found operand has
209        not the value `emptyAST'.
210    ------------------------------------------------------------------------ *)
211
212  PROCEDURE ResolveForwardType (name : CARDINAL);
213   (*
214     Changes the type operand of `name' from a forward operand back to a
215     normal operand. Checks see above.                                       *)
216
217  PROCEDURE ResolveForwardConstant (name : CARDINAL; ast : ASType);
218   (*
219     Changes the constant operand of `name' from a forward operand back to a
220     normal operand. Thereby, the `.ast' field of the operand is set
221     to `ast'. Checks see above.                                             *)
222
223  PROCEDURE ResolveForwardFct (name : CARDINAL; ast : ASType);
224   (*
225     Changes the function operand of `name' from a forward operand back to a
226     normal operand. Thereby, the `.ast' field of the operand is set
227     to `ast'. Checks see above.                                             *)
228
229  (* ----------------------------------------------------------------------- *)
230  (*                                                                         *)
231  (*        Get information about functions from the symbol table            *)
232  (*                                                                         *)
233  (* ----------------------------------------------------------------------- *)
234
235  PROCEDURE ExtractParTypes(name : CARDINAL;
236                            VAR typeList : ARRAY OF EType; VAR len : CARDINAL);
237   (*
238     Search in the symbol list for an operand that has `name' in the `.name'
239     field. If such an operand is not found or is not a function then an
240     error occurs. Otherwise `typeList' gets the types of the parameters
241     (excluding the type of the return value) of the Eve function `name'.
242     `len' returns the number of the parameters of `name'.
243     This information is actually got via a call to `EveType.GetParTypeList'.*)
244
245  PROCEDURE ExtractParNames (name : CARDINAL;
246                             VAR idList : ARRAY OF CARDINAL;
247                             VAR len : CARDINAL);
248   (*
249     Search in the symbol list for an operand that has `name' in the `.name'
250     field. If such an operand is not found or is not a function then an

EveSym                                                                Page:   6
251     error occurs. Otherwise `idList' gets the names of the parameters of
252     the Eve function `name'. `len' returns the number of the parameters of
253     `name'. This information is actually got via a call to
254     `EveType.GetParTypeList'.                                               *)
255
256  PROCEDURE arity (name : CARDINAL) : CARDINAL;
257   (*
258     Search in the symbol list for an operand that has `name' in the `.name'
259     field. If such an operand is not found or is not a function then an
260     error occurs. Otherwise the arity of the function is returned.          *)
261
262  (* ----------------------------------------------------------------------- *)
263  (*                                                                         *)
264  (*       Handling of blocks in Eve (includes scoping of variables)         *)
265  (*                                                                         *)
266  (* ----------------------------------------------------------------------- *)
267
268  PROCEDURE BeginFct (name : CARDINAL);
269   (*
270     This procedure opens a new block for a function and saves the state of
271     the current block.
272
273     Search in the symbol list for an operand that has `name' in the `.name'
274     field. If such an operand is not found or is not a function then an
275     error occurs. Otherwise the index of the the current procedure and the
276     current state of the symbol list are pushed on a stack.
277     The new current procedure is the function `name' and the counter of
278     the nesting levels is increased by one.                                 *)
279
280  PROCEDURE EndFct (name : CARDINAL);
281   (*
282     This procedure closes the current block for a function and restores the
283     state of the surrounding block.
284
285     Get the topmost two entries of the stack. They contain
286       1) the previous state of the symbol list of the surrounding block and
287       2) the current procedure of the surrounding block.
288     Furthermore, the counter of the nesting levels is decreased by one.     *)
289
290  PROCEDURE BeginBlock;
291   (*
292     This procedure opens a new block and saves the state of the current block.
293
294     The index of the current procedure and the current state of the symbol
295     list are pushed on the stack. The counter of the nesting levels is
296     increased by one.                                                       *)
297
298  PROCEDURE EndBlock ;
299   (*
300     This procedure closes the current block and restores the state of the

EveSym                                                                Page:   7
301     surrounding block.
302
303     Get the topmost two entries of the stack. They contain
304       1) the previous state of the symbol list of the surrounding block and
305       2) the current procedure of the surrounding block.
306     Furthermore, the counter of the nesting levels is decreased by one.     *)
307
308  (* ----------------------------------------------------------------------- *)
309  (*                                                                         *)
310  (*               Miscellaneous functions and procedures                    *)
311  (*                                                                         *)
312  (* ----------------------------------------------------------------------- *)
313
314  PROCEDURE isParOrConst (name : CARDINAL) : BOOLEAN;
315   (*
316     Search in the symbol list for an operand that has `name' in the `.name'
317     field. If such an operand is not found an error occurs.
318     Otherwise the function returns TRUE iff the `.kind' field has the
319     value `parKind' or `fctKind'. This function is necessary to decide in
320     `EveSyntAn' whether an Eve object is higher order or first order.       *)
321
322  PROCEDURE DeleteVar (name : CARDINAL);
323   (*
324     If the last entry in the symbol list has `name' in the `.name' field this
325     entry is deleted from the symbol list. Otherwise an error occurs.
326     This procedure is needed in an Eve CASE expression where in the different
327     branches the value of the union is bound to new identifiers that only
328     scope over that branch and can be deleted afterwards.                   *)
329
330  PROCEDURE CurrentLevel () : CARDINAL;
331   (*
332     This function returns the current static nesting level of the Eve program.
333     This value is kept in a global variable of this module.                 *)
334
335  PROCEDURE TestAdd (name : CARDINAL);
336   (*
337     Search in the symbol list for an operand that has `name' in the `.name'
338     field. If such an operand is not found an error occurs.
339     If `name' was defined in a surrounding block then it is global here and
340     it is added to the `.freeVars'  field of the current function if it
341     is a variable or it is added to the `.occurIn' field of the current
342     function if it is a function. This is the only procedure that creates
343     entries in the fields `.freeVars' and `.occurIn'. the information of
344     these fields is needed for the lambda lifting step.                     *)
345
346  PROCEDURE existInSymbolTable (name : CARDINAL) : BOOLEAN;
347   (*
348     Search in the symbol table for an operand that has `name' in the `.name'
349     field. This function returns TRUE iff such an operand exists and this
350     operand is not a forward operand (i.e. the `.forward' field must

EveSym                                                                Page:   8
351     have the value FALSE).                                                  *)
352
353  (* ----------------------------------------------------------------------- *)
354  (*                                                                         *)
355  (*            Explicit operations on the special constant MAIN             *)
356  (*                                                                         *)
357  (* ----------------------------------------------------------------------- *)
358
359  PROCEDURE SetMAINFctType (eType : EType; obj : Object; VAR op : Operand);
360   (*
361     This procedure first checks whether `obj' is the indes of MAIN in the
362     symbol table (if not then an error occurs). Then the operand of
363     MAIN in the symbol table and the opperand `op' get `eType' as the `.type'
364     field. The operand `op' shall contain the operand of MAIN before such
365     that it contains the operand of MAIN with the correct type
366     afterwards. However, this is not checked in that procedure.             *)
367
368  PROCEDURE GetTypeOfMain (VAR found : BOOLEAN; VAR eType : EType);
369   (*
370     This procedure sets `found' to TRUE iff in the operand for MAIN a type
371     has been entered (`.type' field). In any case `eType' gets the type
372     of MAIN (if no type has been entered yet `eType' will be `errorType').  *)
373
374  (* ----------------------------------------------------------------------- *)
375  (*                                                                         *)
376  (*              Procedures needed for the lambda lifting                   *)
377  (*                                                                         *)
378  (* ----------------------------------------------------------------------- *)
379
380  PROCEDURE GetLists (VAR nameList : List;
381                      VAR freeVarsLList, occurInLList, exceptLList : ListList);
382   (*
383     This procedure goes through the whole symbol table and if it finds a
384     function it adds its name to `nameList'. Furthermore, the list of free
385     variables of that function (`.freeVars' field) is added to `freeVarsLList'
386     and the list of functions that are called in the current function
387     (`.occurIn' field) is added to `occurInLList'. The list of all
388     variables that are defined locally in the current function is added
389     to `exceptLList' (this list is got by a call to
390     `EveAST.GetLocalVarsFromAST'). Note that in the output parameters the
391     i-th component of `nameList' corresponds with the i-th components of
392     `freeVarsLList', `occurInLList' and `exceptLList'.                      *)
393
394  PROCEDURE LiftFcts (ast : ASType; nameList : List; fVLL, oILL : ListList;
395                      VAR addedParFctsL : List; VAR newParLL : ListList;
396                      VAR asts : ARRAY OF ASType; VAR len : CARDINAL);
397   (*
398     This procedure is the main part of the lambda lifting step (it is called
399     from `EveLambdaLift.LambdaLift'). It goes through the symbol table
400     (omitting the predefined functions) and puts all nested functions in an

EveSym                                                                Page:   9
401     array (the names of these functions are put into `addedParFctsL' and the
402     ASTs of these function definitions are put into `asts'). Thereby, add
403     the lifted variables to the definitions of
404     these functions (the i-th component of `fVLL' contains a list of
405     all parameters that must occur in the definition of the function
406     that is the i-th component of `nameList'). Those variables that have
407     not been parameters of the function so far and are added in this
408     step are put into the ListList `newParLL'. `len' is length of the
409     resulting lists (and lists of lists).                                   *)
410
411  (* ----------------------------------------------------------------------- *)
412  (*                                                                         *)
413  (*    Explicit deallocation of memory for symbol list and symbol table     *)
414  (*                                                                         *)
415  (* ----------------------------------------------------------------------- *)
416
417  PROCEDURE DisposeTypes ;
418   (*
419     This procedure discards all types that are stored in the symbol table.
420     For that the symbol table is traversed. As the types are only needed in
421     the first two passes this procedure may be called immediately before
422     the code generation.                                                    *)
423
424  PROCEDURE DisposeSymbolList ;
425   (*
426     This procedure discards the whole symbol list. As the symbol list is
427     only needed for handling the scoping of variables correctly, it can
428     be discarded after the first pass.                                      *)
429
430  PROCEDURE DisposeSymbolTable;
431   (*
432     This procedure discards the whole symbol table. The symbol table is
433     needed only for the first two passes. The parts of information that
434     are also needed in the code generation are extracted from the symbol
435     table with some procedures like `EveCode.GetFctNames'. Hence, this
436     procedure may be called after a call to `EveCode.GetFctNames'
437     immediately before the code generation.                                 *)
438
439  (* ----------------------------------------------------------------------- *)
440  (*                                                                         *)
441  (*          Get statistical information about the symbol table             *)
442  (*                                                                         *)
443  (* ----------------------------------------------------------------------- *)
444
445  PROCEDURE StatisticsOfSymTable (VAR size : CARDINAL;
446                                  VAR sizePercent : CARDINAL);
447   (*
448     This procedure gets in `size' the size of the symbol table in terms of
449     entries. In `sizePercent' the usage of the whole symbol table in
450     percent is returned (again in terms of entries).                        *)

EveSym                                                                Page:  10
451
452  (* ----------------------------------------------------------------------- *)
453  (*                                                                         *)
454  (*              Some print operations (only for testing)                   *)
455  (*                                                                         *)
456  (* ----------------------------------------------------------------------- *)
457
458  PROCEDURE PrintOp (op : Operand);
459   (*
460     Print all information of one operand `op'. This includes:
461      - name (the number as well as the real name),
462      - kind
463      - if it is a function:
464         + the list of free variables (`freeVars')
465         + the list of functions that are called in the function (`occurIn')
466      - type
467      - level
468      - forward
469      - information whether the `.ast' field is empty or not.                *)
470
471  PROCEDURE PrintDependencies;
472   (*
473     Print a table of all dependencies of all functions in the symbol table.
474     For that the symbol table has to be traversed. For each function in the
475     symbol table the contents of the `.freeVars' and `.occurIn' fields
476     are printed (the real names not the numbers are printed!).              *)
477
478  PROCEDURE PrintTable (all : BOOLEAN);
479   (*
480     Print the whole symbol table. If all is TRUE also the predefined objects
481     are printed otherwise they are omitted. In the printing the lists
482     `.freeVars' and `.occurIn' are omittet. The `.ast' field is also
483     omitted.                                                                *)
484
485  PROCEDURE PrintList  (all : BOOLEAN);
486   (*
487     Print the whole symbol list. Here only the names (real names as well as
488     numbers) of the operands that are in the symbol list are printed.       *)
489
490  END EveSym.

EveType                                                               Page:   1
 1  DEFINITION MODULE EveType;                (*  Data Encapsulation of Types
 2  =========================================================================
 3   This module contains the opaque data type of an Eve type and all
 4   procedures that are necessary to operate on this data type.
 5   ------------------------------------------------------------------------ *)
 6
 7  FROM FIO   IMPORT File;
 8
 9  TYPE
10        EType;                           (* Hidden data type of an Eve type *)
11
12  CONST
13       dummyName = 1;
14
15  TYPE
16       NamedType = RECORD                        (* A name with the Eve type *)
17                     name : CARDINAL;            (* that is bound to it      *)
18                     type : EType;               (* (needed for tuples and   *)
19                   END;                          (* unions in Eve)           *)
20
21       FwType = RECORD                           (* Tells whether a type has *)
22                 CASE isForward : BOOLEAN OF     (* been defined via a       *)
23                   TRUE : type : EType;          (* definition. Yes ==>      *)
24                 END;                            (* `type' contains the type *)
25                END;
26
27  CONST
28       maxParNr  = 8;                            (* Max number of parameters *)
29                                                 (* of a function            *)
30                                                 (* --> Compiler restriction *)
31  VAR
32       errorType, anyType, anyListType,          (* Predefined types i.e.    *)
33       boolType, charType, intType, strType,     (* that are needed by       *)
34    (* FunctionTypes for predef. fcts *)         (* predefined objects and   *)
35       iiiType, iiType, biType,                  (* therefore already given  *)
36       bbbType, bbType,                          (* from the beginning.      *)
37       ccType, ciType, icType,
38       bccType, biiType, bssType,
39       aLaaLType, aaLType, aLaLType, baLType,
40       baType                                      : EType;
41
42       noForward                                   : FwType;
43
44  (* ----------------------------------------------------------------------- *)
45  (*                                                                         *)
46  (*         Procedures for creating and destructing Eve types               *)
47  (*                                                                         *)
48  (* ----------------------------------------------------------------------- *)
49
50  PROCEDURE CreateType (VAR x : EType);

EveType                                                               Page:   2
51   (*
52     This procedure initializes the type `x'. Before a type is used it must
53     be created with a call of this procedure. This is necessary as `EType'
54     is an opaque data type.                                                 *)
55
56  PROCEDURE DisposeType (VAR x : EType);
57   (*
58     This procedure discards the type `x' and frees the memory that has been
59     allocated for it. As the types are only needed in the first two passes
60     they may be deleted immediately before the code generation (see
61     `EveSym.DisposeTypes' that discards all existing types).                *)
62
63  PROCEDURE DisposeTypeTab;
64   (*
65     This procedure explicitly deletes the local data structure of a type
66     table that is only needed for the creation of a type description file.  *)
67
68  (* ----------------------------------------------------------------------- *)
69  (*                                                                         *)
70  (*           Procedures for making compound Eve types                      *)
71  (*                                                                         *)
72  (* ----------------------------------------------------------------------- *)
73
74   (* -----------------------------------------------------------------------
75      All Make*Type procedures take one or more types (most of them named
76      types) and create a compound type by building a tuple, union, ...
77      out of it. However, the type may already be defined as a forward to
78      allow indirectly recursive definitions. In this case the type has
79      already been created but is empty. As other types may refer to
80      this so far empty type it has to be used in the creation of the
81      new compound type. Therefore, each procedure has the parameter `fw'
82      telling if the type has been defined as forward type and if yes it
83      contains the empty type. If it is not a forward type a new type
84      has to be created. In any case `x' contains afterwards the new
85      compound type.
86      ---------------------------------------------------------------------- *)
87
88  PROCEDURE MakeListType (VAR x : EType; y : EType; fw : FwType);
89   (*
90     Take the type `y' and create a list type where each list element is
91     of type `y'. If it has been defined as forward type `fw.forward'=TRUE
92     and `fw.type' contains the empty type. Note, that here the type
93     of the only component is not associated with a name.
94     Graphically:
95
96      Given: y
97      Result:  x  where
98                        x =   listTy
99                                |
100                                y                                             *)

EveType                                                               Page:   3
101
102  PROCEDURE MakeTupleType (VAR x : EType;
103                           y : ARRAY OF NamedType; len : CARDINAL;
104                           fw : FwType);
105   (*
106     Take the named types in the array `y' (`len' elements) and create a
107     tuple type out of these types. If it has been defined as forward type
108     `fw.forward'=TRUE and `fw.type' contains the empty type. Each component
109     type is bound to a name that is stored together with the type.
110     Graphically:
111
112      Given:   y[0], ..., y[len-1]
113      Result:  x where
114
115                x = tupleTy
116                      |
117                      |-----------------------
118                      |                      |
119                     y[0]       ...      y[len-1]
120                                                                             *)
121
122  PROCEDURE MakeUnionType (VAR x : EType;
123                           y : ARRAY OF NamedType; len : CARDINAL;
124                           fw : FwType);
125   (*
126     Take the named types in the array `y' (`len' elements) and create a
127     union type out of these types. If it has been defined as forward type
128     `fw.forward'=TRUE and `fw.type' contains the empty type. Each component
129     type is bound to a name that is stored together with the type.
130     Graphically:
131
132      Given:   y[0], ..., y[len-1]
133      Result:  x where
134
135                x = unionTy
136                      |
137                      |-----------------------
138                      |                      |
139                     y[0]       ...      y[len-1]
140                                                                             *)
141
142  PROCEDURE MakeFctType (VAR x : EType;
143                         resultType : EType; resultName : CARDINAL;
144                         argTypes : ARRAY OF NamedType; len : CARDINAL;
145                         fw : FwType);
146   (*
147     Take the type `resultType' and the name `resultName' and the named types
148     in the array `argTypes' (`len' elements) and create a
149     function type out of these types. If it has been defined as forward type
150     `fw.forward'=TRUE and `fw.type' contains the empty type. The `resultType'

EveType                                                               Page:   4
151     and `resultName' are stored as the first component in the type.
152     After this the types of the parameters together with their names are
153     stored.
154     Graphically:
155
156      Given:   resultType, resultName, argTypes[0], ..., argTypes[len-1]
157      Result:  x where
158
159                x = fctTy
160                      |
161                      |----------------------------------------------
162                      |                |                            |
163                  resultName      argTypes[0]       ...      argTypes[len-1]
164                  resultType
165                                                                             *)
166
167  (* ----------------------------------------------------------------------- *)
168  (*                                                                         *)
169  (*          Test whether a type is a certain compound Eve type             *)
170  (*                                                                         *)
171  (* ----------------------------------------------------------------------- *)
172
173  PROCEDURE isListType (eType : EType) : BOOLEAN;
174   (*
175     Test whether the type `eType' is a list type. For that only the top
176     level constructor has to be checked (see graphics above).               *)
177
178  PROCEDURE isListOf (eTypeList, eType : EType) : BOOLEAN;
179   (*
180     Test whether the type `eTypeList' is a list type and whether the type
181     of the components of this list is `eType'.                              *)
182
183  PROCEDURE isTupleType (eType : EType) : BOOLEAN;
184   (*
185     Test whether the type `eType' is a tuple type. For that only the top
186     level constructor has to be checked (see graphics above).               *)
187
188  PROCEDURE isUnionType (eType : EType) : BOOLEAN;
189   (*
190     Test whether the type `eType' is a union type. For that only the top
191     level constructor has to be checked (see graphics above).               *)
192
193  PROCEDURE isFctType (eType : EType) : BOOLEAN;
194   (*
195     Test whether the type `eType' is a function type. For that only the top
196     level constructor has to be checked (see graphics above).               *)
197
198  (* ----------------------------------------------------------------------- *)
199  (*                                                                         *)
200  (*   Extract information about the components of the compound Eve types    *)

EveType                                                               Page:   5
201  (*                                                                         *)
202  (* ----------------------------------------------------------------------- *)
203
204  PROCEDURE GetListType (list : EType;
205                         VAR listType : EType);
206   (*
207     This procedure checks whether `list' is a list type (if not then error)
208     and gets the type of the components of the list in `listType'.          *)
209
210  PROCEDURE GetFieldType (tuple : EType; name : CARDINAL;
211                          VAR nr : CARDINAL; VAR fieldType : EType);
212   (*
213     This procedure checks whether `tuple' is a tuple type (if not then error)
214     and gets the type of the component that is bound to the name `name'
215     in `fieldType'. In `nr' the number of this field in the tuple is
216     returned (counting starts with 1).                                      *)
217
218  PROCEDURE GetTupleTypeList (tuple : EType;
219                              VAR typeList : ARRAY OF EType;
220                              VAR len : CARDINAL);
221   (*
222     This procedure checks whether `tuple' is a tuple type (if not then error)
223     and gets a list of all component types in `typeList'. In `len' the
224     length of this list is returned.                                        *)
225
226  PROCEDURE GetVariantType (union : EType; name : CARDINAL;
227                            VAR variantType : EType; VAR nr : CARDINAL);
228   (*
229     This procedure checks whether `union' is a union type (if not then error)
230     and gets the type of the component that is bound to the name `name'
231     in `variantType'. In `nr' the number of this variant in the union is
232     returned (counting starts with 1).                                      *)
233
234  PROCEDURE GetVariantTypeByNumber (union : EType; nr : CARDINAL;
235                                    VAR variantType : EType);
236   (*
237     This procedure checks whether `union' is a union type (if not then error)
238     and gets the type of the `nr'-th component of the union in `variantType'.
239     If `union' has fewer than `nr' components an error occurs.              *)
240
241  PROCEDURE GetParTypeList (x : EType;
242                            VAR idList : ARRAY OF CARDINAL;
243                            VAR typeList : ARRAY OF EType;
244                            VAR len : CARDINAL);
245   (*
246     This procedure checks whether `x' is a function type (if not then error)
247     and gets a list of all component types in `typeList' and a list of
248     the parameter names in `idList'. In `len' the length of these lists
249     is returned. Note that type and name of the return value are not
250     delivered by this procedure. To get this information the procedures

EveType                                                               Page:   6
251     `GetReturnType' and `GetReturnName' have to be used.                    *)
252
253  PROCEDURE GetReturnType (x : EType; VAR returnType : EType);
254   (*
255     This procedure checks whether `x' is a function type (if not then error)
256     and gets the type of the return value in `returnType'.                  *)
257
258  PROCEDURE GetReturnName (x : EType; VAR returnName : CARDINAL);
259   (*
260     This procedure checks whether `x' is a function type (if not then error)
261     and gets the name of the return value in `returnName'.                  *)
262
263  (* ----------------------------------------------------------------------- *)
264  (*                                                                         *)
265  (*                   Test the equality of types                            *)
266  (*                                                                         *)
267  (* ----------------------------------------------------------------------- *)
268
269  PROCEDURE equalType (x, y : EType): BOOLEAN;
270   (*
271     Test whether the types `x' and `y' are equal (due to structure
272     equivalence).                                                           *)
273
274  PROCEDURE TestTypes (typeList1 : ARRAY OF EType; len1 : CARDINAL;
275                       typeList2 : ARRAY OF EType; len2 : CARDINAL;
276                       VAR ok : BOOLEAN);
277   (*
278     Test whether `len1'=`len2' and the types `typeList1[i]' and `typeList2[i]'
279     are equal for all 1<=i<=len1. Iff this is the case `ok' is set to TRUE.
280     This procedure is needed to check whether the types of a function
281     in a forward definition and in its actual definition are equal.         *)
282
283  PROCEDURE TestTypes1 (x,returnType : EType; typeList : ARRAY OF EType;
284                       len : CARDINAL; VAR ok : BOOLEAN);
285   (*
286     Test whether the type `returnType' is equal to the type of the
287     return value of the function type `x' and test whether the type
288     `typeList[i]' is equal to the type of the i-th parameter of the
289     function type `x'. Iff all this is TRUE and the number of parameters
290     of the function type `x' is `len' then `ok' returns TRUE.               *)
291
292  (* ----------------------------------------------------------------------- *)
293  (*                                                                         *)
294  (*                 Miscellaneous operations on types                       *)
295  (*                                                                         *)
296  (* ----------------------------------------------------------------------- *)
297
298  PROCEDURE SetParNames (name : CARDINAL; idList : ARRAY OF CARDINAL;
299                         len : CARDINAL; eType : EType);
300   (*

EveType                                                               Page:   7
301     This procedure checks whether `eType' is a function type
302     (if not then error). It sets the name of the return value that is
303     stored in the type to `name' (overwriting an old value) and it sets
304     the names of the parameters that are stored in the type to
305     `idList[0]', ..., `idList[len-1]'. The number of the parameters of
306     the function type `eType' must be `len' otherwise an error occurs.      *)
307
308  PROCEDURE MaxType (x, y : EType; VAR z : EType);
309   (*
310     This procedure determines the maximal type of the types `x' and `y'
311     and returns this type in `z'. The ordering of the types upon this
312     procedure is based is the following:
313
314                   x < y  iff  x=anyType AND y<>anyType
315
316     All other types are not comparable (so `anyType' is the bottom element in
317     the domain of Eve types). Furthermore, if neither of the two types
318     `x' and `y' is `anyType' and `x' and `y' are not equal then a semantic
319     error occurs. This procedure is needed in e.g. an IF-THEN-ELSE
320     expression where the type of one branch is `anyType' but that of the
321     other branch is a specific type `x'. In this case the type of the
322     whole expression must be `x' and not `anyType'.                         *)
323
324  (* ----------------------------------------------------------------------- *)
325  (*                                                                         *)
326  (*     Procedures handling the creation of a type file containing a        *)
327  (*          description of the type of the constant MAIN                   *)
328  (*                                                                         *)
329  (* ----------------------------------------------------------------------- *)
330
331  PROCEDURE InitTypeFile;
332   (*
333     This procedure initializes a local data structure that is needed for
334     the creation of the type description file, opens the file (``.adt''
335     extension!) and writes the first lines into the file that are
336     independent of the actual type.                                         *)
337
338  PROCEDURE WriteTypeToFile (eType : EType;
339                             start : CARDINAL);
340   (*
341     This procedure transforms the type `eType' from its internal
342     representation into a form that is then written to the type description
343     file (``.adt'' extension). This type description file consists of numbered
344     lines where each line contains a subtype. The letters `I', `C' and
345     `B' are standing for the atomar types of integers, characters and
346     booleans, respectively. The compound data types are denoted by
347     `L' for lists (with one succeeding number denoting the type of the
348     components of that list), `T' for tuples (with as many succeeding
349     numbers as the tuple has components, describing the types of these
350     components) and `U' for unions (with as many succeeding

EveType                                                               Page:   8
351     numbers as the union has components, describing the types of these
352     components).
353     As this procedure is calles recursivley it has an parameter `start' that
354     contains the line number of the nest line to be written to the file.
355     Example:
356      The Eve type `tuple' that is defined by
357
358        IntList == LIST OF INT;
359
360        IBUnion == UNION OF
361                    int  == INT,
362                    bool == BOOL;
363
364        tuple == TUPLE OF
365                   list  : IntList,
366                   union : IBUnion;
367
368     will have the following representation in the type description file
369
370       #0:  1         ; This line marks the root of the type
371       1:   T  2 4    ; The type is a tuple with components described in 2, 4
372       2:   L  3      ; First tuple-component: list (component described in 3)
373       3:   I         ; Component of list: integer
374       4:   U  3 5    ; Second tuple-comp.: union (comp. described in 3, 5)
375       5:   B         ; Second uniuon-component: boolean
376                                                                             *)
377
378  (* ----------------------------------------------------------------------- *)
379  (*                                                                         *)
380  (*    Procedure for printing the internal representation of an Eve type    *)
381  (*                                                                         *)
382  (* ----------------------------------------------------------------------- *)
383
384  PROCEDURE PrintType (eType : EType; nr : CARDINAL);
385   (*
386     This procedure prints the type `eType'. The tree structure is
387     visiualized with indentations and line numbers denoting the nesting
388     level of a node in the type tree (this nesting level is submitted
389     by `nr').                                                               *)
390
391  END EveType.

E.3. SPECIAL MODULES 299

E.3 Special Modules

EveCoIO                                                               Page:   1
 1  DEFINITION MODULE EveCoIO;                 (* Input/Output of the Compiler
 2  ==========================================================================
 3   This module contains some procedure for the input of the Eve program file
 4   and for the output of the the Adam assembler program and expression files.
 5
 6   ------------------------------------------------------------------------- *)
 7
 8  FROM EveStringLib IMPORT LongString;
 9  FROM FIO          IMPORT File;
10
11  VAR
12      success                   : BOOLEAN;    (* Was compilation successful? *)
13      fileName                  : LongString; (* filename from command line  *)
14      inputFile, asmFile,
15      aseFile, adtFile, lisFile : File;       (* File descriptors of input   *)
16                                              (* and output files.           *)
17
18  (* ----------------------------------------------------------------------- *)
19  (*                                                                         *)
20  (*         Procedures for handling the input of the compiler               *)
21  (*                                                                         *)
22  (* ----------------------------------------------------------------------- *)
23
24  PROCEDURE GetCommandLine;
25   (*
26     This procedure checks for the correct number of arguments in the
27     command line and reads the filename of the input file into the global
28     variable `fileName'. Furthermore, the `.lis' file that mirrors all
29     output of the compiler to the screen and includes other protocol
30     information is opened in this procedure.                                *)
31
32  PROCEDURE OpenInFile;
33   (*
34     This procedure opens the input file. It takes the filename that has been
35     read in the command line, adds the directory of the environment
36     variable `ADDIR' in front and the standard extension at the end of the
37     name and then opens the file with that name. The file descriptor
38     `inputFile' refers after this procedure to the opened input file.       *)
39
40  PROCEDURE CloseInFile;
41   (*
42     This procedure closes the input file again after the Eve program has been
43     entirely read.                                                          *)
44
45  PROCEDURE ReadLine (VAR line : ARRAY OF CHAR; VAR len : CARDINAL;
46                      VAR eof  : BOOLEAN);
47   (*

EveCoIO                                                               Page:   2
48     This procedure reads one line from the file `inputFile' and puts that
49     line into `line' (with `len' as its length). Iff the end of the file
50     is reached `eof' returns TRUE. This procedure is needed in
51     `EveGetChar.GetChar'.                                                   *)
52
53  (* ----------------------------------------------------------------------- *)
54  (*                                                                         *)
55  (*         Procedures for handling the output of the compiler              *)
56  (*                                                                         *)
57  (* ----------------------------------------------------------------------- *)
58
59  PROCEDURE OpenAsmFile;
60   (*
61     This procedure opens the Adam assembler program file (`.asm' extension).
62     The name of this file is contained in `asmName'. It must be in the
63     same directory as the input file. The correct extension is automatically
64     added to the filename. If such an `.asm' file already exists it is
65     renamed to a file with `.bak' instead of `.asm' extension.
66     The file descriptor `asmFile' refers to the `.asm' file after a call
67     to this procedure.                                                      *)
68
69  PROCEDURE OpenAseFile;
70   (*
71     This procedure opens the Adam assembler expression file
72     (`.ase' extension). This file will contain the code for evaluating the
73     result expression of the whole program (defined by the body of MAIN).
74     The name of this file is contained in `aseName'. It must be in the
75     same directory as the input file. The correct extension is automatically
76     added to the filename.
77     The file descriptor `aseFile' refers to the `.ase' file after a call
78     to this procedure.                                                      *)
79
80  PROCEDURE OpenAdtFile;
81   (*
82     This procedure opens the type description file (`.adt' extension).
83     This file will contain the type description of the result expression of
84     the whole program (defined by the body of MAIN).
85     The name of this file is contained in `adtName'. It must be in the
86     same directory as the input file. The correct extension is automatically
87     added to the filename.
88     The file descriptor `adtFile' refers to the `.adt' file after a call
89     to this procedure.                                                      *)
90
91  PROCEDURE OpenLisFile;
92   (*
93     This procedure opens the protocol file (`.lis' extension).
94     This file contains all the information that is also printed to the
95     screen during the compilation (i.e. it contains possible errors
96     that occur in the compilation) and some other information.
97     The name of this file is contained in `lisName'. It must be in the

EveCoIO                                                               Page:   3
 98     same directory as the input file. The correct extension is automatically
 99     added to the filename.
100     The file descriptor `lisFile' refers to the `.lis' file after a call
101     to this procedure.                                                      *)
102
103  PROCEDURE CloseAdtFile;
104   (*
105     This procedure closes the type description file after it has been
106     created with a call to `EveType.WriteTypeToFile'.                       *)
107
108  PROCEDURE CloseLisFile;
109   (*
110     This procedure closes the protocol file at the very end of the
111     compilation.                                                            *)
112
113  END EveCoIO.

EveError                                                              Page:   1
 1  DEFINITION MODULE EveError;       (*  Error Handler (used by all 3 passes)
 2  =========================================================================
 3   This module contains the procedures for the error handling. Especially,
 4   the error statements are stored locally in this module and printed
 5   here (only exception: syntax errors; they are printed in `EveSyntAn').
 6   ------------------------------------------------------------------------- *)
 7
 8  VAR
 9      synErrors, semErrors  : CARDINAL;  (* Number of syntax/semantic errors *)
10      restrictions, semOk   : BOOLEAN;   (* Check if semantics still ok      *)
11
12  PROCEDURE LexError    (col, nr : CARDINAL);
13   (*
14     Print a lexical error, encoded by `nr' in the column `col'.
15     This procedure is only called in `EveLexAn'.                            *)
16
17  PROCEDURE SemError    (col, nr : CARDINAL);
18   (*
19     Print a semantic error, encoded by `nr' in the column `col'.            *)
20
21  PROCEDURE Restriction (col, nr : CARDINAL);
22   (*
23     Print a compiler restriction, encoded by `nr' in the column `col'.      *)
24
25  PROCEDURE CompError   (col, nr : CARDINAL);
26   (*
27     Print a compiler error, encoded by `nr' in the column `col'.            *)
28
29  END EveError.

EveGetChar                                                            Page:   1
 1  DEFINITION MODULE EveGetChar;                          (* Elementary Input
 2  ==========================================================================
 3   This module contains procedures for reading the next character from the
 4   input file (block buffered input) and an altered `WriteLn' procedure that
 5   counts the line numbers and starts a new page after every 50 lines.
 6   ------------------------------------------------------------------------- *)
 7
 8  CONST
 9        endFile =  3C;                                 (*  end-of-text; etx  *)
10        endLine = 12C;                                 (*  end-of-line; lf   *)
11
12                                 (* The following Variables are READ-ONLY !! *)
13  VAR
14      nextChar     : CHAR;               (* Character read by GetChar        *)
15      charCol      : CARDINAL;           (* column of the character nextChar *)
16
17  PROCEDURE GetChar;
18   (*
19     Reads next Character from source text and stores it in nextChar.
20     col is the column number of the character in the printed output (starting
21     with 0).
22     After each 80-column line, GetChar supplies a special end-of-line
23     character endLine.
24     Each attempt to read beyond the end of the source text results in
25     nextChar=endFile.
26     In addition to these elementary tasks, GetChar prints the source text
27     with leading line numbers and a page header after every 60th line.
28     After a call to GetChar, the line containing nextChar has already been
29     printed.                                                                *)
30
31  PROCEDURE WriteLn;
32   (*
33     Starts a new line in the listing. If necessary a new page is started and
34     the headline is printed.                                                *)
35
36  END EveGetChar.

EveWrite                                                              Page:   1
 1  DEFINITION MODULE EveWrite;             (*  For printing enumerations etc.
 2  ==========================================================================
 3   This module contains procedures for printing the contents of an
 4   enumeration or a set. Furthermore, it contains a routine that is used
 5   inside this printing routine but that is also of usage in another module
 6   (that is why it is exported).
 7   ------------------------------------------------------------------------- *)
 8
 9  FROM EveStringLib IMPORT LongString;
10
11  CONST maxSetLen = 256;
12
13  TYPE SetType = SET OF [0..maxSetLen-1];                (* General set type *)
14
15  PROCEDURE WriteEnum (enum : CARDINAL; names : ARRAY OF CHAR);
16   (*
17     This procedure prints the `enum'-th substring of `names' where the
18     substrings in `names' must be separate by exactly one ','. When this
19     procedure is called with the ORD of an enumeration value as first
20     parameter and a string that contains the names of the components of
21     that enumeration, separated by a ',', then this procedure prints
22     an enumeration.
23     This is used mainly for testing.                                        *)
24
25  PROCEDURE WriteSet (set : SetType; names : ARRAY OF CHAR);
26   (*
27     This procedure does the same as the previous procedure but on sets.
28     For that a set first has to be transformed into the general set type
29     `SetType'. After this transformation this procedure can be called with
30     a string containing the names of the components in the set.             *)
31
32  PROCEDURE GetSubString (enum : CARDINAL; names : ARRAY OF CHAR) : LongString;
33   (*
34     This procedure gets the `enum'-th substring of the string `names',
35     where the various substrungs are separated by exactly one ','.
36     This procedure is mainly used by the two previous procedures but it is
37     also used in other modules.                                             *)
38
39  END EveWrite.

Bibliography

[Agha, 1986] Gul Agha. An Overview of Actor Languages. SIGPLAN Notices, 21(10):58–67, October 1986.

[Aho et al., 1986] Alfred A. Aho, Ravi Sethi, and Jeffrey D. Ullman. Compilers — Principles, Techniques and Tools. Addison-Wesley, 1986.

[Appel and MacQueen, 1987] Andrew W. Appel and David B. MacQueen. A Standard ML Compiler. In Proceedings of the Conference on Functional Programming Languages and Computer Architecture, volume 274 of Lecture Notes in Computer Science, pages 301–324, Portland, Oregon, USA, September 14–16, 1987. Springer, Berlin.

[Appel, 1989] Andrew W. Appel. A Runtime System. Technical report, Princeton University, February 1989.

[Arvind and Nikhil, 1988] Arvind and R. S. Nikhil. Executing a Program on the MIT Tagged-Token Dataflow Architecture. Computation Structures Group Memo 271, Laboratory for Computer Science, Massachusetts Institute of Technology, Cambridge, MA, June 1988. Also in: PARLE 87, Conference on Parallel Architectures and Languages Europe, Eindhoven, The Netherlands, June 15–19, 1987, Volume 259 of Lecture Notes in Computer Science, Springer, Berlin.

[Arvind et al., 1987] Arvind, Rishiyur S. Nikhil, and Keshav K. Pingali. I-Structures: Data Structures for Parallel Computing. Computation Structures Group Memo 269, Laboratory for Computer Science, Massachusetts Institute of Technology, Cambridge, MA, February 1987. Also in: Proceedings of the Workshop on Graph Reduction, Los Alamos, New Mexico, September 28 – October 1, 1986.

[Augustsson and Johnsson, 1989] Lennart Augustsson and Thomas Johnsson.

Parallel Graph Reduction with the ⟨v, G⟩-machine. In FPCA ’89, The Fourth International Conference on Functional Programming Languages and Computer Architecture, pages 202–213, Imperial College, London, September 11–13, 1989. ACM Press, New York.

[Augustsson, 1987] Lennart Augustsson. Compiling Lazy Functional Languages, Part II. PhD thesis, Department of Computer Sciences, Chalmers University of Technology, Göteborg, Sweden, 1987.

[Backus et al., 1990] John Backus, John H. Williams, and Edward L. Wimmers. An Introduction to the Programming Language FL. In David A. Turner, editor, Research Topics in Functional Programming, pages 219 – 247. Addison-Wesley, 1990.

[Backus, 1978] John Backus. Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs. Communications of the ACM, 21(8):613–641, August 1978.

[Barendregt, 1984] H. P. Barendregt. The Lambda Calculus: Its Syntax and Semantics. North-Holland, 1984.

[Bevan, 1987] D. I. Bevan. Distributed Garbage Collection Using Reference Counting. In Proceedings of PARLE, Parallel Architectures and Languages Europe, Volume 2: Parallel Languages, volume 259 of Lecture Notes in Computer Science, pages 176–187, Eindhoven, The Netherlands, June 15–19, 1987. Springer, Berlin.

[Bird and Wadler, 1988] Richard Bird and Philip Wadler. Introduction to Functional Programming. Series in Computer Science. Prentice-Hall International, Englewood Cliffs, New Jersey, 1988.

[Blaschek et al., 1986] G. Blaschek, G. Pomberger, and F. Ritzinger. Einführung in die Programmierung mit Modula-2 (Introduction to Programming in Modula-2)(in German). Springer, 1986.

[Buchberger, 1985] Bruno Buchberger. Gröbner Bases: An Algorithmic Method in Polynomial Ideal Theory. In N. K. Bose and D. Reidel, editors, Recent trends in Multidimensional Systems, chapter 6, pages 184–232. D. Reidel Publishing Company, Dordrecht-Boston-Lancaster, 1985.

[Burge, 1975] W. H. Burge. Recursive Programming Techniques. Addison-Wesley, Reading, Mass., 1975.

[Burn, 1988] G. L. Burn. A Shared Memory Parallel G-machine Based on the Evaluation Transformer Model of Computation. Draft, GEC Hirst Research Centre, Wembley, UK, November 1988.

[Burn, 1991] Geoffrey L. Burn. Implementing the Evaluation Transformer Model of Reduction on Parallel Machines. Technical Report DOC 90/24, Department of Computing, Imperial College of Science, Technology and Medicine, 1991. To be published in: Journal of Functional Programming, 1(2), April 1991.

[Burstall et al., 1980] R. M. Burstall, D. B. MacQueen, and D. T. Sannella. Hope: An Experimental Applicative Language. In Conference Record of the 1980 LISP Conference, pages 136–143, Stanford University, Stanford, California, August 25–27, 1980. ACM Press. Also: Internal Report CSR-62-80, Department of Computer Science, University of Edinburgh, May 1980, Update February 1981.

[Carriero and Gelernter, 1990] Nicholas Carriero and David Gelernter. How to Write Parallel Programs. MIT Press, 1990.

[Chandy and Misra, 1988] Mani K. Chandy and Jayadev Misra. Parallel Program Design — A Foundation. Addison-Wesley, Reading, Massachusetts, 1988.

[Clarke et al., 1980] T. Clarke, P. Gladstone, C. MacLean, and A. Norman. SKIM — The S, K, I Reduction Machine. In Conference Record of the 1980 LISP Conference, pages 128–135, Stanford University, Stanford, California, August 25–27, 1980. ACM Press.

[Collins, 1975] George E. Collins. Quantifier Elimination for Real Closed Fields by Cylindrical Algebraic Decomposition. In Proceedings of the 2nd GI Conference, volume 33 of Lecture Notes in Computer Science, pages 134–183, Kaiserslautern, 1975. Springer, Berlin.

[Cooper and Morrisett, 1990] Eric C. Cooper and J. Gregory Morrisett. Adding Threads to Standard ML. Technical Report CMU-CS-90-186, School of Computer Science, Carnegie Mellon University, December 1990.

[Cripps et al., 1987] M. D. Cripps, John Darlington, A. J. Field, P. G. Harrison, and M. J. Reeve. The Design and Implementation of ALICE: A Parallel Graph Reduction Machine. In Thakkar, editor, Selected Reprints on Dataflow and Reduction Architectures, pages 300–321. Springer, Berlin, 1987.

[Fairbairn and Wray, 1987] John Fairbairn and Stuart Wray. Tim: A Simple, Lazy Abstract Machine to Execute Supercombinators. In Gilles Kahn, editor, Functional Programming Languages and Computer Architecture, volume 274 of Lecture Notes in Computer Science, pages 34–45, Portland, Oregon, USA, September 14–16, 1987. Springer, Berlin.

[Fairbairn, 1985] J. Fairbairn. Design and Implementation of a Simple Typed Language Based on the Lambda-Calculus. Technical report, University of Cambridge Computer Laboratory, Cambridge, UK, May 1985.

[Field and Harrison, 1988] Anthony J. Field and Peter G. Harrison. Functional Programming. International Computer Science Series. Addison-Wesley, Wokingham, England, 1988.

[Gehani and Roome, 1986] N. H. Gehani and W. D. Roome. Concurrent C. Software — Practice and Experience, 16(9):842–844, September 1986.

[George, 1989] Lal George. An Abstract Machine for Parallel Graph Reduction. In FPCA ’89, The Fourth International Conference on Functional Programming Languages and Computer Architecture, pages 214–229, Imperial College, London, September 11–13, 1989. ACM Press, New York.

[Goldberg, 1988] Benjamin F. Goldberg. Multiprocessor Execution of Functional Programs. Research Report YALEU/DCS/RR-618, Department of Computer Science, Yale University, April 1988.

[Halstead, Jr., 1985] Robert H. Halstead, Jr. Multilisp: A Language for Concurrent Symbolic Computation. ACM Transactions on Programming Languages and Systems, 7(4):501–538, October 1985.

[Harper, 1990] Robert Harper. Introduction to Standard ML. Technical report, School of Computer Science, Carnegie Mellon University, September [Henderson, 1980]

Peter Henderson. Functional Programming. International Series in Computer Science. Prentice Hall, Englewood Cliffs, New Jersey, 1980.

[Hoare, 1985] C. A. R. Hoare. Communicating Sequential Processes. Series in Computer Science. Prentice Hall, Englewood Cliffs, NJ, 1985.

[Hudak and Fasel, 1992] Paul Hudak and Joseph H. Fasel. A Gentle Introduction to Haskell. Technical report, Department of Computer Science, Yale University, 1992.

[Hudak and Goldberg, 1985] Paul Hudak and Benjamin Goldberg. Serial Combinators: Optimal Grains of Parallelism. In Jean-Pierre Jouannaud, editor, Functional Programming Languages and Computer Architecture, volume 201 of Lecture Notes in Computer Science, pages 382–399, Nancy, France, September 16–19, 1985. Springer, Berlin.

[Hudak et al., 1991] Paul Hudak, Simon Peyton Jones, Philip Wadler, Brian Boutel, Jon Fairbairn, Joeseph Fasel, María M. Guzmán, Kevin Hammond, John Hughes, Thomas Johnsson, Dick Kieburtz, Rishiyur Nikhil, Will Partain, and John Peterson. Report on the Functional Programming Language Haskell. Research Report 1.1, Department of Computing Science, University of Glasgow, Glasgow, UK, June 1991. Final Draft.

[Hudak, 1984] Paul Hudak. ALFL Reference Manual and Programmer’s Guide. Technical Report YALEU/DCS/TR–322, Second edition, Yale University Department of Computer Science, New Haven, CT, October 1984.

[Hudak, 1986a] Paul Hudak. Arrays, Non-Determinism, Side-Effects, and Parallelism: A Functional Perspective (Extended Abstract). Technical report, Department of Computer Science, Yale University, New Haven, Connecticut, November 1986.

[Hudak, 1986b] Paul Hudak. Para-Functional Programming. IEEE Computer, 19(8):60–70, August 1986.

[Hudak, 1989] Paul Hudak. Para-Functional Programming in Haskell. Technical report, Department of Computer Science, Yale University, June 1989.

[Hughes, 1982] R. J. M. Hughes. A Semi-Incremental Garbage Collection Algorithm. Software — Practice and Experience, 12:1081–1084, 1982.

[Hughes, 1984] R. J. M. Hughes. The design and implementation of programming languages. PhD thesis, Programming Research Group, University of Oxford, 1984.

[Iannucci, 1988] Robert Alan Iannucci. A Dataflow / von Neumann Hybrid Architecture. Technical Report MIT/LCS/TR-418, Laboratory for Computer Science, Massachusetts Institute of Technology, Cambridge, MA, May 1988.

[Inmos, 1988] Inmos. Occam 2 Reference Manual. Prentice Hall, New York, 1988.

[Inmos, 1989] Inmos Limited, Bristol, UK. 3L Parallel C, IMS D711, User Guide, February 1989.

[Jensen, 1988] Jensen & Partners International. TopSpeed Modula-2 for IBM Personal Computer and Compatibles, User’s Manual, 1988.

[Johnsson, 1987] Thomas Johnsson. Compiling Lazy Functional Languages, Part I. PhD thesis, Department of Computer Sciences, Chalmers University of Technology, Göteborg, Sweden, 1987.

[Kelly, 1989] Paul Kelly. Functional Programming for Loosely-coupled Multiprocessors. Research Monographs in Parallel and Distributed Computing. Pitman, London, 1989.

[King, 1988] K. N. King. TopSpeed Modula-2 for IBM Personal Computer and Compatibles, Language Tutorial. Jensen & Partners International, 1988.

[Knuth, 1968] D. E. Knuth. Semantics of Context-Free Languages. Mathematical Systems Theory, 2(2):127–147, 1968.

[Kranz et al., 1986] David Kranz, Richard Kelsey, Jonathan Rees, Paul Hudak, James Philbin, and Norman Adams. ORBIT: An Optimizing Compiler for Scheme.

In Proceedings of the SIGPLAN 86 Symposium on Compiler Construction, number 21(7) in SIGPLAN Notices, pages 219–233, July 1986.

[Kranz, 1988] David Andrew Kranz. ORBIT: An Optimizing Compiler for Scheme. Technical report, Yale University, New Haven, Connecticut, 1988.

[Landin, 1964] P. J. Landin. The mechanical evaluation of expressions. The Computer Journal, 6:308–320, 1964.

[Loidl, 1991] Hans Wolfgang Loidl. Circular Programs on Compound Data Structures. Technical Report 92-06, RISC-Linz, Johannes Kepler University, Linz, Austria, 1991.

[Loogen, 1990] Rita Loogen. Parallele Implementierung funktionaler Programmiersprachen (Parallel Implementation of Functional Programming Languages (in German). Number 232 in Informatik Fachberichte. Springer, 1990.

[Milner, 1980] Robin Milner. A Calculus of Communicating Systems, volume 92 of Lecture Notes in Computer Science. Springer, 1980.

[Milner, 1984] Robin Milner. A proposal for Standard ML. In 1984 ACM Symposium on Lisp and Functional Programming, pages 184–197, Austin, Texas, August 6–8, 1984.

[Nikhil, 1988] Rishiyur S. Nikhil. ID (Version 88.0) Reference Manual. Computation Structures Group Memo 284, Laboratory for Computer Science, Massachusetts Institute of Technology, Cambridge, MA, March 1988.

[Papadopoulos and Culler, 1990] Gregory M. Papadopoulos and David E. Culler. Monsoon: An Explicit Token-Store Architecture. In The 17th International Symposium on Computer Architecture, number 18(2) in ACM SIGARCH Computer Architecture News, pages 82–91, Seattle, Washington, May 28–31, June 1990.

[Papadopoulos, 1988] Gregory Michael Papadopoulos. Implementation of a General Purpose Dataflow Multiprocessor. PhD thesis, Department of Electrical Engineering and Computer Science, Massachusetts Institute of Technology, Cambridge, MA, August 1988.

[Peyton Jones and Lester, 1991] Simon L. Peyton Jones and David Lester. A Modular Fully-lazy Lambda Lifter in HASKELL. Software — Practice and Experience, 21(5):479–506, May 1991.

[Peyton Jones and Lester, 1992] Simon Peyton Jones and David Lester. Implementing Functional Languages. Prentice Hall, 1992. To appear.

[Peyton Jones and Wadler, 1991] Simon L. Peyton Jones and Philip Wadler. A static semantics for HASKELL. Technical report, Department of Computing Science, University of Glasgow, May 1991.

[Peyton Jones et al., 1987] Simon L. Peyton Jones, Chris Clack, John Salkild, and Mark Hardie. GRIP — a High-Performance Architecture for Parallel Graph Reduction. In Proceedings of the Conference on Functional Programming Languages and Computer Architecture, volume 274 of Lecture Notes in Computer Science, pages 98–112, Portland, Oregon, September 14–16, 1987. Springer, Berlin.

[Peyton Jones, 1987] Simon L. Peyton Jones. The Implementation of Functional Programming Languages. Prentice-Hall, New York, 1987.

[Peyton Jones, 1989] Simon L. Peyton Jones. Parallel Implementations of Functional Programming Languages. The Computer Journal, 32(2):175–186, April 1989.

[Peyton Jones, 1991] Simon L. Peyton Jones. The Spineless Tagless G-machine: second attempt. Technical report, Department of Computing Science, University of Glasgow, June 1991.

[Poplet, 1987] John Poplet. The Development of a Parallel C Compiler. Technical report, Definicon Systems, Inc., Edelsbergstr. 8, 8000 München 21, West Germany, 1987.

[Reppy, 1991] John H. Reppy. Concurrent Programming with Events. Reference Manual Version 0.9.6, Department of Computer Science, Cornell University, Ithaca, NY, October 1991. The Concurrent ML Manual.

[Ruggherio and Sargeant, 1987] C. A. Ruggherio and J. Sargeant.

Control of Parallelism in the Manchaster Dataflow Machine. In Gilles Kahn, editor, Functional Programming Languages and Computer Architecture, volume 274 of Lecture Notes in Computer Science, pages 1–15, Portland, Oregon, USA, September 14–16, 1987. Springer, Berlin.

[Sarkar and Hennessy, 1986] Vivek Sarkar and John Hennessy. Partitioning Parallel Programs for Macro-Dataflow. In 1986 ACM Symposium on Lisp and Functional Programming, pages 202–211, Cambridge, Massachusetts, August 4–6, 1986.

[Schmidt, 1986] David A. Schmidt. Denotational Semantics — A Methodology for Language Development. Allyn and Bacon, Boston, 1986.

[Schreiner, 1990] Wolfgang Schreiner. ADAM & EVE — An Abstract Dataflow Machine and Its Programming Language. Master’s thesis, Johannes Kepler University, Linz, Austria, October 1990. Also: Technical Report 90-42.0, RISC-Linz, Johannes Kepler University, Linz, Austria, 1990. Also: Technical Report 91-1, Austrian Center for Parallel Computation, January 1991.

[Schreiner, 1991] Wolfgang Schreiner. A Model for the Compilation of Dataflow Languages on Conventional Parallel Architectures. Technical Report 91-50, RISC-Linz, Johannes Kepler University, Linz, Austria, 1991.

[Siegl, 1990] Kurt Siegl. Gröbner Bases Computation in STRAND: A Case Study for Concurrent Symbolic Computation in Logic Programming Languages. Master’s thesis, Johannes Kepler University, Linz, Austria, 1990. Also: Technical Report 90-54, RISC-Linz, Johannes Kepler University, Linz, Austria, 1990.

[Stoye, 1985] W. R. Stoye. The implementation of functional languages using custom hardware. PhD thesis, Computer Laboratory, University of Cambridge, May 1985.

[Szymanski, 1991] Boleslaw K. Szymanski, editor. Parallel Functional Languages and Compilers. Frontier Series. ACM Press, 1991.

[Takeichi, 1986] M. Takeichi. A Functional Machine for Fully Lazy Evaluation (Extended Abstract). In Proceedings of the RIKEN Symposium on Functional Programming, pages 54–60, [Takeichi, 1988]

Masato Takeichi. Lambda-Hoisting: A Transformation Technique for Fully Lazy Evaluation of Functional Programs. New Generation Computing, 5:377–391, 1988.

[Traub, 1986] Kenneth R. Traub. A Compiler for the MIT Tagged-Token Dataflow Architecture. Technical Report MIT/LCS/TR-370, Laboratory for Computer Science, Massachusetts Institute of Technology, Cambridge, MA, August 1986.

[Traub, 1991] Kenneth R. Traub. Implementation of Non-Strict Functional Programming Languages. Research Monographs in Parallel and Distributed Computing. Pitman, London, 1991.

[Treleaven, 1990] P. C. Treleaven, editor. Parallel Computers — Object-Oriented, Functional, Logic. Series in Parallel Computing. John Wiley & Sons, Chichester, 1990.

[Turner, 1975] D. A. Turner. An implementation of SASL. Technical Report 4, University of St. Andrews, 1975.

[Turner, 1976] David A. Turner. SASL Reference Manual. Technical report, University of St. Andrews, December 1976.

[Turner, 1979] D. A. Turner. A new implementation technique for applicative languages. Software — Practice and Experience, 9:31–49, 1979.

[Turner, 1985] D. A. Turner. Miranda: A non-strict functional language with polymorphic types. In Jean-Pierre Jouannaud, editor, Functional Programming Languages and Computer Architecture, volume 201 of Lecture Notes in Computer Science, pages 1–16, Nancy, France, September 16–19, 1985. Springer, Berlin.

[Vollmer, 1990] Jürgen Vollmer. Modula-P Reference Manual. GMD Research Group at the University Karlsruhe, Karlsruhe 1, West Germany, July 1990.

[Wadler, 1985] P. Wadler. Introduction to Orwell. Technical report, Programming Research Group, University of Oxford, 1985.

[Watson et al., 1988] Ian Watson, John Sargeant, Paul Watson, and Viv Woods. The Flagship Parallel Machine. In K. D. Reinartz and Chris Jesshope, editors, CONPAR 88, Conference on Algorithms and Hardware for Parallel Processing, Manchester, England, September 12–16, 1988. Cambridge University Press, Cambridge, UK.

[Wentworth, 1989a] E. P. Wentworth. Introduction to Functional Programming using RUFL. Technical Document PPG 89/6, Parallel Processing Group, Department of Computer Science, Rhodes University, Grahamstown 6140, South Africa, May 1989.

[Wentworth, 1989b] E. P. Wentworth. The Rhodes University FLFM (Ver. 1.7) — An Abstract Machine for Functional Languages. Technical Document PPG 89/7, Parallel Processing Group, Department of Computer Science, Rhodes University, Grahamstown 6140, South Africa, May 1989.

[Wikström, 1987] A. Wikström. Functional Programming Using Standard ML. International Series in Computer Science. Prentice-Hall, 1987.

[Wirth, 1984] N. Wirth. Compilerbau (Compiler Design) (in German). Teubner, 1984.

[Wirth, 1988] Niklaus Wirth. Programming in Modula-2. Text and Monographs in Computer Science. Springer, fourth edition, 1988.