Adam & Eve An Abstract Dataflow Machine and Its Programming Language

Diplomarbeit

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

Informatik

eingereicht von

Wolfgang Schreiner

Angefertigt am Institut für Mathematik

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

Eingereicht bei

o.Univ.-Prof. Dr. Bruno Buchberger

September 1990

Abstract

This thesis describes the design and the implementation of the Adam Abstract Dataflow Machine and the functional programming language Eve that will be used for programming this machine. Adam is an abstract parallel computer with a hybrid distributed memory architecture that combines features from register machines and from dataflow computers. Its processor has a rather conventional set of two-address instructions 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. Eve is a simple purely functional language that shows most of the features known in functional programming: strong typing, higher order functions, non-strict functions and data types. A prototype of the Adam concept has been implemented on a multi-transputer system and shows very promising results.

Keywords: Parallel Computation, Dataflow Architectures, Functional Programming.

1

Für meine Eltern

Contents

1 Introduction 11

2 Dataflow Programming 16 2.1 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.2 Functional Programming Languages . . . . . . . . . . . . . . . . . . . . . . . . . . 18

2.2.1 Basic Principles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.2.2 Strictness Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 2.2.3 Parallel Functional Programming . . . . . . . . . . . . . . . . . . . . . . . . 23

2.3 The Dataflow Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.3.1 Basic Idea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 2.3.2 Translating Programs to Dataflow Graphs . . . . . . . . . . . . . . . . . . . 26

2.4 Dataflow Programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 2.4.1 Prime Number Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 2.4.2 Improved Prime Numer Generation . . . . . . . . . . . . . . . . . . . . . . 30

2.5 Dataflow Architectures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 2.5.1 The Manchester Dataflow Computer . . . . . . . . . . . . . . . . . . . . . . 34 2.5.2 The ETL Sigma-1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 2.5.3 The TTDA Tagged Token Dataflow Architecture . . . . . . . . . . . . . . . 35 2.5.4 The ETS Explicit Token Store “Monsoon” . . . . . . . . . . . . . . . . . . . 36

3 The EVE Programming Language 38 3.1 Some Hints for Further Reading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 3.2 Overall Program Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40 3.3 Type Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 3.4 Atomic Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43

3.4.1 Integers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 3.4.2 Booleans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3.4.3 Characters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

3.4.4 Comparison Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.5 Structured Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47

3.5.1 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 3.5.2 Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 3.5.3 Tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 3.5.4 Unions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51

3.6 Constant Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 3.7 Conditional Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 3.8 Case Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54 3.9 Block Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 3.10 Function Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 3.11 Higher Order Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58 3.12 Strictness Properties . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 3.13 Error Behaviour . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 3.14 A Programming Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 3.15 Syntax Definition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

3.15.1 Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 3.15.2 Input File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 3.15.3 Lexical Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68 3.15.4 Context-Free Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 3.15.5 Keywords . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70

3.16 Predefined Identifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 3.16.1 Predefined Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 3.16.2 Predefined Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 3.16.3 Predefined Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

4 The ADAM Abstract Dataflow Machine 74 4.1 Basic Idea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74 4.2 Global Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 4.3 The Arbiter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 4.4 The Processor Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83

4.4.1 The Program Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 4.4.2 The Frame Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 4.4.3 The Context Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 4.4.4 The Trigger Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 4.4.5 The Message Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 4.4.6 The Request Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 4.4.7 The Execution Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93

4.5 The Memory Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 4.5.1 The Heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 4.5.2 The Request Stores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 4.5.3 The Memory Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 4.5.4 Garbage Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104

5 Compiling the EVE Language into ADAM Code 108 5.1 Basic Compilation Steps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 5.2 Arithmetical Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 5.3 Conditional Expressions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 5.4 Function Calls and Function Definitions . . . . . . . . . . . . . . . . . . . . . . . . 114 5.5 Tail-Recursive Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 5.6 Curried Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 5.7 Operations on Data Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117

6 The ADAM Implementation and Evaluation 119

6.1 The ADAM Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119 6.1.1 Tokens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 6.1.2 Program Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 6.1.3 Frame Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 6.1.4 Context Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 6.1.5 Trigger Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 6.1.6 Atomic Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 6.1.7 Heap Cells . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 6.1.8 Summary of Implementation Restrictions . . . . . . . . . . . . . . . . . . . 131

6.2 The ADAM Evaluation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131

A The ADAM Operational Behaviour 134 A.1 Notation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 A.2 The Arbiter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136

A.2.1 The Arbiter Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 A.3 The Processor Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140

A.3.1 Global Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 A.3.2 Global Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 144 A.3.3 Global Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 A.3.4 The Execution Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 A.3.5 Executing an Instruction Sequence . . . . . . . . . . . . . . . . . . . . . . . 148 A.3.6 Receiving a Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 A.3.7 Fulfilling a Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 A.3.8 Reporting the Load . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153

A.4 The Memory Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 A.4.1 Global Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 A.4.2 Global Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 A.4.3 Global Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 A.4.4 The Memory Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 A.4.5 Auxiliary Procedures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 A.4.6 Expensive Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 A.4.7 Cheap Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 A.4.8 Load Reports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175

B The ADAM Instruction Set 176

B.1 Arithmetical-Logical Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 B.1.1 Standard Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 B.1.2 Binary Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180 B.1.3 Binary Instructions with Constants . . . . . . . . . . . . . . . . . . . . . . . 183 B.1.4 Unary Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186

B.2 Control Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 B.2.1 Split Control Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 B.2.2 Duplicate Atomic Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 B.2.3 Delete Pointer Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 B.2.4 Route Control Flow . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190 B.2.5 Wait for Synchronization Event . . . . . . . . . . . . . . . . . . . . . . . . . 191 B.2.6 Deliver Long Constant . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191

B.3 Function Call Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 B.3.1 Call Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 B.3.2 Sending of Arguments and Result Values . . . . . . . . . . . . . . . . . . . 195 B.3.3 Terminate Activation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198

B.4 Storage Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 B.4.1 Allocating Tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 199 B.4.2 Reading the Contents of Tuple Fields . . . . . . . . . . . . . . . . . . . . . 199 B.4.3 Writing the Contents of Tuple Fields . . . . . . . . . . . . . . . . . . . . . . 200

C The ADAM Source Code 203

List of Figures

2.1 A Dataflow Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 2.2 A Firing Sequence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 2.3 Prime Number Generator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 2.4 The Primes Pipeline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 2.5 Improved Prime Number Generator . . . . . . . . . . . . . . . . . . . . . . . . . . 31 2.6 The Primes Circuit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

3.1 Symbolic Derivation (Type Definitions) . . . . . . . . . . . . . . . . . . . . . . . . 64 3.2 Symbolic Derivation (Auxiliary Functions) . . . . . . . . . . . . . . . . . . . . . . . 66 3.3 Symbolic Derivation (Main Function) . . . . . . . . . . . . . . . . . . . . . . . . . . 67

4.1 A Dataflow Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 4.2 The Adam Network . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78 4.3 An Adam Master Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79 4.4 An Adam Worker Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 4.5 The Arbiter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 4.6 The Processor Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 4.7 The Program Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86 4.8 An Activation Frame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 4.9 The Context Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 4.10 The Trigger Store . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91 4.11 Interaction between Processor Stores . . . . . . . . . . . . . . . . . . . . . . . . . . 95 4.12 The Memory Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 4.13 Mapping Tuples to Heap Cells . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 4.14 Garbage Collection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

5.1 The Eve Compilation Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 5.2 A Dataflow Graph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 6.1 Current State of the Adam&Eve System . . . . . . . . . . . . . . . . . . . . . . . 120 6.2 Fibonacci Program Source File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122 6.3 Fibonacci Expression Source File . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 6.4 Fibonacci Expression Type File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 6.5 Fibonacci Execution Log File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 6.6 Fibonacci Execution Statistics (Excerpt) . . . . . . . . . . . . . . . . . . . . . . . . 125 6.7 Token Formats . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 6.8 Program Store Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 6.9 Activation Frame Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 6.10 Context Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 6.11 Trigger Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 6.12 Atomic Values Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 6.13 Heap Cell Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 6.14 Implementation Restrictions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132

A.1 Basic Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136 A.2 Network Quantities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 A.3 Arbiter Operation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139 A.4 Global Processor Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 A.5 Global Processor Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 A.6 Global Processor Types (Continued) . . . . . . . . . . . . . . . . . . . . . . . . . . 143 A.7 Global Processor Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 A.8 Execution Unit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 A.9 Instruction Execution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 A.10 Receiving a Message . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 A.11 Function Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 A.12 Load Reports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 A.13 Global Memory Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 A.14 Global Memory Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 A.15 Global Memory Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156 A.16 Memory Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 A.17 Availability of Tuple Space and Its Allocation . . . . . . . . . . . . . . . . . . . . . 159 A.18 The Garbage Collection Mechanism . . . . . . . . . . . . . . . . . . . . . . . . . . 160 A.19 Deallocation of a Tuple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 A.20 Duplicating a Tuple Reference . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162 A.21 Expensive Memory Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 A.22 Allocation Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164 A.23 Read Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 A.24 Read Requests (Continued) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166 A.25 Submission Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 A.26 Submission Requests (Continued) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 A.27 Cheap Memory Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 A.28 Write Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 171 A.29 Write Requests (Continued) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 A.30 Reference Requests . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 A.31 Load Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 A.32 Load Reports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175

B.1 The Adam Instruction Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177

List of Tables

3.1 Predefined Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 3.2 Type Constructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 3.3 Integer Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3.4 Integer Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 3.5 Boolean Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 3.6 Special Characters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.7 Character Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.8 Comparison Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 3.9 List Operators and Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 3.10 Runtime Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 3.11 The US-ASCII Character Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 3.12 Predefined Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 3.13 Predefined Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 3.14 Predefined Integer Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 3.15 Predefined Boolean Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 3.16 Predefined Character Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 3.17 Predefined Comparison Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 3.18 Predefined List Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 3.19 Error Test Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

6.1 Adam Benchmarks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

Acknowledgements

First of all, I want to thank Prof. Bruno Buchberger whose untiring efforts in organizing the institute, raising funds and setting up academic and industrial contacts have been the basis for the development and the work of the parallel computation group at RISC-Linz. His activities to promote computer science research in Austria, especially his initiation of the “Austrian Center for Parallel Computation”, are a valuable contribution to the future development of our country. I am grateful for his confidence in me and hope that my work will meet this attitude.

Moreover, I want to thank all the members of the parallel computation laboratory at RISC-Linz whose engagement and enthusiasm is the backbone of a lot of valuable and interesting work: Klaus Kusche, who has organized the lab for almost two years now and whose idea initiated my thesis, gave me much valuable help for my work and proof-read several parts of this paper; with Kurt Siegl, I have had many long and interesting discussions on parallel programming and parallel languages; Peter Zettler’s work on Linda is certainly going to become very important for us; Hans-Wolfgang Loidl will continue my work by his diploma thesis on the design and implementation of a compiler for the Eve language.

The course on the semantics of parallel programming languages held by Gerhard Funk in the summer term of 1990 gave me many interesting insights into the connection between different parallel programming paradigms. I want to express my thanks to all participants of this course with whom we organized after each official lecture a private one, in particular (additionally to the members of the parallel group) to Werner Landerl. We would be very happy if he also joined our group.

Furthermore, I want to acknowledge the support of all other students, assistants and faculty at RISC-Linz. It is really a great feeling to work with a team that has so much background in so many varying fields of computer science and mathematics ranging from theoretical foundations to details of software-engineering. I also want to mention the hard work done by our systems manager Georg Gratzl and his assistant Hubert Hofbauer in order to keep the computing environment of RISC-Linz always running and up to date. I am also grateful for the patience of our secretary Maria Wögerbauer when she faced the long periods that I occupied the laser printer with this thesis.

Chapter 1

Introduction

Parallel Computation is today one of the most advanced and most exciting topics in computer science. While in the past the performance of computers with single processors could be steadily enhanced year for year, in the future we can expect essential increases of computing power only by parallel architectures. Today many research institutions, from both the academic and the commercial world, work on the design of parallel architectures, languages, algorithms and tools. A lot of applications that require high computing performance have already been parallelized. This ongoing trend towards parallel computation will strongly intensify in the future.

Research on parallel computation has already a long tradition at RISC-Linz, especially the research on languages and algorithms for parallel symbolic computation. This research has its roots in the seventies (see [Buchberger, 1978]) and resulted in the concept of the parallel L-Machine and the associated L-Language (see [Buchberger, 1985]). An experimental prototype of this machine and the language has been operative for several years now.

One of the latest highlights in the institute’s research has been the design of the new L-Language LL, a parallel general purpose programming language designed to meet the needs of symbolic computation (see [Kusche, 1988]). For reasons of optimum hardware usage and highest possible efficiency, the new L-Language was decided to rely on explicit coarse-grain parallelism. The programmer himself has to specify the parallelism on the level of whole functions or even larger parts of the program. A verification system that allows to prove the correctness of LL programs has been developed in [Funk, 1989].

In contrast to this approach, there has also been much international research on how to exploit the implicit fine-grain parallelism inherent in most (if not all) algorithms. Several hardware architectures have been developed in order to utilize this kind of parallelism in a very efficient way, where dataflow machines seem to be one of the most promising approaches (see [Arvind and Iannucci, 1987]). Especially for functional languages which provide a very large amount of implicit parallelism these kinds of architectures are of great interest (see [Arvind et al., 1988a]). However, also for conventional machines the techniques used for exploiting implicit parallelism (e.g. dataflow analysis) are very important, since they allow the automatic parallelization of basically sequential programs (see e.g. [Boyle, 1989]).

The parallel computation group at RISC-Linz has therefore started to investigate the usefulness of the dataflow approach for applications in symbolic computation: The goal of these investigations has been the design and implementation of an Abstract Dataflow Machine (short Adam) programmed in the functional language Eve and simulating the architecture of a dataflow computer on a conventional parallel machine. The kernel of the Adam architecture has been subject of substantial changes in the course of the design process:

• The original idea initiating these investigations was to simulate the architecture of some concrete pure dataflow architecture such as the British Manchester Dataflow Machine ([Gurd et al., 1985]), the Japanese Sigma-1 ([Yuba et al., 1985]), the TTDA ([Arvind and Nikhil, 1988]) or the ETS Monsoon ([Papadopoulos, 1988]), both developed at the MIT. Based on the assumption that conventional von Neumann architectures are not very suitable for massively parallel computing, the goal of all of these approaches is the development of new microprocessor architectures that efficiently support fine-grain parallelism, have low communication overhead and tolerate long memory latencies.

• However, one of the preliminary goals of our investigations is actually not to develop new hardware architectures but to get some experience how far the dataflow approach is suited for applications in symbolic computation. In order to achieve this goal, it is not really necessary to simulate some concrete dataflow machine but only to simulate dataflow behaviour. Since we intended to implement the Adam architecture on a conventional parallel transputer system ([Inmos, 1988b]), the idea arised to investigate how a conventional processor architecture could be enabled to make use of the dataflow model. The result of these investigations is a hybrid dataflow architecture that basically consists of an conventional instruction set extended by special parallelization and synchronization instructions.

The term “Abstract Dataflow Machine” is therefore somewhat misleading. The Adam architecture does actually not represent a dataflow machine but a machine that is able (but not at all forced) to simulate dataflow behaviour. For our purposes, this approach has in particular the following advantages:

• The Adam architecture is neither fixed to any parallelization nor to any synchronization strategy. In fact, only the compiler determines where to generate parallelism and how to synchronize parallel program paths. We may therefore experiment with various parallelization strategies simply by replacing one code generator of the compiler by another one.

• Utilizing the Adam’s more or less conventional register structure, the compiler may make use of the traditional compilation techniques for register machines. This may help to decrease the time and effort spent on writing a code generator for the compiler.

• In many cases, the compiler may generate more efficient code by optimizing register utilization, removing superfluous synchronization instructions, serializing parallel program paths, etc. By avoiding the ”finest-grain parallelism” that is actually not utilized by any architecture, parallelization overhead may be minimized.

Independently on these considerations, the idea of an hybrid dataflow/von Neumann architecture has recently become a promising research area that finds much international interest ([Iannucci, 1988], [Gao et al., 1988]).

However, the Adam architecture, as defined in this thesis, shows also some restrictions mainly imposed by implementation considerations:

• In contrast to the original intention described in [Schreiner, 1989], the Adam architecture does not provide any support for delayed evaluation. Further considerations revealed that this feature would essentially decrease efficiency and raise a lot of non-trivial problems. Since for our purposes delayed evaluation is certainly a convenient but actually not an essential feature, we decided to omit it in the first approach.

• As defined in this paper, all compound data structures consist of cells of fixed size, i.e. variable-size tuples are mapped to linked lists on the heap. However, the only reason for omitting variable-size storage blocks is to simplify memory management. Since all memory instructions consider the heap as a set of variable-size tuples, it is no problem to replace the actual implementation by a more efficient one while preserving code compatibility.

The Adam architecture is part of a research project and still subject to changes, corrections and improvements. Its substantial kernel has now been stabilized but some features (e.g. the instruction set) may still change. The Adam concept has been implemented on a multi-transputer system and shows rather promising results.

While our motivation for our activities of the Adam architecture should be rather clear, we will now explain our reasons for the design of another programming language (named Eve) that will be used for programming the Adam machine:

The extensive research on functional (respectively dataflow) programming has already resulted in a number of well-known functional languages (Miranda ([Turner, 1985]), ML ([Harper et al., 1988]), ID ([Nikhil, 1988]), Haskell ([Hudak et al., 1990]), …) that are very similiar in their essential features. This shows that the functional programming community has reached a well-founded consensus on the state of the art. It is not our aim to enrich this variety of languages by a new language with extraordinary new facilities. In fact, any of the languages mentioned above would do the job of programming the Adam at least as well as the language that we are going to propose. However, these languages have obtained a complexity that makes a rapid implementention of an experimental prototype (of even a language subset) rather difficult.

Since the prevailing purpose of our investigations is the research on dataflow machines and, in the frame of this project, not the research on compiler construction, we have decided to design the small functional language Eve^{1} providing the main properties of the languages mentioned above while omitting those features which are not really essential but rather complicate an experimental implementation. The main intentions that we pursue with the construction of this language are as follows:

1. We want to investigate the implementation of functional languages on parallel computers using dataflow principles.

2. We want to experiment with symbolic algorithms programmed in a functional language and executed on parallel architectures.

Eve is small and simple, but it is certainly not a toy language. In particular, the language shows the following features:

• Eve is purely functional i.e. the evaluation of programs is free of any kinds of side-effects.

• Eve is non-strict i.e. functions may return results even if some of their arguments have not yet been evaluated.

• Eve is strongly typed i.e. typing errors are detected at compile time.

• Eve is higher order i.e. functions may take other functions as arguments and return functions as their results.

• Eve is safe i.e. runtime errors are caught by error values and do not lead in an unpredictable break of the program.

1EVE is a condensation of the term “Evaluated Expressions”, which is a hint at the functional character of the language.

In order to keep the language small, the following features have been explicitly omitted, either because they just represent less important syntactic sugar or because they make an experimental implementation essentially more complex:

• Pattern Matching,

• Type Inference and Polymorphism,

• Lambda Expressions and Curried Functions,

• List Comprehensions,

• Delayed Evaluation,

• Abstract Data Types and Modules.

The goal of this thesis is to give both an intuitive and exact description of the Adam architecture and the Eve language and to describe the current prototype implementation of the Adam. Except for a general outlook, the thesis does not treat the design and implementation of a compiler translating the Eve language into Adam machine code. This issue is covered by another diploma thesis that is currently under way.

The structure of the thesis is now as follows:

• After this introduction, the chapter Dataflow Programming presents a general overview on the scope of the thesis. It describes the functional style of programming, illustrates the dataflow model with the help of a few parallel programs and summarizes the current state of the art in the international research on dataflow architectures.

• The chapter The EVE Programming Language contains the definition of the functional language Eve. It formally defines the syntax of the language and informally describes its semantics on the basis of several examples. A major program written in Eve shows the capabilities of the language.

• The chapter The ADAM Abstract Dataflow Machine provides an informal but detailled description of the Adam architecture. First the basic idea of the concept is illustrated, then the construction of the machine and its various components are presented in a top-down fashion.

• In the chapter Compiling the EVE Language into ADAM Code, we give an overview in which way a planned Eve compiler for the Adam machine will be constructed. Moreover, we show how several constructs of a functional language can be translated into code executable on the Adam architecture.

• The last chapter on The ADAM Implementation and Evaluation describes the current status of the system’s implementation and presents several of its internal details. A few benchmark programs illustrate the performance of the system and the speedups that could be achieved.

• Appendix The ADAM Operational Behaviour formally defines the Adam architecture by an abstract interpreter: this interpreter is presented by a set of algorithms that describe the behaviour of each component of an Adam worker module.

• Appendix The ADAM Instruction Set formally defines the machine instructions of the Adam architecture: for each instruction, an algorithm is presented that describes the effect of the instruction in the frame of the interpreter given in the previous appendix.

• Finally, Appendix The ADAM Source Code contains the whole Occam 2 source code of a prototype implementation of the Adam concept on a multi-transputer system.

Any person interested in this or other projects of the parallel computation group of RISC-Linz is invited to contact the author (Bitnet address: K315640@AEARN). We gratefully appreciate all kinds of comments, suggestions and criticism.

Dataflow Programming

In this chapter, we present an introduction to the dataflow style of parallel programming. After a motivation for our interest in this topic, a sketch of the basic principles of functional programming is given, then the dataflow model is described. A major example of a dataflow program illustrates the essential features of this model. Finally, an overview of several architectures implementing the dataflow principle in hardware is presented.

2.1 Motivation

Writing correct programs for parallel computers is one of the most difficult tasks in computer science. Up to now, progress in parallel computation has been significantly hindered by the fact that parallel programming has been an extremely tedious task. There arise problems that are essentially more difficult than on sequential machines or totally unknown there:

• Complexity:

In sequential programming, only one execution sequence of statements has to be kept in mind. At any moment, there is only one program state to watch and there is only a limited number of possible influences on this state. In the parallel case, the programmer has to keep track eventually of a large number of parallel paths that interfere with each other. Each of the parallel tasks has a local state and the state of each task may often be altered by any other task. Temporal relationships between the different tasks have to be considered and the correct interaction between parallel processes has to be handled. Altogether, the dynamical behaviour of parallel programs can be orders of magnitude more complex than that of sequential ones.

• Observability:

In distributed memory systems, parallel tasks may reside on processors for that no input from and no output to peripheral devices is possible. Debugging erroneous processes on such processors may become an extremely difficult task. This is in particular the case if the available debugging tools are poor (as usual) and do not allow to inspect the state of the desired tasks. Even for parallel processes on a single processor, it may not be possible to watch the state of any process. In this case, the programmer is forced to draw ingenious conclusions by reasoning about the possible sources of error from their effect on other (observable) processes. Moreover, using debugging tools may essentially alter the behaviour of the program investigated and let the error suddenly disappear (see below).

• Non-Determinism:

From one execution of a program to the next, the temporal relationship between parallel processes may (and usually will) essentially alter. Errors that once have occurred may not be able to be reproduced. The program may return different answers on different program runs or may sometimes run into an deadlock. In general, it is extremely difficult (say impossible) to reconstruct the conditions that lead to some particular behaviour of a parallel program. The use of debugging or trace tools has very often the effect that an error that occurred a couple of times cannot be observed any more.

Altogether, writing a parallel program in general represents a great mental challenge for a human being. One main reason for this fact is the lack of programming tools, systems and, in particular, languages that make parallel programming a more natural task. From the software-technological point of view, the state of parallel programming can be compared with the state of computer science in the early 1960’s with the first high-level languages and compilers coming into existence.

Today, most parallel programming paradigms still rely on the familiar imperative languages based on sequential computers and the von-Neumann principle of computation. These languages are usually extended by several constructs that allow the creation of parallel tasks, their synchronization and the communication between them, e.g. by

• explicit syntactic constructs derived from the CSP model ([Hoare, 1985]),

• operating system functions from Unix or Mach ([Tevanian Jr. et al., 1987]),

• the Linda programming model ([Bjornson et al., 1987]).

Even if some of these programming models (in particular Linda) are rather high-level and seem to provide sufficient support, the situation is not basically changed. Furthermore, there always remains one point of criticism: the programmer still thinks in terms of sequential programming. He basically uses sequential languages to write sequential programs and finally just combines several sequential programs to let them work in parallel.

One radically different approach to this situation is to leave the imperative programming model that is essentially based on sequential von-Neumann computers. Instead of this, the programmer should use a model where true parallel programs are written, i.e. without specifying any sequential paths of execution. Such a programming model can be only based on declarative programming languages.

Declarative programming languages allow to specify the relation between the given input arguments and the desired output values without defining sequential procedures that compute these values. Languages of this kind are usually based on one of two mathematical concepts, either on mathematical functions or logical relations. Hence, we can distinguish between

• Functional Programming Languages and

• Logic Programming Languages.

Since the programmer does not specify a sequential program, the implementation of such a language (consisting of compiler and runtime system) may freely choose the way the program is executed, in particular it may let the program be executed in parallel. This kind of execution is especially supported by the fact, that declarative programs are inherently parallel down to the lowest level. Roughly spoken, any textually independent parts of such a program may be executed in parallel.

Several strategies have been developed for the implementation of declarative programming languages on sequential as well as on parallel computers. One of these strategies is the dataflow principle. This principle is a model for the way a declarative (in particular functional) program may be executed in parallel:

• The dataflow model suggests a particular direction for the parallel implementation of a declarative language. This implementation may usually be based on a conventional already available parallel architecture. However, for almost two decades, several special dataflow computers have been proposed that promise the most efficient utilization of the fine-grained parallelism provided by declarative languages.

The goal of these architectures is to implement the dataflow model directly in hardware, i.e. dataflow processors are no more based on the von-Neumann principle of program execution. While, theoretically, these architectures have a lot of advantages that make them superior to conventional parallel computers, commercial available systems have not yet come into existence. One main reason for this disappointing fact is the large amount of knowledge gathered on the von-Neumann principle of computation, as well from the hardware engineer’s as from the software engineer’s point of view.

• The long-term research on dataflow architectures lead to the wide-spread view that the dataflow model is basically a model for the the construction of parallel architectures and that the dataflow principle might be only implemented on special hardware. However, this is a misunderstanding of facts, since this model is independent of the underlying hardware and may also be implemented on conventional architectures.

Moreover, the dataflow model suggests a particular style of programming regardless of the architecture it is based on. The dataflow style of programming provides several powerful features that are as useful as elegant and have no counterpart in classical (sequential or parallel) programming in imperative programming languages.

In the following sections, we will present the dataflow model using the functional style of programming. Logic programming languages, while basically more powerful than functional languages, have several properties that make their parallelization more difficult. Actually, most parallel implementations of logic languages abandon several important features from logic programming (such as backtracking or unification), which makes them very similiar to functional languages. However, in [Siegl, 1990a] and [Siegl, 1990b], the power of a parallel logic language using dataflow principles is presented on the basis of a concrete application.

2.2 Functional Programming Languages

The basic principles of functional (also called applicative) languages go back to the early days of computer science. One of the first high-level programming languages, Lisp ([McCarthy, 1960]), already incorporates several of the essential features of functional programming^{1}. Actually, the theoretical foundations of functional programming, summarized in the theory of λ-calculus ([Church, 1941]), have been established before the first computers have yet come into existence.

Most of the modern functional programming languages have their roots in [Landin, 1966] and the famous Turing Award lecture [Backus, 1978]. Today, there exist a variety of functional languages. Among them, the most popular ones are

1However, since a lot of imperative extensions have been included to Lisp, only a small subset of the language (Pure Lisp) can be called functional.

• ML ([Cousineau et al., 1985]),

• Hope ([Burstall et al., 1980]),

• Miranda ([Turner, 1985]),

• ID ([Nikhil, 1988]),

• Haskell ([Hudak et al., 1990]).

However, the differences between most of these languages are merely of syntactic kind. Today, a wide consensus has been established on the essential features of functional languages and most research efforts now concentrate on compilation techniques for their efficient implementation ([Peyton Jones, 1987], [Johnsson, 1987], [Augustsson, 1987]).

For the following discussion, we will use a pseudo-functional language similiar to the syntax of Miranda. The examples presented are partially taken from [Bird and Wadler, 1988] that provides an excellent introduction to functional programming.

2.2.1 Basic Principles

A language is called functional if a program written in this language is composed of functions that are free of side-effects. If a function is evaluated, it returns a value but has no effect besides that. In particular, it does not interfere with the evaluation of other functions. Hence, the result of a function only depends on the values of its arguments and of globally defined constants but not on any other influence.

As a consequence of this feature, called Referential Transparency, all functional programs have the Church-Rosser Property. This mathematical property says that, for obtaining the result value of a function, its arguments may be evaluated in any order, in particular in parallel. Any terminating evaluation order determines the same result value (see Section 2.2.3).

However, this useful feature imposes the following restrictions on a functional language:

• A functional language has no variables (in the meaning of storage locations containing variable values) but only named constants whose values are fixed in their scopes. Hence, there is no assignment statement that may alter the value of a variable. Morover, there are no mutable data structures whose components can be individually altered. On the creation of a data structure, all components have to be explicitly defined.

• Since every kind of communication with the outside world is a side-effect, functional languages has no explicit input/output statements. Of course, every system implementing a functional language has to communicate with the human user but this is the task of a dialogue shell and not part of the language itself.

E.g. let us consider the following functional program:

f x y = x + a, if x < 10
= x − a, otherwise

where a = square (y + 1) square x = x ∗ x

If the function f is called by f(5, 7), the formal parameters x and y are bound to the actual arguments 5 and 7, respectively. The result of f is defined by two equations each guarded by a test condition. Since 5 < 10, the result of the function is determined by the right hand side 5 + a of the first equation. a is defined in the where-clause of the definition as square(7 + 1). Since square is in the same clause defined to be the quadratic function, a is bound to 64 and the total result of the function is 69.

This program has computed a rather simple result. Another more complex program involves recursive function definitions and the use of linear lists:

sumprod x y = 0, if null x and null y
= head x + sumprod (tail x) y, if null y
= head y + sumprod x (tail y), if null x
= head x ∗ head y + sumprod (tail x) (tail y), otherwise

This program computes the sum of all products of the corresponding entries of two lists. If one list is shorter than the other one, the remaining elements of the other list are simply added. In sumprod, four equations handle the cases that may appear. null is a primitive predicate testing whether a list is empty, head and tail return the first element of a list and the list without the first element, respectively.

However, the same program may be written in a more elegant way:

sumprod [ ] [ ]  = 0
sumprod a : x [ ] = a + sumprod x [ ]
sumprod [ ] b : y  = b + sumprod y
sumprod a : x b : y = a ∗ b + sumprod x y

This program makes use of a feature called pattern matching that allows to select the appropriate equation of a function by matching the actual arguments against the patterns specified in the head of a function definition. E.g. the pattern a : x (with : being the cons-operator) matches against any list with at least one element. If the match succeeds, the first element of the list is bound to a and the rest of the list is bound to x. Using this feature, programs may become essentially more compact.

The next functions shows how the handling of lists may be simplified:

listsum x y = [a + b | (a, b) ← (x, y)]
divisors x = [y | y ← [1 … x], x mod y = 0]
triads n  = [(x, y, z) | x ← [1 … n], y ← [x … n], z ← [y … n], x^{2} + y^{2} = z^{2}]

All three functions make use of the list comprehension facility that is derived from the set quantifier of mathematics. The result of a list comprehension [e_{x}|x ← l, p_{x}] is the list of all expressions e with the free variable x successively bound to all values of the list l that satisfy condition p. If more qualifiers (i.e. binding statements or conditions) on the right hand side of the | occur, they are all evaluated from left to right and only those bindings that have passed all conditions are considered for the result list.

listsum computes a list whose entries are the sums of the corresponding elements of two input lists. divisors computes all divisors of a given integer x. triads constructs the list of all Pythagorean triads in a given range, i.e. triples of numbers x, y, z such that x^{2} + y^{2} = z^{z}.

Finally, a last example shows that functions themselves may be handled like any other kinds of objects:

oddlist n = filter odd [1 … n]
filter p l = [x | x ← l, p x]
odd n  = (n mod 2 ≠ 0)

filter is a higher-order function taking a list l and a predicate (i.e. a boolean valued function) p as arguments and returning a list that contains all elements of l that fulfill p. oddlist constructs the list of all odd integers in a certain range by calling filter with the test function odd. In general, the use of functions as first-class objects is a very powerfull tool that allows to write concise and efficient programs.

Due to lack of space, we cannot describe a lot of other advanced and very important features of functional programming languages, in particular their sophisticated type systems that have no counterpart in imperative languages. Interested readers may refer to [Bird and Wadler, 1988].

2.2.2 Strictness Properties

Up to now, we have considered functional programs as mere static objects and neglected their dynamic interpretation. In particular, we have not yet discussed the evaluation strategies applicable to functional programs. However, this strategy may have a crucial influence on the dynamic behavior of a program. Consider e.g. the following function definitions:

three x = 3
undef x = undef (x + 1)

Suppose we want to evaluate the expression three(undef (7)). What will the result of the evaluation be? From the mathematical point of view, the answer is clearly 3, since this is the result that three returns regardless of the argument given. However, if we apply the call by value evaluation strategy that is used in most imperative languages, the answer is that the evaluation of the expression does not terminate, since the evaluation of the argument does not. On the other hand, if we just textually replace every occurrence of the formal parameter x in the body of three by the argument undef (7) and then evaluate the body (call by name), the result will be 3, as desired.

Hence, there are some evaluation strategies that are complete in the sense that they may return a proper result of a function, even if (some or all of) its arguments are not defined, while other strategies are not. Evaluation strategies that are complete in this sense are called non-strict and languages that demand such a strategy are called non-strict languages.

The strictness properties of a language may have strong impacts on the runtime behaviour of a program, in particular on its termination. However, also a program’s reaction on runtime errors is influenced by its strictness properties, since the computation of e.g. three (1/0) may either return 3 or trigger a runtime error.

We will now define the concept of strictness more formally. Let the special symbol ⊥ (in words bottom) serve for two purposes:

1. ⊥ may denote a non-terminating value, i.e. an expression, whose result value will never be computed.

2. ⊥ may also denote the error value, i.e. the result of the illegal application of an operator.

We say that an n-ary function fun is strict in arg_{i} (1 ≤ i ≤ n), if the following property holds for all arguments arg_{j} (1 ≤ j ≠ i ≤ n):

fun[arg_{1}, …, arg_{i−1}, ⊥, arg_{i+1}, …, arg_{n}] = ⊥ .

Otherwise we call fun non-strict in arg_{i}.

In other words, a function fun is strict in an argument arg_{i}, if

• whenever the evaluation of arg_{i} does not terminate and return a result, fun also does not terminate and return a result, and

• whenever the evaluation of arg_{i} results in the error value, fun also results in the error value.

A function is called non-strict, if it is non-strict in any of its arguments. An evaluation strategy is non-strict, if it allows non-strict functions. A language is non-strict, if it demands a non-strict evaluation strategy.

Non-strict languages are preferable since they make programming concepts possible that are not known in strict programming languages. Consider e.g. the following function definitions for prime number computation:

prime n  = primes ! n
primes  = 2 : [p | p ← [3, 5, …], isprime p primes]
isprime t p : r = true, if p ∗ p > t
= false, if t mod p = 0
= isprime t r, otherwise

prime n returns the n^{th} prime number by taking the n^{th} element of the infinite list of all primes! primes itself is constructed recursively as a sequence starting with 2 and containing all odd numbers p that can not be divided by any element of primes up to the square root of p.

The “infinite” list primes can be constructed because list construction is non-strict in both of its arguments, i.e. the first element and the rest of the newly constructed list. Hence, this operation may return its result, a cons-cell, even if neither the head nor the tail have yet been evaluated. Only if the value of the head or some element of the tail have to be known, the corresponding arguments have to be evaluated resulting in a list element and another cons-cell, respectively.

Non-strict programming languages are more difficult to implement than strict ones and require generally more computational overhead. In sequential systems, non-strict languages are usually implemented using the delayed evaluation (also called call by need) strategy that only evaluates an expression if its value is required. However, the terms non-strict and delayed evaluation should not be mixed, since the one defines a logical concept while the other is just one particular implementation method.

On parallel architectures, non-strictness may be achieved by creating one parallel process for each argument of a function. The function is immediately activated, i.e. before any argument values have become available. Only if the value of some argument is required for further computation, the function has to wait for the result of the parallel process and synchronization takes place. This is the way non-strict functions are implemented in the dataflow model that is described in the next section.

2.2.3 Parallel Functional Programming

As mentioned in the previous sections, all functional languages have the Church-Rosser property stating that any terminating evaluation order of a functional program yields the same result. Hence, all arguments of a function may be simultaneously evaluated by parallel processes.

There are a lot of advantages that functional languages provide for parallel execution:

• Parallelization

Functional languages are implicitly parallel down to the lowest level. Any implementation of a functional language may utilize different granularities of parallelism, ranging from finegrained parallelism (on the level of primitive operators) up to coarse-grained parallelism (on the level of whole program blocks). Hence, the automatic parallelization of a functional program is much easier than for a corresponding imperative program. No complicated analysis techniques are required for detecting data dependences, side effects, . . ..

• Determinism

All functional programs are fully deterministic. They always return the same result, if they are given the same input. Runtime-dependent errors derived from special temporal relationships between parallel processes (e.g. deadlocks) cannot occur.

• Portability

Functional programs are independent of the underlying architecture, the type and the number of processors, and their configuration. Changes in the hardware do not at all affect the source code of a functional program. Every program that correctly works on some kind of computer architecture will also do so on any other one. In particular, the program may be developed and debugged on a sequential machine using a sequential evaluation strategy and then, without change, ported to a parallel architecture.

Several models for the parallel execution of functional programs have been developed. They usually fall into the categories

• Graph Reduction Models and

• Dataflow Models.

Both of them have gained much attention in the last years. An excellent introduction into the graph reduction model and its application for the implementation of functional programming languages is presented in [Peyton Jones, 1987]. In this thesis, we will concentrate on the dataflow approach as described in the following section.

2.3 The Dataflow Model

2.3.1 Basic Idea

The dataflow principle is one particular model describing the way a functional program may be executed on a parallel machine. The basic idea of the model is as follows:

Take any functional program, e.g.

Figure 2.1: A Dataflow Graph

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

The body of a function is always a (mathematical) expression. This expression can be represented by a directed acyclic graph as illustrated in Figure 2.1. The nodes of the graph represent the operations to be performed and its arcs denote the data dependences between these operations. An arc from operation a to operation b indicates that the result value of a is required by b for further computation. Arcs can be named where the names represent the constant names of the functional program. Arcs without predecessors represent the input arguments of the function and the one arc without successor represents its result value.

The dataflow model now gives such a static graph representation a dynamic interpretation. The idea is that so called tokens transmit values between the nodes by flowing along the arcs of the graph. Each token is a piece of information constructed of two fields

tag value

where tag indicates the instruction the token is on the way to and value represents the transmitted value.

As soon as on all input arcs of an operator tokens have arrived, the operator may fire: it removes all input tokens, performs the desired operation on the token values and generates a result token on its output arc carrying the result of the operation.

The order in which the operations within a graph fire is undefined. Every operator may fire as soon as tokens have arrived at all of its inputs. Operators may fire simultaneously and the execution time may vary for each operation. A possible sequence of firings for the above expression is illustrated in Figure 2.2.

2.3. THE DATAFLOW MODEL 25 Figure 2.2: A Firing Sequence

2.3.2 Translating Programs to Dataflow Graphs

In principle, a whole program consisting of a collection of functions may be translated in a single dataflow graph by embedding the body of each function

• with one input arc for each formal parameter and

• with one output arc for the computed result value

in the graph.

However, only those arcs connecting the instructions within a function are fixed at compile time, we call them static arcs. The connections between different functions depend on the given input data and can be established only at run-time, we call them dynamic arcs. Moreover, more than one activation of a function may (by recursion and parallel execution) simultaneously exist. Hence, we do not represent a whole functional program by a single dataflow graph but by a wood of graphs each of which models one function. The following subsections now discuss the questions:

1. How can several simultaneously existing activations of a function be managed in the same dataflow graph?

2. How can a function call in a dataflow graph be connected to the graph of the called function?

Simultaneous Function Activation

For each function, there exists one particular dataflow graph. However, there may exist more than one activations of the function at the same time. Hence, we need some possibility to distinguish the tokens that flow simultaneously through the same graph but belong to different activations of the function. Thus, to every token an additional context field is attached that indicates the activation that the token belongs to:

cont.addrp value

The token tag now consists of three components: con represents the context of the function, addr denotes the address of the instruction that the token is on the way to and p indicates the (left or right) input port in the case of binary instructions.

Since tokens that belong to different activations of a function may simultaneously flow through the graph, the firing rule presented in the previous section has to be adjusted:

• A token may fire, if a matching pair of input tokens has arrived, i.e. on all input ports of the operation there exist tokens with the same context field.

• If an operator fires, the result value of the operation is combined with a token that has the same context field as the input tokens.

By this rule, tokens from different activations can no more interfere with each other. However, now on each input arc of a binary operator an arbitrary number of tokens may wait for their matching partners.

Embedding Function Calls in a Dataflow Graph

In order to combine a function call with the corresponding dataflow graph, the following activities are neccessary:

• A context has to be created for the new function activation. This context has to be unique (i.e. must not yet have occurred) for this particular function.

• The caller must send to the activation defined by the new context

– the argument tokens,

– the description of its own context and

– the address of the instruction waiting for the result of the function.

• The callee must return the result token back to the caller defined by the context and the instruction address submitted.

For the implementation of non-strict functions, it is important that the activation of a function is independent of the submission of the arguments. The called function must be able to execute and return a result even if none of the arguments have arrived yet.

2.4 Dataflow Programs

Having sketched functional programming languages and the dataflow model, we now want to illustrate the flavour of the dataflow style of parallel programming by means of a major example.

First we want to argue why we think that dataflow programming is different from ordinary functional (or in general declarative) programming:

Theoretically, a “functional programmer” does not have to know anything about details of the underlying hardware and the evaluation strategy used. The only important knowledge is whether the language supports non-strict functions and which of the predefined functions are non-strict and which are not. Hence, dataflow programming should not be distinguishable from ordinary functional programming. Obviously, this is true from the correctness point of view, i.e. any functional program executed using one evaluation strategy remains correct (modulo strictness) when executed by the use of another strategy.

However, dataflow programs, i.e. functional programs using dataflow principles, are generally written in a different style than sequentially executed functional programs. The knowledge about dataflow evaluation strategies helps to avoid synchronization bottlenecks and to enhance the utilization of parallelism inherent in an algorithm. Moreover, many functional programs efficiently working on sequential architectures will not show significant speedups when implemented on parallel architectures, if basic dataflow principles are not considered. In other words, a program is not parallel just because it is functional.

What is now different with dataflow programming? In a nutshell, we can say

Dataflow programming is declarative programming with the minimization of data dependences.

Minimizing data dependences actually meens delaying synchronization to the latest possible moment. One important point of synchronization in functional languages is the conditional construct (also when hidden in pattern matching): since the boolean test condition has to be evaluated before it can be decided which branch to take, the conditional construct is strict in the test condition. In dataflow programming, the user should therefore try to delay boolean tests as long as possible.

The following sections present a major example that illustrates these principles.

2.4.1 Prime Number Generation

In this section, we want to design a dataflow program that creates all prime numbers up to some given bound. For this program, we make use of the following idea: in order to decide whether some integer i is prime, it suffices to divide i by all primes up to \sqrt{i}. Vice versa, if all primes up to some bound n have already been computed, the integers between n and n^{2} may be tested in parallel.

In a functional program, this idea might be implemented as follows. We write a function

sieve p t n

where the formal parameters are interpreted in the following way:

• p is the ascendend list of all odd primes that have already been computed.

• t is some odd integer in the range m to m^{2} where m is the last element (i.e. the maximum) of p.

• n is the upper bound for the primes to be computed. If t > n, p is already the result of the whole program.

With the help of sieve, the desired result of the program may be formulated as follows:

primes n = [], if n < 2
= [2], if n = 2
= 2 : sieve [3] 5 n, otherwise

The first two equations handle trivial cases. The third equation contains the call of sieve where [3] is the list of already computed primes and 5 is the first odd integer to be tested (we do not take [ ] and 3 for this purpose, since our choice makes the test conditions in sieve simpler, see below).

An obvious way to implement sieve would be as follows:

sieve p t n = p, if t > n
= sieve p′ (t + 2) n, if isprime p t
= sieve p (t + 2) n, otherwise

where p′ = append p t

sieve first tests whether t > n, i.e. p is already the desired result. If not, t is tested with respect to p. Depending on the result of this test, sieve is recursively called either with the old list p or with t appended to p.

However, this solution has little inherent parallelism. Since the conditional construct is strict in the boolean test conditon, first t is tested and after the result of this test, one of the branches is taken and sieve is recursively called.

primes n  = [ ], if n < 2
= [2], if n = 2
= 2 : sieve [3] 5 n, otherwise
sieve p t n  = p, if n < t
= sieve p′ (t + 2) n, otherwise

where p′ = append p t

append p : r t = app p : r t, if t < p ∗ p
= p : r, if t mod p = 0
= p : append r t, otherwise
app [ ] e  = [e]
app x : l e  = x : app l e

Figure 2.3: Prime Number Generator

The only parallelism lies in the overlapped execution of append and sieve in the second equation. Recall that the cons operation on lists is non-strict in its arguments. Hence, append may immediately return a result, i.e. the first cons-cell of the result list (with the tail of this cell not yet defined) and sieve may start working with this cell. The one process now successively creates the list while the other one simultaneously processes it. This form of consumer-producer parallelism is very important in dataflow programming.

How can we improve this solution? The basic problem is that the conditional construct forces the evaluation of its test condition and therefore represents a point of synchronization. In our example, it is not difficult to realize how this synchronization may be delayed to a later moment. The only difference between the second and the third equation is the list of primes submitted to the recursive activation of sieve. However, both lists differ only in the last element. In the one case, t is appended to the list, in the other case not. The basic trick is now to shift the decision of whether to append the element or not to the function append. append may start working and create p′ (consisting of the elements of p) regardless whether t is to be appended or not. Only when the last element of the list is processed, this decision has to be made.

Hence, we may reformulate sieve as follows:

sieve p t n = p, if n < t
= sieve p′ (t + 2) n, otherwise

where p′ = append p t

A new list p′ is created independently of t and forwarded to the next activation of sieve that may immediately start to check t + 2 with respect to p′. As we mentioned in the beginning of this section, in order to decide the primality of an integer, only all primes up to the square root of this number have to be known. Hence, not only t + 2 but all integers up to t^{2} may be tested independently of the fact whether t is finally entered into p′ or not.

The dynamic behaviour of the whole dataflow program (listed in Figure 2.3) is depicted in Figure 2.4. As soon as the upper bound n has arrived, \frac{n}{2} pipelined processes, one for each odd integer to be tested, may be created. Each activated process activates its right neighbour, receives a prime list from its left neighbour and immediately forwards it to the newly activated process on its right

Figure 2.4: The Primes Pipeline

side. Simultaneously, the process starts a procedure deciding whether its integer is prime or not. As soon as it has finished this decision (and all primes have been forwarded from its left to its right neighbour), the forwarded list can be closed either by appending the tested integer or not.

Basically, the parallelism inherent in this dataflow program is proportional to the number of integers to be tested. However, in practice the speedup achieved will be more limited by the “resistance” of the pipeline, i.e. by the delay that, in each stage of the pipeline, the forwarded list is subject to. For large numbers of primes, this delay will be a significant factor.

2.4.2 Improved Prime Numer Generation

The program designed in the previous section showed a lot of parallelism. However, it was rather crude in the sense that in each stage of the pipeline a totally new list had to be created just for appending a single element. In this section, we will improve this method by using the concept of difference lists as described in [Abelson and Sussman, 1985] and [Shapiro, 1987].

Difference lists denote a very important programming concept in non-strict programming. A difference list is a particular list representation that allows the constant-time list concatenation. Each list l is represented by a pair of lists l_{1} and l_{2} (notation l_{1}\l_{2}) such that l is always the difference of l_{1} and l_{2}. Two difference lists l_{1}\l_{2} and l_{2}\l_{3} are compatible in the sense that they can in a single step be concentated yielding the result list l_{1}\l_{3}.

We will now redesign the function sieve such that it works on a difference list representation of the primes list. The new version of sieve looks as follows:

sieve p t n = [ ], if n < t
= cons p r t, otherwise

where r = sieve p (t + 2) n

primes n  = [ ], if n < 2
= [2], if n = 2
= 2 : 3 : p, otherwise

where p = sieve 3 : p 5 n

sieve p t n = [ ], if n < t
= cons p r t, otherwise

where r = sieve p (t + 2) n

cons p : l r t = t : r, if t < p ∗ p
= r, if t mod p = 0
= cons l r t, otherwise

Figure 2.5: Improved Prime Number Generator

The interpretation of sieve has now subtly changed. The input parameter p does no more represent the list of all primes less than t but the list of all primes computed. Furthermore, sieve returns no more the list of all primes but only the list of primes greater than or equal t. Thus, the recursive call of sieve returns the list r of all primes greater than or equal t + 2. The part of the primes list that has already been computed by previous sieve activations is represented by the difference list p\r. For the moment, let us ignore the dynamic interpretation of these facts and concentrate on the static (mathematical) relations:

If n < t, the result is the empty list. Otherwise, the result is the list r of all primes greater than t with possibly t consed to the head. The cons routine can simply be defined as follows:

cons p : l r t = t : r, if t < p ∗ p
= r, if t mod p = 0
= cons l r t, otherwise

If the test integer t is larger than the square root of the first prime p, t is itself prime and may be consed to r. If p divides t with remainder 0, the result is r itself. Otherwise, the test has to be continued with the tail l of the prime list.

The dynamic interpretation of the program is a little bit tricky, as one can see from the top-level function of the program:

primes n = 2 : 3 : p

where p = sieve 3 : p 5 n

The result of primes is the list (2 : 3 : p) where p contains all odd prime numbers larger than 3. However, the list (3 : p) is also input to sieve that itself computes p! Feeding results back to the functions that computed them is only possible in non-strict languages where functions need not from the beginning depend on all arguments.

For the following discussion of the dataflow of the program (listed as a whole in Figure 2.5), the reader may refer to Figure 2.6.

Executing this prime generating program, a pipeline of n − 1 sieve processes is created such that for each sieve_{i} (1 ≤ i ≤ n − 1) the following propositions hold (where n = n_{1} = … = n_{n−1}):

Figure 2.6: The Primes Circuit

(1) p_{1} = p_{2} = … = p_{n−1}.

(2) If t_{i} ≤ n, r_{i} is a tail sublist of p_{i}.

(3) p_{i} contains all odd primes less than t_{i}.

(4) If t_{i} ≤ n, r_{i} contains all odd primes greater than or equal t_{i} but smaller than n.

A list r is a tail sublist of p if the concatenation of some list l with r yields p, i.e. l ∥ r = p. The validity of these propositions can be shown as follows:

(1) is obvious since each sieve_{i} passes p_{i} unchanged to sieve_{i+1}.

(2) holds in the case of r_{0}: By the second equation of sieve_{0} and the first equation of cons_{0}, (5 : r_{0}) is returned to primes as the result p. However, the input argument p_{0} is just (3 : p). For any r_{i+1}, either r_{i+1} or (t_{i+1} : r_{i+1}) is returned as r_{i} to sieve_{i}. By assumption, r_{i} is a tail sublist of p_{i} (and by (1) also of p_{i+1}) and so is r_{i+1}.

(3) holds for sieve_{0} since 3 = head p_{0} is the only odd prime less than 5 = t_{0}. Assuming that the proposition holds for sieve_{i}, we now show that it also holds for sieve_{i+1}. By assumption, p_{i} contains all odd primes less than t_{i} = t_{i+1} − 2. If t_{i+1} is not prime, the proposition therefore holds. However, if t_{i+1} is prime, sieve_{i+1} detects this fact by examining all primes of p_{i+1} up to the square root of t_{i+1} and returns as its result the list r_{i} with head t_{i}. However, r_{i} is by (2) a tail sublist of p_{i} and by (1) also of p_{i+1}. Hence, t_{i+1} is also contained in p_{i+1}.

(4) holds for the last pipeline process sieve_{n−1} that returns the empty list because n < t_{n−1}. Each other process sieve_{i−1} (with i ≤ n − 1) receives the list r of all odd primes greater than t_{i−1} = t_{i} + 2 and smaller than n. By (3), sieve_{i−1} may test t_{i−1} with respect to all primes in p_{i−1} that are smaller than or equal the square root of t_{i−1}. Iff t_{i−1} is prime with respect to this test, t_{i−1} is consed to r_{i−1} and returned as result. Otherwise, r_{i−1} itself is returned.

Anyway, the result is the list of all odd primes greater than or equal t_{i−1} and smaller than n.

The partial correctness of the program is a direct consequence of (4). In every activation sieve_{i}, the prime list p_{i} has an open (i.e. not yet defined) tail but, by (3), contains all primes that have previously been computed. By (2) and (4), the not yet defined tail of p_{i} is exactly the result list r_{i} returned by sieve_{i+1}. Vice versa, the already defined part of p_{i} can be represented by the difference list p_{i}\r_{i}.

Dynamically, these relations can be illustrated by two streams of data flowing in both directions of the pipe:

• In one direction, the total prime list p flows through the pipe. In physical terms, p is a pointer to the storage location that represents the head of the primes list. The first pipeline process primes allocates a cons-cell, writes the value 3 in the head of this cell and passes a reference to this cell as first argument sieve_{1}. This reference is forwarded through all activations of sieve. As soon as sieve_{1} has returned p, p is written into the tail of this cell and becomes thereby immediately available to all pipeline processes.

• In the other direction, all computed primes are collected to the stream r and driven back to the direction of the primes process. The last pipeline process sieve_{n−1} returns the empty list to its predecessor sieve_{n−2} (and by this to cons_{n−2}). If cons_{i} has detected that t_{i} is prime, a new cons-cell is allocated and immediately returned to cons_{i−1}. t_{i} is written into the head of the new cell. As soon as the list reference r_{i} has been received from sieve_{i+1} (i.e. from cons_{i+1}), this reference is written into the tail of the cons-cell and becomes thereby available to all other sieve processes. However, if t is not prime, then r_{i} is unchanged passed back through the pipe.

Summarizing, each prime element that is “consed” to the head of the stream r flowing back through the pipe is automatically appended to the primes stream p flowing in the forward direction.

However, we should utter some word of care. Although this revised program has been explained in terms of “streams” of integers “flowing” back and forward through the pipe, we should emphasize that these integers are actually not flowing as independent objects. Each sieve process only once receives a reference to a list of primes. All primes that are computed by other sieve processes are written into this list, hence they become immediately available to each other process. The flow of data is, in this case, just a model describing the complicated data dependences between processes.

This program version still has the same amount of inherent parallelism as that one presented in the previous section. Moreover, it totally copes without unnecessary list copies and is by this far more efficient.

By the program examples sketched in this and the previous section, the reader should have gained some insights into the possibilities of dataflow programming and should have developed some intuition into its particular flavour. Discussions on more advanced and subtle features of this programming methodology can be found in [Arvind et al., 1988b] and [Arvind and Ekanadham, 1987].

2.5 Dataflow Architectures

For almost two decades now, there has been quite a lot of research on the efficient implementation of the dataflow principle in hardware. Several dataflow computers have been proposed in literature promising to efficiently utilize the fine-grain parallelism of declarative programs by hardware primitives for the creation of large numbers of parallel processes, their synchronization and their communication. Some prototypes of dataflow computers have been built in hardware, others have been extensively simulated, e.g.

1. the Manchester Dataflow Computer,

2. the ETL Sigma-1 at the Japanese Electrotechnical Laboratory,

3. the TTDA Tagged Token Dataflow Architecture at the MIT,

4. and its successor, the ETS Explicit Token Store “Monsoon”.

In the following subsections, we give a short overview on these approaches. They all belong to the class of dynamic dataflow architectures which allow multiple concurrent activations of the same function by extending the token tags with unique activation names.

Comprehensive descriptions of several other (in particular earlier) architectures can be found in [Dennis, 1984] and [Arvind and Culler, 1986]. Most of them were static dataflow architectures which imposed the restriction that no more than one activation of a function existed at the same time (and by this prohibited recursion).

2.5.1 The Manchester Dataflow Computer

A machine based on the tagged-token model of dataflow computation has been operational at the University of Manchester since October 1981. This Manchester Dataflow Computer ([Gurd et al., 1985], [Ruggherio and Sargeant, 1987]) has been one of the first dataflow machines actually built in hardware.

The Manchaster Dataflow Computer consists of only one processor whose basic structure is a ring of four modules connected to a host system via an I/O switch:

1. Token Queue,

2. Matching Unit and Overflow Unit,

3. Instruction Store,

4. Processing Unit.

The modules operate independently in a pipelined fashion. Tokens are carried in data packets circulating around the ring. Token packets that are destined for the same instruction are paired together in the Matching Unit using a hash table on the token tags. This unit has limited storage capacity such that an Overflow Unit is required for Programs with large data sets. Paired tokens (and those destined for unary instructions) fetch the appropriate instruction from the instruction store that contains the machine-code for the executing program. The instruction and its input data are forwarded to the Processing Unit for execution. Output tokens are transmitted back to the Matching Unit to enable subsequent instructions. The return path passes through the I/O switch that connects the system to a host processor and to the Token Queue. This queue serves as a buffer unit smoothing uneven rates of generation and consumption of tokens in the ring.

The performance of the Manchaster Dataflow Computer has been extensively evaluated by a lot of benchmark programs written in the single assignment language Sisal ([McGraw et al., 1985]).

2.5.2 The ETL Sigma-1

The ETL Sigma-1 at the Japanese Electrotechnical Laboratory ([Yuba et al., 1985], [Hiraki et al., 1984], [Shimada et al., 1986], [Yuba, 1986], [Hiraki et al., 1986]) has been operative since 1987. It provides a performance of about 600 MFlops with 128 processors. The basic structure of the processors that are connected by a two-level communication network is as follows:

There are five functional units each of which works synchronoulsy as a two stage pipeline:

1. Instruction Fetch Unit (first stage),

2. Matching Memory Unit (first stage),

3. Destination Unit (second stage),

4. Execution Unit (second stage),

5. Structure Memory Unit (both stages).

The processor is based on the tagged-token model of computation. Incoming tokens are forwarded to the Matching Memory Unit where the pairing of tokens is performed. Access to the Instruction Fetch Unit and the corresponding search in the Matching Memory Unit are carried out simultaneously. If the search is not successful, the instruction fetched from the instruction memory is discarded. The Execution Unit performs logical, integer and floating-point arithmetic. The Destination Unit produces an output packet combining the result value of the execution unit together with the destination address sent from the instruction fetch unit. After executing an instruction, the result is sent in package form to the input buffer memories via the network.

The Structure Memory Unit handles arrays that are expressed as named sequences of values. These sequences are called B-structures and obey the single-assignment rule, i.e. any element of a B-structure may be written at most once. The structure memories of all processors connected by the first stage of the interconnection network are shared by all processors of that group.

Another project at the Electrotechnical Laboratory has dealt with the construction of a Lispbased data-driven machine ([Yuba et al., 1983], [Herath et al., 1985], [Yamaguchi et al., 1984], [Yamaguchi et al., 1983]).

2.5.3 The TTDA Tagged Token Dataflow Architecture

The Tagged Token Dataflow Architecture (TTDA) at the MIT is the result of one of the most advanced and longest ongoing activities in dataflow research ([Arvind and Nikhil, 1988]). The TTDA consists of a number of identical dataflow processors and storage units interconnected by an n-cube packet network. The storage units are addressed uniformly in a global address space.

Tokens arriving at a dataflow processor enter a pipeline and go through the following units in sequence:

• Wait-Match Unit:

This unit consists basically of a pool of waiting tokens. Tokens destined for monadic operators go straight through this unit to the Instruction-Fetch Unit. If a token is destined for a dyadic operator, the tokens in the Wait-Match Unit are examined to see if the other operand has already arrived. If so, both tokens are passed to the Instruction-Fetch Unit, if not, the token is deposited to wait for it.

• Instruction-Fetch Unit:

The tag on the operand tokens entering the Instruction-Fetch Unit identifies an instruction to be fetched from program memory. The data values of the tokens, the opcode of the instruction, the destination offsets from the instruction and the context field of the token’s tag are extracted and passed to the to the next stage.

• ALU and Compute-Tag Unit:

These units are actually two ALUs operating in parallel. The ALU takes the operand data values and the opcode, performs the operation and produces a result. The Compute-Tag Unit takes the context and the instruction offsets and computes the tags for the output of the operator. Both units pass their results to the Form-Tokens Unit.

• Form-Tokens Unit:

This unit takes the data value(s) from the ALU and the tags from the Compute-Tag unit and combines them into result tokens.

Output tokens emerging at the bottom of the pipe are routed according to their destination addresses: either back to the processor pipeline or into the network to some storage unit or another processor.

The store units contain so called I-Structures ([Arvind et al., 1987]) i.e. arrays with single assignment semantics and special support for read-write synchronization.

The TTDA has not been built in hardware but extensively simulated on a network of TI-Explorer workstations. A variety of programs have been written in the functional language Id ([Nikhil, 1988], [Nikhil, 1987], [Traub, 1986]) and a lot of interesting results concerning dataflow programming have been gained ([Arvind et al., 1988a], [Arvind and Culler, 1987]).

2.5.4 The ETS Explicit Token Store “Monsoon”

The ETS Explicit Token Store architecture “Monsoon” has been designed as a successor to the TTDA and probably represents the most advanced concept for dataflow computers today ([Papadopoulos, 1988], [Papadopoulos and Culler, 1990]) . The ETS architecture introduces a interesting new concept that is different from the other approaches and removes a main source of inefficiency:

In all the dataflow architectures presented in the previous subsections, some kind of Wait-Match Unit is responsible for the correct synchronization of tokens destined for dyadic operators. However, a token arrived has to be checked against all other tokens in the unit waiting for a match. The matching function and its associated store are mostly realized by a large hash table and sophisticated insertion and extraction algorithms. The resulting hardware is highly complex and rather slow.

In the ETS model, operand matching is performed at compiler-designated offsets within a given activation frame. For each function invocation, a contiguous block of memory is allocated. This activation frame contains for each binary instruction a location that represents the rendezvous point for the corresponding tokens. These location offsets may be already fixed at compile time. Hence, the context field of a token destined for a dyadic operator may explicitly hold the address of the location where the partner token is to be met. No more search for the partner token is neccessary, hashing algorithms and an associative wait store are no more required.

Besides this innovation, the ETS architecture corrects a number of other inefficiencies inherent in previous dataflow machines. Particular emphasis has been laid on a clear and simple processor design in order to support an implementation in hardware.

Hence, the Monsoon has not only been simulated as the TTDA but two monoprocessor prototypes have already been realized in cooperation with a microprocessor manufacturer (Motorola). A first multiprocessor consisting of 8 dataflow processors (with a performance of 10 million tokens per second each) and 8 I-structure memories has been announced to be operational in the spring of 1991. Motorola will also support a Unix-based single processor/single memory workstation for Id program development.

The EVE Programming Language

This chapter is a reference manual for the non-strict and purely functional programming language Eve. Eve has been designed as a simple and efficient high-level tool for programming the parallel Adam machine. On the one hand, this language combines the most important features that are state-of-the-art in functional programming, on the other hand we have omitted all that facilities that are not essential but make a prototype implementation much more difficult.

Eve is rather small and simple, but it is definitely not a toy language. In particular, the language shows the following features:

• Eve is purely functional: An Eve program is a composition of functions that are all totally free of side-effects. The result value of a function only depends on the values of its arguments and of globally defined constants but not on any other influence. Vice versa, the evaluation of any expression returns a value and has no effect besides that.

• Eve is non-strict:

Functions and data structures are in general non-strict. Therefore, functions may return results even if (some or all of) their arguments have not yet been provided. Furthermore, data structures may be handled, even if (some or all) components of them are not yet evaluated.

• Eve is strongly typed: The programmer is required to give explicit type annotations to all newly defined objects. Therefore, the system is able to check the correct typing of all definitions and expressions at compile time. The type system of the language includes several structured data types and also allows the construction of recursively defined types.

• Eve is higher-order:

Functions are first-order objects as well as the objects of atomic or structured types. In particular, functions can take other functions as arguments and return functions as results. Moreover, functions may be also stored as part of data structures.

• Eve is safe:

Eve programs never produce runtime errors that force a halt of the execution. Unforeseeable errors like “Division by Zero” are handled by the use of special error values that are returned as the results of invalid computations. This is especially important in a parallel environment since here it is not desirable that one processor halts on an error while the others continue working.

In spite of the fact that Eve has been designed especially for the needs of parallel computation, parallelism is not mentioned anywhere in the language definition. The parallel evaluation of a functional program is not a matter of the definition of the functional language but a matter of its implementation. Any (also sequential) system that fulfills the requirements of the language definition provides a valid implementation of the language.

In order to keep the language small, the following features have been explicitly omitted, either because they just represent less important syntactic sugar or because they make an experimental implementation essentially more complex^{1}:

• Pattern Matching,

• Type Inference and Polymorphism,

• Lambda Expressions and Curried Functions,

• List Comprehensions,

• Delayed Evaluation,

• Abstract Data Types and Modules.

A former version of this reference manual has been published in [Schreiner, 1989].

3.1 Some Hints for Further Reading

Before we start the description of the Eve language, some hints on notational conventions will help to understand the following sections:

Syntax Notation

The exact syntax rules of the language are given in Section 3.15.3. For the moment, it suffices to distinguish two kinds of syntactic entities:

Terminal symbols, i.e. symbols that can appear in the source code of a program, are written in typewriter type style. All program examples are also typed in this style, e.g.

         F[x]: INT[INT] == x*x

Nonterminal symbols, i.e. symbols that represent classes of tokens, but can not appear in a program text themselves, are written in italic type style, e.g.

Identifier

Layout

Eve does not impose any restrictions upon the format of source texts (free format style). Only the sequence of characters in the text is significant, indentations or other formatting features do not have any effect. As the only exception to this rule, line breaks denote the end of comments, which is described in the following subsection.

1A further version of the language will perhaps include some of them.

Comments

start with the token -- and continue to the end of the line. Everything between this token and the end of the line is ignored. In fact, a line containing a comment is treated, as if it had ended before the --. Comments may not start within text constants (see Section 3.4.3).

     -- This is a comment!

Identifiers

consist of a sequence of letters and digits, but have to start with at least one letter. Eve is case-sensitive, i.e. it distinguishes between lower and capital letters, therefore

     ident, Ident and IDENT

denote three different identifiers. The end of an identifier is recognized by the first character that is neither a letter nor a digit, therefore two identifiers have to be separated by at least one such character.

Keywords and Predefined Identifiers

are written in capital letters, e.g.

     IF NULL[l] THEN l ELSE m.

Keywords (IF, THEN, ELSE) are reserved and may not be redefined, while predefined identifiers (NULL) may^{2} A list of the keywords and predefined identifiers is given in Sections 3.15.5 and 3.16.

3.2 Overall Program Structure

A program is a sequence of definition statements (also called bindings) separated by semicolons, i.e.

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

Each of these statements binds a name (an identifier) to some kind of object. The collection of bindings sets up the environment, in which a (user-given) expression is evaluated.

Typically, the file containing the program’s source code is submitted to a compiler and translated into the machine code of the underlying hardware. Using an interactive dialogue shell, the user may enter expressions, which are evaluated within the environment generated by the compilation. The results of these evaluations are printed on the screen or written into an output file.

According to the kind of object an identifier is bound to, there are three kinds of definition statements:

2However, to do so shows a rather poor programming style.

Type Description
INT Integer numbers
BOOL Boolean values
CHAR Characters
STR Strings

Table 3.1: Predefined Types

• Type definitions,

• Constant definitions,

• Function definitions.

The order of definition statements within the source text is significant, the programmer has to obey two simple rules:

1. The definition of an identifier must textually precede its use: The scope of the identifier starts at the point of its definition and extends over the right hand sides of its own definition statement and all the following ones, as well as over the expression to be evaluated^{3}.

2. Within a level of scope an identifier may be defined at most once, i.e. redefinitions are not allowed. The possibility of redefining predefined identifers is not affected by this rule, since these identifiers are defined in an outer level of scope than the user definitions take place.

These rules require some programming discipline but are not really limitations of Eve. Their only purpose is to facilitate the construction of a compiler for the language.

3.3 Type Definitions

Eve is a strongly typed language, i.e. all operators and functions demand arguments of certain types. Typing errors are detected on compilation time and incorrectly typed programs are rejected. All types are named, i.e. they have to be introduced by a type definition statement of the form

Ident == Type

where Ident is the name of the type to be introduced and Type is its definition.

Eve knows four predefined types (see Table 3.1) and three constructors that build new types from already existing ones^{4} (see Table 3.2).

A type definition may simply rename a previously defined type:

      Number  ==  INT;
      Word    ==  STR;
      Letter  ==  CHAR

3As we will see, there are some relaxations to this rule in order to allow mutually recursive functions (see Section 3.10), data types (see Section 3.5.4) and constants (see Section 3.6).

4A fourth type constructor, the function constructor, is described in Section 3.11.

Constructor Description
LIST OF Sequence of equal type elements
TUPLE OF Fixed size collection of elements
UNION OF Element of a selected type

Table 3.2: Type Constructors

But it may also introduce a new type that is constructed from other types:

      Sequence  ==  LIST OF INT;
      Person    ==  TUPLE OF name: STR, age: INT, sex: BOOL;
      Datum     ==  UNION OF Num == INT, Word == STR

For strong type checking, it is important to know, in which cases two types are considered to be equal. As a rule, two types are equal, if and only if

1. both types have the same type name, or if

2. one type is derived from the other by a sequence of renamings.

In the following example,

      Number   ==  INT;
      Integer  ==  INT

type Number equals INT, as well as Integer equals INT. But Number does not equal Integer, since both have different type names and no type is a renaming of the other one!

The scope of a user-defined type starts at the right hand side of the definition statement itself, therefore directly recursive type definitions like

     Foo == TUPLE OF x: INT, y: Foo;

are principially allowed, but do not make sense. In contrast to directly recursive types, mutually recursive type definitions can be reasonable in some cases but violate the rule that types have to be defined before they can be used (see Section 3.2). Therefore, Eve provides a special kind of type definition, the forward type definition. Its general form is simply

Ident

which makes public the name Ident of a new type but not its construction. From the point of this definition on, references to Ident are allowed, but the actual type definition has to take place in the same level of scope. Hence, mutually recursive type definitions like

      Subtree;

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

are possible. However, a sensible recursive type definition must contain at least one union type (see Section 3.5.4), but the compiler is not forced to check whether type definitions are reasonable or not^{5}.

In the following sections, we will provide a detailed description of the different kinds of types and type constructors.

3.4 Atomic Types

A type is called atomic, if objects of this type can not be partioned into smaller semantic entities. Eve knows three predefined atomic types,

• Integers (INT),

• Booleans (BOOL),

• Characters (CHAR).

3.4.1 Integers

The data type INT provides signed, fixed-size integer values in a range limited by the predefined integer constants

     MININT and MAXINT

denoting the lower bound and the upper bound of the range, respectively. The values of these constants depend on the particular implementation but the assertion MININT < 0 < MAXINT can be expected to hold^{6}.

Numerical expressions, i.e. expressions of type INT, look like common mathematical expressions, e.g.

     a*(b+1)/c-d.

Eve provides five binary integer operators and one unary operator (see Table 3.3) that obey the usual priority rules (the unary - binds stronger than *, /, and % that again bind stronger than + and the binary -).

The result of a remainder operation may be either 0 or it has the sign of the dividend regardless of the sign of the divisor, e.g.

     -17 %  5 = -2,
      17 % -5 =  2.

The predefined function associated to each operator represents the prefix version of this operator^{7}, therefore the expression

5Even if objects of a meaningless type are constructed, they can not cause any serious harm. They are just not very useful.

6Typically, these constant values equal −2^{n} and 2^{n} − 1 on n-bit machines.

7The neccessity for the existence of these functions is explained in Section 3.11

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

Table 3.3: Integer Operators

Function Description
ABS  Absolute Value
ODD  Is number odd?

Table 3.4: Integer Functions

     (-a)*(b-c)

is equivalent to

     MUL[NEG[a],SUB[b,c]].

The use of fixed-size integer numbers raises some problems, since integer operations may return values that are outside the range of type INT (“Overflow”) or not defined (“Zero Division”). Eve handles these errors by the use of special error values, a detailed description of the error behaviour can be found in Section 3.13.

Two more predefined functions operating on integer values are listed in Table 3.4.

• ABS[i] returns the absolute value of the integer i. This function may return the error value, if i is negative and has no positive counterpart within the range of INT or vice versa.

• ODD[i] returns TRUE, if i % 2 = 0 does not hold.

3.4.2 Booleans

The data type BOOL provides two predefined constants, the values

     TRUE and FALSE.

For expressions of type BOOL, three operators (and associated functions) have been defined:

In addition to the usual priority rules (NOT (~) binds stronger than AND (&) which binds stronger than OR (|)), the binary operators obey the short circuit discipline: if a conjunction or disjunction is evaluated, the evaluation starts with the left operand. If this partial result already determines the result of the whole operation (i.e. if it is TRUE in disjunctions or FALSE in conjunctions) the second operand is not evaluated. We say:

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

Table 3.5: Boolean Operators

AND (&) and OR (|) are both strict in their first argument.

The strictness of a function or data structure may have a strong influence on the runtime behaviour and even on the termination of a program. Therefore we will discuss this important point in Section 3.12 in greater detail.

3.4.3 Characters

The data type CHAR provides the characters of the US-ASCII character set^{8}.

Character constants consist of a single character enclosed by single quotation marks (’). Valid characters between these marks are

• Any printable character of the ASCII character set except the backslash character (\) and the single quotation mark (’), e.g.

         'a', 'b', 'Z', '␣', '*', '$'.

• An abbreviation of the form \l, where l is a character code representing a special character (see Table 3.6), e.g.

'\n' (New Line), '\'' (’ itself),

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

         '\036' ($), '\065' (A), '\127' (DEL).

Within a character constant, it is illegal to use the backslash character in another way than just described. A character constant (i.e. the sequence 'c', where c denotes a character) must not be splitted over several lines, therefore the end of a line and the comment token -- are not allowed to appear within it.

Eve has three predefined functions handling the conversion between characters, from characters to integers, and vice versa (see Table 3.7).

• CAP[ch] returns the capital of ch, if ch is a letter, and ch itself, otherwise.

• CHR[int] returns the character with ASCII code int, if int is in the range 0–255, and the error value^{9} otherwise.

• ORD[ch] returns the integer value denoting the ASCII code of character ch.

8A table of this character set can be found in Section 3.15.2.

9For further explanation on error handling, see Section 3.13.

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

Table 3.6: Special Characters

Function Description
CAP  Capitalize a letter
CHR  Character from ASCII code
ORD  ASCII code of a character

Table 3.7: Character Functions

3.4.4 Comparison Operators

Table 3.8 shows the usual comparison operators that have been predefined on all three atomic types.

These operators are overloaded in the sense, that each of them is a placeholder for one of three functions, one function for each atomic type. The asterisk * in the function names of Table 3.8 has actually to be replaced by I (INT), B (BOOL), or C (CHAR) to get the particular comparison function for each data type, e.g.

ILT[2,5], BNE[TRUE,FALSE], CEQ['f','G'].

The comparison order for each type is defined as follows:

• INT: The comparison order of integer values is obvious.

Operator Function Description
<        *LT  Less Than
<=       *LE  Less or Equal
=        *EQ  Equal
#        *NE  Not Equal
>=       *GE  Greater or Equal
>        *GT  Greater Than

Table 3.8: Comparison Operators 3.5. STRUCTURED TYPES 47

• BOOL: Boolean values are ordered by FALSE < TRUE.

• CHAR: Characters are compared according to their ASCII values.

3.5 Structured Types

Eve knows three type constructors that generate new structured types from previously defined types,

• the List Constructor (LIST OF),

• the Tuple Constructor (TUPLE OF),

• the Union Constructor (UNION OF), and one predefined structured type,

• the String type (STR).

3.5.1 Lists

Lists are variable-size sequences of elements of equal type. List types are defined by statements of the form

Ident == LIST OF SimpleType where SimpleType denotes the name of a previously defined type. Some examples of list types are

      NumSeq    ==  LIST OF INT;
      Word      ==  LIST OF CHAR;
      Sentence  ==  LIST OF Word

Eve does not allow nested type definitions, so statements like

     Sentence == LIST OF LIST OF CHAR

are invalid and have to be replaced by the introduction of auxiliary type definitions (as shown above). Every list type possesses its own constructor function, which has the same name as its associated type and builds lists of this type. E.g. if we define

     NumSeq == LIST OF INT,

then the expression

     NumSeq[2,3,5,7,11,13,17]

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.9: List Operators and Functions

constructs a list of type NumSeq which contains the first seven prime numbers. These elements are entered into the list from right to left, therefore 2 is the first element (the head) of the constructed list.

The empty number sequence (i.e. the list without any elements) is constructed by

NumSeq[ ].

All predefined generic list operators and functions^{10} are listed in Table 3.9. However, these functions are not first-order objects and can therefore not be used as arguments to higher order functions (see Section 3.11).

• The operator :: is the infix equivalence of the generic function CONS. It inserts its first argument of type Any into its second argument which is a list of type AnyList, where

AnyList == LIST OF Any.

E.g. both of the expressions

    2 :: NumSeq[3,5,7,11],
    CONS[2,NumSeq[3,5,7,11]]

are equivalent to

    NumSeq[2,3,5,7,11].

• The components of a list can be extracted by the function HEAD and TAIL that return the first element of a list (its “head”) and the list without its first element (the “tail”). As a simplification, both of these selector functions (and only these!) may be applied to a list expression in the form

List.Selector

E.g., both of the expressions

    HEAD[NumSeq[2,3,5,7]]
    NumSeq[2,3,5,7].HEAD

return the value 2, and both of the expressions

10In this case, generic means operating on all kinds of lists.

    TAIL[NumSeq[2,3,5,7]]
    NumSeq[2,3,5,7].TAIL

return the list NumSeq[3,5,7]. It is illegal to apply HEAD or TAIL to an empty list. How this error is handled, is described in Section 3.13.

• NULL[list] returns TRUE, if and only if list is empty i.e. does not contain any element.

3.5.2 Strings

A string is simply a list of characters. Therefore, the predefined standard identifier STR can be user-defined by^{11}

STR == LIST OF CHAR.

Because of this interpretation, all predefined list selectors and functions can also be applied to strings.

String constants consist of a (possibly empty) sequence of characters enclosed by double quotation marks ("). E.g.

"A string"

is a simplified version of the list construction

STR['A',' ','s','t','r','i','n','g'].

The rules for valid characters within string constants are basically the same as for character constants, with two exceptions:

• Double quotation marks (") have to be encoded by the sequence \", while single quotation marks stand for themselves. Hence, the string constant

    "He said \"I don't like programming\"."

generates the string value

    He said "I don't like programming".

• String constants may be split over several lines by the use of the special character \_. The first occurrence of this character within a string constant denotes an interruption of the string, after the next occurrence of \_(which may happen several lines afterwards) the string is continued. Between an string interruption and its continuation only white space (see section 3.15.2) and comments are allowed. E.g. the string constant

    "This is a stri\_-- Break
    \_ng interruption."

generates the string value

    This is a string interruption.

11In spite of this fact, it is strongly recommended not to define ones own private string type by WORD == LIST OF CHAR, because any implementation may handle the type STR in a more efficient way. It is a better strategy to rename the predefined type by WORD == STR, since renamings do not create a new type.

50 CHAPTER 3. THE EVE PROGRAMMING LANGUAGE

3.5.3 Tuples

Tuples are fixed-size collections of elements with equal or different types (i.e. they represent the counterpart to the concept of cartesian products in denotational semantics). Tuple types are defined by statements of the form

Ident == TUPLE OF Id_{1} : Type_{1} , Id_{2} : Type_{2} , … Id_{n} : Type_{n} where Id_{1} to Id_{n} are the field names of the tuple and Type_{1} to Type_{n} their corresponding types, e.g.

      Coordinate  ==  TUPLE OF x: INT, y: CHAR;
      Name        ==  TUPLE OF first: Word, last: Word;
      Address     ==  TUPLE OF street: Word, number: INT

Similar to lists, the components of a tuple must have explicit type names, anonymous type components, such as in

     Name == TUPLE OF first: LIST OF CHAR, last: Word

are not allowed. Tuples are always constructed as a whole by the use of constructor functions. These functions have the same name as their associated types, e.g.

     Coordinate[3,'A']

creates a new tuple with x-field set to 3 and y-field set to 'A'. The field names of a tuple are always local to the tuple, so definitions like

     First == TUPLE OF x: INT,  y: INT;  -- Fields x and y
     Secnd == TUPLE OF x: CHAR, y: CHAR; -- Fields x and y, too!

are perfectly valid. Components of a tuple can be selected by use of the selectors that are associated to each tuple type and have the names of the fields to be selected. The application of these selectors is (similar to the list selectors) of the form

Tuple.Selector E.g. if name is of type Name (as defined above), then the expressions

     name.first,
     name.last

return the first and the second component of the tuple, respectively.

3.5.4 Unions

Sometimes, the type of an object is not known at compile time, but there is only a fixed set of possibilites. In these cases, the union constructor may be used. A union type is rather a collection of types than a single type itself (it represents a concept equivalent to the disjoint unions in denotational semantics). A variant of a union type is one of the types collected in the union. The definition of a union type is of the form

Ident == UNION OF Id_{1} == Type_{1} , Id_{2} == Type_{2} , …, Id_{n} == Type_{n}

where Id_{1} to Id_{n} are the variants of the union, i.e. renamings of the types Type_{1} to Type_{n}. Although definitions of union types look similar to definitions of tuple types, it is important that one does not mix them up: In contrast to tuples, unions are not collections of constants but collections of types. Elements of a union type have as their actual type exactly one variant of the union.

The following statements are examples for the definition of tuple types:

      Data        ==  UNION OF Num == INT, Word == STR;
      Person      ==  UNION OF Man == Male, Woman == Female;
      Coordinate  ==  UNION OF Pol == Polar, Cart == Cartesian

An object of type Data may now be either an integer or a string, as well as a Coordinate value may be given either in polar or in cartesian form.

A union value is constructed by the application of a conversion function which converts an expression from a type that equals one variant of the union to the union type itself. This function has the name of its associated union type but is augmented by the name of the variant. Its general form is

UnionType$Variant[expression].

where the $ separates the name of the union type from the name of the variant. Thus, the expressions

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

create values that are both of type Data, but hold different variants of this type.

As with tuples, the variant names of a union type are local to the union and can therefore be used for definitions outside the union.

As already mentioned, the definition of a name must textually precede all of its applied occurrences. In order to alllow mutually recursive type definitions, Eve knows forward type definitions of the form

Ident.

A forward type definition makes public only the name Ident of a type, but not its construction. From the point of this definition on, references to this type are allowed, but the actual type definition has to take place within the same level of scope. E.g. in the definition sequence

      Subtree;

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

the forward reference of Tree to Subtree is resolved by the use of a forward type definition.

Section 3.8 describes in detail how the actual variant of a union value can be determined and how a union value can be reconverted to its original type.

3.6 Constant Definitions

Constant definitions introduce names for anonymous values. Their general form is

Ident : SimpleType == Expression

Ident is the constant name to be introduced, SimpleType denotes the name of a previously defined type and Expression provides the value of the constant. The result value of Expression has to be of type SimpleType.

The following statements define some constant names:

      i:  INT   ==  (2+3)*5;
      s:  STR   ==  "The art of programming";
      b:  BOOL  ==  (1 > 0) & ("test".HEAD = 't')

The scope of a constant name starts at the point of its definition, hence the following chain of definitions is perfectly valid:

      n:  INT  ==  -217;
      m:  INT  ==  ABS(n);
      a:  INT  ==  n+m

Of course, constant definitions are not restricted to constants of atomic types, but every structured type also has to be given an explicit type name, e.g.

      IntSeq      ==  LIST OF INT;
      Pair        ==  TUPLE OF c: CHAR, i: IntSeq;
      s:  IntSeq  ==  IntSeq[3,5,7,9];
      p:  Pair    ==  Pair['A',2::s]

Since the scope of a constant also extends over the right hand side of its own definition, recursive constant definitions, such as

     x: IntSeq == 1 :: x

are possible. This definition introduces a list x consisting of an infinite sequence of the value 1. In order to allow mutually recursive constant definitions, Eve provides as a special form the forward constant definition. Its general form is

Ident : SimpleType

This definition makes public the name and the type of a new constant but not its value. From the point of this definition on, references to the constant are allowed. However, the actual constant definition has to take place within the same level of scope. Using this feature, mutually recursive constant definitions like

      x2: IntSeq;

      x1: IntSeq   ==  1 :: x2;
      x2: IntSeq   ==  2 :: x1

are possible. These definitions introduce a list x1 consisting of a infinite sequence of alternating values 1 and 2 starting with 1 and a list x2 consisting of a infinite sequence of alternating values 2 and 1 starting with 2.

A recursive constant definition only makes sense, if the expression on the right hand side of the definition contains non-strict operators or functions, e.g. the list constructor CONS (::). On the contrary, a definition like

     undef: INT == undef+1

introduces a name undef for a value that will never be computed, since the strict operator + has to wait until all of its arguments have become available. For a detailed description of the strictness properties of the language, see Section 3.12.

Although the lists x, x1 and x2 defined above are logically inifinite, they can be represented in a finite (and actually very small) amount of computer memory. Altogether their construction only requires three applications of CONS (::), their infinite behaviour is a result of their (mutually) recursive definitions.

3.7 Conditional Expressions

A conditional expression is of the form

IF Cond THEN Expr_{1} ELSE Expr_{2}

where Cond is a boolean expression, the test condition, and Expr_{1} and Expr_{2} are expression of equal type, e.g.

      IF n = 0 THEN 1 ELSE n * Fib[n-1],
      a + (IF a = 1 THEN b ELSE c) * d,
      (IF 1 = 1 THEN 1 ELSE 0) > 0.

The if-then-else construct is strict in the first of its arguments, i.e. if its value is computed, first the boolean test condition is evaluated to TRUE or FALSE. Depending on the result, the expression of one branch of the construct (but not the other one!) is evaluated and returned as the result of the whole expression.

For a more detailed discussion on the strictness, respectively non-strictness of functions and data structures, see Section 3.12.

3.8 Case Expressions

The case construct is used to select the actual type of a union-typed value (see Section 3.5.4) and to extract its actual value. A case expression is an expression of the form

CASE UnionExp

{c_{1}:Variant_{1}}  Exp_{1}
{c_{2}:Variant_{2}}  Exp_{2}
…
{c_{n−1}:Variant_{n−1}} Exp_{n−1}

          ELSE  Exp_{n}

where UnionExp is an expression that returns a union value, Variant_{1} to Variant_{n−1} represent different variants of this union and c_{1} to c_{n} are identifiers used to define new constants. Exp_{1} to Exp_{n} are expressions of equal type.

Each of the branches of the case construct tests the value returned by UnionExp for its actual variant (i.e. for the actual type of the union value). Each variant must not be tested by more than one branch, i.e. Variant_{1} to Variant_{n−1} have to be distinct. If Variant_{i} matches the actual variant of the union value, the identifier c_{i} is bound to the value that the variant holds, i.e. c_{i} has type Variant_{i}. The scope of the constant name c_{i} is Exp_{i}, i.e. c_{i} may be freely used in Exp_{i} but is neither known in the other expressions Exp_{j}, with i ≠ j, nor outside the case expression. The expression Exp_{i} is evaluated and returned as the result of the whole case expression.

If no Variant_{i} matches the actual variant of the union value, the expression Exp_{n} in the else branch of the construct is evaluated and returned as the result of the case 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.

E.g., using the definitions

     -- Types
     Data == UNION OF Num == INT, Letter = CHAR, Word == STR;
     Node == UNION OF Flag == BOOL, Value == INT;
     -- Constants
     d: Data == Data$Word["Foo"];
     n: Node == Node$Value[317]

the expression

     CASE d
         {n: Num}    n*2
         {l: Letter} ORD[l]+1
         ELSE -1

returns the value -1 and the expression

     CASE n

         {f: Flag}  ~f
         {v: Value} v=0

returns the value FALSE. The second example shows a case expression with exhaustive variants, i.e. each of the variants of type Node is handled by one branch, therefore the else part has to be omitted.

There is one point that might be misleading: Every identifier c_{i} defined by

c_{i} : Variant_{i}

is of type Variant_{i} which is a renaming of type SimpleType_{i} in the definition of the underlying union type:

UnionType == UNION OF … Variant_{i} == SimpleType_{i} …

Therefore, in the first case expression shown above, n is a constant of type Num. However it may be freely used in integer expressions, since Num is merely a renaming of INT.

The case construct is strict in its first argument, i.e. in the expression UnionExp. Depending on the actual variant of the union value, one of the expressions Exp_{i} (but none of the other ones!) is evaluated and returned as the result of the whole expression.

A more detailed discussion on the strictness properties of Eve can be found in Section 3.12.

3.9 Block Expressions

A block is an expression of the form^{12}

{

Definition_{1} ; Definition_{2} ; ...

Definition_{n}
}

Expression

The value of a block is the value of the trailing expression that is evaluated in the global environment augmented (and possibly overridden) by the bindings established within the block, e.g.

{

         a:INT == 3;
         b:INT == a+2;

}

         (a + b) * 7

12The curly braces {} replace the more known LET IN construct of other functional languages.

evaluates to 56.

Every definition statement binds an identifier to a value and augments the environment by this new binding. The scope of the binding starts at the point of the definition statement and extends over the right hand side of this definition and all the following ones (up to the end of the block), as well as over the expression to be evaluated. Any binding to an identifier x outside the block is overridden by a redefinition of x within the block. The other way round, the binding of an identifer within a block is invisible outside the block.

Blocks may be nested and follow the lexical scoping discipline. The value of an identifier that has not been defined within a block is taken from the surrounding block. If it has not been introduced there, the value is taken from the block on the next outer level of nestings, and so on, up to the global level of definition statements.

In contrast to many other functional languages, in Eve the scope of an identifier starts at the point of its definition, previous occurrences of the identifier take their values from the surrounding environment. E.g. the nested block expression

      { a:INT == 3 }

{

         b:INT == a;
         a:INT == 5

}

         a + b

evaluates to 8 and not to 10, since the reference to a within the definition of b takes its value from the binding of the surrounding block.

3.10 Function Definitions

Functions are defined by statements of the form

Ident[Ident, …] : SimpleType[SimpleType, …] == Expression

The first Ident is the name of the function to be defined, the other Idents within the brackets define the names of the formal parameters. The first SimpleType denotes the type of the function’s result value, the following ones the types of the parameters in the order of their appeareance within the brackets. The Expression specifies the result value of the function.

There is also a second form of function definitions for functions with named type signatures:

Ident[Ident, …] : SimpleType == Expression

In this case, SimpleType denotes the name of a function type that specifies the types of the parameters as well as the type of the result. For further explanation on function types, see Section 3.11.

The following statements present some of the not very exciting but inevitable examples of function definitions how they can be found in any textbook on functional programming:

     Fac[n]: INT[INT] ==  -- Factorial
       IF n = 0
         THEN 1
         ELSE n * Fac[n-1];
     Fib[n]: INT[INT] ==  -- Fibonacci, Sledge-Hammer Version
       IF n <= 2
         THEN 1
         ELSE {
               a: INT == Fib[n-1];
               b: INT == Fib[n-2]
              }
               a + b;
     Fibo[n]: INT[INT] == -- Fibonacci, Tail-Recursive Version
       {
        -- Local Auxiliary Function
        Iter[a,b,n]: INT[INT,INT,INT] ==
          IF n = 1
           THEN a
           ELSE Iter[b,a+b,n-1];
       }
        Iter[1,1,n]

As shown in these examples, the function bodies (i.e. the expressions on the right hand sides of function definitions) may include local definitions, also function definitions themselves. The formal parameters of a function can be interpreted as constant names that are locally defined, which overrides any binding of these names outside the function.

As usual, function definitions obey the lexical scoping discipline (see Section 3.9). Names that are used within the body of a function, but have neither been introduced as parameters nor by local bindings, take their values from the lexical enclosing environment. E.g. in the definitions

     a: INT == 7;
     F[m,n]: INT[INT,INT] ==
      {
       G[i,j]: INT[INT,INT] == i*j+n
      }
       G[m,a]

the reference to a within the body of F takes its value 7 from the global environment, while the reference to n within the body of G takes its value from the formal parameter of F.

As already mentioned several times, the scope of any name starts at the point of its definition, therefore functions have to be defined, before they can be used. In order to allow mutually recursive functions, Eve knows as a special form the forward function definition:

Ident[Ident, …] : SimpleType[SimpleType, …]

respectively

Ident[Ident, …] : SimpleType

for functions with named type signature (see Section 3.11).

A forward definition statement makes public just the signature of a function, i.e. its head and its type, but not the body of the function itself. From the point of this statement on, references to this function are allowed but the actual function definition has to take place within the same level of scope.

E.g. in the definition sequence

     G[x]: INT[INT];    -- Forward Definition of G
     F[x]: INT[INT] ==  -- Definition of F
       IF ODD[x]
         THEN F[x-1]
         ELSE G[x/2];   -- Use of G
     G[x]: INT[INT] ==  -- Actual Definition of G
       IF x = 0
         THEN 1
         ELSE F[x+2];   -- Use of F

the forward reference to G is resolved by the use of a forward definition statement.

3.11 Higher Order Functions

In Eve, functions are first order citizens like any other types of objects, i.e. they can be stored in data structures, passed to other functions as arguments, returned as results of functions, etc.

For this purpose, Eve provides as a fourth type constructor^{13}, the function constructor. Its general form is

SimpleType[SimpleType, …, SimpleType]

This constructor creates the type that includes all those functions, whose signatures (i.e. the number and the types of parameters as well as the result type) equal the given signature (this type is known as function domain in denotational semantics). Examples for the definition of function types are

      IntFun   ==  INT[INT];  -- Unary integer function, e.g. INEG
      IntTest  ==  BOOL[INT]; -- Test integer function, e.g. ODD
      CharInt  ==  CHAR[INT]; -- Conversion functions, e.g. CHR

The function constructor enables us to write higher order functions, i.e. functions that take other functions as arguments, e.g.

13The three other ones have been described in Section 3.3.

     IntFun  == INT[INT];
     IntList == LIST OF INT;
     IMap[fun,list]: IntList[IntFun,IntList] ==
       IF NULL[list]
         THEN list
         ELSE fun[list.HEAD]::IMap[fun,list.TAIL];

This function IMap takes a function and a list as arguments and applies the function to every element of the list (it “maps the function to the list”). The generated result values are successively entered into a list which is returned as the result of the function.

Using the following function definition with a named type signature,

     Twice[x]: IntFun == x+x

the expression

     IMap[Twice,IntList[1,2,3,4,5]]

maps the function Twice to a list of integers and returns the list of the doubled values. But now, if we want to use IMap to apply the function

     Square[x]: INT[INT] == x*x

to a list of integers, we have to take care. In contrast to our policy of giving all objects explicitely named types, Square has, like most functions, an anonymous type specified by its signature^{14}. Our definition of type equality (see Section 3.3) forces us to convert a function with anonymous type signature to a named type signature. This may be done by the application of a conversion function that has the name of its associated type. E.g. the expression

     IMap[IntFun[Square],IntList[1,2,3,4,5]]

maps the converted function Square to a list of integers and returns the list of the squared values.

Functions can also be generated as results of function calls, e.g. the statement

     Mult[n]: IntFun[INT] ==
       {M[i]: INT[INT] == n*i}
       IntFun[M];

defines a function Mult, which takes an integer n as argument and returns a unary function, which multiplies a given integer i with n.

Functions can also be renamed or stored in data structures, e.g. the statements

14The reason for this is merely pragmatic, since it would be very tedious to define explicit type names for every kind of function signature.

     FunPair == TUPLE OF a: IntFun, b: IntFun;
     A: IntFun == IntFun[IADD]; -- Renaming of IADD
     B: FunPair ==              -- Pair of ISUB and IDIV
       FunPair[IntFun[ISUB],
               IntFun[IDIV]];

define the name A as a synonym for IADD^{15}, and B as a tuple containing the functions ISUB and

IDIV.

Now the existence of the prefix versions of the integer operators and comparison operators becomes clear. Their only purpose is to serve as the first order counterparts of the infix operators when using higher order functions.

However, there are several predefined functions that are not first-order objects and may not be given as arguments to higher-order functions:

1. The generic list functions CONS, HEAD, HEAD and TAIL are not first-order objects.

2. All user-defined data constructors (for tuples, unions and function types) are not first-order objects.

3. The generic error test function FAILED is not a first-order object.

This restriction imposed by the non-polymorphic type system of Eve is actually not very serious: if a well-typed user-defined function is put around a generic function, this function may be freely used as first-order object, e.g.

     IntList == LIST OF INT;
     ListFun == IntList[INT, IntList];
     IntCons[int,list]: ListFun ==
       CONS[int,list];
     IntApply[fun, int, list]: IntList[ListFun, INT, IntList] ==
       fun[int,list]

3.12 Strictness Properties

The strictness properties of functions and data structures may have strong impacts on the runtime behaviour of a program, especially on its termination and on its reaction on runtime errors.

In this section, we will concentrate on the non-termination interpretation of strictness i.e. ⊥ will stand for the non-terminating value. However, if not explicitly stated in the next section, the propositions of this section also hold for the error interpretation.

In Eve there exist a few simple rules for the strictness of functions:

1. User-defined functions are non-strict in all of their arguments, unless the strictness in an argument is forced by the body of the function. This is the case, if the evaluation of the body requires calls of operators or functions that are strict in this argument.

15The term synonym already indicates that any reference to A is actually a reference to IADD. Function renamings do not create new functions!

2. Predefined functions and operators are strict in all of their arguments. This rule may be overridden by some of the following rules.

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

4. The boolean operators & and | (and their associated functions AND and OR) are only strict in their first argument.

5. The infix list construction operator :: and its prefix version CONS are non-strict in both of their arguments.

6. All list, tuple, and union constructors are non-strict in all of their arguments.

7. The if-then-else construct is only strict in its first argument, i.e. the boolean test condition.

8. The case construct is only strict in its first argument, i.e. the union expression.

Some examples may help to understand these rules: we define the non-terminating function

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

and the following functions

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

Then the following propositions hold:

 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.

However, although the expression

ConsL[Forever[0]]

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.10: Runtime Errors

returns a result (namely the list with the first value undefined and the second value 0), the expression

     ConsL[Forever[0]].HEAD

will not return a value, since the head of the constructed list will never be computed!

In Eve, all compound data structures (lists, tuples and also unions) are non-strict in the sense, that the structure as a whole may be used while components of the structure are still undefined.

The consequences of the strictness property on the error behaviour of a program will be discussed in the next section.

3.13 Error Behaviour

Eve handles runtime errors by the use of error values. Whenever some operation is not able to return a proper result, instead of the result the predefined constant ERROR is returned. ERROR is a generic constant, i.e. it is element of every data type that can be defined in Eve and therefore compatible with every type.

All strictness rules of the previous section also hold for error values, with two exceptions:

1. For all predefined binary functions and operators that are strict in both of their arguments, the “non-terminating ⊥” has precedence over the “error ⊥”. If one argument is non-terminating and the other one is an error value, the computation of the function or operator does not terminate.

2. The error test function FAILED is only strict for the “non-terminating ⊥” but not for the “error ⊥”. If applied to an error value, FAILED returns a valid boolean value.

ERROR may only be produced in those cases that are listed in Table 3.10.

If a function or operator is applied to ERROR, the result of the operation depends on the strictness, respectively non-strictness, of the function^{16}:

• If the function is strict in the ERROR argument, the result will also be the ERROR value.

• If the function is not strict in this argument, it will compute some sensible result value.

The generic function FAILED is used to detect, whether the evaluation of some value value has been successful or whether the result is the ERROR constant, i.e.

16See Section 3.12 for a detailed discussion on this topic.

     FAILED[ERROR] = TRUE.

However, FAILED is no first-order object and cannot be used as an argument to higher order functions (see Section 3.11).

The following examples demonstrate some cases, in which ERROR may be returned as result:

      FAILED[1/0]                       =  TRUE
      FAILED[IntList[1/0]]              =  FALSE
      FAILED[CHR[1234]]                 =  TRUE
      FAILED[IF 0=0 THEN 1 ELSE 1/0]    =  FALSE
      FAILED[IF 1=0 THEN 1 ELSE ERROR]  =  TRUE
      FAILED[(1/0)::IntList[1]]         =  FALSE

Take care, although 1/0 is the error value, IntList[1/0] returns a proper result, namely a list whose only element is the error value!

User-defined functions are basically non-strict, i.e. they may return proper results, although one of their arguments is invalid, e.g. using the definition

     F[x,y]: INT[INT,INT] ==
       IF x=0
         THEN 1
         ELSE y;

the expression F[0,1/0] returns the result value 1, although the second argument to the function is the ERROR value.

3.14 A Programming Example

As an illustration of some of the facilities of Eve, we show the design of a function that performs symbolic differentiation of algebraic expressions. This example is taken from [Abelson and Sussman, 1985], where the program has been written in Scheme, a functional dialect of Lisp.

We want to write a function Derive that takes as arguments an algebraic expression and a variable and returns the derivative of the expression with respect to the variable. The algorithm itself is rather simple, but we want to direct our attention to the way Eve supports the solution of the problem.

First of all, how can we represent algebraic expressions? Algebraic expressions may be constants or variables, as well as sums or products of previously constructed expressions^{17}. This description already gives us some hints on how to define expressions:

• Constants may be simply encoded as integers.

• Variables may be represented by character strings.

• Sums and Products are pairs, i.e. tuples, of expressions.

• Since an expression may be either a constant, a variable, a sum or a product, we will represent it as a union consisting of one of these four variants.

64 CHAPTER 3. THE EVE PROGRAMMING LANGUAGE

----------------------------------------------------------
--
-- Symbolic Differentiation (Abelson & Sussman, page 104)
--
----------------------------------------------------------
Constant == INT;      -- Constants
Variable == STR;      -- Variables
Exp;                  -- Forward Definition Exp
SumExp == TUPLE OF    -- Sum Expressions
  addend: Exp,        -- addend + augend
  augend: Exp;
ProdExp == TUPLE OF   -- Product Expressions
  multiplicand: Exp,  -- multiplcand * multiplier
  multiplier:   Exp;
Exp == UNION OF       -- Expressions
  Const == Constant,
  Var   == Variable,
  Sum   == SumExp,
  Prod  == ProdExp;

Figure 3.1: Symbolic Derivation (Type Definitions)

Figure 3.1 shows the several statements needed for these type definitions.

Let’s take a look at the order of these statements: SumExp and ProdExp both refer to Exp, therefore Exp has to made public by a forward type definition (see Section 3.3). Vice versa, Exp refers to SumExp and ProdExp too (mutual recursion), but both of these types have been introduced before.

Now we have to introduce some functions doing auxiliary work in the process of computing the derivative of an expression.

• First, we need a function VarEq that compares two variables. This task is not very difficult, since variables are represented by character strings, which can be compared by the sequential comparison of their elements.

• Then we need two functions MakeSum and MakeProd constructing the sum and the product of two expressions. This task gets a little bit complicated, because we want these functions to perform some simplifications. E.g. the expression 0 + x should not remain a sum expression, but be simplified to x itself. Similar simplifications can be done for 0∗x and 1∗x. In addition, if both parts of a sum or a product are constants, the result value should be computed directly without the construction of a compound expression.

Figure 3.2 shows, how these functions can be programmed.

An interesting point is the definition of the function VarEq. VarEq does not call the function StrEq that performs the actual comparison of strings. On the contrary, VarEq is the function StrEq, or in other words, it is a synonym of this function, so any reference to VarEq is actually a reference to StrEq. Renamings of functions are (like renamings of types) a very powerful tool for program and data abstraction, since any changes in the representation of an object will affect just some small parts of the program.

The actual derivation function, shown in figure 3.3, can now be written straightforward. Depending on the type of the expression, we perform one of the well-known differentiation rules for constants, variables, sums and products.

Our derivation program is still very simple with respect to the simplification of algebraic expressions. This is a very complex algorithmic problem, because, among other reasons, a form that is simplest for one purpose may not be for another. Therefore in “real” applications, combinations of different strategies have to be applied.

3.15 Syntax Definition

3.15.1 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:

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

17We restrict ourselves to these kinds of expressions. It is rather simple to extend them to expressions that also contain subtractions, divisions, etc.

-- Test Strings for Equality
StrEq[s1,s2]: BOOL[STR,STR] ==
IF NULL[s1]
THEN NULL[s2]
ELSE IF NULL[s2]
THEN NULL[s1]
ELSE
(s1.HEAD = s2.HEAD) & StrEq[s1.TAIL,s2.TAIL];
-- Test Variables for Equality
BoolVar == BOOL[Variable,Variable];
VarEq: BoolVar == BoolVar[StrEq];
-- Construct the Sum of Two Expressions
MakeSum[exp1,exp2]: Exp[Exp,Exp] ==
CASE exp1
{c1: Const} CASE exp2
{c2: Const} Exp$Const[c1+c2]   -- exp1 and exp2 constant
ELSE IF c1 = 0                 -- only exp1 constant
  THEN exp2                    --   exp1 = 0
  ELSE Exp$Sum[exp1,exp2]      --   exp1 # 0
ELSE CASE exp2
{c2: Const} IF c2 = 0          -- only exp2 constant
  THEN exp1                    --   exp2 = 0
  ELSE Exp$Sum[exp1,exp2]      --   exp2 # 0
ELSE
Exp$Sum[exp1,exp2];            -- No constants
-- Construct the Product of Two Expresssions
MakeProd[exp1,exp2]: Exp[Exp,Exp] ==
CASE exp1
{c1: Const} CASE exp2
{c2: Const} Exp$Const[c1,c2]   -- exp1 and exp2 constant
ELSE IF c1 = 0 THEN
  Exp$Const[0]                 -- exp1 = 0
ELSE IF c1 = 1 THEN
  exp2                         -- exp1 = 1
ELSE
  Exp$Prod[exp1,exp2]          -- exp1 # 0,1
ELSE CASE exp2
{c2: Const} IF c2 = 0 THEN
  Exp$Const[0]                 -- exp2 = 0
ELSE IF c2 = 1 THEN
  exp1                         -- exp2 = 1
ELSE
  Exp$Prod[exp1,exp2]          -- exp2 # 0,1
ELSE
  Exp$Prod[exp1,exp2];         -- No constants

Figure 3.2: Symbolic Derivation (Auxiliary Functions)

3.15. SYNTAX DEFINITION 67

--------------------------------------------------------------
-- Derive an expressions exp with respect to the variable var
--------------------------------------------------------------
Derive[exp,var]: Exp[Exp,Var] ==
CASE exp
{c: Const}            -- Constants
Exp$Const[0]
{v: Var}              -- Variables
IF VarEq[v,var]
  THEN Exp$Const[1]
  ELSE Exp$Const[0]
{s: Sum}              -- Sums
{
first: Exp == Derive[s.addend,var];
secnd: Exp == Derive[s.augend,var]
}
MakeSum[first,secnd]
{p: Prod}             -- Products
{
first: Exp == p.multiplicand;
secnd: Exp == p.multiplier;
prod1: Exp == MakeProd[first,Derive[secnd,var]];
prod2: Exp == MakeProd[Derive[first,var],secnd]
}
MakeSum[prod1,prod2];

Figure 3.3: Symbolic Derivation (Main Function)

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

[ ] enclose syntactic items that are optional, i.e. that may appear once or not at all in that place^{19}.

{ } enclose syntactic items that may be repeated zero or more times in that place^{20}.

| seperates 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 .

3.15.2 Input File

Input files may consist of all printable characters of the US-ASCII character set (see Table 3.11). 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.15.3 Lexical Structure

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

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.6 except \' and \" .

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

Char | " | \' .

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

Char | ' | \" .

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

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

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

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

3.15. SYNTAX DEFINITION 69

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.11: The US-ASCII Character Set

3.15.4 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 }.

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  = [ + | - ] SimpleExpr { AddOp Term }.
AddOp  = + | - | | .
Term  = Term { MulOp Factor }.
MulOp  = * | / | % | & .

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

pression ) | ~ Factor .

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

Programs

Program = DefinitionSequence .

3.15.5 Keywords

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

     CASE  ELSE  IF     LIST
     OF    THEN  TUPLE  UNION

3.16 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 identifers:

• Types,

• Constants,

• Functions.

3.16.1 Predefined Types

Eve has four predefined types thta are shown in Table 3.12.

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

Table 3.12: 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.13: Predefined Constants

3.16.2 Predefined Constants

Table 3.13 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.

3.16.3 Predefined Functions

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

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.14: Predefined Integer Functions

Name Type  Description

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

Table 3.15: 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.16: Predefined Character Functions

Comparison Functions

Table 3.17 describes the functions that can be used to compare objects of atomic type (i.e. integers, booleans and characters). For each kind of comparison operator, there exist 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.18 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.11).

Error Test Function

The generic error test function shown in Table 3.19 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.17: Predefined Comparison Functions

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.18: Predefined List Functions

Function Type  Description

         FAILED    BOOL[Any] Test for ERROR

Table 3.19: 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.11).

The ADAM Abstract Dataflow Machine

This chapter describes the design of the Adam Abstract Dataflow Machine. The Adam architecture represents a hybrid concept combining features from conventional register architectures and from pure dataflow computers. After an introduction into the basic idea of our approach, we present the architecture in a top-down fashion: first the global structure of the machine is illustrated, then we describe the components of an Adam module in more detail. In Appendix A, we present the operational behaviour of the machine by defining an interpreter that simulates the machine in operation. Finally, in Appendix B the semantics of the individual basic instructions is defined.

A general overview over the Adam has been presented in [Schreiner, 1990] and [Schreiner, 1991].

4.1 Basic Idea

The basic idea of our approach is as follows. We take a mathematical expression, e.g.

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

This expression can be viewed as the body of a function f(x, y) with formal parameters x and y and is translated in a dataflow graph as depicted in Fig. 4.1.

In pure dataflow architectures, this dataflow graph is represented by a dataflow program of the following kind where each instruction consists of an operation code and the address of its successor instruction:

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

Figure 4.1: A Dataflow Graph

Each node of the graph is translated into one instruction of the program where the arcs of the graph are represented by the instructions’ successor addresses. If instructions are only allowed to have one successor, branches have to be translated into explicit COPY instructions duplicating their input values. If the successor is a binary instruction, the predecessor must also specify which of the “input ports” of the successor is referenced.

In most dataflow machines, values are transmitted between different instructions by pieces of information, called “tokens” that flow along the arcs of the dataflow graph. For instructions with more than one input arcs, special hardware mechanisms have to handle the synchronization of the input tokens, i.e. a “rendezvous” between the tokens has to be organized for a proper execution of the instruction.

In the Adam model, the arcs of the dataflow graph denote registers and the nodes represent two-address instructions operating on these registers:

        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:  DIV  x_{1}, y_{2}, 12

Every instruction operates on two registers where the first register receives the result value. After an instruction has been executed, the control flow is routed by the address field of the instruction, i.e. each instruction performs a jump to its successor. SPLIT instructions fork the actual control flow into two parallel paths starting their execution at the specified successors. The execution of an instruction sequence may be unhinderedly continued as long as no WAIT instruction is encontered.

Each WAIT guards a binary event encoded in the first field of this instruction. Each event may have one of the values “occurred” or “not occurred”, initially no event has occurred. If instruction execution reaches a WAIT whose event has not yet occurred, the instruction sequence is interrupted. However, the status of the referred event is set to “occurred”. The next time, this event is tested during the execution of an instruction sequence, the sequence may unhinderedly pass the WAIT.

SPLIT and WAIT control the generation of parallel processes and the synchronization between them, they are the only instructions related to parallelism. However, they suffice to control the program execution in such a way that dataflow semantics is simulated.

We want to emphasize the essential difference between the pure dataflow model and our approach: In dataflow architectures, all data physically “flow” between instructions and, by this, control the computation. In the Adam processor, sequences of instructions are executed without any data flowing between them. Communication between different instructions is done via shared registers, synchronization between parallel instruction sequences are explicitely handled by WAIT statements.

This approach has two essential advantages:

1. In dataflow machines, all data referenced by more than one instructions have to be explicitly duplicated and sent to the required instructions, even if they belong to the same activation of a function and reside on the same processor.

In our model, parallel program paths may share the data held in common registers, an explicit duplication can be avoided in most cases. The compiler may optimize register allocation such that the overall number of registers required is reduced to an absolute minimum.

2. In dataflow architectures, the synchronization model is fixed in hardware. Parallel program paths have to be generated and finally synchronized even if the parallelism is not really utilized. This is true for most approaches, since the distribution of tasks to processors is usually handled on the level of whole functions, i.e. parallel paths within a function body are executed on the same processor.

In our concept, a compiler may, by simple code analysis, detect in which cases parallel paths may be safely (i.e. without changes to the dataflow semantics) sequentially executed and then serialize the code by the use of WAIT statements. Sequentialization is not allowed for the bodies of non-strict functions, i.e. functions that may return values even if not all arguments have yet arrived. In general, a process called strictness analysis is necessary to detect to which extent function bodies may be safely serialized.

Using both optimizations, register sharing and serialization, we may optimize the above program in the following way:

        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:  MULT x_{1}, y_{1}, 7

The WAIT statement is only responsible for synchronizing the parallel tasks delivering their arguments in registers x_{1} and y_{1}. All other instructions may be sequentially executed requiring only one additional register x_{2}.

After this short introduction into the basic principles of our approach, the following sections present the Adam architecture in detail.

4.2 Global Structure

The Adam is a parallel machine with distributed memory. It consists of several equally constructed modules that are connected by a network of unbuffered uni-directional channels. Each module of the network has several input and output channels by which it can receive tokens, i.e. pieces of information, from other modules and send tokens to them. These channels represent the only communication medium of the Adam, there is no global memory that the modules might have access to. One special module, the master, is connected to peripheral devices for input and output. The master handles all kinds of interactions with the outside world. In particular it is responsible for loading and distributing the program to be executed, for triggering the execution of the program and for delivering the result values of the computation.

Figure 4.2 shows a sample network of Adam modules (each double-arrow represents two channels for communication in both directions).

The master module is built of the following components depicted in Figure 4.3:

1. The Input/Output Interface

This interface is the master’s connection to the outside world, i.e. to the host computer by that input and output is handled. The interface communicates by a special protocol with a server program that is sited on the host and is responsible for the access to required peripheral devices. The program to be executed is loaded to the Adam by this interface while the results of the computation are printed on the screen and written into a file on the host.

2. The Network Interface

This interface represents the master’s connection to the network of worker modules. It is linked between two worker modules and is absolutely transparent with respect to network communication. Any token received from one side and not destined for the master is simply forwarded to the other side. However, the interface channels tokens from the master into the net and removes tokens destined for the master from the net.

3. The Result Generator

This unit first triggers the execution of the program in the Adam network and then demands the results of the computation from the network. Since the values received do not have any tags that indicate their types, their interpretation has to be directed by a type descriptor file that is loaded via the input/output interface. If this description e.g. indicates that the result type of a computation is a list of integer tuples, the result generator has to demand the delivery of all tuple references stored in the list and of all tuple elements, respectively. The received values are converted into printable form and then forwarded to the input/output interface.

4. The Statistics Generator

This unit receives several types of statistics tokens that are generated by the worker modules. These messages give a rough impression on the overall load of the network and indicate which modules are more heavily loaded than others. The statistics tokens are converted into 78 CHAPTER 4. THE ADAM ABSTRACT DATAFLOW MACHINE

Figure 4.2: The Adam Network 4.2. GLOBAL STRUCTURE 79

Figure 4.3: An Adam Master Module

Figure 4.4: An Adam Worker Module

printable form and then forwarded to the input/output interface where they are written into a file. The load statistics may later be evaluated to achieve a better performance of the architecture by improving e.g. the load balancing among worker modules.

Each worker module of the Adam consists of three components illustrated in Figure 4.4:

1. The Arbiter

The arbiter is the only unit of a worker module with an interface to its neighbour modules. When the Adam is booted, all arbiters are responsible for distributing the program to be executed over the network. Afterwards, they receive incoming tokens from the local units and from neighbour modules and route them to their correct destinations, either to one of the local units or to another module.

2. The Processor Unit

The processor unit executes the given program and, thereby, performs the actual computation. It receives tokens (basically arguments for the local function activations) from and, vice versa, sends tokens (basically results of local function activations) to other processors in the network. Moreover, it communicates with its memory unit and those on other modules by request and result tokens.

3. The Memory Unit

Figure 4.5: The Arbiter

The memory unit basically consists of a heap-organized store containing tuples of variablesize. These data structures may be allocated and referenced by incoming request tokens. A memory manager handles these requests and returns the result values (either integer values or tuple references). Tuples that are no more in use are reclaimed by a garbage collection mechanism.

The following sections provide an informal but detailed description of the various components of a worker module, i.e. of their structure, their behaviour and their interaction.

4.3 The Arbiter

The arbiter is that component of an Adam module that is responsible for the communication between worker modules. Thus, it is the only unit on a worker module with an interface to the neighbour modules. Moreover, communication between the local units of a module is done via the arbiter, too. The construction of an arbiter is illustrated in Figure 4.5.

When the Adam is booted, the first task of all arbiters is to distribute the program to be executed over all worker modules of the network. A local boot table determines along which paths of the network the program (split into several clusters) is forwarded. After all processor units have received the program, the main task of the arbiters is the correct routing of network tokens between the worker modules.

Each arbiter simultaneously listens on the external input channels that connect the module to its neighbours and on both of the internal input channels from the local processor and the local memory unit. If on one of these channels an input token is available, the arbiter receives the token and removes it from the channel. If more than one input channels are simultaneously ready for transmission, the arbiter non-derministically accepts one the tokens. The other input channels (i.e. the components on their opposite ends) are blocked until the arbiter receives the tokens they hold.

All tokens in the network have equal length and are constructed from the components

Address  Type  Data

that are interpreted as follows:

• Address:

The address field contains the number of the network module that the token is destined for. This unique address is in the range

0 … \underline{ModNum}

where \underline{ModNum} is the number of modules in the network exclusively the master module. The master module always has the network address 0.

• Type:

The type field describes how the piece of information contained in the Data field is to be interpreted and also determines whether to send the token to the processor or to the memory unit of a module. Basically, there are the following types of tokens:

Value-Messages (short Messages) transmitting data values between processor units and memory units (see Section 4.4.5).

Context-Messages transmitting to a processor the description of the instruction to that the result of some function activation is to be sent (see Section 4.4.5).

Call-Requests transmitting the request for the activation of a new function instance to a processor unit (see Section 4.4.6).

Allocation-Requests transmitting the request for the allocation of a new tuple to a memory unit (see Section 4.5.2).

Read-Requests transmitting the request for reading the contents of some tuple field to a memory unit (see Section 4.5.2).

Write-Requests transmitting the request for writing the contents of some tuple field to a memory unit (see Section 4.5.2).

Submission-Requests transmitting the request for submitting the lifted arguments of a curried function whose description is stored in a memory unit (see Section 4.5.2).

Reference-Messages reporting to a memory unit that a reference to some tuple has to be copied or has been deleted (see Section 4.5.4).

Load-Reports transmitting to a processor or memory unit the report about the utilization of another processor or memory unit (see Sections A.3.8 and A.4.8).

Stop-Requests transmitting to all Adam modules the signal that the program has delivered the desired result and that all units have to to terminate (see Sections A.3.7 and A.4.6).

• Data:

The data field contains the actual piece of token information. For all types of tokens, the data field has equal length. However, the interpretation of its contents depends on the type tag of the token.

Incoming tokens are classified according to their type and destination adress and forwarded either to one of the local units on the module or to some neighbour module via one of the output channels that leave the module. Since the transmission of a token is not possible, before the receiver is ready to accept the token, the arbiter is connected to each of the leaving channels by a buffer store. This buffer is used to smooth differences in the communication speed of two neighboured modules. Thus, the sending arbiter is not blocked if the receiver is not yet ready to receive a token.

In order to route each incoming token to the correct output channel, each arbiter has a local routing table. This table contains one entry for each module of the whole network. It is indexed by the adress field of the token and provides the number of the output channel that the token has to be sent through. If the token carries the adress of the arbiter’s own module, the arbiter sends the token according to its type either to the processor or to the memory unit of the module.

A more formal description of the operational behaviour of the arbiter is given in Appendix A.2.

4.4 The Processor Unit

The Adam processor can be considered as a data-driven processor that efficiently supports internal parallelism and tolerates long memory latencies. However, there are essential differencess to the pure dataflow approach: The Adam processor is actually a control-driven processor with several extensions that allow the efficient simulation of data-driven behaviour. Thus, the processor has a hybrid architecture combining features from conventional register-architectures and from true dataflow computers.

This design decision was made for two reasons:

1. We want to show how dataflow and controlflow are related amd that one model can be used to simulate the other. The dataflow paradigm may also be rather efficiently implemented on conventional parallel architectures.

2. This concept allows the more efficient execution of dataflow programs in general (see Section 4.1). Moreover, we may profit by the large amount of knowledge on compilation techniques for register architectures that has been gathered in the last decades.

For the same reasons, the idea of hybrid dataflow/von Neumann hardware architectures has most recently started to become a subject of international interest (see [Iannucci, 1988] and [Gao et al., 1988]).

The Adam processor basically consists of following components illustrated in Figure 4.6:

• Execution Unit: The execution unit is the heart of the processor performing the actual computations within the processor unit. Program execution is basically control-driven but constrained by special synchronization instructions that are used to express the data dependences within a program. This unit receives messages and requests from other modules and also sends messages and requests to processors and memories.

84 CHAPTER 4. THE ADAM ABSTRACT DATAFLOW MACHINE Figure 4.6: The Processor Unit • Program Store:

The program store is a contiguous area of memory that holds the machine code of the program. The code is partitioned into a sequence of code blocks each of which basically corresponds to one user-defined function.

• Frame Store:

The frame store is a heap-organized store holding a set of activation frames each of which corresponding to one particular function activation. Each frame represents the actual state of its activation, i.e. it holds the contents of the regisers and the state of the events.

• Context Store:

The context store is a table of activation descriptors, called contexts. Each context uniquely describes one function activation on the local processor unit by the base addresses of the activation’s code block and of its register frame. These descriptors are used to keep references to activations as small as possible.

• Trigger Store:

The trigger store contains a set of instruction triggers each of which is the description of a parallel task that is ready to be executed by has not yet been. Each time a new task is forked, a trigger for this task is put into this store that is the the source of internal parallelism on the processor.

• Message Store:

The message store contains all message tokens that have been sent to the processor but have not yet been accepted by the execution unit. These tokens are used to transport pieces of information between processor and memory units. Each message may be handled without consuming any new processor resources.

• Request Store:

The request store contains all call requests that have been sent to the processor element but have not yet been fulfilled by the execution unit. Each request asks the processor to create a new activation of a specific function, which requires the allocation of a new activation frame.

The following subsections provide a more detailed description of the processor components.

4.4.1 The Program Store

The program store (illustrated in Figure 4.7) is a linear adressed array that is filled with Adam machine code^{1} before program execution is started. All instructions have equal length and consist of the fields

OpCode  OpWord

where OpCode denotes the operation code of the instruction and OpWord represents its operand.

Instructions are grouped to so called code blocks each of which is basically the body of one user-- defined functions. The first word in each code block is actually not an instruction but a function descriptor that consists of the fields

1Also some other values (e.g. constants) are deposited in the program store.

86 CHAPTER 4. THE ADAM ABSTRACT DATAFLOW MACHINE Figure 4.7: The Program Store

EventNum  RegNum

that are interpreted as follows:

• EventNum

denotes the number of frame slots that are required to hold the synchronization bits of each activation of this code block. Each of these bits represents a synchronization event, i.e. a point of synchronization between two parallel instruction paths. Further details on this topic are discussed in Section 4.4.7.

• RegNum

denotes the number of registers (i.e. slots in the activation frame) required by every activation of the function to store intermediate values. The slots are called registers since they roughly correspond to the registers of conventional von Neumann-processors. However, since function activations work in parallel, each instance needs its own register set, i.e. its own frame.

For a detailed description of the use of these descriptors see Section 5.4.

The first BlockNum words of the program store (where BlockNum is the number of code blocks in the store) do not contain machine instructions but the base addresses of all code blocks. These adresses are used to keep function references independent of the actual code addresses within the program store^{2}

4.4.2 The Frame Store

The frame store is a heap-organized store that contains the activation frames of the processor. Each frame is a contiguous memory area that represents the local workspace for one particular function activation. Each activation has its own frame that it has exclusively access to. A frame is constructed of a sequence of slots of length SlotLen that are used as follows (see Figure 4.8):

• Synchronization Bits:

Every activation of a function requires EventNum frame slots to hold the EventNum ∗ SlotLen bits that represent the synchronization events of the activation. The first slots of a frame are reserved for this purpose. Each of the bits within these slots may be tested and inverted by the synchronization instruction WAIT that is used to express the data dependencies within a program (see Section 4.4.7 for more details).

• Result Address:

The next two slots contain the processor number ProcNum, the context Context and the offset Offset of the instruction to that the result of the activation is to be delivered. When the function activation has computed a result, this result value is transmitted by a message token to the instruction denoted by these fields (see Section 5.4 for more details).

• Registers:

The following RegNum slots of the frame represent the registers that the function activation may freely use in order to store intermediate values. All instructions of the Adam instruction set operate on these registers each of which is able to contain a fixed-size value, i.e. basically an integer or a reference to a tuple on the heap (see Section 4.4.7 for more details).

2For the moment, the ADAM does not take any advantage from the relocability of code blocks. However, this possibility is reserved for future architecture improvements.

88 CHAPTER 4. THE ADAM ABSTRACT DATAFLOW MACHINE Figure 4.8: An Activation Frame

Figure 4.9: The Context Store

The allocation of an activation frame is always the first action when invoking a function and its deallocation is always the last one before a function is terminated. The compiler is responsible for generating code that is synchronized in such a way that the frame is disposed if and only if no parallel task is active (i.e. in the trigger store) and no message is on the way to that frame any more (see Section 5).

4.4.3 The Context Store

The context store is a heap-organized store that contains one item (the context) for each activation of a function. Each context is a unique description of the function activation and consists of the fields

FrameBase  CodeBase

that are interpreted as follows (see Figure 4.9):

• FrameBase: This field contains the base adress of the activation frame, i.e. a pointer to the begin of the frame in the activation store. Actually it does not reference the first slot of the frame but the first slot that is used as a register (the slots before contain the synchronization bits and the result adress of the activation, see Section 4.4.2).

• CodeBase:

This field contains the base adress of the activation’s code block, i.e. a pointer to the begin of the block in the program store. Again, the adress does actually not reference the first word of the block but its first instruction (the first word of each block contains a description of the resources that are required by any activation of the function, see Section 4.4.1).

The context store is used to keep references to function activations as small as possible. This indirection table has been introduced since there are many more activation references than activations. Every activation reference contains a context field that indizes the actual activation description in the context store. Thus, the reference becomes much smaller than without this indirection step.

4.4.4 The Trigger Store

The trigger store contains a set of instruction triggers, i.e. descriptions of parallel tasks that are ready to execute but have not yet been triggered. The execution unit uses this store in order to remember all of those tasks it has temporarily put aside, although they might principially be executed. Each trigger consists of the fields

Context  Offset

that are interpreted as follows:

• Context:

This field holds the index of an item in the context store. The referenced context contains the complete description of the function activation that the task belongs to (i.e. the base adresses of activation frame and code block).

• Offset:

This field contains the offset of the task’s first instruction, i.e. its position relative to the begin of of the code block denoted by Context.

Both fields uniquely determine a parallel task on the processor (see Figure 4.10). At any time, the execution unit is free use the information contained in a trigger in order to start the corresponding task and remove the trigger from the store.

4.4.5 The Message Store

The message store represents a buffer between arbiter and execution unit containing the message tokens that have been sent to the processor. Each message contains the fields

Type  Data

where Type indicates the interpretation of the value transmitted in Data. The following types of messages are possible:

• Value-Messages transmitting values between function activations (basically function arguments and result values), 4.4. THE PROCESSOR UNIT 91

Figure 4.10: The Trigger Store • Context-Messages transmitting result contexts to function activations, i.e. addresses of the

instructions that function results are to be sent to,

• Load-Reports transmitting workload descriptions between processor units.

Each time the execution unit accepts a token from this store, it is also given its actual load, i.e. the number of messages the store holds. If the store is empty, the load number is zero and the delivered message value is undefined. Otherwise the execution unit handles the message according to its type. How this is done in detail, is described in Section A.3.

4.4.6 The Request Store

The request store contains the call-request tokens that have been sent to the processor. Each call-request contains the fields

Type  Context  Offset  ModAddr  FunDescr

that are interpreted as follows:

• Type:

gives the description how the request has to be handled. There exist two possibilites:

Fast Call: In this case, the address of the instruction to that the context number of the new activation is to be sent also indicates the address of the instruction that waits for the result of the function call. No separate context message (see Section 4.4.5) is required and the function activation may immediately start working.

Tail Call: The address of the instruction waiting for the function result will be sent in a separate context message. This function call mechanism enables the compiler to optimize tailrecursive functions: the last instance of a chain of tail-recursive function activations may return the result directly to the caller of the top-level instance without passing the intermediate chain. Moreover, each activation may terminate as soon as it has passed all arguments to its successor (see Section 5.5 for more details).

• Context, Offset and ModAddr

uniquely determine the operation to which the context of the new function activation is to be sent: ModAddr denotes the address of a processor, Context describes some activation on that processor and Offset indicates some operation within this activation (see Section 4.4.4). The context index of the new activation is packed into a message token and sent to the denoted instruction.

• FunDescr:

indicates the function to be activated. In detail, this field holds a pointer to one of the first BlockNum words in the code store containing the base addresses of all function code blocks.

For further description of the function call mechanisms, see Section 5.4.

4.4.7 The Execution Unit

The execution unit of the Adam processor has an instruction set that is similar to that of conventional machines but is extended by several features that allow the simulation of dataflow-like behaviour. The Adam instruction set shows the following characteristic features:

1. The instruction set basically consists of two-address instructions augmented by the address of the successor instruction. Thus, it is of the form

OpCode  OpX  OpY  Succ .

OpCode denotes the operation code of the instruction, OpX and OpY hold the numbers of the registers (i.e. of slots in the current activation frame) on which the operation is to be performed. Actually, these numbers are offsets to the begin of the register section of the frame, i.e. an operation field of 0 addresses the first register. Succ denotes the address of the instruction to be executed next in the form of an offset to the current instruction address. E.g. the instruction

                              i: ADD 1, 2, +4

at program address i causes the execution unit to add the contents of register 2 in the actual frame to the contents of register 1 and store the result in register 1. The next instruction to be executed has address i + 4 in the program store.

2. Sequences of instructions may be executed without any blocking as long as the execution unit does not encounter a WAIT instruction^{3}. This instruction denotes one particular point of synchronization between two parallel tasks. It has the form

WAIT  Event  Succ .

Event denotes the number of a synchronization bit within the current activation frame. A WAIT instruction is executed by testing the denoted bit and then inverting it. If the tested bit holds a 0, the synchronization event has not yet occurred. In this case, the bit is set to 1 and the current instruction sequence is terminated. If tested bit already holds a 1, the synchronization event has been triggered by a parallel task with another execution of WAIT. The event bit is reset to 0^{4} and the current instruction sequence is continued with the instruction denoted by the Succ field. E.g. if there are two parallel program paths with a wait instruction in each path such as

                             i: WAIT 4, +k − i

…

                             j: WAIT 4, +k − j

…

                             k: ADD 2, 3, +1

(with the instructions at addresses i and j being executed by parallel tasks), that path that first encounters its WAIT is interrupted. By this, synchronization bit 4 originally initialized with 0 is set to 1. The other task may now, without interruption, pass its WAIT and continue execution with the ADD instruction at address k. In this case, synchronization may have been necessary because the arguments for the ADD instruction have been computed by two parallel tasks and stored into registers 2 and 3, respectively.

3Actually, also the instructions ALLOC and READ referencing the heap store (see Section 5.7 and the function call instructions FASTCALL and TAILCALL (see Section 5.4) cause an instruction sequence to be interrupted.

4A compile time analysis of the source program is able to optimize the allocation of synchronization bits by determining in which cases already used bits may safely be reused.

3. Each of the instructions in the Adam instruction set has (at most) one successor instruction denoted by the Succ operand field. The only exception of this rule is the SPLIT instruction that has the form

SPLIT Succ1 Succ2

SPLIT forks the actual program path into two parallel tasks starting at instructions Succ1 and Succ2 . The description of the task denoted by Succ1 is put into the trigger store. The processor is now free to execute this task at any time. However, the actual program execution is continued with the task denoted by Succ2 . E.g., the instruction

                               i: SPLIT +1, +2

creates a trigger with the description of the parallel task starting at instruction i + 1. Actual execution is continued at address i + 2.

SPLIT and WAIT are the only Adam instructions related to parallelism. However, they suffice for a perfect simulation of all features of dataflow behaviour.

The interaction between execution unit and the different processor stores is illustrated in Figure 4.11:

Each time the execution of an instruction sequence is started, the description of the actual activation (i.e. the base addresses of code block and register frame) is stored in two registers. Any register number encoded in an instruction is handled as an offset to the frame pointer, hence, every function activation has its private set of registers. All event numbers referenced by WAIT instructions are handled as negative offsets to the frame base address, hence, every function activation has its private sets of events each of which is represented by a bit in the frame.

If a SPLIT instruction is executed, the start address of one of the parallel branches is pushed on the trigger store while the other one is continued by the current instruction sequence.

If a WAIT instruction fails, the actual instruction sequence is terminated. In this case, the context is switched and a new activation is executed. This may be triggered by one of three events:

1. A trigger is popped from the trigger store and the task defined by this trigger is executed. In this case, both of the registers holding the base addresses of actual code block and activation frame are updated by the values that the trigger denotes and execution is started with the first instruction of the parallel task^{5} .

2. A message is received from the message store. In this case, a new instruction sequence is started beginning with the instruction that represents the destination of the message. Such a message may either hold the argument for a function activation or it may hold the result value delivered by some function activation or it may also hold the result of a memory request (see the next section).

3. An activation request is received from the request store. In this case, some neighbour processor has sent a request to activate a new instance of some particular function. If the processor decides to fulfill this request, a new activation frame and a new context descriptor are allocated and a reference to the context descriptor is sent back to the caller.

5An important alternative is the concept to make context switches if and only if the trigger store is already empty. In this case, triggers only have to hold the address of the first instruction of the parallel task while the activation frame of the new task is the same as the actual one. Each trigger then represents a thread i.e. a lightweight process consisting only of a program counter but inheriting its environment from the father process.

4.4. THE PROCESSOR UNIT 95 Figure 4.11: Interaction between Processor Stores The decision which of the two possibilities to take depends on the actual load of the processor. If it is heavily loaded, argument messages will be preferred, if the load is low, more activation requests will be fulfilled. The decision between these two alternatives is (besides the decision to which processor to send activation requests to) the basic tool for load balancing in the Adam architecture.

In Appendix A.3, we define the operational behaviour of the Adam processor by describing the algorithm for an interpreter that executes the instruction set of the machine. The semantics of all Adam instructions is defined in Appendix B. In Chapter 5, we give a short description how several constructs of the high-level language Eve may be compiled into Adam machine code.

4.5 The Memory Unit

Since the processor units are only able to handle atomic (integer) data, the management of compound data structres has been shifted to separate memory units. Each worker module contains one memory unit that is constructed of the following components (see Figure 4.12):

• The Heap holds a set of tuples i.e. data structures of arbitrary but fixed size. Each field of a tuple is either empty or holds an integer atom or a tuple reference. All heaps on all worker modules together represent a global store, i.e. all tuple references are unique within the network.

• The Memory Manager handles incoming requests to allocate a new tuple (ALLOC), to write a value into some tuple field (WRITE) or to return the contents of some tuple field (READ). Furthermore, it is responsible for garbage collection i.e. for reclaiming the space that is occupied by tuples that are referenced no more.

• The “Expensive” Store and the “Cheap” Store hold all incoming requests classified into two categories: the request may require the allocation of additional heap memory (“expensive”) or it may not (“cheap”). The distinction between both kinds of requests helps to efficiently manage the heap store.

Although the Adam heap is logically global, it is physically distributed among all worker modules. This has the following advantages:

• Requests to local memory units may be handled faster than requests to distant memory units and do not burden the network.

• Memory requests may be handled simultaneously by different modules.

• Network traffic is distributed over the network.

The later propositions rely on the assumption, that the workload is, at least roughly, balanced among memory units and that memory requests are spread over all modules. However, there is some evidence (see [Goldberg, 1988]) that for fine-grained parallelism this assumption can be expected to hold.

On the other hand, the distributed memory architecture requires a distributed model of garbage collection. This is accomplished by using a reference counting scheme with weighted references (see Section 4.5.4).

There are some essential details that distinguish the Adam heap store from that of sequential architectures.

4.5. THE MEMORY UNIT 97 Figure 4.12: The Memory Unit • The allocation of tuples is separated from writing their contents. Special synchronization

mechanmisms enable to use tuple references before tuple fields are yet defined (non-strict data structures).

• Each tuple carries the information which of its fields have already been written and which not. If a read request demands some value that is not yet defined, the request is queued and answered as soon as the required value has arrived.

• The contents of a tuple field may be written at most once, i.e. the heap store obeys the single assignment rule. Any attempt to redefine a tuple field triggers a runtime error.

All these features together are essential for avoiding long memory latencies but preserving the determinism of parallel programs. They have been introduced by the concept of I-structures as described in [Arvind et al., 1987].

The following sections provide an overview on the components of the memory unit. A more formal description of their operational behaviour can be found in Appendix A.4.

4.5.1 The Heap

Logically, the heap store is a set of tuples that consist of an arbitrary but fixed number of fields. Each field may contain an integer value or the reference to another tuple. This memory model is able to represent all of the data types in high-level (functional) programming languages, such as tuples, lists, algebraic types, etc. All storage instructions of the Adam instruction set (see Appendix 5.7) make use of this heap model.

However, in order to keep storage mangagement simple, the physical construction of the heap is somewhat different. Actually, the heap only consists of fixed-size storage cells each of which is constructed two fields, the head and the tail, and is attached with an additional tag.

Internally, a logical tuple consisting of n fields, is mapped to n − 1 physical heap cells in the following way (see Figure 4.13):

• The first n − 2 tuple fields are mapped to n − 2 heap cells. The head component of the i-th cell holds the value of the i-th tuple field while the tail component contains a pointer to the (i + 1)-th cell. These cells are marked as open in their tag fields.

• Both of the last tuple fields are mapped to a single heap cell. The head of this cell holds the (n − 1)-th field while the tail holds the n-th field. This node is marked as closed in its tag field.

As an important special case, linked lists are totally mapped to closed heap cell: each list cell contains exactly two fields that denote the first element of the list and a pointer to its rest, respectively.

In detail, a heap cell consists of the fields

Tag  Head  Tail

that are interpreted as follows:

• The head and the tail are that cell components that user-defined data may be stored into, i.e. each of them is able to hold an integer value or a tuple reference.

Figure 4.13: Mapping Tuples to Heap Cells

• The tag contains system-internal information required for a correct heap management. It is built of the following components:

Open  RefCnt  PtrHd  AvHd  PtrTl  AvTl

– The Open field holds a bit that indicates the interpretation of the cell:

∗ If Open is set, the tail field holds a pointer to the heap cell that represents next tuple field.

∗ If Open is not set, the tail field holds the next (i.e. the last) field of the tuple.

– The RefCnt field contains a measure for the number of pointers that reference the cell. Actually, this measure does not denote the number of the pointers itself but the sum of their weights. However, if that number decreases to zero, the cell can not be accessed any more and may be reclaimed. For further details on this topic, see Section 4.5.4.

– The fields PtrHd and PtrTl indicate whether the contents of the cell’s head respectively tail have to be interpreted as integer values or as tuple references. This information is neccessary for the correct the garbage collection mechanism used (also see Section 4.5.4).

– The fields AvHd and AvHd indicate whether the contents of the cell’s head respectively tail have already been written or not. Depending on this fact, incoming read requests may be fulfilled or have to be deferred (see Section 4.5.2).

4.5.2 The Request Stores

The memory unit receives two kind of requests that are distributed among different kind of request stores:

1. Expensive Requests:

A request is called expensive, if handling this request may require the allocation of additional heap cells. Obviously, allocation requests but also some other kinds of requests belong to this category.

2. Cheap Requests

All other requests are called cheap, since they differ from the first kind of requests in some essential sense: the memory manager may always handle these requests without consuming additional memory resources.

This distinction helps to manage the resources of the memory unit as efficient as possible (see Section 4.5.3). The various kinds of requests are now described in detail.

Allocation Requests

Each allocation request asks the memory manager to reserve space for a tuple of some arbitrary but fixed length. If the manager accepts this request, it allocates the required space and returns the address of the tuple to the sender of the request. If the manager is not willing to accept the request (e.g. due to lack of unused space) it forwards the request to a memory unit that is sited on one of the neighbour modules (or puts it back into its request store again).

An allocation request token is constructed as follows:

Num  Cont  Off  Mod

The fields of the token are interpreted in the following way:

• Num denotes the required number of tuple fields.

• Cont, Off and Mod are the context, the offset and the module number of the instruction to that the new tuple reference is to be sent.

If the memory manager receives an allocation request, there are two cases:

• There is enough memory left to fulfill the request, i.e. there are enough heap cells available to represent the desired tuple. In this case, the required cells are allocated and properly initialized (i.e. linked together) to represent the tuple. The address of the first cell is returned as the address of the tuple.

• There are less free heap cells available than required to fulfill the request. In this case, the manager forwards the request as a whole to that memory unit that it thinks to be least loaded among its neighbours.

As an alternative to this scheme, the memory manager might also decide to fulfill the request partially, i.e. to allocate enough memory to represent some part of a tuple and to send a request for the rest of the memory to another module. However, allocating the components of a tuple on different processors has some essential drawback: since the reference to a tuple is defined as the reference to the cell that holds its first component, read requests may have to be forwarded to other modules, which leads to an increased network traffic. In order to minimize communication overhead, this possibility is therefore not investigated furthermore.

Read Requests

A read request token consists of the fields

Num  Cont  Off  Mod  Tuple

where the various fields have the following interpretation:

• Tuple and Num denote the address of a tuple (i.e. the address of the first heap cell representing the tuple) and the number of a field within this tuple.

• Cont, Off and Mod denote the context, offset and module address of the instruction to that the contents of the tuple field are to be sent.

There are three cases that determine how a read request has to be handled:

1. The desired tuple field does not exist, i.e. the referenced tuple consists of fewer fields than required. This case indicates a severe compilation error, the generated code is in some way invalid. Hence, the memory manager terminates execution reporting the kind of error occurred.

2. The tuple component is still empty, i.e. its contents have not yet arrived in the memory unit. In this case, the tuple field actually holds the reference to a list of all those instructions that have up to now requested the contents of this field. A new heap cell holding the return address of the new read request is allocated and linked to this list.

3. The tuple component holds a valid value (integer or tuple reference). In this case, the value is packed into a message token and sent to the denoted instruction.

The second case is the reason for read requests being expensive requests. It may be neccessary to allocate a heap cell in order to store the read request in the memory unit.

Submission Requests

A submission request is the demand to interpret some storage tuple as the description of a curried function and to to submit the lifted arguments stored in the tuple to some specific activation of that function. Basically, such a request is constructed of the fields

Context  Module  Tuple

with the fields being interpreted as follows:

• Context and Module denote the address of a function activation, i.e. the address of its context and its module, respectively.

• Tuple denotes the address of the tuple representing the description of the curried function.

The description of the curried function stored in the tuple is constructed as follows:

⟨f, n⟩  arg_{1}  arg_{2}  …  arg_{i}

• The first tuple field consists of a pair ⟨f, n⟩ where f is the address of the function and n is the parameter number of the first of its i lifted arguments.

• The remaining i field hold the lambda-lifted arguments of the function, i.e. all those arguments that represent the global variables referenced in the original function body.

The memory manager handles this request by packing the lifted arguments into message tokens and sending the tokens to the denoted activation. In detail, the j^{th} argument is sent to the (n+j)^{th} instruction of the function activation. If some of the arguments have not yet defined, read requests with the appropriate result addresses are constructed and queued into the concerned fields (see Subection 4.5.2). As soon as the arguments arrive, the corresponding requests are fulfilled.

Since (parts of) the request may have to be stored in the memory unit, submission requests are expensive requests. For more details on the handling of curried functions, see Section 5.6.

Write Requests

A write request token is constructed of the components

Type  Num  Value  Tuple

with the various fields being interpreted as follows:

• Type denotes the kind of value to be written, i.e. whether it is an integer value or a tuple pointer.

• Tuple and Num denote the address of some tuple and the number of an empty field within this tuple, respectively.

• Value denotes the (integer or pointer) value that is to be written into this component.

How the write request is handled, depends on several conditions:

• If the referenced tuple field does not exist, this indicates a severe program error. In this case, the memory manager terminates execution and reports the kind of error occurred.

• If the tuple field already holds a valid datum i.e. if another write request has already been sent to this field, the single assignment rule (see Section A.4) is violated. Since this also indicates a severe program error, execution is terminated, too.

• Otherwise the tuple field has not yet been written, i.e. it contains a reference to a list of instruction addresses to that the datum is to be sent. Before the memory manager writes the datum into the tuple field, it packs it into a message token and sends copies of this token to all the denoted instructions. At last, the datum is written into the tuple field and the available bit is set.

Reference Messages

A reference message informs the memory manger that a reference to some tuple has been deleted or has to be duplicated and that therefore the reference count field of that cell has to be updated. There are three kind of reference messages

1. Increment messages,

2. Decrement messages,

3. Copy messages.

Increment messages and decrement messages are basically constructed from the fields

Type  Context  Offset  Module  Tuple

that are interpreted as follows:

• Type denotes the kind of reference request the token represents:

– \underline{IncRef} tells the manager that the pointer Tuple is to be duplicated.

– \underline{DecRef} tells the manager that the pointer Tuple is to be deleted.

• Tuple denotes a reference to some tuple whose reference count has to be updated as a consequence of the message.

• Module, Context and Offset denote the module address, the context and the offset of the instruction that has sent this request. This kind of information is only required in the case of increment requests, otherwise it is ignored.

Decrement messages have to be sent only if tuple references are explicitly deleted (which is very seldom the case). Moreover, since the garbage collection scheme uses weighted references, increment messages are only rarely necessary. Actually, this kind of message has to be only sent if a reference with weight is to be duplicated.

Copy messages are constructed from the fields

\underline{CopRef}  Field  Reference  Module  Tuple

that are interpreted as follows:

• Tuple denotes a reference to some tuple whose reference count has to be incremented.

• Field, Reference and Module denote some field of a particular tuple on some memory unit to that the result of the request is to be sent.

Copy messages have to be submitted if some read request demands the value of a tuple field that itself holds a tuple reference with minimal weight. In this case, the field must not be copied but a copy message has to be sent to the memory unit that holds the referenced tuple demanding a new reference with greater weight.

For a detailled description of the garbage collection mechanism, see Section 4.5.4.

Load Messages

A load message informs the memory manager that the load of some neighbour memory unit has significantly changed. It basically consists of the fields

Mod  MemLoad  RequLoad

with following interpretation:

• Mod is the module address of the memory unit that sent this token.

• MemLoad is the number of heap cells allocated in that unit.

• RequLoad is the number of expensive requests that wait for service in that unit.

The memory manager uses this piece of information to update an internal table that holds the load of all neighboured memory units. Depending on whether the load of this unit has increased or decreased, in the future the manager will forward less respectively more allocation requests to that unit.

4.5.3 The Memory Manager

The memory manager is responsible for the managemenot of all incoming memory requests. According to some particular strategy, it first decides which kind of request (i.e. an expensive or a cheap request) to accept and then provides the desired service.

The manager holds a local table of its load and that of all memory units sited on the neighbour modules. As soon as the load of a memory unit has significantly changed, the corresponding manager informs its “colleagues” about the new situation. This piece of information basically consists of the number of heap cells that are currently in use and the number of expensive requests waiting on that unit.

If the manager is not willing to fulfill some particular allocation request (e.g. due to lack of unused memory or if some neighbour is significantly less loaded) it uses the information stored in its load table to forward that request to one of its neighbours.

The formal definition of the memory manager’s operational semantics is given in Section A.4.

4.5.4 Garbage Collection

The Adam garbage collection mechanism uses a simple distributed reference counting algorithm that basically relies on the idea of [Bevan, 1987]. For the sake of simplicity, improvements of this algorithm as suggested in [Rudalics, 1988a] and [Rudalics, 1988b] have not yet been considered.

The major features of the algorithm are as follows:

• It is relatively simple.

• It is inherently parallel and especially suitable for distributed memory architectures.

• It is true real-time and does not require any separate garbage collection phase.

• It requires few additional messages and no synchronization between messages.

• It is relatively space efficient using only a few bits per reference.

The basic idea of this algorithm is to associate to each reference a weight and to each tuple a reference count. The algorithm always maintains the invariant

The reference count of a tuple is equal to the sum of the weights of all references to it.

This ensures that the reference count of an object is zero if and only if it is not referenced any more. In this case, the heap cells that represent the tuple may be reclaimed for further use.

In detail, the algorithm works as follows (illustrated in Figure 4.14):

• Allocating Tuples:

If a new tuple is allocated, the tuple is given some arbitrary reference count which has to be a positive integer power of two. The binary logarithm of this value is stored as a weight in the new reference that is returned as result of the allocation request.

• Deleting References:

If a storage reference is explicitly deleted, a decrement message is sent to the memory unit that holds the referenced tuple. This message contains the reference to be deleted consisting of the address of a tuple and a weight. The reference count field of the denoted tuple is decreased by the weight (i.e. by the stored value taken to the power of two). If the reference count field decreases to zero, the tuple is not referenced any more and may be reclaimed.

• Duplicating References:

If a storage reference is duplicated, both of the new references get half of the weight of the original reference. Since reference weights are always given as binary logarithms, this means to decrease the weight field by one. If the reference weight is one, the reference must not be immediately copied but an increment request has to be sent to the memory unit containing the referenced tuple. The reference count field of the tuple is increased by the maximum weight minus one (considering the weight of the old reference) and a new reference to the cell is created with its weight field set to its maximum. This reference is sent to the instruction that wishes to duplicate the reference, which may now be done as previously described. If the reference count field of the tuple already holds the maximum value that can be stored in that field, this value is considered as “infinity”. All further increment and decrement requests do not have any more effect one the reference count, i.e. the tuple will never be reclaimed.

Basically, tuple references have to be duplicated (i.e. their weight fields have to be decreased) in the following cases:

1. The tuple reference is forwarded to a parallel task but the actual process will also use the reference. In this case, the reference has to be duplicated before the reference is made accessible to the other task. This is necessary since both tasks may now make use of the reference as described in the next paragraph.

2. A READ, SUBMIT or WRITE request is to be sent to a tuple but the tuple reference has still to be used for further activities. In this case, the reference has to be duplicated before the request token is submitted. This is necessary since the reference count of the tuple has to be decreased by the weight of the reference stored in the request token. Otherwise, a tuple might be reclaimed although requests are still on the way to the tuple.

106 CHAPTER 4. THE ADAM ABSTRACT DATAFLOW MACHINE Figure 4.14: Garbage Collection A tuple reference has to be explicitly deleted (and a decrease message to be sent to the tuple) only if a process receives the reference and does neither forward it to a parallel process nor use it for any memory request. However, this is very rarely the case.

The basic difference between this approach and the original idea is the fact, that it does not make use of indirection cells as suggested in [Bevan, 1987]. Instead of this, we allow the reference count of a tuple to be increased up to some upper bound. The advantage of this method is that the allocation of additional heap cells and chains of indirection cells (slowing down tuple references) can be avoided.

The only drawback is the fact that, once the reference count of a tuple has reached its maximum, the cell will never be reclaimed any more. However, there is much statistical evidence ([Rudalics, 1988b]), that also for very small reference count fields (3 bit) more than 99% of all cells will be reclaimed.

Another disadvantage of our scheme (as of all reference counting algorithms) is the fact that circular structures are not reclaimed, since their reference counts will never decrease to zero any more. However, we are of the opinion that the simplicity of this algorithm together with its suitability for the distributed memory model justifies to ignore this particular (and not very serious) insufficiency.

Compiling the EVE Language into ADAM Code

In this chapter, we give a general introduction into the concepts used for translating the functional language Eve into machine code for the Adam architecture. The goal of this overview is not to provide an exact definition of the compilation process, but to give some intuition into its basic principles as opposed to the compilation of imperative languages for conventional architectures. A detailled description of a compiler for the Eve programming language will be published in the frame of another diploma thesis [Loidl, 1992].

5.1 Basic Compilation Steps

A compiler translating the Eve language into Adam machine code may handle the compilation process in four passes as illustrated in Figure 5.1. Several parts of this process are strongly related to the compilation of sequential programming languages as described in [Aho et al., 1986].

1. Lexical Analysis and Syntax Analysis

The first compiler pass handles the lexical analysis of an Eve source program i.e. the construction of syntax tokens (terminal symbols) from the characters of the source file. A top-down parser receives the tokens delivered from the lexical analyzer in order to perform a syntax analysis of the program and to construct the corresponding syntax tree. The contextfree grammar of Eve can be easily transformed into an equivalent LL(1) grammar, thus the parser may be implemented using the method of recursive descent.

Besides the syntax of the program, the parser has (supported by an attributed Eve grammar) to check its correct semantics, in particular it is responsible for type checking the program. Since in Eve the definition of an object must always precede its use, the correct typing of all definitions and expressions can be easily checked in a single pass.

This first compilation pass is already able to catch all syntactic and semantic programming errors and to reject invalid programs. The result of this step is the program translated into a form of enriched lambda calculus as described in [Peyton Jones, 1987]. Basically, the extensions added to the pure lambda calculus consist of the “let-in” construct for the (local) definition of named constants and functions. Moreover, for a correct delivery of the result values by the Adam master module (see Section 3.2), a type descriptor file has to be generated indicating the data type of the result.

5.1. BASIC COMPILATION STEPS 109 Figure 5.1: The Eve Compilation Process 2. Lambda-Lifting

In Eve like in most other functional programming languages, the definition of local functions are allowed that may refer to global constants defined in the enclosing environment. In particular, a local function may refer to the parameters of the outer function even if these parameters have not been passed as explicit arguments (lexical scoping).

However, in the Adam machine architecture there is no global environment. The only values that a function has access to are those that have been submitted as arguments. Before Adam code may be generated from an Eve program, the program has to be “flattened” in the sense that all local functions are lifted to the top level and all global variables used within them are passed as additional arguments.

This transformation is performed by the lambda lifting algorithm ([Johnson, 1985]) transforming lexically scoped functional programs into sets of super-combinators (i.e. functions without free variables). This super-combinator form of a program is a special case of the enriched lambda-calculus and may now serve as the basis for code generation.

3. Code Generation and Optimization

In this step, the super-combinator form of the program is translated into Adam assembly code. Basically, from the body of each super-combinator a dataflow program may be generated (as described in [Traub, 1986]) and this dataflow program may then be translated one-to-one into Adam assembly code.

However, in order to utilize the full power of the Adam facilities, a number of optimizations should be done:

(a) A strictness analysis of each super-combinator determines which parts of the function body depend on which function arguments. Those parts that successfully compute a result if and only if (several or) all arguments have become available may safely wait for the arrival of these arguments and can then be sequentially executed. However, special care has to be taken to preserve the non-strictness semantics of the Eve language: the body of a function that may return results independently of some argument must not be synchronized in advance with that particular argument.

(b) Register allocation and assignment may be optimized by avoiding those value duplications whose only purpose is to give several tasks simultaneous access to the duplicated values. If none of the tasks changes the contents of the register storing the value, the duplication is superfluous and can be removed. Moreover, registers that are no more used by one task may in certain cases be reused by other tasks. However, a correct synchronization is of essential importance for this kind of optimizations.

(c) Several other optimizations known from conventional architectures (like peephole optimization) may applied to the Adam architecture, too.

The result of this pass is a program in Adam assembly code. The code generator does not directly generate object code in order to make the result of the compilation readable for human beings such that inefficiencies can be detected and removed by more optimizations.

4. Assembly

Finally, the last pass of the compiler translates the assembly code generated by the previous compilation phase into object code that may be executed on the Adam architecture.

The following sections describe in more detail which code may be generated by the compilation of several Eve language constructs.

Figure 5.2: A Dataflow Graph

5.2 Arithmetical Expressions

Any arithmetical expression can be translated in a directed acyclic graph. The nodes of this graph represent the operations performed and its edges represent the data dependencees between the operations. An edge e from operation op_{1} to op_{2} indicates that the result value of op_{1} is an input argument to op_{2}. For example, the expression

x_{0} ∗ y_{0} + x_{1}/y_{1}

can be translated in the dataflow graph shown in Figure 5.2.

Obviously, the nodes of a dataflow graph have to be translated into Adam instructions. But how shall the data dependences be translated? In the Adam architecture, the only communication medium between two instructions is the register set of the actual activation frame. The dependency edge from ∗ to + can therefore be expressed by a register r_{0} which takes the result value of ∗ and is input to +. Obviously, ∗ is a successor instruction of +, therefore the control flow has to be routed from instruction ∗ to instruction +.

In the same way, the dependency edge from / to + can be translated by a register r_{1} that receives the result of the / operation. The control flow has also to be routed from / to +.

Since + has more than one input arguments, i.e. the result values of the operations ∗ and /, synchronization between ∗ and / is necessary. This can be expressed by inserting a WAIT instruction testing a synchronization event e_{0}. But also for the synchronization of the input arguments x_{0}, y_{0} for the ∗ operation, respectively x_{1}, y_{1} for the / operation, two WAIT instructions with synchronization events e_{1} and e_{2} are required.

Compiling the expression x_{1} ∗ y_{1} + x_{2}/y_{2} into Adam code may therefore yield the following result (the instructions 0:WAIT and 1:WAIT are the possible entry points to this piece of code, i.e. both of them have no predecessors in the code block):

0: WAIT e_{1}, +2  – Synchronization between x_{0}, y_{0}
1: WAIT e_{2}, +2  – Synchronization between x_{1}, y_{1}
2: MUL x_{0}, y_{0}, +2 – x_{0} := x_{0} ∗ y_{0}
3: DIV x_{1}, y_{1}, +1 – x_{1} := x_{1}/y_{1}
4: WAIT e_{0}, +1  – Synchronization between 2: and 3:
5: ADD x_{0}, x_{1}, +1 – x_{0} := x_{0} + x_{1}

Since the Adam machine code consists of two-address instructions, the first of the input registers also gets the result value of the operation.

However, this is not the only possibility of synchronization. The expression might also be compiled into following code block (with again instructions 0: and 1: being the entry points):

0: WAIT e_{1}, +1  – Synchronization between x_{0}, y_{0}
1: WAIT e_{2}, +2  – Synchronization between x_{1}, y_{1}
2: WAIT e_{3}, +1  – Synchronization between 0: and 1:
3: MUL x_{0}, y_{0}, +1 – x_{0} := x_{0} ∗ y_{0}
4: DIV x_{1}, y_{1}, +1 – x_{1} := x_{1}/y_{1}
5: ADD x_{0}, x_{1}, +1 – x_{0} := x_{0} + x_{1}

This kind of synchronization is also perfectly valid and generates a piece of code that is sequentially executed. This small example already shows one of the big advantages of the architecture: By seperating synchronization instructions from computational instructions we are given any freedom in choosing the optimal kind of synchronization (with respect to the tradeoff between parallelism and memory workload).

However, the use of registers and seperate synchronization instructions may also help to optimize code generation in the case that the same variable occurs in an expression more than once. E.g. a clever compiler may generate from the expression

(x + y) ∗ (x − y)

the following piece of code:

0: WAIT e_{0}, +1  – Synchronization between x, y
1: COPY z, x, +1 – z := x
2: ADD x, y, +1 – x := x + y
3: SUB z, y, +1 – z := z − y
4: MUL x, z, +1 – x := x ∗ z

saving two superfluous WAIT instructions! In pure dataflow architectures, the expression above would result in two COPY operations for each of the values x and y. However, thies only yields two parallel program paths executed on the same processor and then again makes two synchronization operations necessary.

5.3 Conditional Expressions

The correct treatment of conditional expressions is a little bit difficult in the sense that each branch has to be correctly synchronized with all parallel paths of the surrounding expression. Take, for example, the expression

if a = 0 then 1 else a + b with free variables a and b and let this expression be the body of a function f(a, b). A translation of this expression into Adam code may look as follows (in order to illustrate the control flow, statements have been attached with symbolic labels instead of numerical addresses):

…  …, r_{0}, a  – a has arrived in r_{0}
…  …, r_{1}, b  – b has arrived in r_{1}
a:  COPY  r_{2}, r_{0}, +1  – r_{2} := a
EQUALC  0, r_{2}, +1  – r_{2} := (a = 0)
SPLIT  b, +1  – Signal r_{2} available
SWITCH  r_{2}, true_{1}, false_{1} – Conditional jump
error_{1}: RETURN  r_{2}, +1  – Return ERROR
WAIT  e_{2}, term  – Wait for b and terminate
b:  WAIT  e_{1}, +1  – Wait for r_{2}
SWITCH  r_{2}, true_{2}, false_{2} – Conditional jump
error_{2}: WAIT  e_{2}, term  – Synchronize with error_{1}
true_{2}: WAIT  e_{3}, term  – Synchronize with true_{1}
false_{2}: WAIT  e_{4}, wait  – Synchronize with false_{1}
true_{1}: RETURNC  1, +1  – Return 1
WAIT  e_{3}, term  – Wait for b and terminate
false_{1}: WAIT  e_{4}, wait  – Wait for b
wait:  ADD  r_{0}, r_{1}, +1  – r_{0} := a + b
RETURN  r_{0}, term  – Return r_{0} and terminate

          term: TERMINATE

As soon as a has become available, the processor can decide which branch of the conditional to execute. The boolean value indicating the result of this decision is stored in r_{2}. However, before the branch is actually entered, a parallel path starting with b:WAIT is forked that indicates that the comparison result in r_{2} is available.

When b has arrived, b:WAIT synchronizes with the parallel path that has been forked after the comparison operation. Since the comparison result of available, the actual path can decide with which branch a synchronization is required.

In contrast to the usual conditional statements, SWITCH has three exits, one exit for each of the possible results ‘true’, ‘false’ and ‘error’, respectively.

• If a has been the error value, r_{2} also contains the error value. If this is the case, error_{1}:RETURN is executed delivering ERROR as its result. However, before the whole activation may terminate, the actual path has to wait for the arrival of b that is signalled by the event e_{3}. As soon as this synchronization has been performed, the function activation may terminate.

• If a = 0, true_{1}:RETURNC is executed returning the constant 1 as its result. Again, before the activation may terminate, the actual path has to wait for the arrival of b signalled by the event e_{3}.

• If a ≠ 0, false_{1}:WAIT performs a synchronization with the arrival of b. Afterwards, a + b is computed and the sum is returned as the result of the activation. The function activation may then be immediately terminated.

As shown above, each branch of the conditional statement has to be synchronized with each of the arguments required by any branch of the construct^{1}. While a result may be delivered by one branch even if some global arguments are missing, the whole activation must not be terminated before all missing arguments have become available.

5.4 Function Calls and Function Definitions

The compilation of a function call requires the generation of code for the following tasks:

1. Sending a request for the creation of a new function activaton,

2. Sending the function arguments to the new activation,

3. Receiving the result value of the activation.

For example, a function call f(a, b) with arguments a and b may be compiled into the following piece of code (with entry points at the addresses 0, 1 and 2):

0: …  …, +3  – Trigger 3 :
1: …  r_{0}, …, +6 – a in r_{0}
2: …  r_{1}, …, +8 – b in r_{1}
3: FASTCALL f, +1  – Request activation of f
4: COPY  r_{3}, r_{3}, +2 – Activation address in r_{3}
5: …  …, r_{4}, +8 – Result in r_{4}
6: SPLIT  +1, +4  – Parallel paths 7 : and 10 :
7: WAIT  e_{0}, +1  – Synchronize 1: and 6:
8: SEND  r_{0}, r_{3}, 0  – Send a to f as argument 0
9: JUMP  +3  – Successor 12 ::
10: WAIT  e_{1}, +2  – Synchronize 2: and 6:
11: SEND  r_{1}, r_{3}, 1  – Send b to f as argument 1
12: WAIT  e_{2}, +1  – Synchronize 9: and 11:

There are three parallel paths by that this piece of code may be simultaneously entered:

• The instruction at address 0 may be executed as soon as it becomes certain that f is to be activated. Its successor instruction is 3:FASTCALL that submits a call request token into the network.

• The instruction at address 1 may be executed as soon as the value a of the first argument of f has become available. Its successor instruction is 7:WAIT that synchronizes the path with the task calling the function f.

• The instruction at address 2 may be executed as soon as the value b of the second argument of f has become available. Its successor instruction is 10:WAIT that synchronizes the path with the task calling the function f.

The submitted call request token carries the request to activate a new instance of f and informs the processor fulfilling this request that the address of the newly created activation has to be sent to 4:COPY. In this case, the only purpose of the instruction is to tell the processor to store the activation address received into r_{3} and jump to successor 6:SPLIT. The result value of the activation

1However, it is not necessary to synchronize each path with a different event as shown in the example.

will be sent to the instruction immediately next in the program store, i.e. to the instruction at address 5.

After the activation address has been stored into r_{3}, 6:SPLIT forks program execution into two parallel paths starting at 7:WAIT and 10:WAIT. Each WAIT synchronizes with one of the tasks that have computed arguments a and b, respectively. As soon as any of these arguments has become available, it is sent to the new function activation. After both arguments have been submitted, the corresponding parallel tasks are joined by 12:WAIT.

The code block depicted above has two parallel exit paths. The one path starting at address 5 is triggered as soon as the result value of the function activation has become available. The other path is the result of joining both paths that have submitted arguments a and b, respectively. It can be used to signal to the program surrounding the code block that all instructions of the code block have been executed. This is necessary in order to determine when a function activation may be terminated.

Compiling a function definition requires the generation of code for

• Receiving the function arguments,

• Executing the body of the function,

• Returning the function’s result value,

• Terminating the function activation.

For example, if we want to compile the function definition f(a, b) with formal parameters a and b, we may generate the following piece of code:

0:  …  …, +i  – Trigger Instruction
1:  …  …, r_{0}, +j – Receive a into r_{0}
2:  …  …, r_{1}, +k – Receive b into r_{1}
…  – Body Computes Result into r_{2}
n−3: WAIT  e_{1}, +1  – Synchronize with Trigger
n−2: RETURN  r_{2}, +1  – Send Result in r_{2} to Caller
n−1: WAIT  e_{2}, +1  – Synchronize n−2: with Rest of Body
n:  TERMINATE  – Deallocate Activation Frame

The first instruction at address 0 is triggered as soon as the new activation is created, yet before any of the function arguments has arrived. This “trigger instruction” starts the execution of the code block, since it is possible that instructions of the body may be executed before all (or even any) of the arguments have arrived. Moreover, the trigger indicates that the “return address” has been delivered to the current activation, i.e. the address of the instruction to that the result of the function activation is to be sent. No RETURN statement must be executed before the result address has arrived (it is possible that function arguments arrive before the result address does, see the next section).

n−3:WAIT synchronizes the body that has computed the result with the trigger path that indicates that the result address has arrived. n−2:RETURN delivers the computed result value stored in r_{2} to the denoted instruction. Afterwards, program execution has to be synchronized with all those instructions for that it is not guaranteed that they have been executed even if a result has been computed. At last, if it is sure that no instruction in the function body is under execution any more, the activation may be terminated and its corresponding frame be deallocated.

5.5 Tail-Recursive Functions

A function is called tail-recursive, if depending on the values of the input arguments, the function computes its result either directly (without any recursive call) or returns the result of a recursive call without any change as its own result value.

Tail-recursive functions may be optimized such that the last instance of the chain of recursive function activations delivers its result directly to the caller of the top-level activation without passing the value through the intermediate chain. Besides the speedup achieved, this method has the essential advantage that each function activation may terminate and deallocate its frame, as soon as it has passed all of the arguments to the next activation.

However, since the function call mechanism is non-strict (i.e. functions may be called before their arguments have become available), it is not possible to use only one activation frame for the whole chain of recursive activations. A function instance may activate the next one, even if the arguments of this instance are still under computation. As a consequence, only those function instances are active and occupy resources, for which still useful work is to do. This feature leads to an essentially more efficient memory utilization.

Compiling a tail-recursive function call f(a, b) may yield the following code block:

0: …  …, …, +3 – Trigger 3:
1: …  r_{0}, …, +6 – Receive Argument 0 as r_{0}
2: …  r_{1}, …, +8 – Receive Argument 1 as r_{1}
3: WAIT e_{5}, +1 – Wait for Result Context
4: TAILCALL f, +1  – Request Activation of f
5: CONTEXT r_{3}, +1  – Submit own Result Context
6: SPLIT  +1, +4  – Parallel paths at 7: and 11:
7: WAIT  e_{1}, +1  – Synchronize 1: and 6:
8: SEND  r_{4}, r_{3}, 0  – Send r_{4} as a
9: JUMP  +3  – Jump to Synchronization
10: WAIT  e_{2}, +2  – Synchronize 2: and 6:
11: SEND  r_{5}, r_{3}, 1  – Send r_{5} as b
12: WAIT  e_{3}, +1  – Synchronize 9: and 11:

Similiar to standard function calls presented in the previous section, this code block has three entry paths indicating the arrival of arguments a and b and triggering the function call, respectively.

The essential difference is denoted by the instructions 4:TAILCALL and 5:CONTEXT. In contrast to FASTCALL, TAILCALL submits a call request without telling the new activation where to return the result value to. CONTEXT receives the address of the new activation and submits the result address of the actual activation to the new one. By this, the result value will be directly sent to the caller of the actual activation, i.e. the actual function does not receive any result value! The previous synchronization of 4:WAIT with the actual result address guarantees that the submitted result address is well-defined.

The optimization described in this section is not restricted to tail-recursive functions. It may be applied in all cases in that the result of a function call is, without any change, the result of another function call. Therefore, it may in many cases help to generate more efficient programs with respect to time and, in particular, space.

5.6 Curried Functions

The Adam architecture also supports the use of curried functions, i.e. functions that are passed as function arguments or returned as function values. A curried function is a i + 1 tuple on the heap consisting of the fields

⟨f, n⟩  arg_{1}  arg_{2}  …  arg_{i}

that have the following interpretation:

• The first field consists of a pair ⟨f, n⟩, where f is the address of the function (i.e. of its code block) and n is the parameter number of the first of the i fixed arguments.

• The remaining i fields hold the lifted arguments of the function, i.e. all those arguments that are submitted independent of the actual function call.

The curried function is now represented by a pointer that references this tuple. This pointer may be handled lie any atomic value, in particular it may be passed as argument or returned as result value of a function. If a function call f(a, b) with a curried function f and arguments a and b is compiled, the following piece of code may be generated:

0: …  …, r_{0}, +3 – Tuple reference in r_{0}
1: …  …, r_{1}, +7 – Argument a in r_{1}
2: …  …, r_{2}, +9 – Argument b in r_{2}
3: READ r_{0}, 0, +1 – Read First Tuple Field ⟨f, n⟩
4: APPLY r_{3}, +1  – ⟨f, n⟩ in r_{3}, Request Activation
5: SUBMIT r_{0}, r_{4}, +2 – Submit Lifted Arguments
6: …  …, r_{5}, +6 – Result value in r_{5}
7: SPLIT +1, +3  – Successors 8: and 10:
8: WAIT  e_{0}, +1  – Synchronization 1: and 7:
9: SEND  r_{0}, r_{3}, 0  – Send a as Argument 0
10: JUMP  – Jump to Successor Instruction
11: WAIT  e_{1}, +2  – Synchronization 2: and 7:
12: SEND  r_{1}, r_{3}, 1  – Send b as Argument 1

Before the actual activation of f can be done, 3:READ has to fetch the code block address of f (stored in the first field of the referenced tuple) from the heap. Then 4:APPLY requests the allocation of a new activation frame for f. As soon as the address of the new activation has arrived, 5:SUBMIT triggers the submission of the fixed arguments stored in the referenced tuple on the heap. At last, 9:SEND and 12:SEND submit the actual function arguments.

The activation of curried functions only requires three more network messages than the activation of functions that are already fixed at compile time: two for demanding and receiving the description of the function from the heap (READ) and one for triggering the submission of the lambda-lifted arguments stored on the heap (SUBMIT). In all other matters, the use of curried functions is not essentially different.

5.7 Operations on Data Structures

Basically, there are two operations on compound data structures:

1. Construction of a Structure,

2. Selection of a Structure Component.

In Eve, there are several compound data types (lists, strings, tuples, unions). However, if is not difficult to realize how these types can be mapped to the tuple concept available on the level of the Adam architecture.

Basically, the selection of a tuple component by a READ statement can be done without any problems. However, one has consider that READ is a blocking operation and must be done in a separate parallel process in order to preserve the non-strictness of the Eve language.

While the selection of a tuple field is rather simple, the construction of a tuple is more complicated. In Eve a data structure can only be constructed as a whole, i.e. all components of the structure must be defined. Consider e.g. the expression

a :: b

constructing a list cell with head a and tail b and let this expression be the body of a function f(a, b). While in the high-level language Eve the allocation of a list cell and the definition of its components are indivisible, they are strongly separated on the level of the Adam architecture:

…  …, alloc  – Trigger allocation
…  …, x0, wrHd – a arrived in r_{0}
…  …, x1, wrTl – b arrived in r_{1}
alloc: ALLOC  2, +1  – Allocate list cell
COPYP  r_{2}, r_{2}, +1  – Reference in r_{2}, half weight
RETURN  x2, +1  – Return cell reference
COPYP  x2, x2, +1  – Half weight of r_{2}
SPLIT  wrHd, wrTl  – Write head and tail in parallel
wrHd: WAIT  e_{1}, +1  – Wait for a and cell
WRITEI  r_{0}, r_{2}, 0  – Head r_{2} := r_{0}
WAIT  e_{3}, term  – Join with tail path
wrTl: WAIT  e_{2}, +1  – Wait for b and cell
WRITEP  r_{1}, x2, 1  – Tail r_{2} := r_{1}
WAIT  e_{3}, term  – Join with head path
term: TERMINATE  – Terminate activation

As soon as the body of the function is activated (i.e. the result context has arrived), the new list cell may be allocated and its reference be written into register r_{2}. However, before this reference may be delivered as the result of the activation, its weight has to be divided by 2, which is done by the COPYP instruction (for details on the garbage collection process, see Section 4.5.4). The reference with the updated weight is returned to the caller of the activation. Since the reference will be used by two independent WRITE operations, its weight has again to be subdivided. Afterwards, the actual task is split into two parallel paths, each of which is to be synchronized with the arrival of the contents of the list cell’s head and tail, respectively.

Each of the pathes triggered by the arrival of the values a and b has to be synchronized with the allocation of the list cell. Then the values (an integer atom and a list pointer) are simultaneously written into the corresponding field (head or tail) of the cell. Afterwards, both pathes join together and the activation may terminate.

For the preservation of the Eve non-strictness properties, it is absolutely necessary that the head and tail of a list cell are written in parallel. If the contents of one field is already available but the other field has not yet been defined, a process referring the already defined field must not be blocked. However, in this case any sequentialization of the writing processes might will prevent one field to be written because the contents of the other field have not yet arrived!

The ADAM Implementation and Evaluation

This chapter contains a general overview on the prototype implementation of the Adam on a conventional parallel machine. The basic features and restrictions of this implementation are sketched and the performance and speedup of several parallel benchmark programs are presented. The full source code of this prototype implementation is given in Appendix C.

6.1 The ADAM Implementation

The Adam abstract machine has been especially designed for an efficient simulation on conventional parallel computers. A prototype implementation of an Adam interpreter has been written for the IMPULS 2400, a transputer-based multiprocessor installed at RISC-Linz. The machine consists of 16 nodes each of which is equipped with an Inmos T800 32-bit processor with a performance of about 10 Mips ([Inmos, 1988b]) and 4 MB of local RAM. Communication between processors is provided by bidirectional serial links at a speed of about 20 Mbit/s. The topology of the machine may be dynamically configured before the start of program execution.

The Adam interpreter is totally written in Occam 2 ([Inmos, 1988a]) and is organized in such a way that each transputer simulates one Adam module. The major components of a module (arbiter, processor unit and memory unit) are implemented by parallel processes sited on the corresponding transputer. The channels between Adam modules correspond to links between transputers where each bidirectional link is able to represent two unidirectional channels. Since each transputer has four links, only Adam topologies are possible where each module is connected to at most four neighbours.

The only part of the Eve compiler that has been implemented up to now is the final pass generating Adam object code from Adam assembly language. This part has been (and the whole compiler will be) written in TopSpeed Modula-2 ([Jensen, 1988]) and is operational on a DOS based PC/AT with an 80386 processor. With the help of this assembly program, the user may now manually create Adam assembly code that is translated into object code and executed by the Adam interpreter.

Currently, the Adam&Eve system can be used as illustrated in Figure 6.1 (the following figures describe each step with the help of a sample program for fibonacci number generation):

1. Program 120 CHAPTER 6. THE ADAM IMPLEMENTATION AND EVALUATION

Figure 6.1: Current State of the Adam&Eve System A text file is written holding the Adam assembly code for each user-defined function of a program (see Figure 6.2). The assembly code may contain symbolic function names, operation codes and jump labels, comments and other syntactic sugar for a simplified program development. The assembly program processes this file, resolves the symbolic references and creates an Adam object file, the program file.

2. Expression A text file is written holding the Adam assembly code for the expression to be evaluated within the scope of a program (see Figure 6.3). This assembly code may refer to the functions defined in the program file described in the previous paragraph. The assembly program processes this file, resolves the symbolic references and creates an Adam object file, the expression file. The seperation between program file and expression file allows to submit several expressions for evaluation without each time reloading the basically same program to the transputer network.

3. Type A text file is written describing the type of the result computed by the expression described above (see Figure 6.4). This type descriptor file allows in a simple way the (recursive) definition of any type that may be expressed in an Eve program. It is used to direct the result generator during the execution of the program as described below.

4. Execution Program file, expression file and type descriptor file are submitted to the Adam interpreter running on the transputer system. This is done, according to an interpreter option, either in interactive or in batch mode. The interpreter distributes program file and expression file over all transputers in the network and submits the type descriptor file to the result generator on the master module. The generator triggers the start of the execution and, directed by the type descriptor, demands the components of the result from the network. The result is, simultaneously with program execution, printed to the screen and written into a log file (see Figure 6.5).

5. Statistics Depending on an interpreter option, some kind of runtime statistics may be generated and written into a file (see Figure 6.6). This statistics illustrates the utilization of each processor unit and each memory unit of the network. It may be used in order to learn about the dynamic behaviour of the program and to detect insufficiencies and bottlenecks of the implementation.

In the final system, the user will of course be freed from these low-level details. He will submit an Eve source file to the compiler that will generate the Adam program file. An dialogue shell will interactively allow the user to enter expressions to be evaluated in the scope of this program. From these expressions, the compiler will generate expression files and type descriptor files that will be successively submitted to the Adam interpreter that will evaluate them and print the results to the screen.

The following subsections provide some internal details of the implementation, in particular the construction of tokens, stores, etc.

6.1.1 Tokens

Each kind of token in the Adam has equal length of 11 bytes. The first byte is reserved for the token’s address field (an Adam network may therefore consist of up to 256 modules), the following one for its type field. The remaining 9 bytes contain the piece of information the token is carrying and are structured as shown in Figure 6.7.

122 CHAPTER 6. THE ADAM IMPLEMENTATION AND EVALUATION

PROGRAM Fibonacci
EXPORT Fib                       ; function Fib
; ---------------------------------------------------------------------------
; Fib[n]: INT[INT] ==
;   IF n <= 1
;     THEN 1
;     ELSE Fib[n-1] + Fib[n-2]
; ---------------------------------------------------------------------------
FUNCTION Fib(3,4)                ; maximum event and register number
        JUMP      start          ; start trigger path
        COPYI     1, 2, start    ; receive n in r1 and r2
start:  WAIT      0, +1          ; synchronize trigger with n
        COPYI     3, 2, +1       ; r3 := n
        LESSEQC   1, 3, +1       ; r3 := n <= 1
        SWITCH    3, true, false ; if n <= 1 then true else false
error:  RETURN    3, term        ; return ERROR and terminate
true:   RETURNC   1, term        ; return 1 and terminate
false:  SPLIT     fib1, fib2     ; compute Fib[n-1] and Fib[n-2] in parallel
fib1:   SUBC      1, 1, +1       ; r1 := r1-1
        FASTCALL  Fib, +1        ; call Fib
        COPYI     3, 3, send1    ; r3 := address Fib
        COPYI     1, 1, add      ; r1 := result Fib
send1:  SEND      1, 3, 1        ; send r1 to r3 as n
        WAIT      1, end         ; synchronize with send2
fib2:   SUBC      2, 2, +1       ; r2 := r2-2
        FASTCALL  Fib, +1        ; call Fib
        COPYI     4, 4, send2    ; r4 := address Fib
        COPYI     2, 2, add      ; r2 := result Fib
send2:  SEND      2, 4, 1        ; send r2 to r4 as n
        WAIT      1, end         ; synchronize with send1
add:    WAIT      2, +1          ; wait for r1 and r2
        ADD       1, 2, +1       ; r1 := r1 + r2
        RETURN    1, end         ; return r1
end:    WAIT      3, term        ; synchronize add with send1/2
term:   TERMINATE                ; terminate activation
END Fib
END Fibonacci

Figure 6.2: Fibonacci Program Source File 6.1. THE ADAM IMPLEMENTATION 123

; ---------------------------------------------------------------------------
;
; Fib21.ASE                                               Wolfgang Schreiner
;                                                         (c) RISC-Linz 1990
; Compute the 21st Fibonacci Number
;
; ---------------------------------------------------------------------------
EXPRESSION Fib21
IMPORT Fib              ; function Fib
BEGIN (2,3)             ; maximum event and register number
    TAILCALL  FIB, +1   ; tail-recursive call of Fib
    CONTEXT   0, +1;    ; address of Fib in r0, submit own context
    SENDC     21, 0, 1  ; submit 21 to Fib
    TERMINATE           ; terminate activation
END Fib21

Figure 6.3: Fibonacci Expression Source File

; ---------------------------------------------------------------------------
;
; Fib21.ADT                                               Wolfgang Schreiner
;                                                         (c) RISC-Linz 1990
; Type Description for Fibonacci Number Generator
;
; ---------------------------------------------------------------------------
; root line
#0: 1       ; activate function 0, main type is labeled with 1
; main type
 1: I       ; main type is INT

Figure 6.4: Fibonacci Expression Type File

This is ADAM V1.0 (16 processors used).
Enter name of program file (.ADM): FIBONACC
Enter name of expression file (.ADE): FIB21
(\USR\SCHREINER\ADTEST\FIBONACC.ADM)(\USR\SCHREINER\ADTEST\FIB21.ADE)(\USR\SC
HREINER\ADTEST\FIB21.ADT)
Program started.
17711
Result received (9.8 s).
Program terminated (9.8 s).
Another Expression (y/n)? n
Another Program (y/n)? n
Bye.

Figure 6.5: Fibonacci Execution Log File

6.1.2 Program Store

The program store consists of 32-bit words each of which may be interpreted as an

• Instruction,

• Code Block Address,

• Resource Descriptor,

• Long Constant.

These types of program words are now described in greater detail (see Figure 6.8):

• Instructions:

The most significant byte of the word holds the operation code of the instruction, the operand word of the instruction is sited in the remaining three bytes. The various fields of an operand word (see Section A.3.2) are always aligned to byte boundaries, which has the following consequences:

– Register Descriptors occupy 1 byte, i.e. a function activation may address at most 256 registers.

– Instruction Offsets occupy 1 byte, i.e. the distance between an instruction and its successor may be at most 256 words^{1}.

– Small Constants occupy 1 byte, i.e. they may represent values between −128 to +127.

– Event Descriptors occupy 2 bytes, i.e. a function activation may use at most 65535 synchronization events.

– Function Descriptors occupy 2 bytes, i.e. a program may consist of maximal 65535 code blocks.

• Code Block Addresses:

These addresses occupy a whole 32-bit word. Hence, programs may consist of up to 2^{32} − 1 instruction words.

1Longer Jumps can be done by the JUMP instruction (see Section B.2.4).

6.1. THE ADAM IMPLEMENTATION 125

Proc  Frame Store Utilization             Request Store Utilization
-----------------------------------------------------------------------------
P8    4.4 *                               5E-02
P8    5.4 **                                0.2
P8    6.7 **                              5E-02
P8    7.5 **                                0.1
P2    3.5 *                               1E-01
P2    3.9 *                               2E-02
P3   15.8 *****                            10.7 ***
P8    8.4 ***                             2E-02
P8   10.3 ***                             1E-01
P8   11.5 ***                               0.9
P8   12.8 ****                              1.6
P8   14.2 ****                              2.6 *
P8   15.8 *****                             4.0 *
P4    2.3 *                                12.3 ****
P8   17.6 *****                             5.0 **
P8   19.5 ******                            6.0 **
P8   21.7 *******                           7.0 **
P8   24.1 *******                           9.0 ***
P2    4.4 *                               2E-02
P4    2.6 *                                14.5 ****
P4    2.9 *                                18.3 *****
P4    3.2 *                                21.0 ******
P4    3.5 *                                23.1 *******
P4    3.9 *                                24.5 *******
P2    3.9 *                               2E-02
P2    3.5 *                                 0.5
P5    9.3 ***                             2E-02
P2    3.9 *                                 6.8 **
P2    4.4 *                                 8.4 ***
P4    4.4 *                                23.9 *******
P2    4.9 *                                 9.7 ***
P2    5.4 **                                8.1 **
P2    6.1 **                                6.6 **
P6    4.4 *                                 7.9 **
P2    6.7 **                                4.1 *
P2    7.5 **                                0.8
P4    4.9 *                                30.0 *********
P4    5.4 **                               31.1 *********
P7    5.4 **                                2.1 *
P7    6.1 **                                3.0 *
P7    6.7 **                                3.7 *
P4    6.1 **                               35.1 ***********
P7    7.5 **                                1.1
P4    6.7 **                               33.3 **********
P7    8.4 ***                             2E-02
P4    7.5 **                               31.3 *********
P4    8.4 ***                              29.6 *********
P7    9.3 ***                             2E-02

Figure 6.6: Fibonacci Execution Statistics (Excerpt)

126 CHAPTER 6. THE ADAM IMPLEMENTATION AND EVALUATION

Value Message dMod \underline{mess} resvd

Context  Offset

Value

87 80  72  64  48  32  0

Context Message dMod \underline{cntxt} resvd

Context  Offset

Mod resvd dContext
87 80  72  64  48  32  24  16  0

Call Request dMod

\underline{fCall} \underline{tCall}

resvd

Context  reserved

Mod resvd Function
87 80  72  64  48  32  24  16  0

Submission Request dMod

\underline{submit}

resvd

Context  Offset

rMod TupleAddress
87 80  72  64  48  32  24  0

Allocation Request dMod \underline{alloc} Num

Context  Offset

rMod  reserved
87 80  72  64  48  32  24  0

Read Request dMod \underline{read} Num

Context  Offset

rMod TupleAddress
87 80  72  64  48  32  24  0

Write Request dMod

\underline{wrInt} \underline{wrPtr}

Num

Value

resvd  TupleAddr
87 80  72  64  32  24  0

Reference Message

dMod

\underline{incRef} \underline{decRef}

resvd

Context  Offset

rMod TupleAddress
87 80  72  64  48  32  24  0

Copy Message dMod

\underline{copRef}

resvd

Num  rTupleAddress

rMod TupleAddress
87 80  72  64  56  32  24  0
Load Message  dMod \underline{pLoad} \underline{mLoad} rMod  Value1  Value2
87 80  72  64  32  0
Stop Request  dMod \underline{stop} Mod  reserved  reserved
87 80  72  64  32  0

Figure 6.7: Token Formats

Instruction

OpC Op1 Op2 Op3
31 24 16  8  0

Code Block Address  CodeBase
31  0

Resource Descriptor

reserved Evts Regs
31  16  8  0

Long Constant  Integer Atom
31  0

Figure 6.8: Program Store Format

• Resource Descriptors:

The least significant byte of a resource descriptor holds the number of registers that each activation of this function requires. The next byte represents the number of of words required for each activation frame to represent the synchronization events used.

• Long Constants:

A long constant occupies a full word and can therefore represent an integer atom (see Section 6.1.6).

6.1.3 Frame Store

The frame store holds a number of activation frames that consist of 32-bit words, where each word is able to represent a

• Register,

• Result Address,

• Result Module,

• Set of Synchronization Bits.

These types of frame word are now described in detail (see Figure 6.10):

• Register:

A register is able to hold an atomic value, i.e. basically an integer atom or a tuple reference (see Section 6.1.6).

32 bit

Synchron. Bits  ⋯

32 bit

Result Module

reserved  Mod

Result Address

Context  Offset

Atomic Value

Registers  ⋯

Atomic Value

31  16  8  0

Figure 6.9: Activation Frame Format

• Result Address:

The more significant halfword of the result address holds a reference to the context that describes the activation to which the result value of the actual activation is to be delivered. The less significant halfword hold the address of the instruction within that activation that shall receive the result.

• Result Module:

The address of the module to which the result value is to be sent is stored in the least significant byte of the word.

• Synchronization Bits:

Each word is able to hold 32 synchronization bits each of which may be tested and inverted with the WAIT instruction (see Section B.2.5).

6.1.4 Context Store

The context store consists of 64-bit double words each of which represents a context, i.e. the description of a particular function activation (see Figure 6.10):

The less significant 32-bit word of the context holds the base adress of the function activation’s code block, the more significant word holds the base address of its frame. The frame store as well as the program store may therefore consist of maximal 2^{32} − 1 words.

FrameBase  CodeBase
63  32  0

Figure 6.10: Context Format

Context  Offset
31  16  0

Figure 6.11: Trigger Format

6.1.5 Trigger Store

The trigger store consists of 32-bit words each of which represents a trigger, i.e. the description of an executable task (see Figure 6.11):

The more signifcant 16-bit halfword of the trigger holds the number of the context describing the activation in which the task may be executed. There may therefore exist up to 2^{1}6 concurrent activations on a processor unit.

The less significant halfword of the trigger holds the address of the instruction within the code block of the denoted activation. A code block may therefore consist of maximal 2^{1}6 instructions.

6.1.6 Atomic Values

An atomic value occupies a 32-bit word where the least significant bit always holds an error flag indicating whether the contents of the word are valid or not. The remaining 31-bit may be interpreted as an

• Integer Atom,

• Tuple Reference,

• Activation Address,

• Curried Function.

These types of atomic values are constructed as follows (see Figure 6.12):

• Integer Atom:

The 31-bit data part of an atomic value is able to represent integer values in the range from −2^{30} to +2^{30} − 1.

Integer Atom  Value  {0 \atop 1}
32  0

Tuple Reference

Mod TupleAddr rw {0 \atop 1}
32 24  3 1 0

Activation Adress

rsvd Mod Context {0 \atop 1}
31 25 17  1 0

Curried Function

rsvd Num FunDescr {0 \atop 1}
31 25 17  1 0

Figure 6.12: Atomic Values Format

• Tuple Reference:

Both of the least significant bits of the data part hold the weight of the reference. The weight is therefore in the range from 2^{0} = 1 to 2^{4} = 16. The following 21 bits hold the actual address of the referenced tuple. The local heap of each memory unit may therefore contain up to 2^{21} cells. The most significant byte of the word holds the address of the module on which the referenced tuple is is sited.

• Activation Address:

The least significant halfword of the data field holds the context number of the activation. The following byte holds the address of the module on which the activation has been created.

• Curried Function:

The less significant halfword of the data field holds the description of the curried function. The following byte holds the number of the function parameter that the first of the curried arguments corresponds to.

6.1.7 Heap Cells

The heap store of a memory unit consists of 80-bit cells. Figure 6.13 depicts the construction of such a cell:

• Both of the two less significant 32-bit words represent the head and tail data fields.

• The most significant 16-bit halfword represents the tag of the cell carrying system-internal information:

– The more significant byte of the halfword holds the reference count of the tuple. Hence, a maximal reference count of 256 can be represented.

Heap Cell  Tag  Head  Tail
79  64  32  0

Tag

RefCnt

rsvd Ty AH PH AT PT
13  8  0

Deferred Read

resvd Mod

Context  Offset

NextCell

79 72  64  48  32  0

Figure 6.13: Heap Cell Format

– The least significant two bits of the tag hold system information on the tail field, i.e. the available flag and the pointer flag of the tail.

– The following two bits hold system information on the head field, i.e. the available flag and the pointer flag of the head.

– The next bit holds the type flag of the cell. This bit indicates whether the cell is open (i.e. has a successor reference stored in the tail) or closed (i.e. has some user datum stored in the tail).

Deferred read requests are stored in a heap cell as illustrated in Figure 6.13:

• The module address of the instruction to that the demanded value is to be delivered is stored in the less significant byte of the tag field.

• The context and the offset of the instruction are stored in two consecutive halfwords of the head field.

• The pointer to the next of the list of deferred read requests is stored in the tail field of the heap cell.

6.1.8 Summary of Implementation Restrictions

Table 6.14 gives a summary of several restrictions imposed by the current implementation of Adam on our transputer network.

6.2 The ADAM Evaluation

A variety of Adam assembly programs has been written in order to investigate the following criteria of our prototype implementation:

1. the correctness of the interpreter,

2. its absolute performance,

Number of Modules  ≤ 256
Length of Program Store ≤ 2^{32}
Length of Activation Store ≤ 2^{32}
Length of Context Store  ≤ 65536
Number of Code Blocks  ≤ 65536
Length of Code Blocks  ≤ 65536
Integer Values i  −2^{30} ≤ i ≤ +2^{30} − 1
Small Constants c  −128 ≤ c ≤ 127
Distance to Successor  ≤ 256
Registers per Activation  ≤ 256
Events per Activation  ≤ 65536
Heap Cells per Module  ≤ 2^{21}
Pointer Reference Weight ≤ 2^{4}
Tuple Reference Count  ≤ 256

Figure 6.14: Implementation Restrictions

3. the relative speedups achieved.

1. Correctness

We have been able to debug the prototype of our interpreter in a relatively short time. It is now such stable that new errors can only very rarely be detected. However, many more (and also larger) programs have to be written in order to test all features of the implementation more thoroughly. One major weakness known but not yet debugged is the fact that under certain circumstances the interpreter may, after the delivery of the program result, run into a deadlock when it tries to terminate all processors of the network.

2. Performance

Our main concern in the implementation of the interpreter has been a simple and clear program design suitable for further debugging and maintenance. Hence, we have attached importance only to the optimization of a few critical parts of the program, e.g. the execution unit of the Adam processor. However, the overall performance of the system is surprisingly high:

At RISC-Linz, we have also installed a commercial transputer implementation of Strand88, a parallel language which includes several principles of dataflow programming ([Foster and Taylor, 1989]). First comparisons with this implementation show that the average execution time of a Strand88 program is about 50% of a comparable Adam program. However, in the case of an extensive use of non-strict data structures (like the difference lists mentioned below), Adam beats Strand88 with a factor of more than 10!

3. Speedup

Several parallel benchmark programs have been written and show rather promising speedups. Among them, there are the programs

• Fibo: The doubly recursive fibonacci number generator presented in Section 6.1.

• Primes0: A prime number generator using a lot of intermediate lists (described in Section 2.4.1).

• Primes1: A prime number generator using difference lists (described in Section 2.4.2).

1T / 2W 2T / 2W 4T / 4W  8T / 8W  16T / 16W
Program Input  Ring  Ring  Full  Chordal Ring  Torus
Fibo  20  54(1.0)  28(1.9)  16(3.3)  10(5.4)  7(7.7)
Primes0 2000 302(1.0) 163(1.8) 93(3.2)  59(5.1)  41(7.4)
Primes1 10000 237(1.0) 127(1.9) 88(2.7)  65(3.7)  51(4.6)

Table 6.1: Adam Benchmarks

Some of the results are given in Table 6.1. Each column of the table represents one particular processor configuration with the number of transputers (T) used, the number of worker modules (W) simulated and the topology of the transputer network listed above. Each item in the table denotes the elapsed time in seconds, the values in parentheses show the relative speedups achieved.

The efficiency (speedup/processor ratio) ranges in both of the first test programs from more than 90% to about 50% in the worst case. However, these results have been gained with varying topologies. They may by somewhat misleading since we do not yet know which topology shows the best properties for the Adam architecture.

The third test program, the prime number generator Primes1, is in absolute terms much faster than the Primes0 program. This is not very surprising since Primes0 is extremely slowed down by allocating a lot of superfluous auxilary lists. However, the relative speedups achieved by Primes1 are signifcantly lower than for Primes1. One of the main reasons may perhaps be the fact that all processors access the same list of primes. Those processors on that the first five or six elements of this list are stored, have to fulfill many more read requests and are therefore much more loaded than the other processors. However, we still have to investigate this effect more thoroughly.

First investigations have shown that an effective load balancing is currently one of the main problems of the Adam architecture. The tools used up to now are too rough to keep the overall load of the network on an equal level. Many inpredictible ups and downs in the workload of each processor cause a lot of superfluous network traffic since too many activation requests are sent to neighbour processors (instead of handled by the actual processor). This property often results in extreme deviations in the runtime of the same program (up to 100%!). As an extreme example, the computation of the 20^{th} fibonacci number once took more time than the computation of the 21^{th} one! Thus, there remains a lot of work to be done in order to achieve better balancing strategies. However, the difficulty of load balancing is a general feature of all true dataflow architectures, too.

In the near future, we will exhaustively test the Adam concept using various processor configurations in order to learn more about the dynamical behaviour of the machine (which is many aspects still rather mysterious to us). We will investigate several methods for load balancing and will try to improve the overall performance of the system by removing some existing bottlenecks.

The project Adam&Eve is continued by another diploma thesis ([Loidl, 1992]) dealing with the implemenation of a compiler for the Eve language. In the frame of this work, we will also experiment with several other parallelelization and synchronization strategies than the pure dataflow principle. Such experiments are possible because parallelization and synchronization are handled by the code generation phase of the compiler and are not fixed in the Adam architecture itself. We are confidential that a lot of interesting results will be gained in the future.

Appendix A

The ADAM Operational Behaviour

We define the operational semantics of the Adam machine by an algorithm that describes the behaviour of each component of an Adam worker module. In particular, the semantics of a processor unit is defined by an interpreter that executes Adam machine code. We believe that this form of presentation may be adequate for two reasons:

1. While the main concern of the semantics definition of an abstract machine is of course its static behaviour (i.e. the mapping of programs to the computed result values), we feel that also the dynamical behaviour of the machine is of particular interest. However, this runtime behaviour is more adequately described by an algorithm than by some form of static definition (as in denotational semantics).

2. The algorithm may serve as the basis for an implementation of the machine, either in hardware or in software. Actually, it already defines the main parts of an interpreter simulating the Adam machine on some kind of conventional computer. Since this is one of our preliminary goals, the algorithmic definition may help to save time and effort and, more important, to preserve correctness of the implementation.

Actually, we have first defined the operational behaviour of the machine by the algorithms presented in the following subsections and then implemented a prototype of Adam by the use of this algorithmic definition. Even if some details have been implemented in a slightly different (i.e. more efficient but semantically equivalent) way, this description has proved to be a valuable tool.

A.1 Notation

Before we start to describe the operational behaviour of the machine, we will give a short introduction into the notation that we use to describe the algorithms in the following sections.

The algorithms are presented in a Pascal-like syntax where following typesetting conventions have been obeyed:

• Keywords are written in boldface style in all lower case (e.g. case).

• Functions appear in roman style with upper case initials (e.g. ‘Execute’). If the function is not refined any more but handled as a “black box”, then it is written in italic style (e.g. AllocFrame).

• Constants appear in underlined style, enumeration type constants in all lower case (e.g. \underline{incRef}), other constants with upper case initials (e.g. \underline{LinkNum}).

• Types appear in Roman Style with upper case initials (e.g. ‘Word’).

• Variables appear in Roman Style in all lower case (e.g. ‘offset’).

These conventions will also be used in the ordinary text explaining the algorithms. However, in this situtation, functions, types and variables are enclosed by single quotes to distinguish the “metalevel” identifers from “object-level” text (e.g. the variable ‘offset’ as opposed to an instruction offset).

Besides the usual Pascal-like constructs the following additional language constructs will be used:

• Record Types are construced by

Type_{1} × … × Type_{n}

with Type_{i} being the type of the i^{th} component of the record.

• Record Values are constructed by

⟨Value_{1}, …, Value_{n}⟩

with Value_{i} being the i^{th} component of the record.

• Union Types are constructed by

Type_{1} | … | Type_{n}

with Type_{i} being the type of the i^{th} variant of the union.

• Type Coercions are denoted by

Value:Type

with the meaning that the object Value is in some sensible way^{1} converted into an object of type Type.

• The Token is used for two purposes:

– On the left hand side of assignment statements, such as in

⟨var_{1}, var_{2}, ⟩ := ⟨val_{1}, val_{2}, val_{3}⟩

it indicates that the value of the corresponding tuple field (in this case ‘val_{3}’) is not required.

– On the right hand side of assignment statements, such as in

⟨var_{1}, var_{2}, var_{3}⟩ := ⟨val_{1}, val_{2}, ⟩

it indicates that the value of the corresponding tuple field (in this case ‘var_{3}’) is not changed.

type

Number = Unbounded Integer Number

Boolean = (\underline{true}, \underline{false}); (* Logical Values *)

Bit = (\underline{zero}, \underline{one}); (* Basic Storage Unit *)

Bits(n) = array [0 … n−1] of Bit; (* BitField *)

Figure A.1: Basic Types

Figure A.1 shows the basic types that are used in the following sections:

• ‘Number’ denotes the set of all (positive and negative) integer numbers. These numbers are not restricted by any bound. If they are used in numerical computations, no overflow may occur.

• ‘Boolean’ denotes the set of the logical truth values \underline{true} and \underline{false}. The usual logical connectives are defined on that type.

• ‘Bit’ denotes the basic unit of storage. In contrast to ‘Boolean’, no operations are predefined on that type.

• ‘Bit(n)’ defines a generic type that depending on the value of the number ‘n’ denotes a storage field of ‘n’ Bits. All storage classes in the Adam architecture will be derived from this type.

A.2 The Arbiter

Network Quantities

Figure A.2 shows the definition of some network quantities that are globally used in an Adam module. They describe the interface between various modules and between the units of a module.

• \underline{ModNum} is the total number of all modules in the network (exclusively the master module), i.e. all valid module addresses are in the range 0 to \underline{ModNum} (the master has address 0). \underline{LnkNum} denotes the number of modules that the local module is directly connected to (i.e. the number of the neighbour modules). \underline{ActMod} is the address of the actual worker module considered. All of these “constants” have to be initially defined during the setup of the network.

• \underline{NetWdLen} and \underline{ModWdLen} define the bit length of each word transmitted by ‘external” links (i.e. by links between different modules) and by “internal” links (i.e. by links from the arbiter to the local processor unit and memory unit, respectively).

The following type definitions use these constants to define some corresponding address and link types as well as the structure of tokens transmitted by these links:

1How these coercions are handled is of little importance for the logical behaviour of the machine and for the moment neglected. Chapter 6 describes how these low-level facilities are handled in our prototype implementation.

A.2. THE ARBITER 137

const

\underline{LinkNum} = Number of Modules the Actual Module is Connected to; \underline{ModNum} = Number of Modules in the Network (exclusively Master); \underline{ActMod} = Address of the Actual Module Considered;

\underline{NetWdLen} = Length of Words Transmitted by Network; \underline{ModWdLen} = Length of Words Transmitted between Units within a Module;

type

NetLink = Bits(\underline{NetWdLen}); (* Link between Modules *) ModLink = Bits(\underline{ModWdLen}); (* Link to Processor/Memory Unit *)

ModAddr = [0..\underline{ModNum}]; (* Module Addresses *) LinkAddr = [1..\underline{LinkNum}]; (* Links leaving the Module *)

(* Network Tokens *) Token = ModAddr × TokenType × TokenInfo; TokenType = ProcType | MemType; ProcType = (\underline{mess}, \underline{cont}, \underline{fCall}, \underline{tCall}, \underline{pLoad}, \underline{stop}); MemType = (\underline{alloc}, \underline{read}, \underline{wrInt}, \underline{wrPtr}, \underline{submit}, \underline{mLoad}, \underline{incRef}, \underline{decRef}, \underline{copRef}, \underline{stop}); TokenInfo = ProcToken | MemElement; (* Token Info Field *)

var

(* External and Internal Links *) inpLink, outLink: array LinkAddr of NetLink; (* External Links *) inpProc, inpMem: NetLink; (* From Processor and Memory *) outProc, outMem:ModLink; (* To Processor and Memory *)

Figure A.2: Network Quantities • A ‘Token’ is the high-level interpretation of a word transmitted by the network. It consists

of an address field ‘ModAddr’, a type field ‘TokenType’ and the field ‘TokenInfo’ carrying the actual information of the token. Since tokens may be destined for processor units and memory units, ‘TokenType’ is the union of ‘ProcType’ and ‘MemType’ as well as ‘TokenInfo’ is the union of ‘ProcInfo’ and ‘MemInfo’. The detailled interpretation of these fields can be found in Section A.3.2 and Section A.4.2, respectively.

The variable definitions demand some further explanation, too:

• ‘inpLink’ and ‘outLink’ are both arrays that describe the communication links that enter respectively leave the module. Assignment statements of the form

outLink[i] := word word := inpLink[j]

will be interpreted as acts of communication with the neighbours of the actual module.

• ‘inpProc’ and ‘inpMem’ are the links by that the arbiter receives network tokens from the processor unit and the memory unit, respectively. These ‘internal’ links are constructed in exactly the same way as the ‘external’ links that establish the connection between different modules.

• ‘outProc’ and ‘outMem’ denote the links through that the arbiter may send tokens to the processor unit and the memory unit, respectively. In contrast to the other links, tokens on these links do not carry module addressses any more, since they have already found their destination module. Therefore, these links are defined in a different way.

A.2.1 The Arbiter Operation

Figure A.3 describes the operational behaviour of the arbiter:

• The arbiter holds a local routing table ‘route’ that provides a mapping from module addresses to link numbers. Indexed with the address of a module, it delivers the number of the link through that tokens destined for this module have to be forwarded. This table has to be initialized first.

• Before program execution may begin, the whole program code has to be loaded into the program store of each memory unit. The arbiters are responsible for the correct distribution of that program code among all worker modules. We leave the details of this system setup undefined.

• The procedure ReadyLink listens on all of the (internal and external) input channels of the arbiter and returns the word ‘netWd’ that has been received by one of these channels. We do not define the strategy that this procedure uses for the serialization of “simultaneous” transmission requests, but some kind of priority among internal and external links will probably have to be obeyed.

• The destination address ‘mod’ of ‘netWd’ is examined in order to determine whether the token has already arrived in the destined module. In this case, the field ‘type’ determines whether the token has to be forwarded to the module’s processor unit or to its memory unit. In either of these cases, the module address has become superfluous and is not transmitted with the rest of the token. If the token is on its way to another module, the arbiter has to A.2. THE ARBITER 139

Arbiter

var route: array ModAddr of LinkAddr; netWd: NetLink; mod: ModAddr; type: TokenType; info: TokenInfo; out: LinkAddr; terminate: Boolean;

Initialize route; Transfer Program into Processor Unit and Forward it to Other Units;

terminate := \underline{false}; repeat

netWd := ReadyLink(inpLink, inpProc, inpMem); ⟨mod, type, info⟩ := netWd:Token; if mod = \underline{ActMod} then

case type of

\underline{mess}, \underline{cont}, \underline{fCall}, \underline{sCall}, \underline{pLoad}:

outProc := ⟨type, info⟩:ModLink; \underline{alloc}, \underline{read}, \underline{wrInt}, \underline{wrPtr}, \underline{submit}, \underline{incRef}, \underline{decRef}, \underline{mLoad}:

outMem := ⟨type, info⟩:ModLink; \underline{stop}:

outProc := ⟨type, ⟩:ModLink; outMem := ⟨type, ⟩:ModLink; terminate := \underline{true}

endcase else

out := route[mod]; outLink[out] := netWd

endif until terminate;

Route all stop tokens destined for other modules

Figure A.3: Arbiter Operation forward it through one of the output links ‘outLink’. This communication is only possible if the arbiter on the other side of the link is ready to receive the token. Otherwise the actual arbiter is blocked^{2}.

• The special token type ‘stop’ signals that the master module has received its result data and that all modules in the network may terminate execution. In this case, the arbiter forwards copies of the tokens to processor and memory unit. It then leaves the transfer loop and handles the transmission of the stop tokens destined for other worker modules. If all worker modules have received their stop tokens, the arbiter may stop its execution^{3}.

A.3 The Processor Unit

The following sections describe the operational behaviour of the processor unit by an algorithm that interprets Adam machine code and performs the desired “microcode” actions. Actually for sake of lucidity, the description is split into two parts:

• The following subections define the overall behaviour of the processor unit regardless of the instruction set of the machine.

• Appendix B provides the exact definition of the “microcode” of each instruction.

A.3.1 Global Constants

Figure A.4 shows the constants that are globally known throughout the the following algorithms and subalgorithms.

• Integer Bounds:

The first constants define the upper and lower bounds for the fixed-size types ‘Integer’ and ‘Constant’ that will be defined in the next subsection.

• Lengths of Storages:

The next constants define the lengths of the various types of storages that the processor has access to, i.e. the number of words every storage consists of.

• Length of Storage Words:

Within each type of storage, all words have equal length. However, different storages generally have different word lengths that are fixed by these constants.

• Restrictions:

These constants denote restrictions that arise from the limited size of tokens and storage words. Among them, there are the maximal number of addressable registers per code block (‘MaxReg’) or the maximal address distance from one instruction to its successor instruction (‘MaxSucc’).

A.3. THE PROCESSOR UNIT 141

const

(* Integer Bounds *) \underline{MinInt} = Minimal Representable Integer Number; \underline{MaxInt} = Maximal Representable Integer Number; \underline{MinCon} = Minimal Representable Small Constant; \underline{MaxCon} = Maximal Representable Small Constant;

(* Lengths of Storages *) \underline{ProgLen} = Length of Program Store; \underline{FrameLen} = Length of Frame Store; \underline{ContLen} = Length of Context Store; \underline{TrigLen} = Length of Trigger Store; \underline{MessLen} = Length of Message Store; \underline{RequLen} = Length of Request Store; \underline{HeapLen} = Length of Memory Unit Heap;

(* Lengths of Storage Words *) \underline{ProgWdLen} = Length of Program Store Word; \underline{FrameWdLen} = Length of Frame Store Word; \underline{ContWdLen} = Length of Context Store Word; \underline{TrigWdLen} = Length of Trigger Store Word; \underline{MessWdLen} = Length of Message Store Word; \underline{RequWdLen} = Length of Request Store Word;

(* Restrictions *) \underline{MaxReg} = Number of Addressable Registers per Activation; \underline{MaxEvnt} = Number of Addressable Events per Activation; \underline{MaxSucc} = Maximal Distance to Successor Instruction; \underline{MaxOff} = Maximal Length of CodeBlock; \underline{MaxBlock} = Maximal Number of Code Blocks per Processor; \underline{MaxWght} = Maximal Weight of Pointer;

Figure A.4: Global Processor Constants

142 APPENDIX A. THE ADAM OPERATIONAL BEHAVIOUR

type

(* Bounded Numbers *) Integer = [\underline{MinInt}..\underline{MaxInt}]; (* Representable Integer Number *) Constant = [\underline{MinCon}..\underline{MaxCon}]; (* Small Integer Numbers *)

(* Storage Words *) ProgWord = Bits(\underline{ProgWdLen}); (* Word of Program Store *) FrameWord = Bits(\underline{FrameWdLen}); (* Word of Frame Store *) ContWord = Bits(\underline{ContWdLen}); (* Word of Context Store *) TrigWord = Bits(\underline{TrigWdLen}); (* Word of Trigger Store *) MessWord = Bits(\underline{MessWdLen}); (* Word of Message Store *) RequWord = Bits(\underline{RequWdLen}); (* Word of Request Store *)

(* Storage Indizes *) FramePtr = [0..\underline{FrameLen}−1]; (* Pointer into Frame Store *) ContPtr = [0..\underline{ContLen}−1]; (* Pointer into Context Store *) ProgPtr = [0..\underline{ProgLen}−1]; (* Pointer into Program Store *)

(* Components of the Operand Part of an Instruction *) Register = [0..\underline{MaxReg}−1]; (* Addressable Registers *) Event = [0..\underline{MaxEvnt}-1]; (* Addressable Events *) InstOff = [0..\underline{MaxSucc}-1]; (* Addressable Successor Instructions *) FunDescr = [0..\underline{MaxBlock}−1]; (* Descriptor of Code Block *) JumpOff = [−\underline{BlockOff}..+\underline{BlockOff}]; (* Jump Offsets within Code Blocks *)

(* Operand Formats *) OpReReIn = Register × Register × InstOff; OpCoReIn = Constant × Register × InstOff; OpReInIn = Register × InstOff × InstOff; OpReReCo = Register × Register × Constant; OpCoReCo = Constant × Register × Constant; OpReIn = Register × InstOff; OpCoIn = Constant × InstOff; OpInIn = InstOff × InstOff; OpFuIn = FunDescr × InstOff; OpEvIn = Event × InstOff; OpJu = JumpOff;

(* Instruction Formats *) OpCode = All Possible Operation Codes (Appendix B); OpWord = OpReReIn | OpReCoIn | OpReInIn | OpReReCo | OpReIn

| OpCoIn | OpInIn | OpEvIn | OpJu;

Instruction = OpCode × OpWord;

(* Contents of Trigger and Context Store *) BlockOff = [0..\underline{BlockLen}−1]; (* Code Block Offset *) Trigger = ContPtr × BlockOff; (* Operation Trigger *) Context = FramePtr × ProgPtr; (* Activation Descriptor *)

Figure A.5: Global Processor Types A.3. THE PROCESSOR UNIT 143

type

(* Function Descriptors *) CurryFun = FunDescr × Constant; (* Curried Function Descriptor *) ActivAddr = ModAddr × ContPtr; (* Activation Address *)

(* Heap References *) TupleAddr = [0..\underline{HeapLen}−1]; (* Address of Heap Tuples *) RefWght = [0..\underline{MaxWght}−1]; (* Reference Weight *) TupleRef = TupleAddr × RefWght; (* Local Tuple Reference *) Pointer = ModAddr × TupleRef; (* Global Tuple Reference *)

(* Message Tokens *) OpArg = ContPtr × BlockOff × FrameWord; (* Value Message *) ConArg = ContPtr × BlockOff × ModAddr × ContPtr; (* Context Message *) Argument = OpArg | ConArg; (* Argument Message *) LoadRep = LinkAddr × ProcLoad; (* Load Report *) Message = Argument × LoadRep; (* Message Token *)

(* Request Tokens *) Request = ContPtr × BlockOff × ModAddr × FunDescr;

(* Token Sent to Processor Unit *) ProcToken = Message | Request;

(* Atomic Data *) Info = Integer | Pointer | ActivAddr | CurryFun; (* Atomic Information *) Data = Info × Boolean; (* Information plus Error Flag *)

(* Resource and Load Measures *) Resource = Event × Register; (* Activation Resources *) ContLoad = [0..\underline{ContLen}]; (* Load of Context Store *) TrigLoad = [0..\underline{MessLen}]; (* Load of Trigger Store *) MessLoad = [0..\underline{MessLen}]; (* Load of Message Store *) FrameLoad = [0..\underline{FrameLen}]; (* Load of Frame Store *) RequLoad = [0..\underline{RequLen}]; (* Load of Request Store *) LoadDescr = FrameLoad × RequLoad; (* Load Descriptor *)

(* Processor Actions *) ProcAction = (\underline{execute}, \underline{receive}, \underline{request}, \underline{report});

Figure A.6: Global Processor Types (Continued)

A.3.2 Global Types

Figures A.5 and A.6 show the types that are used throughout the following algorithms.

• Bounded Numbers:

‘Integer’ and ‘Constant’ denote finite subsets of the infinite type ‘Number’ both of which bounded by some constant upper and lower bounds. ‘Integer’ is the basic kind of information supported by the Adam architecture, while ‘Constant’ represents a rather small subsect of ‘Integer’ that is used to optimize many of the integer operations occurring in programs.

• Storage Words:

These types define the basic entities that the various classes of storages are constructed from.

• Storage Indizes:

These types denote the various kinds of addresses by that each word in the program store, the frame store or the context store may be directly referenced. The other storage classes are organized in a different way (see Section A.3.3).

• Components of the Operand Part of an Instruction:

These types define the components that the operand part of every instruction (i.e. the instruction word without the operation code) is constructed from. The components may contain constants (‘Constant’), slots within the actual activation frame (‘Register’), addresses of synchronization points within the frame (‘Event’), offsets to successor instructions (‘InstOff’), function descriptors (‘FunDescr’) instruction offsets for large jumps (‘JumpOff’).

• Operand Formats and Instruction Formats:

These classes of types describe the possible interpretations of an instruction, i.e. the semantics of its various fields. The operation code field is always the same but the interpretation of the operand word (i.e. the rest of the instruction word) depends on this code. A definition of all operation codes can be found in Appendix B.

• Contents of Trigger and Context Store;

‘Trigger’ and ‘Context’ define the interpretations of the words in the trigger and in the context store, respectively.

• Function Descriptors:

‘FunDescr’ uniquely describes a function by pointing to a word in the program store that holds the base address of the denoted code block. ‘CurryFun’ is the description of a curried function by holding a field ‘FunDescr’ and a field ‘Constant’ that indicates the parameter number of the first lambda-lifted arguments (for details, see Section 5.6). ‘ActivAddr’ uniquely desribes a particular function activation in the network by context pointer and module address.

• Heap References:

These types define the construction of heap references, i.e. references to tuples stored in some memory unit. Each pointer consists of a module address (indicating the address of the memory unit), a tuple address (indicating a particular tuple within the unit) and a reference weight (used for garbage collection). For more details, see Section A.4.

2Actually, it will be sensible to insert buffer queues between arbiter and output link in order to smooth different communication speeds and keep communication latencies as small as possible.

3In a concrete implementation, some communication protocol has to be introduced that organizes a correct “shutdown” of the network and inhibits deadlocks. Moreover, after the network has terminated, the arbiter will reset itself to its initial state in order to receive a new program to be executed

• Argument Messages, Load Reports, Message Tokens:

The following classes of types define the structure of message tokens, i.e. tokens that transmit pieces of information between processor and/or memory units. Basically, there are two types of messages, argument messages and load reports. The former type again may be subdivided into value messages and context messages.

• Request Tokens:

These types define the construction of request tokens, i.e. tokens that transmit call requests from one processor to another.

• Atomic Data:

These types define the atomic data that are stored in registers and that all instructions work on. These values may be fixed-size numbers (‘Integer’), references into memory units (‘Pointer’), references to function activations (‘ActivAddr’) or descriptions of curried functions (‘CurryFun’). Additionally, each value is augmented with a flag that indicates whether the value has to be considered as valid or not (error value).

• Processor Action:

This type denotes the possible kinds of actions that the processor may be engaged in (see Section A.3.4).

• Resource and Load Measures:

The following types define various measures that are used to compare the resource utilization of different processors and to estimate the influence that the creation of a new function activation will have on this utilization.

A.3.3 Global Variables

Figure A.7 declares the global variables that are referenced in the following algorithms and subalgorithms.

• Basic Stores:

The variables ‘ Store’ denote the various storage classes that the processor has access to. The program store ‘progStore’, the frame store ‘frameStore’ and the context store ‘contStore’ are arrays that can be directly referenced by the corresponding types of indizes. On the other hand, the trigger store ‘trigStore’, the message store ‘messStore’ and the request store ‘requStore’ are defined by the pseudo-constructor collection of. We use this terminology to leave the actual organization of each of these stores undefined. The algorithms uses the pseudo-function Fetch to get a value from one of these stores and the pseudo-procedure Put into to store a value into them^{4}. However, the order in which values are returned by these stores is not defined.

• Load Descriptors:

The following variables hold all values that characterize the utilization of the processor i.e. the number of words occupied in each storage. These values serve as the basis of all “stratetic” decisions of the processor, in particular which kind of action to perform next, to accept or to forward a foreign call request and to determine the processor to which a call request is sent.

4This instruction is only used in combination with the trigger store, as the processor unit does not write values into the message store and the request store.

var

(* Basic Stores *) progStore: array [0..\underline{ProgLen}−1] of ProgWord; frameStore: array [0..\underline{FrameLen}−1] of FrameWord; contStore: array [0..\underline{ContLen}−1] of ContWord; trigStore: collection of TrigWord; messStore: collection of MessWord; requStore: collection of RequWord;

(* Load Descriptors *) frameLoad: FrameLoad; contLoad: ContLoad; trigLoad: TrigLoad; requLoad: RequLoad; messLoad: MessLoad;

(* Descriptors of Connected Modules *) procLoad: array LinkAddr of LoadDescr; (* Load of Neighbour Modules *) modAddr: array LinkAddr of ModAddr; (* Address of Neighbour Modules *) linkAddr: array LinkAddr of LinkAddr; (* ‘Reverse’ Link Address *)

(* Termination Flag *) terminate: Boolean;

Figure A.7: Global Processor Variables ExecutionUnit

var action: Action; (* Actual Processor Action *)

Load Program into Program Store;

Initialize modAddr and linkAddr;

procLoad[1..\underline{LinkNum}] := ⟨0, 0⟩:ProcLoad;

⟨frameLoad, contLoad, trigLoad, requLoad, messLoad⟩ := ⟨0:FrameLoad, 0:ContLoad, 0:TrigLoad, 0:RequLoad, 0:MessLoad⟩;

terminate := \underline{false};

repeat (* Interpreter Loop *)

action := ProcAction(procLoad,

frameLoad, contLoad, trigLoad, requLoad, messLoad); case action of

\underline{execute}: Instruction (* Execute Instruction *) \underline{receive}: Reception (* Receive Message *) \underline{request}: Request (* Fulfill Request *) \underline{report}: Report (* Report Processor Load *)

endcase until terminate

Figure A.8: Execution Unit

• Descriptors of Connected Modules:

The table ‘procLoad’ contains the knowledge that the processor has on the utilization of each of its neighbour processors. These data consist of the load of the neighbour’s frame store and the load of its request store. Although this amount of knowledge is very limited and the processor therefore has a verry narrow view of the whole network, it should serve as a sufficient basis for balancing the load in the network.

The table ‘modAddr’ holds the network addresses of all neighbour modules. The array ‘linkAddr’ contains the index of the entry into that the load of the actual module is to be written on each neighbour module: If ’linkAddr[i]’ = j, then the neighbour processor with number ’modAddr[i]’ will store the load of the actual module into the entry procLoad[j].

• Termination Flag:

This flag signals that a stop request has been sent to the processor and that it therefore has to terminate execution. This stop request is sent by the master module to each module of the network indicating that the program has successfully delivered a result. The module therefore has to reset itself to its initial state in order to load a new program for execution.

A.3.4 The Execution Unit

At the start of the execution, the Adam machine program is loaded into the program store of the module (i.e. each processor holds the same contents in its program store). Then the arrays ‘modAddr’ and ‘linkAddr’ are initialized to hold the addresses of the neighbour modules and to give its own load report some position in the ‘procLoad’ arrays of the neighbours. ‘procLoad’ holds the information that the processor has about the load of its neighbours. Its values are initally set to zero as well as the variables that hold the processor’s own load measures. The termination flag is reset to \underline{false} and the interpretation loop is entered.

Depending on the processor’s load values, the subalgorithm ProcAction decides which kind of action is to be performed next. We let the strategy undefined that the processor uses for its decision, hence, this algorithm is not specified in more detail.

There are four possible kinds of actions the processor may perform (illustrated in Figure A.8):

1. Execute Instruction Sequence:

In this case, the processor fetches a trigger from the trigger store, determines the task that is described by the trigger and starts the execution of the specified program path.

2. Receive a Message:

If the processor performs this kind of task, it fetches a token from the message store and stores the value it carries into the denoted slot of some activation frame. Basically, there are two kinds of messages, argument messages for the communication between user-created function activations and load reports transmitting system-internal reports between processors and memories.

3. Fulfill Requests:

If the processor decides to accept a function request, it creates a new activation of a specific function. Basically, this requires the allocation of a new activation frame in the frame store and the reservation of a a context descriptor in the context store. If this is not possible (due to lack of free space), the processor forwards the request to that processor among its neighbours that it thinks to be least loaded.

4. Report Processor Load:

This action is performed, if the processor load has significantly changed so that the neighbour processors should be informed about the new situation. In this case, the processor sends load report messages to all of its neighbours modules and to the master module.

If the termination flag is set to \underline{true}^{5} the top level loop is left and the processor becomes ready for “meta-communication” (basically loading a new program). However, we do not describe these actions in detail.

A.3.5 Executing an Instruction Sequence

The execution of a sequence of instructions (described by the algorithm in Figure A.9) is initiated by an instruction trigger fetched from the trigger store. Since this store holds descriptions of all that tasks that may be executed without further preliminaries, the processor is free in its decision which trigger to take. Therefore we may leave the organization of the trigger store undefined.

Each trigger consists of two fields, the context field and the offset field. The context is a reference to some specific entry in the context store. This entry again consists of two components, a frame base and a code base. The frame base is a reference to the first register in the frame of some function activation, while the code base is a reference to the first instruction of the function’s code block. These components are extracted and saved in some variables for further use. The trigger’s instruction offset is added to the context’s code base which yields the address of the instruction to be executed.

5As we will see in Section A.3.7, this is only the case if a stop request has been received.

Instruction

var context: ContPtr; codeBase, instruction: ProgPtr; offset: BlockOff; frameBase: FramePtr; opC: OpCode; opWd: OpWord;

⟨context, offset⟩ := Fetch(trigStore):Trigger;

trigLoad := trigLoad − 1;

⟨frameBase, codeBase⟩ := ContextStore[context]:Context; instruction := codeBase + offset:ProgPtr;

break := \underline{false};

repeat ⟨opC, opWd⟩ := ProgramStore[instruction]:Operation; Execute Instruction opC

until break

Figure A.9: Instruction Execution

The program word referenced by this address is fetched and split into the components operation code and operand word. Depending on the operation code, a specific procedure for the particular instruction is called. These procedures (representing the processor‘s “micro-code”) are described in Appendix B in detail.

After having executed the instruction, a break flag (initally set to \underline{true}) is tested. If the test still yields \underline{true}, the processor assumes that the instruction address has been updated with the address of the instruction’s successor and continues the execution. Otherwise some synchronization event has caused an interruption of the instruction sequence and the loop is left.

A.3.6 Receiving a Message

Receiving a Message requires the following tasks (depicted in Figure A.10):

1. Fetching a message token from the message store,

2. Identifying the message type,

3. Extracting the value that the message carries and the address of its destination

4. Routing the message value to its destination with respect to the type of the message and its address.

In detail, the processor performs the following actions:

• The processor fetches a token from the message store together with the actual load of this store. If the load is zero, the store is empty and the contents of the token are undefined. Otherwise the token’s type and value fields are extracted.

Reception

var type: TokenType; mData: Message; data: Data; rOff, offset: BlockOff; rMod: ModAddr; rCont, context: ContPtr; op: Register; link: LinkAddr; load: ProcLoad;

⟨⟨type, mData⟩, messLoad⟩ := Fetch(messStore):⟨ProcToken, messLoad⟩;

if messLoad > 0 then (* Message Received *)

case type of

\underline{mess}: (* Message Destined for Operation *) ⟨context, offset, data⟩ := mData:OpArg; ⟨frameBase, codeBase⟩ := ContextStore[context]:Context; ⟨ , ⟨ , op, ⟩⟩ := progStore[codeBase+offset:ProgPtr]:OpReReIn; frameStore[frameBase+op:FramePtr] := data:FrameWord; Put ⟨context, offset⟩:TrigWord into trigStore; trigLoad := trigLoad+1:TrigLoad

\underline{context}: (* Message Carries Result Context *) ⟨rCont, rOff, rMod, context⟩ := mData:ResArg; ⟨frameBase, codeBase⟩ := contStore[context]:Context; frameStore[frameBase−1:FramePtr] := ⟨rCont, rIns⟩:FrameWord; frameStore[frameBase−2:FramePtr] := rMod:FrameWord; Put ⟨context, 0:BlockOff⟩:TrigWord into TriggerStore;

\underline{stop}: (* Stop Token Encountered *)

Empty Request Store terminate := \underline{true}

\underline{pLoad}: (* Message Carries Processor Load *) ⟨link, load⟩ := mData:LoadRep; procLoad[link] := load;

endcase endif

Figure A.10: Receiving a Message • If the type field has the value \underline{mess}, the message value is argument to an instruction within

some function activation^{6}. In this case, the data field of the message contains a pointer to the context defining the function activation and an offset value denoting a particular instruction of this activation. These pieces of information are used to fetch the indicated instruction and to determine the address of its right operand, i.e. the number of the a register within the actual frame. The message value is stored in the referenced register and a trigger for the instruction is put into the trigger store.

• If the type field has the value \underline{context}, the message carries a result address to some function activation, i.e. the information to which instruction in which activation on which processor the function has to deliver the result value of its computation. This result address is stored in the activation frame of the referenced function and the first instruction in the associated code block is triggered.

• If the type field of the token is \underline{stop}, the master has signalled all processors that the desired result value has been delivered and that execution is to be terminated. In this case, the request store is emptied i.e. all requests are fetched and discarded until the stop token has encountered there, too. Then the termination variable is set.

• If the message token’s type field has the value \underline{pLoad}, the message is not used for program communication between functions but for “meta-communication” between processors. In this case, the message carries the report about the load some neighbour processor, i.e. the load of its frame store and its request store. The actual processor stores these pieces of information in the variable ‘procLoad’ and continues execution.

A.3.7 Fulfilling a Request

The request queue carries the messages that require the processor to activate a new instance of a specific function, i.e. to allocate an activation frame of appropriate size and a context that describes the new activation. The behaviour of the processor when receiving a call request is illustrated in Figure A.11.

• First, the processor fetches a token from the request store together with the actual load of this store. If the load is zero, the store is empty and the contents of the token are undefined. Otherwise the type of the token, the description of the function to activate and the address of the instruction waiting for the request acknowledge are exctracted.

• If the request type is \underline{stop}, the token does actually not carry a request for activating a function but for terminating the execution of the program. This “stop request” is sent by the master module to all processors in the network and signals that the program has successfully delivered a result. In this case, the message store is emptied by fetching and discarding all messages until the stop token is received there, too. Then the top-level loop is left by setting the termination flag.

• Otherwise, the token carries the description of a function to activate. This description consists of a pointer to the base address of the function’s code block in the program store. The first word of the code block contains a description of the function’s resource requirements, i.e. the number of slots needed for synchronization bits and registers, respectively. If a block of the appropriate size is free in the frame store and if there is at least one word left in the context store, the resources are allocated and the new context is initialized. If there is not enough memory left, the request message is forwarded to that module that the processor thinks to be least loaded.

6By convention, messages are always sent to the right (i.e. the second) input argument of an instruction.

Request

var type: ProcType; rData: Request; events: Event; regs: Register; frame: FramePtr; off: BlockOff; cont, context: ContPtr; mod, min: ModAddr; fun: FunDescr; code: ProgPtr;

⟨⟨type, rData⟩, requestLoad⟩ := Fetch(requStore):⟨ProcToken, RequLoad⟩; if requestLoad > 0 then

(* Call Request Received *) if type = \underline{stop} then

Empty Message Store terminate := \underline{true}

else (* Activate Function *) ⟨type, cont, off, mod, fun⟩ := rData:ResArg; code := progStore[fun:ProgPtr]:ProgPtr; ⟨events, regs⟩ := progStore[code-1:ProgPtr]: Resource; if AvailContext ∧ AvailFrame(events, regs) then

(* Allocate Context and Frame *) ⟨context, contLoad⟩ := AllocContext; ⟨frame, frameLoad⟩ := AllocFrame(events, regs); contStore[context] := ⟨frame, code⟩:ContWord; if type = \underline{fCall}

(* Enter Result Context and Release Trigger *) frameStore[frame−2:FramePtr] := mod:FrameWord; frameStore[frame−1:FramePtr] :=

⟨con, off+1:ProgPtr⟩:FrameWord; Put ⟨newContext, 0:BlockOff⟩:TrigWord into trigStore

endif

(* Return New Context *) inpProc := ⟨mod, \underline{mess},

⟨cont, off, ⟨⟨\underline{ActMod}, newContext⟩, \underline{false}⟩⟩⟩:NetLink

else (* Forward Call Request to Minimal Loaded Processor *) min := MinLoadedProc(procLoad,

trigLoad, frameLoad, contLoad, messLoad, requLoad); inpProc := ⟨min, type, rData⟩:NetLink

endif endif

endif

Figure A.11: Function Calls Report

var i: LinkAddr;

(* Report Processor Load to Master *)

inpProc := ⟨0:ModAddr, \underline{pLoad}, \underline{ActMod}, ⟨frameLoad:Integer, requLoad:Integer⟩⟩⟩:NetLink

(* Report Processor Load to Neighbour Modules *)

for i := 1 to \underline{LinkNum} do

inpProc := ⟨procAddr[i], \underline{pLoad},

⟨linkAddr[i], ⟨frameLoad:Integer, requLoad:Integer⟩⟩⟩:NetLink endfor

Figure A.12: Load Reports

• The request token also holds the address of the operation that the description of the function activation is to be sent to. If the type field of the token is \underline{fCall}, this address additionally denotes the instruction to that the result of the function activation is to be sent: it is in the same activation but with a instruction address incremented by one. In this case, the result address is computed and stored in the new activation frame. The first instruction of the function is put into the trigger store and the function by this already activated. However, if the type field contains \underline{tCall}, the result address will be sent in a seperate “context message” and the function is not yet activated. Finally, a message token is sent to the caller transmitting the module address and the address of the context describing the new function activation.

A.3.8 Reporting the Load

If the processor load has significantly changed, this information is reported to the master and to all the modules that the actual module is connected to. For this purpose, the processor looks up its table ‘procAddr’ to determine the neighbours’ module addresses and sends a load report message (carrying the actual load of the frame store and of the request store) to each of them. These pieces of information roughly characterize the current state of the processor’s load and the state expected for the near future. They therefore represent a satisfying tool to balance the workload among the worker modules.

Figure A.12 shows the algorithm in detail.

A.4 The Memory Unit

The following sections define the operational behaviour of a memory unit by an algorithm that describes the activities of its memory manager.

A.4.1 Global Constants

const

\underline{ExpLen} = Number of Tokens in “Expensive” Request Store; \underline{CheapLen} = Number of Tokens in “Cheap” Request Store; \underline{StackLen} = Number of Tuple References in Deallocation Stack

\underline{HeapWdLen} = Length of Heap Words in Bits;

\underline{CheapWdLen} = Length of Cheap Store Words in Bits;

\underline{ExpWdLen} = Length of Expensive Store Words in Bits;

\underline{MaxRef} = Maximal Reference Count (= “Infinity”);

Figure A.13: Global Memory Constants

Figure A.13 describes the global constants that the memary manager references (in addition to the constants defined in Section A.2 and Section A.3.1):

• ‘ExpLen’ and ‘CheapLen’ define the number of words that can be stored in the “cheap”, respectively “expensive” request Store. ‘StackLen’ is the maximal depth of the deallocation stack used during the garbage collection process.

• ‘HeapWdLen’, ‘CheapWdLen’ and ‘ExpWdLen’ define the bit lengths of the words stored in the heap and the request stores, respectively.

• ‘MaxRef’ defines the maximal reference count that can be represented in each word of the heap store. Since a reference count value of ‘MaxRef’ cannot be increased any more, this constant represents “infinity” and will be never again decreased to zero.

A.4.2 Global Types

Figure A.14 defines the global types that will be used in the following algorithms (in addition to the types defined in Section A.2 and Section A.3.2):

• The first statements define the types of words stored in the heap (‘HeapWord’) and the request stores (‘CheapWord’ and ‘ExpWord’).

• ‘MemInfo’ denotes the contents of tokens sent to the memory unit. This type is subdivided into cheap requests (‘CheapRequ’) and expensive requests (‘ExpRequ’) both of which again are subdived into various subrequests.

• ‘Cell’ is the high-level interpretation of the storage word ‘HeapWord’. It consists of a ‘Tag’ field and two ‘Data’ fields.

• The next types define various kind of load measures (‘HeapLoad’, ‘ExpLoad’ and ‘CheapLoad’) and the information that each memory unit has about the load of its neighbours (‘MemLoad’).

• ‘StackPtr’ denotes the values that the top of the deallocation stack may receive during the garbage collection process. ‘MemAction’ denotes the kind of actions the memory manager may perform, i.e. its “degrees of freedom”.

A.4. THE MEMORY UNIT 155

type

(* Storage Words *) HeapWord = Bits(\underline{HeapWdLen}); CheapWord= Bits(\underline{CheapWdLen}); ExpWord = Bits(\underline{ExpWdLen});

(* Contents of Memory Tokens *) MemInfo = ExpRequest | CheapRequest;

(* Expensive Memory Requests *) ExpRequest = ExpType × ExpInfo; ExpType = (\underline{alloc}, \underline{read}, \underline{submit}, \underline{stop}); ExpInfo = Constant × ContPtr × Offset × ModAddr × TupleData; TupleData = TupleRef × Boolean;

(* Cheap Memory Requests *) CheapRequest = CheapType × CheapInfo; CheapType = (\underline{writeInt}, \underline{writePtr}, \underline{incRef}, \underline{decRef}, \underline{copyRef}, \underline{mLoad}); CheapInfo = WriteInfo | RefInfo | CopyInfo | LoadInfo; WriteInfo = Constant × Data × TupleData; RefInfo = ContPtr × Offset × ModAddr × TupleData; CopyInfo = Constant × TupleRef × ModAddr × TupleRef; LoadInfo = ModAddr × MemLoad;

(* Heap Cells *) Cell = Tag × Data × Data; Tag = TagInfo | ModAddr; TagInfo = Boolean × RefCnt × FieldInfo × FieldInfo; FieldInfo = Boolean × Boolean;

(* Memory Unit Load Parameters *) MemLoad = HeapLoad × ExpLoad; HeapLoad = [0..HeapLen]; ExpLoad = [0..ExpLen]; CheapLoad = [0..CheapLen];

(* Miscellaneous *) StackPtr = [1..StackLen]; MemAction = (\underline{exp}, \underline{cheap}, \underline{report});

Figure A.14: Global Memory Types

var

(* Storages *)

heap: array [1..HeapLen] of HeapWord; expStore: collection of ExpWord; cheapStore: collection of CheapWord; stack: array [1..StackLen] of TupleRef;

(* Load Parameters *)

heapLoad: HeapLoad;

expLoad: ExpLoad;

cheapLoad: CheapLoad;

memLoad: array [1..LinkMod] of Load;

(* Network Parameters *)

modAddr:array LinkAddr of ModAddr;

linkAddr: array LinkAddr of LinkAddr;

(* Miscellaneous *)

free: TupleAddr; (* List of Free Heap cells *)

action: MemAction; (* Next Action to Execute *)

top: StackPtr; (* Top of Deallocation Stack *)

Figure A.15: Global Memory Variables

A.4.3 Global Variables

Figure A.15 shows the definition of the global variables referenced by the memory manager (in addition to that ones that have been defined in Section A.2).

• ‘heap’, ‘expStore’ and ‘cheapStore’ are the storages that the memory manager operates on. While the former one is directly referenced by addresses, access to the later ones is provided by the pseudo-instructions Fetch and Put into. As in Section A.3.3 we leave the concrete representation of these storages undefined. ‘stack’ is used for the recursive deallocation of tuples during the garbage collection process.

• Various load parameters ‘ Load’ describe the utilization of each kind of storage, i.e. the number of words occupied. ‘memLoad’ holds the information that the memory manager has about the load of each neighbour memory.

• ‘modAddr’ holds the addresses of all neighbour modules, ‘linkAddr’ holds for each neighbour module the position on that the load of the actual module is stored in the neighbour’s local table ‘memLoad’.

• ‘free’ holds a pointer to a list of all unreferenced heap words, i.e. all that storage cells that may be delivered as results of allocation requests. ‘action’ denotes the kind of action the memory manager executes next. ‘top’ is the actual top of the deallocation stack used during the garbage collection process.

MemoryManager

var i: LinkAddr; action: MemAction;

Initialize linkMod and ownMod;

memLoad[1..LnkMod] := ⟨0, 0⟩: MemLoad; ⟨heapLoad, expLoad, cheapLoad⟩ := ⟨0:HeapLoad, 0:ExpLoad, 0:CheapLoad⟩;

free := 1:TupleAddr; for i:=1 to \underline{HeapLen} do

heap[i] := ⟨ , , (i+1):Value⟩: HeapWord; endfor; heap[\underline{HeapLen}] := ⟨ , , 0:Value⟩: HeapWord;

terminate := \underline{false}; repeat (* Manager Loop *)

action := MemAction(heapLoad, expLoad, cheapLoad, memLoad); case action of

\underline{exp}: ExpRequest; \underline{cheap}: CheapRequest; \underline{report}: LoadReport

endcase until terminate;

Figure A.16: Memory Manager

A.4.4 The Memory Manager

Figure A.16 describes the behaviour of the memory manager:

• Having started its execution, the memory manager does a lot of initalization work: the tables ‘linkMod’ and ‘ownMod’ have to be defined in order to describe the memory manager’s view of the actual network configuration. Furthermore, all load parameters have to be set to 0. Then the heap words have to be linked together in order to form the list ‘free’ of all unused words in the heap. The termination flag ‘terminate’ is set to \underline{false} and the main loop is entered.

• Having entered the main loop, the memory manager uses the function MemAction to determine the kind of action to be executed next. Its decision basically depends on the load of the various storages and the load of its neighbour modules. Having decided to perform a particular kind of action, the manager calls one of the procedures ‘CheapRequ’, ‘ExpRequ’ or ‘LoadReport’.

• The main loop is left, as soon as ‘terminate’ has been set to \underline{true}. As we will see in Section A.4.6, this is only the case if a stop request has arrived.

A.4.5 Auxiliary Procedures

Figures A.17, A.18, A.19 and A.20 depict some auxiliary procedures that will be used by the algorithms presented in the following sections:

• Availability of Heap Space:

Figure A.17 shows the subalgorithm ‘AvailTuple’ that checks whether enough heap cells are available for the allocation of a new tuple of particular size. For a tuple with ‘n’ fields, ‘n’−1 heap heaps have to be allocated. The availability of these cells is determined by examining the value of ‘heapLoad’.

• Allocation of a Tuple:

Figure A.17 depicts the allocation of a tuple by the subalgorithm ‘AllocTuple’. If a tuple with ‘n’ fields is to be allocated, ‘n’ has to be larger than 1, i.e. tuples that consist of a single field are not allowed (but also not necessary). The first ‘n’−1 cells of the ‘free’ list are unlinked from this list and properly initalized:

– The available-bits ‘avHd’ are set to \underline{false} in order to signalize, that the head fields of the cell have not yet been written. ‘avTl’ of the ‘n’-th cell is also set to \underline{false}, since the corresponding tail field will contain the last field of the tuple. The data fields that correspond these bits are set to zero, since no instruction has requested their contents yet.

– For the first ‘n’−1 cells of the chain, ‘open’ is set to \underline{true}, for the last cell, it is set to \underline{false}.

– The reference count of the first cell of the chain (that is also the reference count of the whole tuple) is set to \underline{MaxWght}, which reflects the weight of the new cell reference created.

– Finally, the ‘free’ list and the measure ‘heapLoad’ are updated.

AvailTuple(n: Constant): Boolean

return (heapLoad + n:HeapLoad − 1:HeapLoad ≤ HeapLen:HeapLoad)

AllocTuple(n: Constant): TupleAddr

var new: TupleAddr; tail: Data;

if n ≤ 1 then ErrorHalt endif; new := free; ⟨ , , tail⟩ := heap[new]: cell;

while n > 2 do

heap[free] :=

⟨⟨\underline{true}, \underline{MaxWght}, ⟨\underline{false}, ⟩ ⟨\underline{false}, ⟩⟩, 0:Data, tail⟩: HeapWord; free := tail:TupleAddr; n := n−1

endwhile

heap[free] := ⟨⟨\underline{false}, , ⟨\underline{false}, ⟩ ⟨\underline{false}, ⟩⟩, 0:Data, 0:Data⟩: HeapWord; free:= tail:TupleAddr; heapLoad := heapLoad + n:HeapLoad − 1:HeapLoad;

return new

Figure A.17: Availability of Tuple Space and Its Allocation GarbageCollection(tuple: TupleAddr, wght: RefWght)

var count, weight: RefCnt;

⟨top, stack[0]⟩ := ⟨1, ⟨tuple, wght⟩⟩;

while top > 0 do

top := top − 1:StackPtr; ⟨tuple, wght⟩ := stack[top]; weight := 2wght: RefCount; ⟨⟨ , count, , ⟩, , ⟩ := heap[tuple]:Cell;

if count < weight then

ErrorHalt elsif count = \underline{MaxRef} then

NoOp elseif count = weight then

DeallocTuple(tuple) else

heap[tuple] := ⟨⟨ , count−weight, , ⟩, , ⟩; endif

endwhile

Figure A.18: The Garbage Collection Mechanism DeallocTuple(tuple: TupleAddr)

var free0, addr: TupleAddr; open, open0, avHd, ptrHd, avTl, ptrTl, errHd, errTl: Boolean; head, tail: Info; mod: ModAddr; ref: TupleRef; wght: RefWght;

free0 := free; free := tuple; ⟨⟨open0, , ⟨avHd, ptrHd⟩, ⟨avTl, ptrTl⟩⟩, ⟨head, errHd⟩, ⟨tail, errTl⟩⟩ := heap[cell]:cell;

open := \underline{true}; while open do

if ¬avHd then ErrorHalt endif; if ptrHd & ¬errHd then

⟨mod, ref⟩ := head:Pointer; if (mod ≠ \underline{ActMod}) then

inpMem := ⟨mod, \underline{decRef}, ⟨ref, errHd⟩⟩:NetLink else (* push reference on deallocation stack *) if top > \underline{MaxStack} then ErrorHalt endif; stack[top] := ref; top := top + 1:StackPtr

endif endif; heapLoad := heapLoad − 1:HeapLoad; open := open0; if open then tuple := tail; ⟨⟨open0, , ⟨avHd, ptrHd⟩, ⟨avTl, ptrTl⟩⟩, ⟨head, errHd⟩, ⟨tail, errTl⟩⟩ := heap[cell]:cell;

endwhile;

if ¬AvTl then ErrorHalt endif; if ptrTl & ¬errTl then

⟨mod, ref⟩ := tail:Pointer; if (mod ≠ \underline{ActMod}) then

inpMem := ⟨mod, \underline{decRef}, ⟨ref, errTl⟩⟩:NetLink else (* push reference on deallocation stack *)

stack[top] := ref;

top := top + 1:StackPtr endif

endif;

heap[tuple] := ⟨ , , free0:Data⟩:HeapWord

Figure A.19: Deallocation of a Tuple Copy(tuple: Data, done: Boolean)

var addr: TupleAddr; wght: RefWght; error: Boolean;

⟨⟨ , ⟨addr, wght⟩⟩, error⟩ := data;

if data = 0:NodeAddr then (* nil pointer *)

done := \underline{true} elsif error then (* error pointer *)

done := \underline{true} elsif wght = 0:RefWght then (* minimum weight *)

done := \underline{false} else

done := \underline{true};

data := ⟨⟨ , ⟨ , wght−1:RefWght⟩⟩, ⟩ endif

Figure A.20: Duplicating a Tuple Reference

• The Garbage Collection Mechanism

Figure A.18 shows how the garbage collection mechanism is handled by the subalgorithm ‘GarbageCollection’. If some tuple reference has been deleted, the reference count of the corresponding tuple has to be updated. If the count decreases to zero, the tuple may be reclaimed. In this case, all the tuple references stored in the tuple are deleted, the reference counts of the denoted tuples have to be updated and the garbage collection mechanism may recursively proceed.

The procedure ‘GarbageCollection’ uses a global deallocation stack for recalling all those tuple references that have been deleted. Initially, the original reference is pushed on this stack. Iteratively, a reference is popped from the stack, its weight is extracted and the reference count of the denoted tuple is fetched. If the count is less than the weight, a severe program error has occurred and execution is terminated. If the count has already reached its maximum (“infinity”), the count is not changed any more i.e. the tuple will never be reclaimed. Otherwise, the reference count field is decreased by the weight of the deleted reference. If it becomes zero, the deallocation routine ‘DeallocTuple’ is called that reclaims the tuple cells and pushes all references stored in the tuple on the deallocation stack. The process proceeds until the deallocation stack is empty i.e. no more tuple references have been deleted.

• Deallocation of a Tuple:

How the heap space of a tuple is reclaimed by subalgorithm ‘DeallocTuple’, is illustrated in Figure A.18:

– The memory manager follows the ‘tail’ links of the tuple’s cell chain and investigates the contents of the ‘head’ fields: If some ‘avHd’ bit is not yet set to \underline{true}, some fatal error has occured, since in this case a write request may still be on the way to the tuple field. In this case, execution is terminated with an error message. The same is true for the ‘tail’ field of the last cell of the chain.

ExpRequest

var expRequ: ExpRequ; type: ExpType; info: ExpInfo;

⟨expLoad, expRequ⟩ := expStore:⟨ExpLoad, ExpRequest⟩; if ExpLoad > 0:ExpLoad then

⟨type, info⟩ := expRequ:ExpRequest; case type of

\underline{alloc}: AllocRequest(info); \underline{read}: ReadRequest(info); \underline{submit}: SubRequest(info); \underline{stop}: Empty cheap request store;

terminate := \underline{true} endcase

endif

Figure A.21: Expensive Memory Requests

– If some of these fields holds a pointer, the memory manager checks whether the referenced tuple is sited on the actual memory unit. If so, the reference is pushed on the deallocation stack such that the reference count of the denoted tuple can be updated. Otherwise, a decrease message is sent to the memory module with the referenced tuple in order to trigger the garbage collection there.

All cells of the tuple are linked to the ‘free’ list and ‘heapLoad’ is updated.

• Duplicating a Tuple Reference The subalgorithm ‘Copy’ depicted in Figure A.20 handles the duplication of tuple references. If a tuple reference has to be duplicated, both copies receive half of the weight of the original reference. However, if the reference is nil or error then the copy operation does not change the original pointer. If the reference weight has already reached its minimum (1 = 2^{0}), then the reference may not be copied any more and ‘done’ is set to false. Otherwise, the weight field of the reference is decreased by 1, which means to divide the weight by 2, since all weights are represented by their binary logarithms.

A.4.6 Expensive Requests

Figure A.21 shows the memory manager handling an “expensive” request:

• First, a request token ‘expRequ’ is fetched from the store ‘expStore’ together with the actual load ‘expLoad’ of the store. If ‘expLoad’ is zero, the store is empty and the contents of ‘expRequ’ are undefined.

• Otherwise, the ‘type’ field and the ‘info’ field of the token are extracted. Depending on the type of the request, one of the procedures ‘AllocRequest’, ‘ReadRequest’ or ‘SubRequest’ is called with the argument ‘info’. If ‘type’ is \underline{stop}, the token denotes a stop request. In this case, the cheap request store is emptied (by reading all requests until a stop token encounters there, too) and the flag ‘terminate’ is set.

AllocRequest(info: ExpInfo)

var n: Constant; context: ContPtr; offset: BlockOff; mod, mem: ModAddr; tuple: TupleAddr; ptr: Pointer;

⟨n, context, offset, mod, ⟩ := info; if AvailTuple(n) then

tuple := AllocTuple(n);

ptr := ⟨⟨\underline{ActMod}, ⟨tuple, \underline{MaxWght}⟩⟩, \underline{false}⟩:Pointer; inpMem := ⟨mod, \underline{mess}, ⟨context, offset, ptr⟩⟩:NetLink;

else mem := MinLoadedMem(heapLoad, expLoad, cheapLoad, memLoad); inpMem := ⟨mem, \underline{alloc}, info⟩:NetLink;

endif

Figure A.22: Allocation Requests

Allocation Requests

Figure A.22 shows the activities of the memory manager after having received an allocation request, i.e. the request to allocate space for a tuple of certain size:

• The ‘info’ field of the request is split into its components, where ‘n’ denotes the number of tuple fields required and ‘context’, ‘offset’ and ‘mod’ represent the network address of the instruction to that the result value is to be delivered.

• If there is enough memory left, the tuple is allocated and ‘cell’ is set to the address of this tuple. Then the actual tuple reference ‘ptr’ is constructed by combining ‘cell’ with the module address \underline{ActMod}, the reference weight \underline{MaxWght} and the error flag \underline{false}. The reference is sent by a message token to the denoted instruction.

• If there is not enough memory left to fulfill the allocation request, the memory manager determines the memory unit it thinks to be loaded least among (the module itself and) its neighbours and forwards the request to that particular memory unit.

Read Requests

Figures A.23 and A.24 show the behaviour of the memory manager when handling a read request, i.e. the request to deliver the contents of some particular tuple field:

• First, the read request is split into its components, where ‘n’ is the number of the field to be read, ‘context’, ‘offset’ and ‘mod’ denote the address of the instruction to that the result is to be delivered, ‘cell’ represents the address of the tuple that holds the field, ‘wght’ is the weight of the reference and ‘error’ its error flag.

ReadRequest(info: ExpInfo)

var n, num: Constant; context: ContPtr; offset: BlockOff; mod, tmod: ModAddr; tptr: TupleRef; tuple, tuple0, new: TupleAddr; wght: RefWght; error, open, avHd, avTl, ptrHd, ptrTl, gc: Boolean; refCnt: RefCnt; head, tail: Data;

⟨n, context, offset, mod, ⟨⟨tuple, wght⟩, error⟩⟩ := info; ⟨n, tuple0⟩ := ⟨num, tuple⟩;

while open & n > 0:Constant do (* Find Desired Tuple Field *) ⟨⟨open, refCnt, ⟨avHd, ptrHd⟩, ⟨avTl, ptrTl⟩, head, tail⟩ := heap[tuple]:Cell; tuple := tail:TupleAddr; n := n − 1:Const

endwhile;

if n > 1 then ErrorHalt endif; gc := \underline{true};

Continued in Figure A.24

Figure A.23: Read Requests

Continued from Figure A.23

if n = 0 then (* Value Stored in Head Field *)

if avHd & ¬ptrHd then (* Head Available and Not Pointer *)

inpMem := ⟨mod, \underline{mess}, ⟨context, offset, head⟩⟩:NetLink elsif avHd & ptrHd then (* Head Available and Pointer *)

Copy(head, done); if done then (* Copy Head Pointer *)

heap[tuple] := ⟨ , head, ⟩:HeapWord;

inpMem := ⟨mod, \underline{mess}, ⟨context, offset, head⟩⟩:NetLink;

GarbageCollection(tuple0, wght) else (* Send Copy Request *)

⟨tmod, tptr⟩ := head;

inpMem := ⟨tmod, \underline{copRef}, ⟨num, ⟨⟨tuple0, wght⟩, error⟩, \underline{ActMod}, tptr⟩; heap[tuple] := ⟨⟨ , , ⟨\underline{false}, ⟩, ⟩, 0:Data, ⟩:HeapWord; ⟨gc, avHd⟩ := ⟨\underline{false}, \underline{false}⟩

endif endif if ¬avHd then (* Delay Read Request *)

new := AllocTuple(1:Constant); heap[new] := ⟨mod, ⟨context, offset⟩:Data, head⟩:HeapWord; heap[tuple] := ⟨ , new:Data, ⟩:HeapWord; if gc then GarbageCollection(tuple0, wght) endif

endif

else (* Value Stored in Tail Field *)

if avTl & ¬ptrTl then (* Tail Available and Not Pointer *)

inpMem := ⟨mod, \underline{mess}, ⟨context, offset, tail⟩⟩:NetLink elsif avTl & ptrTL then (* Tail Available and Pointer *)

Copy(tail, done); if done then (* Copy Tail Pointer *)

heap[tuple] := ⟨ , , tail⟩:HeapWord;

inpMem := ⟨mod, \underline{mess}, ⟨context, offset, tail⟩⟩:NetLink;

GarbageCollection(tuple0, wght) else (* Send Copy Request *)

⟨tmod, tptr⟩ := tail;

inpMem := ⟨tmod, \underline{copRef}, ⟨num, ⟨⟨tuple0, wght⟩, error⟩, \underline{ActMod}, tptr⟩; heap[tuple] := ⟨⟨ , , , ⟨\underline{false}, ⟩⟩, , 0:Data⟩:HeapWord; ⟨gc, avTl⟩ := ⟨\underline{false}, \underline{false}⟩

endif endif if ¬avTl then (* Delay Read Request *)

new := AllocTuple(1:Constant); heap[new] := ⟨mod, ⟨context, offset⟩:Data, head⟩:HeapWord; heap[tuple] := ⟨ , , new:Data⟩:HeapWord; if gc then GarbageCollection(tuple0, wght) endif

endif

endif

Figure A.24: Read Requests (Continued)

• The storage cell that holds the particular field of the tuple is searched by traversing n−1 times the chain of ‘tail’ pointers linking the cells that the tuple is constructed of.

• If the appropriate cell is found, the desired field may be the ‘head’ or the ‘tail’ field of the cell. The corresponding available flag (‘avHd’ respectively ‘avTl’) is tested. If it is \underline{false}, the field does not yet hold the desired value. In this case, the field actually contains a pointer to a list of all those instructions that have up to now demanded this value. A storage cell is allocated to hold the return address of the read request. This cell is linked into the list of the deferred read requests. As soon as the desired value will become available, this and the other deferred requests will be fulfilled (see Section 4.5.2).

• If the tuple field already holds the desired value, the type flag of the field is checked. If the stored value is an integer atom, a message token is constructed and the integer is sent to the denoted instruction. Otherwise, the value is a pointer i.e. a reference to another tuple. In this case, it is duplicated by the function ‘Copy’ that decreases the weight of the reference. If ‘Copy’ succeeds, the reference is (a) written back into the tuple (with the weight decreased) and (b) delivered by a message token to the denoted instruction. However, if the reference weight is already 1 = 2^{0}, ‘Copy’ has to fail. In this case, a copy request is sent to the memory unit that holds the tuple referenced by the pointer to be duplicated. The tuple field is marked as empty and the read request is queued. As soon as a new reference (with a new weight) is delivered, the read request can be fulfilled.

• Having processed the request, the reference contained in the request is deleted. Hence, the memory manager triggers the garbage collection process by calling the function ‘Garbage-Collection’ that decreases the reference count of the actual tuple and reclaims its cells if the tuple is not referenced any more. However, if a copy request was sent to another memory module, the reference was not deleted but forwarded to this module. In this case, the garbage collection process has to be suppressed (since the total reference count remains unchanged) by resetting the flag ‘gc’.

Submission Requests

Figures A.25 and A.26 show the memory manager dealing with an submission request:

• First, the ‘info’ field of the the submission request is split into its components: ‘context’ and ‘mod’ denote the address of the activation to which the arguments of the higher-order function referenced by ‘tuple’ are to be submitted, ‘wght’ is the weight of the reference and ‘error’ its error flag. If ‘error’ is set to \underline{true} or ‘cell’ holds the nil-pointer, a severe program error has occurred and execution is terminated.

• Otherwise, ‘tuple’ is the address of a tuple containing the description of a higher-order function: The first tuple field holds the pair of a function descriptor and a value ’n’ denotes the parameter number of the first lifted function argument i.e. this argument is the ‘n’-th argument of the function. All other tuple fields hold these lifted arguments in sequence. The heap word referenced by ‘cell’ is now split into its components. If the ‘error’ flag of the head component is set to \underline{true} or the head is not yet available, a severe program error has occurred and execution is terminated.

• By traversing the chain of ‘tail’ links, the memory manager reads the contents of each lifted argument i.e. the ‘head’ field of each cell in the chain and the ‘tail’ field of the last cell. If the i-th tuple field contains an integer atom, a message token with this argument is constructed and sent to the denoted activation, i.e. to the (‘n’+i)-th instruction of the activation. If it contains a tuple reference, the reference is duplicated in the same way as for read requests SubRequest(info: ExpInfo)

var context: Context; mod: ModAddr; cell, next, new: TupleAddr; wght: RefWght; error, open, avHd, avTl: Boolean; count: RefCnt; head, tail, head0, tail0: Data; fun: FunDescr; n: Constant;

⟨context, , mod, ⟨⟨tuple, wght⟩, error⟩⟩ := info; if error ∨ (tuple = 0:TupleAddr) then ErrorHalt endif; ⟨⟨open, count⟨avHd, ⟩, ⟩, head0, tail0⟩ := heap[tuple]:Cell; if ¬avHd then ErrorHalt endif; ⟨head, tail⟩ := ⟨head0, tail0⟩; ⟨⟨ , n⟩, error⟩ := head:Data; if error then ErrorHalt endif;

while open (* Submit Lambda-Lifted Arguments *) next := tail:TupleAddr; ⟨⟨open, , ⟨avHd, avPtr⟩, ⟨avTl, avPtr⟩⟩, head, tail⟩ := tuple[next]:Cell; if avHd & ¬ptrHd then (* Head Available and Not Pointer *)

inpMem := ⟨mod, \underline{mess}, ⟨context, n:BlockOff, head⟩⟩:NetLink elsif avHd & ptrHd then (* Head Available and Pointer *)

Copy(head, done);

if done then (* Copy Head Pointer *)

heap[tuple] := ⟨ , head, ⟩:HeapWord;

inpMem := ⟨mod, \underline{mess}, ⟨context, offset, head⟩⟩:NetLink else (* Send Copy Request *) ⟨tmod, tptr⟩ := head; inpMem := ⟨tmod, \underline{copRef},

⟨n+1:Constant, ⟨⟨tuple0, wght⟩, error⟩, \underline{ActMod}, tptr⟩; heap[tuple] := ⟨⟨ , , ⟨\underline{false}, ⟩, ⟩, 0:Data, ⟩:HeapWord count := count+1:RefCnt

endif elsif ¬AvHd then (* Delay Read Request *)

new := AllocTuple(1:Constant);

heap[new] := ⟨mod, ⟨context, offset⟩:Data, head⟩:HeapWord; heap[tuple] := ⟨ , new:Data, ⟩:HeapWord

endif n:= n+1:Constant;

endwhile;

Continued in Figure A.26

Figure A.25: Submission Requests

Continued from Figure A.25

if avTl & ¬ptrTl then (* Tail Available and Not Pointer *)

inpMem := ⟨mod, \underline{mess}, ⟨context, n:BlockOff, tail⟩⟩:NetLink elsif avTl & ptrTL then (* Tail Available and Pointer *)

Copy(tail, done); if done then (* Copy Tail Pointer *)

heap[tuple] := ⟨ , , tail⟩:HeapWord;

inpMem := ⟨mod, \underline{mess}, ⟨context, n, tail⟩⟩:NetLink; GarbageCollection(tuple0, wght)

else (* Send Copy Request *)

⟨tmod, tptr⟩ := tail;

inpMem := ⟨tmod, \underline{copRef},

⟨n+1:Constant, ⟨⟨tuple0, wght⟩, error⟩, \underline{ActMod}, tptr⟩; heap[tuple] := ⟨⟨ , , ⟨\underline{false}, ⟩⟩, , 0:Data⟩:HeapWord; count := count+1:RefCnt;

endif elsif ¬AvTl then (* Delay Read Request *)

new := AllocTuple(1:Constant); heap[new] := ⟨mod, ⟨context, offset⟩:Data, head⟩:HeapWord; heap[tuple] := ⟨ , , new:Data⟩:HeapWord;

endif heap[tuple0] := ⟨⟨ , count, , ⟩, , ⟩; GarbageCollection(tuple0, wght)

Figure A.26: Submission Requests (Continued)

CheapRequest

var type: CheapType; info: CheapInfo;

⟨type, info⟩ := Fetch(cheapStore):CheapRequest; case type of

\underline{wrInt}, \underline{wrPtr}:

WriteRequest(type, info); \underline{incRef}, \underline{decRef}, \underline{copRef}:

RefRequest(type, info); \underline{mLoad}:

LoadMessage(info) \underline{stop}:

Empty Expensive Store; terminate := \underline{true}

endcase

Figure A.27: Cheap Memory Requests

(i.e. a copy request may have to be submitted). If the head is not yet available, the manager allocates a storage cell, writes the address of the destination instruction into this cell and links it to the queue of deferred reads.

• At last, the reference contained in the submission request is deleted and the garbage collection process is triggered. However, before ‘GarbageCollection’ may be called, the reference count field of the tuple has to be updated by adding the weights of all the references to this tuple that were contained in copy requests sent to other memory units. Since, all of these references had weight 1, it now suffices to increase the reference count by the number of requests sent.

A.4.7 Cheap Requests

Figure A.27 describes the memory manager’s behaviour when having received a “cheap” request. Depending on the request type (write requests, reference requests and load reports), one of the subalgorithms ‘WriteRequests’, ‘RefRequest’ or ‘LoadMessage’ is called. However, if a stop token has encountered, the expensive store is emptied and the termination flag is set.

WriteRequests

Figures A.28 and A.29 show the activities of the memory manager after having received a write request:

• First, the ‘info’ field of the request is split into its components ‘n’ (the field number of the tuple), ‘data’ (the value to be written into that field), ‘tuple’ (the address of the tuple), ‘wght’ (the weight of this reference) and ‘error’ (the error flag of the reference).

• If the error bit of the reference is set to \underline{true} or the reference holds the nil-pointer, the write request is invalid, which signals a severe program error. In this case, execution is terminated with an error message.

WriteRequest(type: CheapType, info: WriteInfo)

var n, num: Constant; data, head, tail, nTail: Data; error, open, going, avHd, avTl, isAtom, isHd: Boolean; infHd, infTl: FieldInfo; tuple, tuple0, next: TupleAddr; wght: RefWght; refCnt: RefCnt; mod, tmod: ModuleAddr; tref: Pointer; context: ContPtr; offset: BlockOff;

⟨num, data, ⟨⟨tuple, wght⟩, error⟩⟩ := info; if error | (tuple = 0:TupleAddr) then ErrorHalt endif; ⟨n, tuple0 ⟩ := ⟨num, tuple⟩; ⟨⟨open, , ⟨avHd, ptrHd⟩, ⟨avTl, ptrTl⟩⟩, head, tail⟩ := heap[tuple0]:Cell;

while open & (n > 0:Constant) do (* Search Tuple Cell *)

n := n − 1:Constant; tuple0 := tail:TupleAddr; ⟨⟨open, , ⟨avHd, ptrHd⟩, ⟨avTl, ptrTl⟩⟩, head, tail⟩ := heap[tuple0]:Cell;

endwhile;

if n > 1:Constant then ErrorHalt endif; ⟨ , error⟩ := data; ⟨isHd, isAtom⟩ := ⟨(n=0)|open|error, type=\underline{wrInt} | data = 0:Data⟩;

if isHd then (* Head Field of Cell *)

if avHd then ErrorHalt endif; next := head:TupleAddr; heap[tuple0] := ⟨⟨ , , ⟨\underline{true}, ¬isAtom⟩, ⟩, , ⟩

else (* Tail Field of Cell *)

if avTl then ErrorHalt endif; next := tail:TupleAddr; heap[tuple0] := ⟨⟨ , , , ⟨\underline{true}, ¬isAtom⟩⟩, , ⟩

endif;

Continued in Figure A.29

Figure A.28: Write Requests

Continued from Figure A.28

going := \underline{true}; while going & (next ≠ 0:TupleAddr) do (* Fulfill Deferred Reads *)

if isAtom then

⟨mod, ⟨context, offset⟩, nTail⟩ := heap[next]:Cell; inpMem := ⟨mod, \underline{mess}, ⟨context, offset, data⟩⟩:NetLink; heap[next] := ⟨ , , free:Data⟩:HeapWord; free := next; next := nTail:TupleAddr; heapLoad := heapLoad − 1;

else

Copy(data, done);

if done then (* Pointer Could Be Copied *)

if isHd then heap[tuple0] := ⟨ , data, ⟩ else heap[tuple0] := ⟨ , , data⟩

endif inpMem := ⟨mod, \underline{mess}, ⟨context, offset, data⟩⟩:NetLink; free := next; next := nTail:TupleAddr; heapLoad := heapLoad − 1;

else (* Send Copy Request *)

⟨⟨tmod, tptr⟩, ⟨ := data;

inpMem := ⟨tmod, \underline{copRef}, ⟨num, ⟨⟨tuple, wght⟩, \underline{false}⟩, \underline{ActMod}, tptr⟩; if isHd

then heap[tuple] := ⟨⟨ , , ⟨\underline{false}, , ⟩⟩, next, ⟩:HeapWord; else heap[tuple] := ⟨⟨ , , ⟨\underline{false}, ⟩⟩, , next⟩:HeapWord;

endif;

going := \underline{false} endif

endif

endwhile

if next = 0:NodeAddr then GarbageCollection(tuple, wght) endif

Figure A.29: Write Requests (Continued)

• Otherwise, the appropriate heap cell is searched by following the ‘tail’ links of the chain starting at the cell addressed by ‘tuple’. If the chain holds too few tuple fields, execution is terminated with an error message.

• If the desired cell is found, the available bit of the appropriate field of the cell is investigated. If the field already holds a value, the “single assignment” rule of the heap is violated and execution is terminated with an error message. Otherwise, the available bit is set and the type of the value to be written is determined.

• In this case, the cell field holds a reference to a list of all those instructions to that the is to be delivered. If an integer atom is to be written (also the nil-pointer and the error value represent integer atoms), a message token holding the desired value is constructed and sent to all the denoted instructions. The heap cells used to store the requests are reclaimed into the free list.

• However, if a tuple reference has to be written, the weight of the reference has to be decreased by the routine ‘Copy’ each time before a message token is sent to one of the denoted instructions. If the weight decreases to its minimum, the rest of the deferred read requests cannot be fulfilled any more. They have to remain in the waiting queue and a copy reference message is sent to the module that holds the tuple referenced by the pointer to be duplicated. That module will send a reference with a new weight by another write message such that more deferred requests will be fulfilled.

• If no copy reference request had to be submitted (and hence no more waiting read requests are left), the garbage collection process is triggered. The tuple reference of the write request is deleted, the reference count of the tuple is updated and the tuple is reclaimed into the free list, if neccessary.

Reference Requests

Figure A.30 shows the behaviour of the memory manager after having received a reference request:

• If the request is an increment request, a new tuple reference with maximal weight has to be delivered to some instruction. The ‘info’ field of the request is split into the components ‘context’, ‘offset’ and ‘mod’ that denote the address of the instruction to that the new pointer is to be delivered and the reference ‘tuple’. Since the weight of a ‘tuple’ reference in an increment request is always 1, the reference count is increased by 2\underline{MaxWght} −1, where \underline{MaxWght} is the maximal weight of a reference. However, the reference count of the tuple has an upper bound of \underline{MaxRef} that must not be exceeded. Anyway, ‘tuple’ is given the weight \underline{MaxWght} and sent to the denoted instruction.

• If the request is an decrement request, a tuple reference is to be deleted. In this case, the reference ‘tuple’ and its weight ‘wght’ are extracted from the token. Then the routine ‘GarbageCollection’ is called that updates the reference count of the denoted tuple and reclaims its heap cells into the free list, if required.

• If the request is an copy request, a new tuple reference with maximal weight has to be delivered to some memory unit by a write message. The values ‘field’, ‘ptr’, and ‘mod’ representing the address of some particular tuple field and ‘tuple’ denoting the tuple reference to be copied are extracted. The reference count field of the addressed tuple is adjusted in the same way as for increment requests. However, the new reference with maximal weight is delivered by a write request token to the memory unit on that the tuple field (fixed by ‘field’, ‘ptr’ and ‘mod’) is sited into that the new reference is to be written.

RefRequest(type: CheapType, info: (RefInfo|CopyInfo))

var context: ContPtr; offset: BlockOff; mod: ModAddr; tuple: TupleAddr; wght: RefWght; refCnt: RefCnt; field: Constant; ptr: TupleRef;

if type = \underline{incRef} then

⟨context, offset, mod, ⟨⟨tuple, ⟩, ⟩⟩ := info:RefInfo; ⟨⟨ , refCnt, , ⟩, , ⟩ := heap[tuple]:Cell; heap[tuple] :=

⟨⟨ , Min(\underline{MaxRef}, refCnt+(2\underline{MaxWght} − 1):RefCnt), , ⟩, , ⟩:HeapWord; inpMem := ⟨mod, \underline{mess},

⟨context, offset, ⟨\underline{ActMod}, ⟨tuple, MaxWght⟩⟩, error⟩⟩:NetLink;

elsif type = \underline{decRef} then

⟨ , , , ⟨⟨tuple, wght⟩, ⟩⟩ := info:RefInfo; GarbageCollection(tuple, wght)

else (* type = \underline{copRef} *)

⟨field, ptr, mod, ⟨⟨tuple, ⟩, ⟩⟩ := info:CopyInfo; ⟨⟨ , refCnt, , ⟩, , ⟩ := heap[tuple]:Cell; heap[tuple] :=

⟨⟨ , Min(\underline{MaxRef}, refCnt+(2\underline{MaxWght} − 1):RefCnt), , ⟩, , ⟩:HeapWord; inpMem := ⟨mod, \underline{wrPtr}, field

⟨⟨⟨\underline{ActMod}, ⟨tuple, \underline{MaxWght}-1:RefWght⟩⟩, \underline{false}⟩, ptr⟩:NetLink;

endif

Figure A.30: Reference Requests LoadMessage(info: LoadInfo)

var pos: LinkAddr; load: MemLoad;

⟨pos, load⟩ := info:LoadInfo;

memLoad[pos] := load;

Figure A.31: Load Messages

LoadReport

var i: LinkAddr;

(* Send Load Report to Master *)

inpMem := ⟨0:ModAddr, \underline{mLoad}, ⟨\underline{ActMod}, ⟨heapLoad, expLoad⟩⟩⟩:NetLink

(* Send Load Report to Neighbour Modules *) for i:=1 to \underline{LinkNum} do

inpMem := ⟨modAddr[i], \underline{mLoad}, ⟨linkAddr[i], ⟨heapLoad, expLoad⟩⟩⟩:NetLink endfor

Figure A.32: Load Reports

Load Messages

Figure A.31 shows the activities of the memory manager when dealing with a load message:

The incoming message is split into its components ‘pos’ and ‘load’ where ‘pos’ denotes a position in the working load table ‘memLoad’ and ‘load’ is the datum to be stored at this position. ‘pos’ has such a value that the entry ‘linkMod[pos]’ holds the address of the neighbour module that has sent the load message. Using these pieces of information, ‘memLoad’ is updated. The new load description will influence future decisions of the manager, in particular to which memory units allocation requests may be favourably forwarded.

A.4.8 Load Reports

Figure A.32 shows the memory manager’s behaviour after having decided to inform the neighbours about its actual load:

To each neighbour (and also to the master unit), a memory load message is sent holding the actual load of the module. Each of these messages carries the table position ‘linkAddr[i]’ at which this load description will be stored into the local array ‘memLoad’ of the neighbour module ‘i’ (for details, see Section A.4.3).

Appendix B

The ADAM Instruction Set

This chapter defines the operational semantics of the Adam instruction set. This set consists of 49 instructions that may be subdivided into four major classes:

1. Arithmetical-Logical Instructions,

2. Control Instructions,

3. Function Call Instructions,

4. Storage Instructions.

Figure B.1 shows a list of these instructions, their names, their types (see Section A.3.2) and one-line descriptions. The parenthesized name extensions denote special forms of the instructions. However, due to lack of space only the principal types are presented in the list.

When the processor unit starts the execution of an instruction sequence, it fetches an instruction word from the program store, exctracts its operation code and operand word and performs some task according to the value of the operation code.

Each instruction is therefore defined by a subalgorithm that describes the task the processor performs using the operand word of this instruction. Each subalgorithm is presented in a scheme of the form

         OPCODE Format

Description

Algorithm

where OPCODE is the operation code of the instruction, Format denotes the interpretation of the operand word as defined in Section A.3.2 and Description gives a short hint on the purpose of the instruction.

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 Actual 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 B.1: The Adam Instruction Set

B.1 Arithmetical-Logical Instructions

The arithmetical-logical instructions perform the “actual” computations, i.e. they construct new data from given ones. According to the number of operands each operation works on, they may be divided in three subclasses:

1. Binary Operations,

2. Binary Operations with one Constant Operand,

3. Unary Operations,

where the second class is a slight variant of the first one. In order to avoid duplications (and possible inconsistencies), we will describe these classes with the help of some standard functions that are given in the next section.

B.1.1 Standard Functions

The following statements define some mathematical functions with operands of type ‘Integer’ and results of type ‘Data’. ‘Integer’ is that subrange of the infinite type ‘Number’ that can be represented in one word of the frame store. ‘Data’ is basically ‘Integer’ augmented with an error flag indicating whether the value it holds is valid or not (see Section A.3.2). Each function converts its operands into the infinite type ‘Number’ and performs the desired operation on that type. If the result can be represented in type ‘Integer’ it is converted to that form, otherwise the error flag is set.

For more details on this topic, see Section A.3.2.

add(left, right): Integer × Integer → Data

Integer Addition

⟨result, error⟩ where

sum:Number = left:Number + right:Number; error:Boolean = sum ∉ [MinInt..MaxInt]; result:Integer =

if error then left else sum:Integer; end

sub(left, right): Integer × Integer → Data

Integer Subtraction B.1. ARITHMETICAL-LOGICAL INSTRUCTIONS 179

⟨result, error⟩

where

diff:Number = left:Number − right:Number; error:Boolean = diff ∉ [MinInt..MaxInt]; result:Integer =

if error then left else diff:Integer; end

mul(left, right): Integer × Integer → Data

Integer Multiplication

⟨result, error⟩

where prod:Number = left:Number ∗ right:Number; error:Boolean = prod ∉ [MinInt..MaxInt]; result:Integer =

if error then left else prod:Integer; end

div(left, right): Integer × Integer → Data

Integer Division

if right:Number = 0 then ⟨left, \underline{true}⟩ else ⟨result, error⟩

where quot:Number = left:Number / right:Number; error:Boolean = quot ∉ [MinInt..MaxInt]; result:Integer =

if error then left else quot:Integer; end

mod(left, right): Integer × Integer → Data

Integer Remainder Computation

if right:Number = 0 then ⟨left, \underline{true}⟩ else ⟨result, \underline{false}⟩

where b:Number = left:Number; a:Number = right:Number; result:Number = a − b ∗ (a/b)

end

and(left, right): Integer × Integer → Data

Boolean Conjunction

⟨left:Boolean ∧ right:Boolean, \underline{false}⟩

or(left, right): Integer × Integer → Data Boolean Disjunction

⟨left:Boolean ∨ right:Boolean, \underline{false}⟩;

equal(left, right): Integer × Integer → Data Test for Integer Equality

⟨left:Number = right:Number, \underline{false}⟩

less(left, right): Integer × Integer → Data Test for Integer Strict Order

⟨left:Number < right:Number, \underline{false}⟩

lesseq(left, right): Integer × Integer → Data Test for Integer Reflexive Order

⟨left:Number ≤ right:Number, \underline{false}⟩

B.1.2 Binary Instructions

The algorithms of all binary arithmetical-logical instructions are instances of the instruction scheme BINAL listed below. This scheme takes a function as argument that computes a result value of type ‘Data’ from two ‘Integer’ arguments.

         BINAL(f:  Integer × Integer → Data) OpReReIn

Binary Arithmetical-Logical Instructions

var regL, regR: Register; succ: InstOff; opL, opR: FramePtr; infL, infR: Info; errL, errR: Boolean; result: Data;

⟨regL, regR, succ⟩ := opWd:OpReReIn; opL := frameBase + regL:FramePtr; opR := frameBase + regR:FramePtr; ⟨infL, errL⟩ := frameStore[opL]:Data; ⟨infR, errR⟩ := frameStore[opR]:Data; errL := errL ∨ errR; if errL

then result := ⟨infL, \underline{true}⟩

else result := f(infL:Integer, infR:Integer) endif frameStore[opL] := result:FrameWord; instruction := instruction + succ:ProgPtr

The operand word of all binary arithmetical-logical instructions consists of the components ‘regL’, ‘regR’ and ‘succ’ where ‘regL’ and ‘regR’ denote register numbers within the current activation frame and ‘succ’ is the program offset to the successor instruction. The values of the denoted registers are loaded and their error flags are tested. If one of the operands is an error value, the result of the whole operation is also the error value, since all binary arithmetical-logical instructions demand their operands to be valid. Otherwise, the appropriate function is applied to the operands and the result value is stored into the first register. The instruction address is updated with the successor of the current instruction and a new cycle starts.

The following schemes define the semantics of all binary-arithmetical logical instructions by applying the BINAL to some of the functions defined in the previous section.

         ADD   OpReReIn

Integer Addition

BINAL(add)

         SUB     OpReReIn

Integer Subtraction

BINAL(sub)

MUL       OpReReIn

Integer Multiplication

BINAL(mul)

DIV  OpReReIn

Integer Division

BINAL(div)

MOD    OpReReIn

Integer Remainder

BINAL(mod)

AND      OpReReIn

Boolean Conjunction

BINAL(and)

OR       OpReReIn

Boolean Disjunction

BINAL(or)

EQUAL       OpReReIn

Test for Integer Equality

BINAL(equal)

LESS            OpReReIn

Test for Integer Strict Order

BINAL(less)

         LESSEQ             OpReReIn

Test for Integer Reflexive Order

BINAL(lesseq)

B.1.3 Binary Instructions with Constants

If binary instructions have a small constant as one of their arguments, this argument may be directly encoded in the instruction itself, as described in the scheme BINALC.

         BINALC(f:  Integer × Integer → Info)  OpCoReIn

Binary Arithmetical Logical Instructions with a Constant

var reg: Register; con: Constant; succ: InstOff; op: FramePtr; inf: Info; val: Value; err: Boolean;

⟨con, reg, succ⟩ := opWd:OpReCoIn; op := frameBase + reg:FramePtr; ⟨inf, err⟩ := frameStore[op]:Data; if err

then result := ⟨inf, \underline{true}⟩;

else result := f(inf:Integer, con:Integer) endif frameStore[op] := result:FrameWord; instruction := instruction + succ:ProgPtr

This scheme is similar to the BINAL scheme of the previous section, except the fact that the second operand does not denote a register number but a constant value itself. This integer value needs not be tested for the error flag, since only proper integer constants may be encoded in an instruction.

The following schemes define the semantics of binary instructions with one constant argument by applying the BINALC scheme to some of the functions defined in Section B.1.1.

The instructions SUBRC, DIVRC, MODRC, LESSRC, LESSEQRC denote instructions, where the register and the constant have changed their input order in the case of non-commutative functions.

         ADDC              OpCoReIn

Integer Addition of a Constant

BINALC(add)

SUBC                OpCoReIn

Integer Subtraction of a Constant

BINALC(sub)

SUBRC                        OpCoReIn

Reverse Integer Subtraction with a Constant

BINALC(subr)

where

subr(left, right): Integer × Integer → Data =

sub(right, left) end

MULC                    OpCoReIn

Integer Multiplication with a Constant

BINALC(mul)

DIVC              OpCoReIn

Integer Division by a Constant

BINALC(div)

DIVRC                    OpCoReIn

Reverse Integer Division by a Constant

BINALC(divr)

where

divr(left, right): Integer × Integer → Data =

div(right, left) end

MODC                OpCoReIn

Integer Remainder by a Constant

BINALC(mod)

MODRC                       OpCoReIn

Reverse Integer Remainder with a Constant

BINALC(modr)

where

modr(left, right): Integer × Integer → Data =

mod(right, left) end

EQUALC               OpCoReIn

Integer Constant Test for Equality

BINALC(equal)

LESSC                   OpCoReIn

Integer Constant Test for Strict Order

BINALC(less)

LESSRC                         OpCoReIn

Reverse Integer Constant Test for Strict Order

BINALC(lessr)

where

lessr(left, right): Integer × Integer → Data =

less(right, left) end

LESSEQC                    OpCoReIn

Integer Constant Test for Reflexive Order 186 APPENDIX B. THE ADAM INSTRUCTION SET

BINALC(lesseq)

LESSEQRC                          OpCoReIn

Reverse Integer Constant Test for Reflexive Order

BINALC(lesseqr)

where

lesseqr(left, right): Integer × Integer → Data =

lesseq(right, left) end

B.1.4 Unary Instructions

The instruction scheme UNAL defines the behaviour of most of the unary arithmetical-logical instructions. Similar to the binary scheme, the right operand is fetched from the frame store, then it is tested for its error flag. If the error flag is set to \underline{true}, the error flag of the result value is also set. Otherwise, a unary function is called and the result is stored into the frame store.

UNAL(f: Integer → Info)           OpReReIn

Scheme for Unary Arithmetical-Logical Instructions

var regL, regR: Register; succ: InstOff; infL, infR: Info; errR: Boolean; result: Data;

⟨regL, regR, succ⟩ := opWd:OpReReIn; ⟨infR, errR⟩ := frameStore[frameBase+regR:FramePtr]:Data; if errR

then result := ⟨infR, \underline{true}⟩ else result := f(infR:Integer)

endif; frameStore[frameBase+regL:FramePtr] := result:FrameWord; instruction := instruction + succ:ProgPtr

NEG   OpReReIn

Integer Negation

UNAL(neg)

where

neg(i): Integer → Data = ⟨result, error⟩

where negi:Number = − i:Number; error:Boolean = negi ∉ [MinInt..MaxInt]; result:Integer = if error then i else negi;

end end

         NOT   OpReReIn

Boolean Negation

UNAL(not) where

not(i): Integer → Data = ⟨¬ i:Boolean, \underline{false}⟩ end

The instruction FAILED tests the errror flag of its operand, and delivers as its result a boolean value that says whether the value is the error value or not.

         FAILED   OpReReIn

Test for Error Value

var regL, regR: Register; infL, infR: Info; errR, delR: Boolean; mod: ModAddr;

⟨regL, regR, succ⟩ := opWd:OpReReIn;

⟨infR, errR⟩ := frameStore[frameBase+opR:FramePtr]:Data; infL := (errR:Boolean = \underline{true}); frameStore[frameBase+opL:FramePtr] := ⟨infL, \underline{false}⟩:FrameWord; instruction := instruction + succ:ProgPtr

B.2 Control Instructions

Most of the control instructions route the control flow of the execution, some of them perform related tasks.

B.2.1 Split Control Flow

The SPLIT instruction is the only instruction with two successors. It forks an instruction sequence into two parallel program paths:

The first successor field of the operand word is used to form a trigger that is stored it into the trigger store. Thereby, this successor is declared as the begin of a parallel task which the processor may handle laterwards. The instruction sequence is continued with the instruction that is denoted by the second successor field of the operand word.

         SPLIT                     OpInIn

Split Control Flow Into Parallel Paths

var succ1, succ2: InstOff;

⟨succ1, succ2⟩ := opWd:OpInIn;

Put ⟨context,

instruction−codeBase+succ1:ProgPtr⟩:TrigWord into trigStore; instruction := instruction + succ2:ProgPtr

B.2.2 Duplicate Atomic Values

The instructions COPYI and COPYP are used to duplicate atomic values (i.e. integers) and pointers (i.e. tuple references), respectively: The contents of the frame register denoted by the second operand are copied into the register denoted by the first one and execution is continued with the successor instruction.

         COPYI         OpReReIn

Duplicate an Integer Value

var regL, regR: Register; succ: InstOff;

⟨regL, regR, succ⟩ := opWd:OpReReIn; frameStore[frameBase+regL:FramePtr] :=

frameStore[frameBase+regR:FramePtr] instruction := instruction + succ:ProgPtr

In the case of the COPYP instruction, the task gets significantly more complicated:

Before a tuple reference may be duplicated, the processor has to look up its weight. If the weight is greater than 1 = 2^{0}, the reference weight is divided by 2 (i.e. the binary logarithm stored in the weight field is decreased by 1) and the modified pointer is copied. If the field has already reached its minimum, an increase request is formed and sent to the memory unit that the referenced tuple holds. The instruction sequence has then to be interrupted. It is continued, as soon as the memory unit sends a message token to the interrupted COPYP instruction. This message delivers a new pointer that references the same tuple as before but has a weigth greater than zero and may therefore be duplicated without further trouble.

For more details on the weighted reference counting algorithm for garbage collection, see Section 4.5.4.

B.2. CONTROL INSTRUCTIONS 189

COPYP         OpReReIn

Duplicate a Tuple Pointer

var regL, regR: Register; succ: InstOff; ref: TupleRef; weight: RefWght; del, err: Boolean; mod: ModAddr; tuple: TupleAddr;

⟨regL, regR, succ⟩ := opWd:OpReReIn; ⟨⟨mod, ref⟩, err⟩ :=

frameStore[frameBase+regR:FramePtr]:Data; ⟨tuple, weight⟩ := ref:TupleRef; if err | tuple = 0:TupleAddr then weight := \underline{MaxWght}:RefWght endif; if weight:Number > 0 then

frameStore[frameBase+regL:FramePtr] :=

⟨⟨mod, ⟨tuple, weight−1:Weight⟩⟩, err⟩:FrameWord; frameStore[frameBase+regR:FramePtr] :=

⟨⟨mod, ⟨tuple, weight−1:Weight⟩⟩, err⟩:FrameWord; instruction := instruction + succ:ProgPtr

else

inpProc := ⟨mod, \underline{incRef},

⟨context, offset, \underline{ActMod}, ⟨ref, err⟩⟩⟩:NetLink; break := \underline{true}

endif

B.2.3 Delete Pointer Values

For correct garbage collection, each memory unit has to be informed if a reference to one of the tuples it holds is deleted, i.e. if the pointer value in a frame register is no more used. The instruction DELP looks up the contents of the denoted register and sends a decrement message to that memory unit that holds the referenced tuple. Having submitted this message, the pointer may be deleted, i.e. the register may be overwritten. For more details on the garbage collection mechanism, see Section 4.5.4.

         DELP       OpReIn

Delete Pointer Value

var reg: Register; succ: InstOff; mod: ModAddr; ref: TupleRef; err: Boolean;

⟨reg, succ⟩ := opWd:OpReIn;

⟨⟨mod, ref⟩, err⟩ := frameStore[frameBase+reg:FramePtr]:Data; if ¬err & (ref:Number ≠ 0) then

inpProc := ⟨mod, \underline{decRef}, ⟨ref, err⟩⟩:NetLink; endif; instruction := instruction + succ:CodePtr

B.2.4 Route Control Flow

The SWITCH instruction is the only instruction whose successor is not fixed but depends on the value of its register operand. The operand field of this instruction consists of three parts, one register number and two instruction offsets.

• If the specified register contains the boolean value \underline{true}, the control flow is routed to the instruction referenced by the first instruction offset, i.e. the instruction counter is increased by this offset.

• If the register contains the value \underline{false}, the control flow is routed to the instruction denoted by the second instruction offset.

• If the register contains an error value, the instruction sequence continues with that instruction that is immediately next in the program store.

         SWITCH   OpReInIn

Switch Control Flow Almost all instructions have the addresses of their successors directly encoded in their operand words as offsets to the actual instruction addresses. However, the maximum instruction offset that may be represented in this way may be to small to express the desired jump. If the compiler is not able to avoid such a “long jump” by code shifts or if “backward jumps” are necessary, the instruction JUMP may be used. The operand field of this instruction just contains a large signed instruction offset. The program counter is updated by this offset and thus a jump to the desired instruction is performed.

var

reg: Register; succ1, succ2: InstOff; err: Boolean; ⟨reg, succ1, succ2⟩ := opWd:OpReInIn; ⟨info, err⟩ := frameStore[frameBase+reg:FramePtr]; if err then

instruction := instruction + 1:ProgPtr else if info:Boolean = \underline{true} then

instruction := instruction + succ1:ProgPtr else

instruction := instruction + succ2:ProgPtr endif

         JUMP        OpJu

Route Control Flow

instruction := instruction + opWd:ProgPtr

B.2.5 Wait for Synchronization Event

The WAIT instruction is the only tool for synchronization between parallel instruction paths. This instruction has two arguments, an event number and an instruction offset. The event number is a reference to a certain synchronization bit within the actual activation frame (see Section 4.4.2). The value of this synchronization bit is fetched and immediately inverted in the frame. If the bit has the value \underline{true}, the synchronization event has already been triggered b a parallel task and the current instruction sequence may continue. Otherwise, the sequence has to be interrupted.

         WAIT                 OpEvIn

Wait for Synchronization Event

var event: Event; succ: InstOff; slotNum, bitNum: Number; frameAddr: FramePtr; isSet: Boolean;

⟨event, succ⟩ := opWd:OpEvIn;

slotNum := event:Number / FrameWordLen;

bitNum := event:Number − FrameWordLen∗slotNum; frameAddr := frameBase − (slotNum+3):FramePtr; GetAndInvertBit(isSet, bitNum, frameAddr); if isSet

then instruction := instruction + succ:CodePtr; else break := \underline{true}

endif

B.2.6 Deliver Long Constant

If binary instructions have a small constant as one of their arguments, this constant may be encoded directly in the instruction itself (see Section B.1.3). However, large constants as well as error constants have to be explicitely generated by the CONST instruction. This instruction assumes, that the next word in the program store does not contain an instruction but a numeric constant (flagged with an error bit). This constant is read and stored into a specified register and the instruction sequence is continued.

         CONST          OpReIn

Generate Large Constant

var reg: Register; succ: InstOff;

⟨reg, succ⟩ := opWd:OpReIn;

frameStore[FrameBase+reg:FramePtr] :=

progStore[instruction+1:ProgPtr]:FrameWord; instruction := instruction + succ:ProgPtr

B.3 Function Call Instructions

The following instruction are used to handle function calls, in particular to submit call requests and to submit function arguments.

B.3.1 Call Requests

In the Adam architecture, there are two kinds of call requests, fast calls and tail calls. Both of them submit call request tokens into the network containing

1. the description of the function to activate and

2. the address of the instruction to that the description of the new function activation is to be sent.

This token is sent to that processor unit among the neighbour modules that the actual processor thinks to be least loaded. Then the instruction sequence is interrupted. As soon as the description of the new function activation has arrived, this is continued and the arguments may be sent to the activation.

The essential distinction between fast calls and tail calls is based on the fact how the address of the instruction is transmitted that shall receive the result of the activation:

Fast Calls are used, if the address of the instruction denoted by the call request (that will receive the activation description) also implies the address of the instruction that the activation’s result is to be sent to. In detail, the “result instruction” has to be sited in the program store immediately after the “activation instruction”.

Tail-Recursive Calls are used, if the result address is submitted in a seperate context message. This is especially useful, if the result value of a function is not submitted to the caller of the function but to another function activation. This feature has been introduced in order to optimize tail recursive function calls.

For more details on how to generate code for function calls, see Chapter 5.

Fast calls are generated by the instruction FASTCALL.

         FASTCALL                  OpFuIn

Request Standard Function Activation

var fun: FunDescr; mod: ModAddr; succ: InstOff;

⟨fun, succ⟩ := opWd:OpFuIn; mod := MinLoaded(procLoad

triggerLoad, frameLoad, tableLoad, messageLoad, requestLoad); inpProc := ⟨mod, \underline{fCall},

⟨context, instruction-codeBase+succ:CodePtr, \underline{ActMod}, fun⟩⟩:NetLink;

break := \underline{true}

Tail-recursive calls are generated by the instruction TAILCALL.

         TAILCALL                      OpFuIn

Request Tail-Recursive Function Activation

var fun: FunDescr; mod: ModAddr; succ: InstOff;

⟨fun, succ⟩ := opWd:OpFuIn; mod := MinLoaded(procLoad

triggerLoad, frameLoad, tableLoad, messageLoad, requestLoad); inpProc := ⟨mod, \underline{tCall},

⟨context, instruction-codeBase+succ:CodePtr, \underline{ActMod}, fun⟩⟩:NetLink;

break := \underline{true}

The CONTEXT instruction submits the result address stored in the frame of the current function activation to a newly created activation. Together with the instruction TAILCALL, this feature can be used to optimize tail recursive calls in following way: Each instance in a sequence of tail recursive calls uses TAILCALL to forward the result address of its caller, then submits the arguments to the next instance and finally terminates. The last instance in the chain will return its result directly to the caller of the first instance without passing all intermediate instances.

         CONTEXT                    OpReIn

Submit Actual Result Address to Callee

var reg: Register; succ: InstOff; mod, resMod: ModAddr; cont: ContextPtr; resAddr: Trigger;

⟨reg, succ⟩ := opWd:OpReIn;

⟨mod, cont⟩ := frameStore[frameBase+reg:FramePtr]:ActivAddr; resMod := frameStore[frameBase-2]:ModAddr; resAddr := frameStore[frameBase-1]:Trigger; inpProc := ⟨mod, \underline{cMess}, ⟨resAddr, resMod, cont⟩⟩:NetLink; instruction := instruction + succ:CodePtr;

The APPLY instruction performs the same task as FASTCALL for variable function arguments. Its first argument denotes a register that contains the description of a function. This function descriptor is fetched and a call request is submitted. Since the function descriptor is not fixed, it may also be the error value. In this case, the request message is cancelled and error values are sent to the instructions that wait for the context of the function activation and the result value of the function call, respectively.

         APPLY                          OpReIn

Request Activation of a Non-Fixed Function

var reg, reg1, reg2: Register; succ: InstOff; fun: FunDescr; mod: ModAddr;

⟨reg, succ⟩ := opWd:OpReIn;

⟨⟨fun, ⟩, err⟩ := frameStore[frameBase+reg:FramePtr]:Data; if err then

⟨ , ⟨ , reg1, ⟩⟩ :=

progStore[instruction+succ:ProgPtr]:OpReReIn; ⟨ , ⟨ , reg2, ⟩⟩ :=

progStore[instruction+succ:ProgPtr+1:ProgPtr]:OpReReIn; frameStore[frameBase+reg1:FramePtr]:=

⟨ , \underline{true}⟩:FrameWord; frameStore[frameBase+reg2:FramePtr] :=

⟨ , \underline{true}⟩:FrameWord; else

mod := MinLoaded(procLoad,

triggerLoad, frameLoad, tableLoad, messageLoad, requestLoad); inpProc := ⟨mod, \underline{fCall},

⟨context, instruction-codeBase+succ:CodePtr, \underline{ActMod}, fun⟩⟩:NetLink;

endif

break := \underline{true}

B.3.2 Sending of Arguments and Result Values

The SEND instruction has two registers and a small constant as its arguments. The first register contains the module address and context number of a function activation, the second one the argument to submit. The constant denotes the parameter number of the argument, i.e. the offset address of the instruction that the argument is to be sent to. SEND constructs a message token and sends the argument to the appropriate instruction in the specified function activation. The instruction sequence is continued with the immediately next instruction in the program store.

However, if the description of the function activation is actually an error value, SEND cancels the transmission the argument and continues the instruction sequence.

         SEND                      OpReReCo

Send Argument to a Function Activation

var regL, regR: Register; n: Constant; arg: FrameWord; mod: ModAddr; context: ContextPtr; err: Boolean;

⟨regL, regR, n⟩ := opWd:OpReReCo;

arg := frameStore[FrameBase+regL:FramePtr]; ⟨⟨mod, context⟩, err⟩ :=

frameStore[FrameBase+regR:FramePtr]:ActivAddr; if ¬err then

inpProc := ⟨mod, \underline{mess}, ⟨context, n:BlockOff, arg⟩⟩:NetLink endif; instruction := instruction + 1:CodePtr

The instruction SENDC performs the same task as the SEND instruction for arguments that are small constant integer values.

         SENDC                      OpCoReIn

Send a Constant to a Function Activation

var reg: Register; c, n: Constant; mod: ModAddr; context: ContextPtr; err: Boolean;

⟨c, reg, n⟩ := opWd:OpCoReCo;

⟨⟨mod, context⟩, err⟩ :=

frameStore[FrameBase+reg:FramePtr]:ActivAddr; if ¬err then

inpProc :=

⟨mod, \underline{mess}, ⟨context, n:BlockOff, ⟨c, \underline{false}⟩⟩⟩:NetLink endif; instruction := instruction + 1:CodePtr

The SUBMIT instruction triggers the transmission of the fixed arguments of a curried function that are together with the description of the function stored in a tuple on the heap. The instruction has two register arguments ‘regL’ and ‘regR’: ‘regL’ contains the address of the tuple that holds the curried function, while ‘regR’ denotes the context of some recently created activation of that function. If the error bits of both arguments are not set, SUBMIT constructs a submission request holding the new context and sends this request to the memory unit where the tuple is stored. The memory unit will send the lifted function arguments to the denoted activation.

B.3. FUNCTION CALL INSTRUCTIONS 197

SUBMIT                             OpReReIn

Request Submission of Curried Function Arguments

var regL, regR: Register; succ: InstOff; mod, cMod: ModAddr; ref: TupleRef; context: ContPtr; err, cErr: Boolean;

⟨regL, regR, succ⟩ := opWd:OpReReIn; ⟨⟨mod, ref⟩, err⟩ :=

frameStore[frameBase+regL:FramePtr]:Pointer; ⟨⟨cMod, context⟩, cErr⟩ :=

frameStore[frameBase+regR:FramePtr]:ActivAddr; if ¬err ∧ ¬cErr then

inpProc := ⟨mod, \underline{submit}, ⟨context, cMod, ⟨ref, error⟩⟩⟩:NetLink; endif instruction := instruction + succ:ProgPtr;

The RETURN instruction is used to deliver the result value of a function activation. A value message is constructed that takes its value from the first operand register and is submitted to the instruction whose address is stored as result address in the current activation frame. Afterwards, the instruction sequence is continued as specified by the successor field of the operand word.

RETURN               OpReIn

Send Result Value back to Caller

var reg: Register; succ: InstOff; value: FrameWord; retMod: ModAddr; retCon: ContPtr; retIns: ProgPtr;

⟨reg, succ⟩ := opWd:OpReIn; value := frameStore[frameBase+reg:FramePtr]; retMod := frameStore[frameBase-2:FramePtr]:ModAddr; ⟨retCon, retIns⟩ := frameStore[frameBase-1:FramePtr]:Trigger; inpProc := ⟨retMod, \underline{mess}, ⟨retCon, retIns, value⟩⟩:NetLink; instruction := instruction + succ:ProgPtr

RETURNC performs the same task as RETURN except for the fact that the result value is encoded as a small integer constant in the instruction’s operand word.

         RETURNC                      OpCoIn

Send Constant Result Value back to Caller

var reg: Register; succ: InstOff; con: Constant; retMod: ModAddr; retCon: ContPtr; retIns: ProgPtr;

⟨con, succ⟩ := opWd:OpCoIn;

retMod := frameStore[frameBase-2:FramePtr]:ModAddr;

⟨retCon, retIns⟩ := frameStore[frameBase-1:FramePtr]:Trigger; inpProc := ⟨retMod, \underline{mess}, ⟨retCon, retIns, ⟨con:Integer, \underline{false}⟩⟩⟩:NetLink; instruction := instruction + succ:ProgPtr

B.3.3 Terminate Activation

The TERMINATE instruction is the only instruction that has no argument and no successor. Its single purpose is to deallocate the current activation frame and by this to terminate the actual function instance. When this instruction is executed, there must not exist any reference to this activation in the whole network. In particular, there must not exist any more triggers for this activation in the trigger store and there must not be any more messages on the way to this activation.

         TERMINATE             Op

Terminate Actual Activation

var events, regs: Number;

⟨events, regs⟩ := progStore[codeBase-1:ProgPtr]:Resource;

tableLoad := DeallocContext(context);

frameLoad := DeallocFrame(frameBase, events, regs); break := \underline{true}

B.4 Storage Instructions

The following sections define all instructions that deal with tuples stored in the memory units of the worker modules. The operations performed by these instructions can be classified as follows:

1. Allocating tuples,

2. Reading the contents of tuple fields,

3. Writing the contents of tuple fields.

B.4.1 Allocating Tuples

The processor’s task for the allocation of a tuple simply consists of sending an allocation message to some memory unit. For simplicity, allocation requests are always sent to the memory unit that resides on the same module as the actual processor unit. However, the memory unit is free to forward allocation requests to other memory units (e.g. if the required amount of storage is not available). After having sent the request, the instruction sequence is interrupted and continued as soon as the memory unit sends the address of the newly allocated tuple.

Each tuple may consist of an arbitrary but fixed number of atomic components (i.e. integer values or pointers). The number of the components required is encoded as a small constant within the operand part of the ALLOC instruction.

         ALLOC            OpCoIn

Request Allocation of Tuple

var n: Constant; succ: InstOff;

⟨n, succ⟩ := opWd:OpCoIn; inpProc := ⟨\underline{ActMod}, \underline{alloc},

⟨n, context, (instruction+succ-codeBase):BlockOff, \underline{ActMod}⟩⟩:NetLink;

break := \underline{true}

B.4.2 Reading the Contents of Tuple Fields

In order to receive the contents of some tuple field, the processor has to send a read request to the appropriate memory unit. The instruction READ performs this task.

A READ instruction has as arguments a register number, a small constant and an instruction offset. The specified register holds the description of a tuple (basically consisting of module address and tuple address). The small constant denotes the number of the tuple field to be read where 0 references the first field of the tuple.

Finally, the offset value defines the instruction to that the content of the tuple field are to be sent. Using these pieces of information, a read request token is formed and sent to the memory unit.

         READ              OpCoReIn

Read Contents of a Tuple Field

var n: Constant; reg, reg0: Register; succ: InstOff; inf: Info; err: Boolean; mod: ModAddr; tuple: TupleAddr;

⟨n, reg, succ⟩ := opWd:OpCoReIn;

⟨inf, err⟩ := frameStore[frameBase+reg:FramePtr]:Data; ⟨mod, tuple⟩ := inf:Pointer; if err | tuple = 0:TupleAddr then

⟨ , ⟨ , reg0, ⟩⟩ := progStore[instruction+succ:CodePtr]; frameStore[frameBase+reg0:FramePtr] := ⟨ , \underline{true}⟩:FrameWord; instruction := instruction + succ:CodePtr

else

Send ⟨mod, \underline{read},

⟨n, context, instruction+succ:CodePtr, \underline{ActMod}, ⟨tuple, err⟩⟩⟩:NetLink;

break := \underline{true} endif

B.4.3 Writing the Contents of Tuple Fields

In order to write the desired contents into some tuple field, the processor unit has to send a write request to the memory unit. A write request consists of the address of the specified memory unit, the address of the tuple in that unit, the number of the tuple field and the contents that shall be written into that field. In order to allow the correct collection of tuples that are not more used, the processor has to tell the memory unit whether the contents to be written are integer values or pointers that themselves reference other tuples.

Therefore, there exist two operations for writing the contents of a tuple field, WRITEI for integer values and WRITEP for pointers. A third instruction, WRITEC, is used for writing small integer constants that may be directly encoded into the instruction.

WRITEI and WRITEP have as arguments two registers and a small constant. The first register contains the datum to be written, while the second one holds the description of the tuple (i.e. module address and tuple address). The constant denotes the number of the tuple field to be written.

After having submitted the write request, program execution is continued with the instruction that is immediately next in the program store.

B.4. STORAGE INSTRUCTIONS 201

WRITEI             OpReReCo

Write Integer into a Tuple Field

var regL, regR: Register; n: Constant; data: Word; mod: ModAddr; tuple: TupleAddr; err: Boolean;

⟨regL, regR, n⟩ := opWd:OpReReCo; data := frameStore[frameBase+regL:FramePtr]:Data; ⟨⟨mod, tuple⟩, err⟩ := frameStore[frameBase+regR:FramePtr]:Data; if err then ErrorHalt endif; inpProc := ⟨mod, \underline{wrInt}, ⟨n, ⟨tuple, err⟩, data⟩⟩:NetLink; instruction := instruction + 1:CodePtr;

WRITEP             OpReReCo

Write Pointer into a Tuple Field

var regL, regR: Register; n: Constant; data: Word; mod: ModAddr; tuple: TupleAddr; err: Boolean;

⟨regL, regR, n⟩ := opWd:OpReReCo; data := frameStore[frameBase+regL:FramePtr]:Data; ⟨⟨mod, tuple⟩, err⟩ := frameStore[frameBase+regR:FramePtr]:AddrInfo; if err then ErrorHalt endif; inpProc ⟨mod, \underline{wrPtr}, ⟨n, ⟨tuple, err⟩, data⟩⟩:NetLink; instruction := instruction + 1:CodePtr;

The WRITEC instruction has the value to be written directly encoded in its operand word, which is only possible for small integer constants.

202 APPENDIX B. THE ADAM INSTRUCTION SET

WRITEC                     OpCoReCo

Write Integer Constant into a Tuple Field

var reg: Register; n, c: Constant; data: Word; mod: ModAddr; tuple: TupleAddr; err: Boolean;

⟨c, reg, n⟩ := opWd:OpCoReCo; data := ⟨c:Integer, \underline{false}⟩; ⟨⟨mod, tuple⟩, err⟩ := frameStore[frameBase+reg:FramePtr]:Data; if err then ErrorHalt endif; inpProc := ⟨mod, \underline{wrInt}, ⟨n, ⟨tuple, err⟩, data⟩⟩:NetLink; instruction := instruction + 1:ProgPtr;

Appendix C

The ADAM Source Code

A prototype of the Adam interpreter has been implemented in Occam 2 on a network of Inmos T800 transputers. This appendix contains the full source code of this interpreter (but not of the Adam assembly program). The source consists of of a sample network description (extension .PGM), the actual program files (extension .OCC) and several include files (extension .INC). This collection of files is organized into the following groups:

1. Network Description

This file contains a sample network description for a chordal ring topology consisting of 8 worker modules and one master module.

ADAM Network Description

File Name Description  Page
ADAM8.PGM Adam Network Description 205

2. General Include Files

These include files define several constants and protocols used in almost all procedures that the interpreter consists of.

General Include Files

File Name Description Page
ADERROR.INC Error Messages  212
ADGLOBAL.INC Global Constants  216
ADNET.INC  Network Parameters 218

3. The ADAM Master Module

This group consists of all source and include files used for the implementation of the Adam master module.

The Adam Master Module (Sources)

File Name  Description  Page ADCONN.OCC  Network Connection  220 ADGENER.OCC Result Generator  225 ADINOUT.OCC Input/Output Interface 247 ADSTAT.OCC Statistics Generator 265 ADMASTER.OCC Master Main Program 268

204 APPENDIX C. THE ADAM SOURCE CODE

The Adam Master Module (Includes)

File Name  Description  Page
ADSTAT.INC  Statistics Constants  270
ADMASTER.INC Master Module Parameters 271

4. The ADAM Worker Module This group consists of all source and include files used for the implementation of the Adam worker module.

The Adam Worker Module (Sources)

File Name  Description  Page

ADARBIT.OCC Arbiter  273

ADMEMORY.OCC Memory Unit  283
ADPROC.OCC Processor Unit 322
ADMODULE.OCC Worker Main Program 349

The Adam Worker Module (Includes)

File Name Description Page
ADARBIT.INC Arbiter Parameters  351
ADEXEC.INC  Processor Execution Unit 352
ADMEMORY.INC Memory Unit Parameters 396
ADPROC.INC  Processor Unit Parameters 398

ADAM8.PGM                                                                  Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADAM8.PGM                                              Wolfgang Schreiner
 4  -- Adam Configuration                                     (c) RISC-Linz 1990
 5  --
 6  -- A Topology Description for a Network of 8 Worker Modules.
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- External References
12  -----------------------------------------------------------------------------
13
14  -- include files
15  #INCLUDE "adglobal.inc" -- global constants
16  #INCLUDE "adnet.inc"    -- network constants and protocols
17  #INCLUDE "hostio.inc"   -- server constants and protocols
18  #INCLUDE "linkaddr.inc" -- link address constants
19
20  -- compilation units
21  #USE "admaster.c8h" -- the adam master module
22  #USE "admodule.c8h" -- the adam network module
23
24  -----------------------------------------------------------------------------
25  -- Network Channel Definitions:
26  --
27  --   net.i.j is the channel from worker module i to worker module j,
28  --   0 denotes the master module.
29  -----------------------------------------------------------------------------
30
31  -- input, output channels from/to server
32  CHAN OF SP fs, ts:
33
34  -- the ring connection
35  CHAN OF Net.Link net.0.1:
36  CHAN OF Net.Link net.1.0:
37  CHAN OF Net.Link net.1.2:
38  CHAN OF Net.Link net.2.1:
39  CHAN OF Net.Link net.2.3:
40  CHAN OF Net.Link net.3.2:
41  CHAN OF Net.Link net.3.4:
42  CHAN OF Net.Link net.4.3:
43  CHAN OF Net.Link net.4.5:
44  CHAN OF Net.Link net.5.4:
45  CHAN OF Net.Link net.5.6:
46  CHAN OF Net.Link net.6.5:
47  CHAN OF Net.Link net.6.7:
48  CHAN OF Net.Link net.7.6:
49  CHAN OF Net.Link net.7.8:
50  CHAN OF Net.Link net.8.7:
51  CHAN OF Net.Link net.8.0:
52  CHAN OF Net.Link net.0.8:
53
54  -- first chordal connection
55  CHAN OF Net.Link net.8.2:

206 APPENDIX C. THE ADAM SOURCE CODE

ADAM8.PGM                                                                  Page   2
56  CHAN OF Net.Link net.2.8:
57  CHAN OF Net.Link net.2.4:
58  CHAN OF Net.Link net.4.2:
59  CHAN OF Net.Link net.4.6:
60  CHAN OF Net.Link net.6.4:
61  CHAN OF Net.Link net.6.8:
62  CHAN OF Net.Link net.8.6:
63
64  -- second chordal connection
65  CHAN OF Net.Link net.1.3:
66  CHAN OF Net.Link net.3.1:
67  CHAN OF Net.Link net.3.5:
68  CHAN OF Net.Link net.5.3:
69  CHAN OF Net.Link net.5.7:
70  CHAN OF Net.Link net.7.5:
71  CHAN OF Net.Link net.7.1:
72  CHAN OF Net.Link net.1.7:
73
74  -----------------------------------------------------------------------------
75  -- The Adam Topology:
76  --
77  --   8 worker modules are connected to a ring (with the master module being
78  --   linked between workers 8 and 1). Additionally, there are two rings
79  --   connecting modules 1, 3, 5, 7 and 2, 4, 6, 8 respectively.
80  --
81  --   For further explanation of the following procedure arguments, see the
82  --   source file 'ADMASTER.OCC' for the master module and the source file
83  --   'ADMODULE.OCC' for the worker modules.
84  --
85  -----------------------------------------------------------------------------
86  PLACED PAR
87
88    ---------------------------------------------------------------------------
89    -- master module on root transputer
90    ---------------------------------------------------------------------------
91    PROCESSOR 0 T800
92
93      -- place channels on links
94      PLACE fs      AT link0.in:
95      PLACE ts      AT link0.out:
96      PLACE net.1.0 AT link1.in:
97      PLACE net.0.1 AT link1.out:
98      PLACE net.8.0 AT link2.in:
99      PLACE net.0.8 AT link2.out:
100
101      -- the master module
102      Adam.Master(net.1.0, net.0.8, net.8.0, net.0.1, fs, ts)
103
104    ---------------------------------------------------------------------------
105    -- worker module on transputer 1
106    ---------------------------------------------------------------------------
107    PROCESSOR 1 T800
108
109      -- place channels
110      PLACE net.3.1  AT link0.in:

ADAM8.PGM                                                                  Page   3
111      PLACE net.1.3  AT link0.out:
112      PLACE net.0.1  AT link1.in:
113      PLACE net.1.0  AT link1.out:
114      PLACE net.7.1  AT link2.in:
115      PLACE net.1.7  AT link2.out:
116      PLACE net.2.1  AT link3.in:
117      PLACE net.1.2  AT link3.out:
118
119      -- dummy arrays of network channels
120      [4]CHAN OF Net.Link in:  -- input from neighbours
121      [4]CHAN OF Net.Link out: -- output to neighbours
122
123      -- overlay channel arrays on links
124      PLACE in  AT link0.in:
125      PLACE out AT link0.out:
126
127      -- network module 1
128      Adam.Module(1(BYTE),
129        [3(BYTE), 8(BYTE), 7(BYTE), 2(BYTE)],
130        [2(BYTE), 3(BYTE), 0(BYTE), 1(BYTE)],
131        [1, 255, 3, 0, 3, 0, 2, 2, 1], 1,
132        [TRUE, FALSE, FALSE, TRUE], in, out)
133
134    ---------------------------------------------------------------------------
135    -- worker module on transputer 2
136    ---------------------------------------------------------------------------
137    PROCESSOR 2 T800
138
139      -- place channels
140      PLACE net.4.2  AT link0.in:
141      PLACE net.2.4  AT link0.out:
142      PLACE net.1.2  AT link1.in:
143      PLACE net.2.1  AT link1.out:
144      PLACE net.8.2  AT link2.in:
145      PLACE net.2.8  AT link2.out:
146      PLACE net.3.2  AT link3.in:
147      PLACE net.2.3  AT link3.out:
148
149      -- dummy arrays of network channels
150      [4]CHAN OF Net.Link in:  -- input from neighbours
151      [4]CHAN OF Net.Link out: -- output to neighbours
152
153      -- overlay channel arrays on links
154      PLACE in  AT link0.in:
155      PLACE out AT link0.out:
156
157      -- network module 2
158      Adam.Module(2(BYTE),
159        [4(BYTE), 1(BYTE), 8(BYTE), 3(BYTE)],
160        [2(BYTE), 3(BYTE), 0(BYTE), 1(BYTE)],
161        [1, 1, 255, 3, 0, 3, 0, 2, 2], 1,
162        [FALSE, FALSE, FALSE, FALSE], in, out)
163
164    ---------------------------------------------------------------------------
165    -- worker module on transputer 3

ADAM8.PGM                                                                  Page   4
166    ---------------------------------------------------------------------------
167    PROCESSOR 3 T800
168
169      -- place channels
170      PLACE net.5.3  AT link0.in:
171      PLACE net.3.5  AT link0.out:
172      PLACE net.2.3  AT link1.in:
173      PLACE net.3.2  AT link1.out:
174      PLACE net.1.3  AT link2.in:
175      PLACE net.3.1  AT link2.out:
176      PLACE net.4.3  AT link3.in:
177      PLACE net.3.4  AT link3.out:
178
179      -- dummy arrays of network channels
180      [4]CHAN OF Net.Link in:  -- input from neighbours
181      [4]CHAN OF Net.Link out: -- output to neighbours
182
183      -- overlay channel arrays on links
184      PLACE in  AT link0.in:
185      PLACE out AT link0.out:
186
187      -- network module 3
188      Adam.Module(3(BYTE),
189        [5(BYTE), 2(BYTE), 1(BYTE), 4(BYTE)],
190        [2(BYTE), 3(BYTE), 0(BYTE), 1(BYTE)],
191        [2, 2, 1, 255, 3, 0, 3, 0, 1], 2,
192        [TRUE, FALSE, FALSE, TRUE], in, out)
193
194    ---------------------------------------------------------------------------
195    -- worker module on transputer 4
196    ---------------------------------------------------------------------------
197    PROCESSOR 4 T800
198
199      -- place channels
200      PLACE net.6.4  AT link0.in:
201      PLACE net.4.6  AT link0.out:
202      PLACE net.3.4  AT link1.in:
203      PLACE net.4.3  AT link1.out:
204      PLACE net.2.4  AT link2.in:
205      PLACE net.4.2  AT link2.out:
206      PLACE net.5.4  AT link3.in:
207      PLACE net.4.5  AT link3.out:
208
209      -- dummy arrays of network channels
210      [4]CHAN OF Net.Link in:  -- input from neighbours
211      [4]CHAN OF Net.Link out: -- output to neighbours
212
213      -- overlay channel arrays on links
214      PLACE in  AT link0.in:
215      PLACE out AT link0.out:
216
217      -- network module 4
218      Adam.Module(4(BYTE),
219        [6(BYTE), 3(BYTE), 2(BYTE), 5(BYTE)],
220        [2(BYTE), 3(BYTE), 0(BYTE), 1(BYTE)],

ADAM8.PGM                                                                  Page   5
221        [2, 1, 2, 1, 255, 3, 0, 3, 0], 1,
222        [FALSE, FALSE, FALSE, FALSE], in, out)
223
224    ---------------------------------------------------------------------------
225    -- worker module on transputer 5
226    ---------------------------------------------------------------------------
227    PROCESSOR 5 T800
228
229      -- place channels
230      PLACE net.7.5  AT link0.in:
231      PLACE net.5.7  AT link0.out:
232      PLACE net.4.5  AT link1.in:
233      PLACE net.5.4  AT link1.out:
234      PLACE net.3.5  AT link2.in:
235      PLACE net.5.3  AT link2.out:
236      PLACE net.6.5  AT link3.in:
237      PLACE net.5.6  AT link3.out:
238
239      -- dummy arrays of network channels
240      [4]CHAN OF Net.Link in:  -- input from neighbours
241      [4]CHAN OF Net.Link out: -- output to neighbours
242
243      -- overlay channel arrays on links
244      PLACE in  AT link0.in:
245      PLACE out AT link0.out:
246
247      -- network module 5
248      Adam.Module(5(BYTE),
249        [7(BYTE), 4(BYTE), 3(BYTE), 6(BYTE)],
250        [2(BYTE), 3(BYTE), 0(BYTE), 1(BYTE)],
251        [0, 2, 1, 2, 1, 255, 3, 0, 3], 2,
252        [FALSE, FALSE, FALSE, FALSE], in, out)
253
254    ---------------------------------------------------------------------------
255    -- worker module on transputer 6
256    ---------------------------------------------------------------------------
257    PROCESSOR 6 T800
258
259      -- place channels
260      PLACE net.8.6  AT link0.in:
261      PLACE net.6.8  AT link0.out:
262      PLACE net.5.6  AT link1.in:
263      PLACE net.6.5  AT link1.out:
264      PLACE net.4.6  AT link2.in:
265      PLACE net.6.4  AT link2.out:
266      PLACE net.7.6  AT link3.in:
267      PLACE net.6.7  AT link3.out:
268
269      -- dummy arrays of network channels
270      [4]CHAN OF Net.Link in:  -- input from neighbours
271      [4]CHAN OF Net.Link out: -- output to neighbours
272
273      -- overlay channel arrays on links
274      PLACE in  AT link0.in:
275      PLACE out AT link0.out:

ADAM8.PGM                                                                  Page   6
276
277      -- network module 6
278      Adam.Module(6(BYTE),
279        [8(BYTE), 5(BYTE), 4(BYTE), 7(BYTE)],
280        [2(BYTE), 3(BYTE), 0(BYTE), 1(BYTE)],
281        [0, 3, 2, 1, 2, 1, 255, 3, 0], 0,
282        [FALSE, FALSE, FALSE, FALSE], in, out)
283
284    ---------------------------------------------------------------------------
285    -- worker module on transputer 7
286    ---------------------------------------------------------------------------
287    PROCESSOR 7 T800
288
289      -- place channels
290      PLACE net.1.7  AT link0.in:
291      PLACE net.7.1  AT link0.out:
292      PLACE net.6.7  AT link1.in:
293      PLACE net.7.6  AT link1.out:
294      PLACE net.5.7  AT link2.in:
295      PLACE net.7.5  AT link2.out:
296      PLACE net.8.7  AT link3.in:
297      PLACE net.7.8  AT link3.out:
298
299      -- dummy arrays of network channels
300      [4]CHAN OF Net.Link in:  -- input from neighbours
301      [4]CHAN OF Net.Link out: -- output to neighbours
302
303      -- overlay channel arrays on links
304      PLACE in  AT link0.in:
305      PLACE out AT link0.out:
306
307      -- network module 7
308      Adam.Module(7(BYTE),
309        [1(BYTE), 6(BYTE), 5(BYTE), 8(BYTE)],
310        [2(BYTE), 3(BYTE), 0(BYTE), 1(BYTE)],
311        [0, 0, 3, 2, 1, 2, 1, 255, 3], 3,
312        [FALSE, FALSE, FALSE, FALSE], in, out)
313
314    ---------------------------------------------------------------------------
315    -- worker module on transputer 8
316    ---------------------------------------------------------------------------
317    PROCESSOR 8 T800
318
319      -- place channels
320      PLACE net.2.8  AT link0.in:
321      PLACE net.8.2  AT link0.out:
322      PLACE net.7.8  AT link1.in:
323      PLACE net.8.7  AT link1.out:
324      PLACE net.6.8  AT link2.in:
325      PLACE net.8.6  AT link2.out:
326      PLACE net.0.8  AT link3.in:
327      PLACE net.8.0  AT link3.out:
328
329      -- dummy arrays of network channels
330      [4]CHAN OF Net.Link in:  -- input from neighbours

ADAM8.PGM                                                                  Page   7
331      [4]CHAN OF Net.Link out: -- output to neighbours
332
333      -- overlay channel arrays on links
334      PLACE in  AT link0.in:
335      PLACE out AT link0.out:
336
337      -- network module 8
338      Adam.Module(8(BYTE),
339        [2(BYTE), 7(BYTE), 6(BYTE), 1(BYTE)],
340        [2(BYTE), 3(BYTE), 0(BYTE), 1(BYTE)],
341        [3, 3, 0, 0, 2, 1, 2, 1, 255], 3,
342        [FALSE, TRUE, TRUE, FALSE], in, out)
343
344  -----------------------------------------------------------------------------
345  -- End of ADAM8.OCC
346  -----------------------------------------------------------------------------

ADERROR.INC                                                                Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADERROR.IN                                            Wolfgang Schreiner
 4  -- Error Messages                                        (c) RISC-Linz 1990
 5  --
 6  -- Includes all error messages used in the Adam machine.
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- head of all Adam assertion errors
12  -----------------------------------------------------------------------------
13
14  VAL []BYTE adam.assert IS "ADAM assertion failed: ":
15
16  -----------------------------------------------------------------------------
17  -- master module error messages
18  -----------------------------------------------------------------------------
19
20  -- file name too long
21  VAL []BYTE file.len   IS "Interface> File name too long.":
22
23  -- missing file names
24  VAL []BYTE prog.miss  IS "Interface> Program file name missing.":
25  VAL []BYTE expr.miss  IS "Interface> Expression file name missing.":
26
27  -- replacing file extensions
28  VAL []BYTE file.ext   IS "Interface> Error when replacing file extension.":
29
30  -- program file errors
31  VAL []BYTE prog.open  IS "Interface> Error when opening program file.":
32  VAL []BYTE prog.read  IS "Interface> Error when reading program file.":
33  VAL []BYTE prog.close IS "Interface> Error when closing program file.":
34
35  -- expression file errors
36  VAL []BYTE expr.open  IS "Interface> Error when opening expression file.":
37  VAL []BYTE expr.read  IS "Interface> Error when reading expression file.":
38  VAL []BYTE expr.close IS "Interface> Error when closing expression file.":
39
40  -- type file errors
41  VAL []BYTE type.open  IS "Interface> Error when opening type descriptor file.":
42  VAL []BYTE type.read  IS "Interface> Error when reading type descriptor file.":
43  VAL []BYTE type.close IS "Interface> Error when closing type descriptor file.":
44  VAL []BYTE type.err   IS "Interface> Error when compiling type description.":
45
46  -- output file errors
47  VAL []BYTE out.open   IS "Interface> Error when opening ouput file.":
48  VAL []BYTE out.write  IS "Interface> Error when writing to output file.":
49  VAL []BYTE out.close  IS "Interface> Error when closing output file.":
50
51  -- temporary file errors
52  VAL []BYTE tmp.open   IS "Interface> Error when opening temporary file.":
53  VAL []BYTE tmp.read   IS "Interface> Error when reading temporary file.":
54  VAL []BYTE tmp.write  IS "Interface> Error when writing to temporary file.":
55  VAL []BYTE tmp.close  IS "Interface> Error when closing temporary file.":

ADERROR.INC                                                                Page   2
56  VAL []BYTE tmp.delete IS "Interface> Error when deleting temporary file.":
57
58  -- keyboard input errors
59  VAL []BYTE key.read   IS "Interface> Error when reading keyboard input.":
60
61  -- type descriptor compilation errors
62  VAL []BYTE type.label IS "Generator> Invalid label in type desription.":
63  VAL []BYTE type.redef IS "Generator> Type label redefined.":
64
65  -- invalid token type
66  VAL []BYTE token.type IS "Generator> Invalid token received.":
67
68  -- invalid union variant
69  VAL []BYTE union.err  IS "Generator> Invalid union variant received.":
70
71  -- no load token receved
72  VAL []BYTE load.exp   IS "Statistics> Load token expected.":
73
74  -- string concatentation failed
75  VAL []BYTE str.cat    IS "Statistics> String concatenation failed.":
76
77  -- append signal received at invalid time
78  VAL []BYTE app.err   IS  "Connector> Invalid append signal received.":
79
80  -- write operation failed
81  VAL []BYTE write.err IS  "Interface> Error when writing string to file.":
82
83  -----------------------------------------------------------------------------
84  -- processor unit error messages
85  -----------------------------------------------------------------------------
86
87  -- program store too small for program
88  VAL []BYTE prog.full  IS "Execution> Program Store Full.":
89
90  -- message buffer or request buffer is not a multiple of token size
91  VAL []BYTE buff.err   IS "Processor> Buffer Invalid Size.":
92
93  -- message buffer or request buffer full
94  VAL []BYTE buff.full  IS "Processor> Buffer Full.":
95
96  -- program constant 0 encountered in division or remainder computation
97  VAL []BYTE const.zero IS "Instruction> DIV/MOD Constant Zero.":
98
99  -- boolean value neither represents true nor false
100  VAL []BYTE true.false IS "Instruction> SWITCH flag neither TRUE nor FALSE.":
101
102  -- tuple reference error flag set in write instruction
103  VAL []BYTE ref.err    IS "Instruction> WRITE Tuple Reference Error.":
104
105  -- unknown instruction code
106  VAL []BYTE inst.err   IS "Instruction> Unknown Opcode.":
107
108  -- invalid request token type
109  VAL []BYTE requ.err   IS "Execution> Invalid Request Type.":

ADERROR.INC                                                                Page   3
111  -- invalid put attempt, trigger store is full
112  VAL []BYTE trig.full  IS "Execution> Trigger Store Full.":
113
114  -- invalid fetch attempt, trigger store is empty
115  VAL []BYTE trig.empt  IS "Execution> Trigger Store Empty.":
116
117  -- invalid allocation attempt, context store is full
118  VAL []BYTE cont.full  IS "Execution> Context Store Full.":
119
120  -- size of requested allocation frame larger than Free.Max
121  VAL []BYTE frame.size IS "Execution> Frame Too Large.":
122
123  -- not all synchronization events occurred when deallocating activation frame
124  VAL []BYTE synch.err  IS "Execution> Synchronization Error.":
125
126  -----------------------------------------------------------------------------
127  -- memory unit error messages
128  -----------------------------------------------------------------------------
129
130  -- tuple reference nil or error bit set
131  VAL []BYTE ref.err    IS "Memory> Invalid tuple reference.":
132
133  -- field number out of range
134  VAL []BYTE field.err  IS "Memory> Invalid field number.":
135
136  -- attempty to write a tuple field twice
137  VAL []BYTE mult.write IS "Memory> Single assignment violated.":
138
139  -- reference weight larger than reference count
140  VAL []BYTE weight.err IS "Memory> Reference weight error.":
141
142  -- function descriptor not yet available
143  VAL []BYTE submit.err IS "Memory> Submisson error.":
144
145  -- error during the allocation of heap cells
146  VAL []BYTE  alloc.err IS "Memory> Allocation error.":
147
148  -- error during the deallocation of heap cells
149  VAL []BYTE  dealloc.err IS "Memory> Deallocation error.":
150
151  -- error during the deallocation of heap cells
152  VAL []BYTE  space.err IS "Memory> Insufficient heap.":
153
154  -----------------------------------------------------------------------------
155  -- arbiter error messages
156  -----------------------------------------------------------------------------
157
158  -- network map invalid, program must not be forwarded to boot link
159  VAL []BYTE boot.err   IS "Arbiter> Can*'t forward program to boot link.":
160
161  -- acknowledge received from wrong link
162  VAL []BYTE ack.err    IS "Arbiter> Acknowledge signal from invalid link.":
163
164  -- arbiter buffer full
165  VAL []BYTE arbit.full IS "Arbiter> Token buffer is full.":

ADERROR.INC                                                                Page   4
166
167  -----------------------------------------------------------------------------
168  -- End of ADERROR.INC
169  -----------------------------------------------------------------------------

ADGLOBAL.INC                                                               Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADGLOBAL.INC                                           Wolfgang Schreiner
 4  -- Global Constants                                       (c) RISC-Linz 1990
 5  --
 6  -- All constants that are globally referenced in the Adam interpreter
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- network constants
12  -----------------------------------------------------------------------------
13
14  VAL INT Mod.Num  IS  8:      -- number of network modules (excl. master)
15  VAL INT Link.Num IS  4:      -- number of modules each module is connected to
16
17  VAL INT Cluster.Size IS 128: -- number of words per program cluster
18  VAL INT Token.Size IS 3:     -- number of words per token
19
20  VAL INT Max.Debug IS 40:     -- maximal length of debugging message
21
22  ----------------------------------------------------------------------------
23  -- value ranges
24  ----------------------------------------------------------------------------
25
26  -- integer ranges
27  VAL INT32 Max.Int     IS  1073741823(INT32): -- maximal integer value  2^30-1
28  VAL INT32 Min.Int.Abs IS  1073741824(INT32): -- min. int. value absolute 2^30
29  VAL INT32 Min.Int     IS -1073741824(INT32): -- minimal integer value -2^30
30
31  -- constant ranges
32  VAL INT Max.Con     IS  127(INT): -- maximal constant value 2^7-1
33  VAL INT Min.Con.Abs IS  128(INT): -- minimal constant value absolute 2^7
34  VAL INT Min.Con     IS -128(INT): -- minimal constant value absolute -2^7
35  VAL INT Range.Con   IS  256(INT): -- constant range 2^8
36
37  -----------------------------------------------------------------------------
38  -- error codes
39  -----------------------------------------------------------------------------
40
41  -- least significant bit 1, i.e. error code must always be odd!
42  VAL INT32 Error.Overflow IS 1(INT32): -- integer overflow
43  VAL INT32 Error.ZeroDiv  IS 3(INT32): -- zero division (remainder)
44  VAL INT32 Error.Read     IS 5(INT32): -- nil pointer dereferenced
45
46  ----------------------------------------------------------------------------
47  -- table of integers with exactly one bit set (used for bit operations)
48  ----------------------------------------------------------------------------
49
50  VAL [32]INT32 bits IS
51    [#00000001(INT32), #00000002(INT32), #00000004(INT32), #00000008(INT32),
52     #00000010(INT32), #00000020(INT32), #00000040(INT32), #00000080(INT32),
53     #00000100(INT32), #00000200(INT32), #00000400(INT32), #00000800(INT32),
54     #00001000(INT32), #00002000(INT32), #00004000(INT32), #00008000(INT32),
55     #00010000(INT32), #00020000(INT32), #00040000(INT32), #00080000(INT32),

ADGLOBAL.INC                                                               Page   2
56     #00100000(INT32), #00200000(INT32), #00400000(INT32), #00800000(INT32),
57     #01000000(INT32), #02000000(INT32), #04000000(INT32), #08000000(INT32),
58     #10000000(INT32), #20000000(INT32), #40000000(INT32), #80000000(INT32)]:
59
60  -----------------------------------------------------------------------------
61  -- Transputer Constants
62  -----------------------------------------------------------------------------
63
64  VAL INT Ticks IS 15625: -- transputer clock ticks per second
65
66  -----------------------------------------------------------------------------
67  -- End of ADGLOBAL.INC
68  -----------------------------------------------------------------------------

ADNET.INC                                                                  Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADNET.INC                                              Wolfgang Schreiner
 4  -- AdaM Global Network Constants and Protocols            (c) RISC-Linz 1990
 5  --
 6  -- Defines all the constants and protocols that refer to the communication
 7  -- between different ADAM modules and between different units within a
 8  -- module.
 9  --
10  -----------------------------------------------------------------------------
11
12  -----------------------------------------------------------------------------
13  -- protocols
14  -----------------------------------------------------------------------------
15
16  -- network protocol (between modules)
17  PROTOCOL Net.Link
18    CASE
19      net.startprog                    -- new program signal
20      net.appendprog                   -- append program signal
21      net.program; [Cluster.Size]INT32 -- program cluster
22      net.endprog                      -- end of program signal
23      net.ack                          -- acknowledgement signal
24      net.token;   [Token.Size]INT32   -- network token
25  :
26
27  -- unit protocol (within modules)
28  PROTOCOL Mod.Link
29    CASE
30      mod.startprog                    -- new program signal
31      mod.appendprog                   -- append program signal
32      mod.program; [Cluster.Size]INT32 -- program cluster
33      mod.endprog                      -- end of program signal
34      mod.ack                          -- acknowledgement signal
35      mod.token;   [Token.Size]INT32   -- network token
36  :
37
38  -- debug protocol (from units to debug procedure)
39  PROTOCOL Debug IS INT :: []BYTE:
40
41  -----------------------------------------------------------------------------
42  -- token tags
43  -----------------------------------------------------------------------------
44
45  -- stop token
46  VAL BYTE Stop.Token  IS #00(BYTE):
47
48  -- processor tokens (the more significant nibble of tag has to be 0 or 1)
49
50  -- message tokens (the more significant nibble of tag has to be 0!)
51  VAL BYTE Mess.PToken   IS #01(BYTE): -- value message
52  VAL BYTE Cont.PToken   IS #02(BYTE): -- context message
53  VAL BYTE PLoad.PToken  IS #03(BYTE): -- processor load message
54
55  -- request tokens (the more significant nibble of tag has to be 1!)

ADNET.INC                                                                  Page   2
56  VAL BYTE FCall.PToken  IS #14(BYTE): -- fast call request
57  VAL BYTE TCall.PToken  IS #15(BYTE): -- tail call request
58
59  -- memory tokens (the more significant nibble of tag has to be 2 or 3)
60
61  -- cheap request tokens (the more significant nibble of tag has to be 2!)
62  VAL BYTE Alloc.MToken  IS #20(BYTE): -- allocation request
63  VAL BYTE Read.MToken   IS #21(BYTE): -- read request
64  VAL BYTE Submit.MToken IS #22(BYTE): -- submission request
65
66  -- expensive request tokens (the more significant nibble of tag has to be 3!)
67  VAL BYTE WrInt.MToken  IS #33(BYTE): -- write integer request
68  VAL BYTE WrPtr.MToken  IS #34(BYTE): -- write pointer request
69  VAL BYTE MLoad.MToken  IS #35(BYTE): -- memory load message
70  VAL BYTE IncRef.MToken IS #36(BYTE): -- increase reference request
71  VAL BYTE DecRef.MToken IS #37(BYTE): -- decrease reference request
72  VAL BYTE CopRef.MToken IS #38(BYTE): -- copy tuple reference
73
74  -----------------------------------------------------------------------------
75  -- End of ADNET.INC
76  -----------------------------------------------------------------------------

ADCONN.OCC                                                                 Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADCONN.OCC                                             Wolfgang Schreiner
 4  -- Master Module Network Connection                       (c) RISC-Linz 1990
 5  --
 6  -- Connects the Adam master module to the network.
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- External References
12  -----------------------------------------------------------------------------
13
14  -- include files
15  #INCLUDE "adglobal.inc" -- global constants
16  #INCLUDE "adnet.inc"    -- network constants and protocols
17  #INCLUDE "aderror.inc"  -- error messages
18  #INCLUDE "admaster.inc" -- master module constants and protocols
19  #INCLUDE "hostio.inc"   -- server constants and protocols
20
21  -- compilation units
22  #USE "hostio.lib"       -- server input/output routines
23
24  -----------------------------------------------------------------------------
25  -- Connect Adam Master to Network
26  --
27  --   This procedure represents the connection between master module and
28  --   network. It can be placed between any two modules of the network
29  --   and is totally transparent for these two modules, i.e. all incoming
30  --   tokens are unchanged forwarded. The only exception are tokens that
31  --   are destined for the master module. They are filtered by the
32  --   connection and sent to the master module.
33  --
34  --   The modules on both sides of the connector are regarded as the roots
35  --   of two independent module trees to each of which the program is
36  --   forwarded, from each of which acknowledge signals are received and to
37  --   each of which stop tokens are sent. However, the trigger for the
38  --   start of the program execution is always sent to the left module.
39  --
40  -- IN  from.left, from.right:  input channels from connected modules
41  -- OUT to.left, to.right:      output channels to connected modules
42  -- IO  from.result, to.result: channels from/to result generator
43  -- OUT to.stat:                output channel to statistics generator
44  -- OUT from.inout, to.inout:   channels from/to input/output interface
45  -- IO  fs, ts:                 input/output channels from/to server
46  -----------------------------------------------------------------------------
47  PROC Adam.Connector(CHAN OF Net.Link from.left, to.right, from.right,
48    to.left, from.result, to.result, to.stat, from.inout, to.inout,
49    CHAN OF SP fs, ts)
50
51    BOOL going:      -- termination variable
52    BOOL waiting:    -- termination variable
53    BOOL append:     -- append signal received
54    BYTE addr:       -- token address
55    BYTE type:       -- token type

ADCONN.OCC                                                                 Page   2
56    BOOL wait.left:  -- waiting for event from left module
57    BOOL wait.right: -- waiting for acknowledge from right module
58
59    [Token.Size]INT32 token:     -- network token
60    [Cluster.Size]INT32 cluster: -- program cluster
61
62    ---------------------------------------------------------------------------
63    -- Assert Condition
64    --
65    --   Checks condition and prints error message to screen,
66    --   if neccessary.
67    --
68    -- IN cond: test condition
69    -- IN mess: error message
70    ---------------------------------------------------------------------------
71    PROC Assert(VAL BOOL cond, VAL []BYTE mess)
72
73      -------------------------------------------------------------------------
74      -- Body of Assert
75      -------------------------------------------------------------------------
76      SEQ
77        IF
78          cond
79            SKIP
80          TRUE
81            SEQ
82              so.write.string.nl(fs, ts, adam.assert)
83              so.write.string.nl(fs, ts, mess)
84              STOP
85    :
86
87    ---------------------------------------------------------------------------
88    -- Body of Adam.Connector
89    ---------------------------------------------------------------------------
90    SEQ
91
92      WHILE TRUE
93        SEQ
94
95          ---------------------------------------------------------------------
96          -- transmit program to network
97          ---------------------------------------------------------------------
98
99          -- receive signal for start of program transmission
100          from.inout ? CASE
101            net.startprog
102              SEQ
103                to.left ! net.startprog
104                to.right ! net.startprog
105                append := FALSE
106            net.appendprog
107              SEQ
108                to.left ! net.appendprog
109                to.right ! net.appendprog
110                append := TRUE

ADCONN.OCC                                                                 Page   3
111
112          -- receive program clusters and forward them to both modules
113          going := TRUE
114          WHILE going
115            from.inout ? CASE
116              net.program; cluster                 -- program cluster
117                SEQ
118                  to.left ! net.program; cluster
119                  to.right ! net.program; cluster
120              net.appendprog                       -- append signal
121                SEQ                                -- (must not occur twice)
122                  Assert(NOT append, app.err)
123                  to.left ! net.appendprog
124                  to.right ! net.appendprog
125                  append := TRUE
126              net.endprog                          -- end of program signal
127                SEQ
128                  to.left ! net.endprog
129                  to.right ! net.endprog
130                  going := FALSE
131
132          -- wait for acknowledge signals and forward signal to interface
133          from.left ? CASE net.ack
134          from.right ? CASE net.ack
135          to.inout ! net.ack
136
137          ---------------------------------------------------------------------
138          -- handle result requests from result generator
139          ---------------------------------------------------------------------
140
141          -- receive requests from result generator and return results to it,
142          -- and also handle token transmissions between modules
143          going := TRUE
144          WHILE going
145            ALT
146
147              -- token received from result generator
148              from.result ? CASE net.token; token
149                SEQ
150                  -- extract type of token
151                  [4]BYTE toks RETYPES token[2]:
152                  type := toks[1]
153                  going := type <> Stop.Token
154                  IF
155                    going
156                      -- forward request to left module
157                      to.left ! net.token; token
158                    TRUE
159                      SEQ
160                        -- forward stop token to both modules
161                        to.left ! net.token; token
162                        to.right ! net.token; token
163
164              -- token received from left neighbour
165              from.left ? CASE net.token; token

ADCONN.OCC                                                                 Page   4
166                SEQ
167                  [4]BYTE toks RETYPES token[2]:
168                  SEQ
169                    addr := toks[2]
170                    type := toks[1]
171                  CASE addr
172                    0(BYTE)
173                      -- token received for master module
174                      CASE type
175                        Mess.PToken
176                          -- result value received
177                          to.result ! net.token; token
178                        ELSE
179                          -- statistics received
180                          to.stat ! net.token; token
181                    ELSE
182                      -- forward token to right neighbour
183                      to.right ! net.token; token
184
185              -- token received from right neighbour
186              from.right ? CASE net.token; token
187                SEQ
188                  [4]BYTE toks RETYPES token[2]:
189                  SEQ
190                    addr := toks[2]
191                    type := toks[1]
192                  CASE addr
193                    0(BYTE)
194                      -- token received for master module
195                      CASE type
196                        Mess.PToken
197                          -- result value received
198                          to.result ! net.token; token
199                        ELSE
200                          -- statistics received
201                          to.stat ! net.token; token
202                    ELSE
203                      -- forward token to left neighbour
204                      to.left ! net.token; token
205
206          ---------------------------------------------------------------------
207          -- terminate program execution
208          ---------------------------------------------------------------------
209
210          -- wait for acknowledge signals and skip tokens
211          wait.left, wait.right := TRUE, TRUE
212          WHILE (wait.left OR wait.right)
213            ALT
214              wait.left & from.left ? CASE
215                net.token; token
216                  SKIP
217                net.ack
218                  wait.left := FALSE
219              wait.right & from.right ? CASE
220                net.token; token

ADCONN.OCC                                                                 Page   5
221                  SKIP
222                net.ack
223                  wait.right := FALSE
224
225          -- send acknowledge signal to input/output interface
226          to.inout ! net.ack
227
228      -- never done (just to avoid compiler warnings, if not used elsewhere)
229      so.write.string(fs, ts, "")
230
231  :
232  -----------------------------------------------------------------------------
233  -- End of ADCONN.OCC
234  -----------------------------------------------------------------------------

ADGENER.OCC                                                                Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADGENER.OCC                                            Wolfgang Schreiner
 4  -- Result Generator                                       (c) RISC-Linz 1990
 5  --
 6  -- Triggers generation of result and converts it into printable form.
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- External References
12  -----------------------------------------------------------------------------
13
14  -- include files
15  #INCLUDE "adglobal.inc" -- global constants
16  #INCLUDE "aderror.inc"  -- error messages
17  #INCLUDE "adnet.inc"    -- network constants and protocols
18  #INCLUDE "admaster.inc" -- master protocols and constants
19  #INCLUDE "hostio.inc"   -- server constants and protocols
20
21  -- compilation units
22  #USE "string.lib"       -- string handling facilities
23  #USE "hostio.lib"       -- server input/output routines
24
25  -----------------------------------------------------------------------------
26  -- Result Generator
27  --
28  --  Triggers program execution, receives result tokens and transforms
29  --  result values in printable form.
30  --
31  -- IN  from.net:   input channel from network
32  -- OUT to.net:     output channel to network
33  -- IN  from.inout: input channel from input/output interface
34  -- OUT to.inout:   output channel to input/output interface
35  -- IO  fs, ts:     interface to server (for debugging purposes)
36  -----------------------------------------------------------------------------
37  PROC Adam.Generator(CHAN OF Net.Link from.net, to.net,
38    CHAN OF Result to.inout, from.inout, CHAN OF SP fs, ts)
39
40    [CType.Size]BYTE type:   -- type description characters
41    [CLabel.Size]BYTE label: -- positions of type descriptor lines
42    [Token.Size]INT32 token: -- network token
43
44    INT fun:  -- number of function to be activated
45    INT root: -- root of type description tree
46
47    ---------------------------------------------------------------------------
48    -- Assert Condition
49    --
50    --   Checks condition and prints error message to screen,
51    --   if neccessary.
52    --
53    -- IN cond: test condition
54    -- IN mess: error message
55    ---------------------------------------------------------------------------

ADGENER.OCC                                                                Page   2
56    PROC Assert(VAL BOOL cond, VAL []BYTE mess)
57
58      -------------------------------------------------------------------------
59      -- Body of Assert
60      -------------------------------------------------------------------------
61      SEQ
62        IF
63          cond
64            SKIP
65          TRUE
66            SEQ
67              so.write.string.nl(fs, ts, adam.assert)
68              so.write.string.nl(fs, ts, mess)
69              STOP
70    :
71
72    ---------------------------------------------------------------------------
73    -- Compile Type Descriptor File
74    --
75    --   Receives type descriptor file and compiles it into standard form.
76    --   If type descriptor invalid, reports this fact to inout interface
77    --   and waits for new type descriptor file. Only returns after having
78    --   compiled valid type description.
79    --
80    -- OUT type:  array of type descriptions
81    -- OUT label: array of label references
82    -- OUT root:  root of type descriptions
83    -- OUT fun:   number of function to be activated
84    ---------------------------------------------------------------------------
85    PROC Compile.Type([CType.Size]BYTE type, [CLabel.Size]BYTE label,
86      INT root, INT fun)
87
88      BYTE pos:   -- actual position in type array
89      INT  len:   -- length of actual text line
90      INT  i:     -- actual position in actual text line
91      BOOL okay:  -- compilation successfull?
92      BOOL going: -- termination variable
93
94      [Type.Size]BYTE  line:    -- actual text line
95      [CType.Size]BOOL used:    -- which labels have been used?
96      [CType.Size]BOOL defined: -- which labels have been defined?
97
98      -------------------------------------------------------------------------
99      -- Skip White Space
100      --
101      --   Skips blanks and comments from line
102      --
103      -- IN okay: no error occurred until now
104      -- IN line: actual text line
105      -- IN len:  length of line
106      -- IO i:    starting position of search, afterwards position of
107      --          first non-blank character (= len, if none)
108      -------------------------------------------------------------------------
109      PROC White.Space(VAL BOOL okay, VAL []BYTE line, VAL INT len, INT i)

ADGENER.OCC                                                                Page   3
111        BOOL going: -- termination variable
112
113        -----------------------------------------------------------------------
114        -- Body of White.Space
115        -----------------------------------------------------------------------
116        IF
117          okay
118            SEQ
119              going := i < len
120              WHILE going
121                CASE line[i]
122                  ' '       -- blank
123                    SEQ
124                      i := i+1
125                      going := i<len
126                  '*t'      -- tabulator
127                    SEQ
128                      i := i+1
129                      going := i<len
130                  Comm.Chr  -- comment
131                    SEQ
132                      i := len
133                      going := FALSE
134                  ELSE      -- non-blank character
135                    going := FALSE
136          TRUE
137            -- error already occurred, skip line
138            SKIP
139      :
140
141      -------------------------------------------------------------------------
142      -- Get Unsigned Number
143      --
144      --   Reads unsigned number from line.
145      --
146      -- IO okay: no error occurred until now
147      -- IN line: actual text line
148      -- IN len:  length of line
149      -- IO i:    start position for search, position of first non-number
150      --          character afterwards (= len, if none)
151      -- OUT num: result number
152      -------------------------------------------------------------------------
153      PROC Number(BOOL okay, VAL []BYTE line, VAL INT len, INT i, INT num)
154
155        BOOL going: -- termination variable
156
157        -----------------------------------------------------------------------
158        -- Body of Number
159        -----------------------------------------------------------------------
160        SEQ
161          -- skip white space
162          White.Space(okay, line, len, i)
163          okay := okay AND (i < len) AND is.digit(line[i])
164          -- read number
165          going := okay

ADGENER.OCC                                                                Page   4
166          num := 0
167          WHILE going
168            SEQ
169              num := (10*num)+((INT line[i])-48)
170              i := i+1
171              going := (i < len) AND is.digit(line[i])
172      :
173
174      -------------------------------------------------------------------------
175      -- Determine Root of Type Description
176      --
177      --   The first non-whitespace line of type description has to contain a
178      --   root description of the form  "#<number1>:<number2>" where <number1>
179      --   is the number of the function to be activated and <number2> is the
180      --   label of the line that describes the result type of the function.
181      --   This procedure reads all lines up to this line and returns the
182      --   function number and the label of the root.
183      --
184      -- OUT okay: everything is okay until now
185      -- OUT root: label of root (only iff okay)
186      -- OUT fun:  function number
187      -------------------------------------------------------------------------
188      PROC Compile.Root(BOOL okay, INT root, INT fun)
189
190        INT i:      -- actual line position
191        BOOL going: -- termination variable
192        INT len:    -- length of actual line
193
194        [Type.Size]BYTE line: -- actual line of file
195
196        -----------------------------------------------------------------------
197        -- Body of Compile.Root
198        -----------------------------------------------------------------------
199        SEQ
200
201          going := TRUE
202          WHILE going
203            SEQ
204
205              -- fetch line from file
206              from.inout ? CASE
207                is.type; len :: line
208                  i := 0
209                end.type
210                  okay := FALSE
211
212              -- skip white space
213              White.Space(okay, line, len, i)
214
215              IF
216                NOT okay
217                  -- terminate loop
218                  going := FALSE
219                i = len
220                  -- skip empty line

ADGENER.OCC                                                                Page   5
221                  SKIP
222                TRUE
223                  -- read root description
224                  SEQ
225                    -- root character
226                    IF
227                      line[i] = Root.Chr
228                        i := i+1
229                      TRUE
230                        okay := FALSE
231                    -- function number
232                    Number(okay, line, len, i, fun)
233                    -- skip white space
234                    White.Space(okay, line, len, i)
235                    -- label character
236                    okay := okay AND (i < len)
237                    IF
238                      okay AND (line[i] = Label.Chr)
239                        i := i+1
240                      TRUE
241                        okay := FALSE
242                    -- label number
243                    Number(okay, line, len, i, root)
244                    okay := okay AND (root < CLabel.Size)
245                    -- rest of line only white space
246                    White.Space(okay, line, len, i)
247                    okay := okay AND (i = len)
248                    -- terminate loop
249                    going := FALSE
250      :
251
252      -------------------------------------------------------------------------
253      -- Get Type Label
254      --
255      --  Reads type label from line and enters it into type description
256      --
257      -- IO okay: no error occurred until now
258      -- IN line: actual text line
259      -- IN len:  length of line
260      -- IO i:    start position for search, position of first non-label
261      --          character afterwards (= len, if none)
262      -- IN pos:  next position in type descriptor
263      -------------------------------------------------------------------------
264      PROC Type.Label(BOOL okay, VAL []BYTE line, VAL INT len, INT i,
265        VAL BYTE pos)
266
267        INT  num:   -- label number
268
269        -----------------------------------------------------------------------
270        -- Body of Type.Label
271        -----------------------------------------------------------------------
272        SEQ
273          -- get unsigned label number
274          Number(okay, line, len, i, num)
275          -- number must be new label

ADGENER.OCC                                                                Page   6
276          okay := okay AND (num < CLabel.Size) AND (NOT defined[num])
277          -- enter label
278          IF
279            okay
280              SEQ
281                Assert(NOT defined[num], type.redef)
282                defined[num] := TRUE -- label defined
283                label[num] := pos    -- label references type description
284            TRUE
285              SKIP
286      :
287
288      -------------------------------------------------------------------------
289      -- Get Type Description
290      --
291      --  Reads type description from line and enters it into type array
292      --
293      -- IO okay: no error occurred until now
294      -- IN line: actual text line
295      -- IN len:  length of line
296      -- IO i:    start position for search, position of first non-type
297      --          character afterwards (= len, if none)
298      -- IO pos:  next position in type descriptor
299      -------------------------------------------------------------------------
300      PROC Type.Descr(BOOL okay, VAL []BYTE line, VAL INT len, INT i,
301        BYTE pos)
302
303        INT num: -- actual type label
304
305        -----------------------------------------------------------------------
306        -- Body of Type.Descr
307        -----------------------------------------------------------------------
308        SEQ
309
310          -- Skip whitespace
311          White.Space(okay, line, len, i)
312
313          IF
314            okay AND (i < len)
315
316              -- recognize types
317              CASE line[i]
318
319                -- atomic types
320                Int.Chr, Bool.Chr, Char.Chr, Str.Chr
321                  SEQ
322                    -- enter type
323                    type[(INT pos)] := line[i]
324                    type[(INT pos)+1] := End.Type
325                    pos, i := BYTE((INT pos)+2), i+1
326                    -- rest of line whitespace
327                    White.Space(okay, line, len, i)
328                    okay := okay AND (i = len)
329
330                -- list type

ADGENER.OCC                                                                Page   7
331                List.Chr
332                  SEQ
333                    -- enter type
334                    type[INT pos] := line[i]
335                    pos, i := BYTE((INT pos)+1), i+1
336                    -- read label number
337                    Number(okay, line, len, i, num)
338                    okay := okay AND (num < CLabel.Size)
339                    IF
340                      okay
341                        -- enter label
342                        SEQ
343                          used[num] := TRUE
344                          type[INT pos] := (BYTE num)
345                          type[(INT pos)+1] := End.Type
346                          pos := BYTE((INT pos)+2)
347                      TRUE
348                        SKIP
349
350                -- tuple or union type
351                Tuple.Chr, Union.Chr
352                  SEQ
353                    -- enter type
354                    type[INT pos] := line[i]
355                    pos, i := BYTE((INT pos)+1), i+1
356                    -- at least one label
357                    okay := i < len
358                    -- enter type labels
359                    WHILE okay AND (i < len)
360                      SEQ
361                        -- read label number
362                        Number(okay, line, len, i, num)
363                        okay := okay AND (num < CLabel.Size)
364                        IF
365                          okay
366                            -- enter label
367                            SEQ
368                              used[num] := TRUE
369                              type[INT pos] := (BYTE num)
370                              pos := BYTE((INT pos)+1)
371                          TRUE
372                            SKIP
373                        -- skip white space
374                        White.Space(okay, line, len, i)
375                    -- signal end of type
376                    type[INT pos] := End.Type
377                    pos := BYTE ((INT pos)+1)
378
379                -- unknown type
380                ELSE
381                  okay := FALSE
382
383            -- error already occurred
384            TRUE
385              okay := FALSE

ADGENER.OCC                                                                Page   8
386      :
387
388      -------------------------------------------------------------------------
389      -- Compile Type Descriptor Line
390      --
391      --   Receives one line of type descriptor and compiles it into
392      --   standard form.
393      --
394      -- IO okay:  actual line and all previous lines successfully compiled
395      -- IN line:  actual line to be compiled
396      -- IN len:   length of actual line
397      -- IO pos:   actual position in type array
398      -------------------------------------------------------------------------
399      PROC Compile.Line(BOOL okay, [Type.Size]BYTE line, INT len, BYTE pos)
400
401        -----------------------------------------------------------------------
402        -- Body of Compile.Line
403        -----------------------------------------------------------------------
404        SEQ
405
406          -- actual position in line
407          i := 0
408
409          -- skip blanks and comments
410          White.Space(okay, line, len, i)
411
412          IF
413            (NOT okay) OR (i = len)
414              -- skip line
415              SKIP
416            TRUE
417              -- compile line
418              SEQ
419                -- type label
420                Type.Label(okay, line, len, i, pos)
421                -- white space
422                White.Space(okay, line, len, i)
423                -- label character
424                IF
425                  okay AND (i < len) AND (line[i] = Label.Chr)
426                    i := i+1
427                  TRUE
428                    okay := FALSE
429                -- actual type description
430                Type.Descr(okay, line, len, i, pos)
431                -- rest of line whitespace
432                White.Space(okay, line, len, i)
433                okay := okay AND (i = len)
434
435      :
436
437      -------------------------------------------------------------------------
438      -- Body of Compile.Type
439      -------------------------------------------------------------------------
440      SEQ

ADGENER.OCC                                                                Page   9
441
442        -- receive type description and compile it successfully
443        okay := FALSE
444        WHILE NOT okay
445          SEQ
446
447            -- no error occurred until now
448            okay := TRUE
449
450            -- initalize label references
451            SEQ i = 0 FOR CType.Size
452              defined[i], used[i] := FALSE, FALSE
453
454            -- position of next free item in type array
455            pos := 0(BYTE)
456
457            -- compile root description
458            Compile.Root(okay, root, fun)
459
460            -- compile type description
461            going := TRUE
462            WHILE going
463              from.inout ? CASE
464                is.type; len :: line
465                  -- receive type descriptor line
466                  Compile.Line(okay, line, len, pos)
467                end.type
468                  -- receive end of type description signal
469                  going := FALSE
470
471            -- check if all referenced labels defined
472            SEQ i=0 FOR CLabel.Size
473              okay := okay AND ((NOT used[i]) OR (defined[i]))
474
475            -- signal success of compilation
476            to.inout ! type.succ; okay
477
478    :
479
480    ---------------------------------------------------------------------------
481    -- Convert Result Tokens into Printable Form
482    --
483    --   Receives result tokens, converts them in printable form and forwards
484    --   them to inout interface. If the result type is a structure, it
485    --   releases request tokens to network in order to receive the components
486    --   of the structure.
487    --
488    -- IN type:  array of type descriptions
489    -- IN label: array of label references
490    -- IN root:  root of type descriptions
491    ---------------------------------------------------------------------------
492    PROC Result([CType.Size]BYTE type, [CLabel.Size]BYTE label, INT root)
493
494      INT   top:     -- top of stack
495      INT   variant: -- tuple variant

ADGENER.OCC                                                                Page  10
496      INT32 result:  -- contents of message token
497      INT   len:     -- lenght of output string
498      BOOL  error:   -- value has error flag set
499
500      [Line.Size]BYTE line:    -- output string
501      [Token.Size]INT32 token: -- network token
502
503      -- top elements of stacks
504      INT   act.type:  -- actual type pointer
505      INT   act.field: -- actual field offset
506      BYTE  act.entry: -- actual entry point
507      INT32 act.list:  -- actual list pointer
508
509      -- stacks
510      [Stack.Size]INT   stack.type:  -- stack of pointers to type characters
511      [Stack.Size]INT   stack.field: -- stack of field offsets
512      [Stack.Size]BYTE  stack.entry: -- stack of entry points
513      [Stack.Size]INT32 stack.list:  -- stack of list pointers
514
515      -------------------------------------------------------------------------
516      -- Receive Token
517      --
518      --   Receives message token and checks message for error flag.
519      --
520      -- OUT result: value of message
521      -- OUT string: error message (if error = TRUE)
522      -- OUT len:    length of error message (if error, 0 otherwise)
523      -- OUT error:  error flag set
524      -------------------------------------------------------------------------
525      PROC Receive(INT32 result, [Line.Size]BYTE string, INT len, BOOL error)
526
527        [Token.Size]INT32 token: -- network token
528
529        -----------------------------------------------------------------------
530        -- Body of Receive
531        -----------------------------------------------------------------------
532        SEQ
533
534          -- wait for token
535          from.net ? CASE net.token; token
536
537          -- token must be message token
538          [4]BYTE toks RETYPES token[2]:
539          Assert(toks[1]=Mess.PToken, token.type)
540
541          -- extract result
542          result := token[0]
543
544          -- error flag set?
545          error := (result /\ 1(INT32)) = 1(INT32)
546          IF
547            error
548              SEQ
549                len := 6
550                [string FROM 0 FOR len] := "ERROR("

ADGENER.OCC                                                                Page  11
551                append.int(len, string, INT (result >> 1), 0)
552                string[len] := ')'
553                len := len+1
554            TRUE
555              SKIP
556      :
557
558      -------------------------------------------------------------------------
559      -- Copy Tuple Reference
560      --
561      --   Copies tuple reference by decreasing the weight field of the
562      --   reference. If the weight decreases to zero a 'increase reference'
563      --   message is sent to the denoted memory module. This module returns
564      --   a new reference (with a non-zero weight field) that may then be
565      --   copied.
566      --
567      -- IO ref: tuple reference to be copied.
568      -------------------------------------------------------------------------
569      PROC Copy(INT32 ref)
570
571        INT wght: -- reference weight field
572
573        [Token.Size]INT32 token: -- network token
574
575        -----------------------------------------------------------------------
576        -- Body of Copy
577        -----------------------------------------------------------------------
578        IF
579          ref = 0(INT32)
580
581            -- nil pointer
582            SKIP
583
584          (ref /\ #00000001(INT32)) > 0(INT32)
585
586            -- error flag set
587            SKIP
588
589          TRUE
590            SEQ
591
592              -- actual reference weight
593              wght := (INT (ref >> 1)) /\ #00000003
594
595              IF
596                wght = 0
597                  SEQ
598
599                    -- construct request token
600                    [4]BYTE t RETYPES token[2]:
601                    [4]BYTE r RETYPES ref:
602                    SEQ
603                      t[2] := r[3]               -- module address
604                      t[1] := IncRef.MToken      -- increase reference request
605                    token[0] := ref              -- tuple reference

ADGENER.OCC                                                                Page  12
606                    [4]BYTE t RETYPES token[0]:
607                    t[3] := 0(BYTE)              -- actual module address
608
609                    -- send token into network
610                    to.net ! net.token; token
611
612                    -- receive token from network
613                    from.net ? CASE net.token; token
614
615                    -- extract new reference
616                    ref := token[0]
617                    wght := (INT (ref >> 1)) /\ #00000003
618
619                TRUE
620                  SKIP
621
622              -- decrease weight field
623              ref := (ref /\ #FFFFFFF9(INT32)) \/ (INT32 ((wght-1) << 1))
624      :
625
626      -------------------------------------------------------------------------
627      -- Body of Result
628      -------------------------------------------------------------------------
629      SEQ
630
631        -- push root of type expression on stack
632        stack.type[0] := INT label[root] -- pointer to root type character
633        stack.entry[0] := 0(BYTE)        -- actual entry point
634        top := 0                         -- top of stack
635
636        WHILE top >= 0
637          SEQ
638
639            -- pop actual values from stack
640            act.type  := stack.type[top]
641            act.field := stack.field[top]
642            act.entry := stack.entry[top]
643            act.list  := stack.list[top]
644            top := top-1
645
646            CASE type[act.type]
647
648              -----------------------------------------------------------------
649              -- integer value
650              -----------------------------------------------------------------
651              Int.Chr
652                SEQ
653                  Receive(result, line, len, error)
654                  IF
655                    error
656                      SKIP
657                    TRUE
658                      SEQ
659                        len := 0
660                        append.int(len, line, ASHIFTRIGHT(INT result, 1), 0)

ADGENER.OCC                                                                Page  13
661
662              -----------------------------------------------------------------
663              -- boolean value
664              -----------------------------------------------------------------
665              Bool.Chr
666                SEQ
667                  Receive(result, line, len, error)
668                  IF
669                    error
670                      SKIP
671                    ASHIFTRIGHT(INT result, 1) = 1
672                      SEQ
673                        len := 4
674                        [line FROM 0 FOR len] := "TRUE"
675                    TRUE
676                      SEQ
677                        len := 5
678                        [line FROM 0 FOR len] := "FALSE"
679
680              -----------------------------------------------------------------
681              -- character value
682              -----------------------------------------------------------------
683              Char.Chr
684                SEQ
685                  Receive(result, line, len, error)
686                  IF
687                    error
688                      SKIP
689                    TRUE
690                      SEQ
691                        line[0] := '*''
692                        result := INT32 ASHIFTRIGHT(INT result, 1)
693                        VAL [4]BYTE chr RETYPES result:
694                        line[1] := chr[0]
695                        line[2] := '*''
696                        len := 3
697
698              -----------------------------------------------------------------
699              -- string value
700              -----------------------------------------------------------------
701              Str.Chr
702
703                CASE act.entry
704
705                  -------------------------------------------------------------
706                  -- receive pointer to string
707                  -------------------------------------------------------------
708                  0(BYTE)
709                    SEQ
710
711                      -- receive token with pointer to string
712                      Receive(result, line, len, error)
713                      IF
714                        error
715                          SKIP

ADGENER.OCC                                                                Page  14
716                        TRUE
717                          SEQ
718                            -- push continuation on stack
719                            top := top+1
720                            stack.type[top] := act.type
721                            stack.list[top] := result
722                            stack.entry[top] := 1(BYTE)
723                            -- construct print message
724                            len := 1
725                            line[0] := '*"'
726
727                  -------------------------------------------------------------
728                  -- non-error pointer received, character of string required
729                  -------------------------------------------------------------
730                  1(BYTE)
731
732                    IF
733
734                      act.list = 0(INT32)
735
736                        -- rest line is empty
737                        SEQ
738                          -- construct print message
739                          len := 1
740                          line[0] := '*"'
741
742                      TRUE
743
744                        -- request next character from line
745                        SEQ
746
747                          -- copy string reference
748                          Copy(act.list)
749
750                          -- push continuation on stack
751                          top := top+1
752                          stack.type[top] := act.type
753                          stack.list[top] := act.list
754                          stack.entry[top] := 2(BYTE)
755
756                          -- construct request token
757                          [4]BYTE act RETYPES act.list:
758                          BYTE    mod  IS act[3]:
759                          [3]BYTE ref  IS [act FROM 0 FOR 3]:
760                          SEQ
761                            [4]BYTE toks RETYPES token[2]:
762                            SEQ
763                              toks[2] := mod
764                              toks[1] := Read.MToken
765                              toks[0] := 0(BYTE)
766                            -- ignore context and offset (not required)
767                            [4]BYTE toks RETYPES token[0]:
768                            SEQ
769                              toks[3] := 0(BYTE)
770                              [toks FROM 0 FOR 3] := ref

ADGENER.OCC                                                                Page  15
771
772                          -- send token into network
773                          to.net ! net.token; token
774
775                          -- receive result
776                          Receive(result, line, len, error)
777
778                          IF
779                            error
780                              SKIP
781                            TRUE
782                              SEQ
783                                -- construct print message
784                                len := 1
785                                result := result >> 1
786                                VAL [4]BYTE res RETYPES result:
787                                line[0] := res[0]
788
789                  -------------------------------------------------------------
790                  -- tail of string is required
791                  -------------------------------------------------------------
792                  2(BYTE)
793                    SEQ
794
795                      -- construct read token for rest of string
796                      [4]BYTE act RETYPES act.list:
797                      BYTE    mod  IS act[3]:
798                      [3]BYTE ref  IS [act FROM 0 FOR 3]:
799                      SEQ
800                        [4]BYTE toks RETYPES token[2]:
801                        SEQ
802                          toks[2] := mod
803                          toks[1] := Read.MToken
804                          toks[0] := 1(BYTE)
805                        -- ignore context and offset (not required)
806                        [4]BYTE toks RETYPES token[0]:
807                        SEQ
808                          toks[3] := 0(BYTE)
809                          [toks FROM 0 FOR 3] := ref
810
811                      -- send token to network
812                      to.net ! net.token; token
813
814                      -- receive pointer to string
815                      Receive(result, line, len, error)
816
817                      IF
818                        error
819                          SKIP
820                        TRUE
821                          SEQ
822                            -- push continuation on stack
823                            top := top+1
824                            stack.entry[top] := 1(BYTE)
825                            stack.type[top] := act.type

ADGENER.OCC                                                                Page  16
826                            stack.list[top] := result
827                            -- empty print message
828                            len := 0
829
830              -----------------------------------------------------------------
831              -- list value
832              -----------------------------------------------------------------
833              List.Chr
834
835                CASE act.entry
836
837                  -------------------------------------------------------------
838                  -- start of list encountered
839                  -------------------------------------------------------------
840                  0(BYTE)
841                    SEQ
842
843                      -- receive message token with pointer to list
844                      Receive(result, line, len, error)
845
846                      IF
847                        error
848                          SKIP
849                        result = 0(INT32)
850                          SEQ
851                            -- construct print message
852                            len := 2
853                            [line FROM 0 FOR 2] := "[]"
854                        TRUE
855                          SEQ
856                            -- construct print message
857                            len := 1
858                            line[0] := '['
859                            -- push continutation on stack
860                            top := top+1
861                            stack.type[top] := act.type
862                            stack.list[top] := result
863                            stack.entry[top] := 2(BYTE)
864
865                  -------------------------------------------------------------
866                  -- continuation of list encountered
867                  -------------------------------------------------------------
868                  1(BYTE)
869                    SEQ
870
871                      -- receive message token with pointer to tail of list
872                      Receive(result, line, len, error)
873
874                      IF
875                        error
876                          SKIP
877                        result = 0(INT32)
878                          SEQ
879                            -- construct print message
880                            len := 1

ADGENER.OCC                                                                Page  17
881                            line[0] := ']'
882                        TRUE
883                          SEQ
884                            -- construct print message
885                            len := 1
886                            line[0] := ','
887                            -- push continutation on stack
888                            top := top+1
889                            stack.type[top] := act.type
890                            stack.list[top] := result
891                            stack.entry[top] := 2(BYTE)
892
893                  -------------------------------------------------------------
894                  -- head of list is required
895                  -------------------------------------------------------------
896                  2(BYTE)
897                    SEQ
898
899                      -- copy list reference
900                      Copy(act.list)
901
902                      -- push list continutation on stack
903                      top := top+1
904                      stack.type[top]  := act.type
905                      stack.list[top]  := act.list
906                      stack.entry[top] := 3(BYTE)
907
908                      -- push element type and pointer on stack
909                      top := top+1
910                      stack.type[top] :=
911                        INT label[INT type[act.type+1]]
912                      stack.entry[top] := 0(BYTE)
913
914                      -- construct read token for head of list
915                      [4]BYTE act RETYPES act.list:
916                      BYTE    mod  IS act[3]:
917                      [3]BYTE ref  IS [act FROM 0 FOR 3]:
918                      SEQ
919                        [4]BYTE toks RETYPES token[2]:
920                        SEQ
921                          toks[2] := mod
922                          toks[1] := Read.MToken
923                          toks[0] := 0(BYTE)
924                        -- ignore context and offset (not required)
925                        [4]BYTE toks RETYPES token[0]:
926                        SEQ
927                          toks[3] := 0(BYTE)
928                          [toks FROM 0 FOR 3] := ref
929
930                        -- send token to network
931                        to.net ! net.token; token
932
933                        -- empty print message
934                        len := 0

ADGENER.OCC                                                                Page  18
936                  -------------------------------------------------------------
937                  -- tail of list is required
938                  -------------------------------------------------------------
939                  3(BYTE)
940                    SEQ
941
942                      -- push continuation on stack
943                      top := top + 1
944                      stack.type[top]  := act.type
945                      stack.entry[top] := 1(BYTE)
946
947                      -- construct read token for rest of list
948                      [4]BYTE act RETYPES act.list:
949                      BYTE    mod  IS act[3]:
950                      [3]BYTE ref  IS [act FROM 0 FOR 3]:
951                      SEQ
952                        [4]BYTE toks RETYPES token[2]:
953                        SEQ
954                          toks[2] := mod
955                          toks[1] := Read.MToken
956                          toks[0] := 1(BYTE)
957                        -- ignore context and offset (not required)
958                        [4]BYTE toks RETYPES token[0]:
959                        SEQ
960                          toks[3] := 0(BYTE)
961                          [toks FROM 0 FOR 3] := ref
962
963                        -- send token to network
964                        to.net ! net.token; token
965
966                        -- empty print message
967                        len := 0
968
969              -----------------------------------------------------------------
970              -- tuple value
971              -----------------------------------------------------------------
972              Tuple.Chr
973
974                CASE act.entry
975
976                  -------------------------------------------------------------
977                  -- start of tuple encountered
978                  -------------------------------------------------------------
979                  0(BYTE)
980                    SEQ
981
982                      -- receive message token with pointer to tuple
983                      Receive(result, line, len, error)
984
985                      IF
986                        error
987                          SKIP
988                        TRUE
989                          SEQ

ADGENER.OCC                                                                Page  19
 991                            -- construct print message
 992                            len := 1
 993                            line[0] := '('
 994
 995                            -- push continuation on stack
 996                            top := top+1
 997                            stack.type[top] := act.type
 998                            stack.field[top] := 0
 999                            stack.list[top] := result
1000                            stack.entry[top] := 2(BYTE)
1001
1002                  -------------------------------------------------------------
1003                  -- continuation of tuple encountered
1004                  -------------------------------------------------------------
1005                  1(BYTE)
1006
1007                    IF
1008                      type[act.type+(act.field+2)] = End.Type
1009                        SEQ
1010                          -- construct print message
1011                          len := 1
1012                          line[0] := ')'
1013                      TRUE
1014                        SEQ
1015                          -- construct print message
1016                          len := 1
1017                          line[0] := ','
1018                          -- push continuation on stack
1019                          top := top+1
1020                          stack.type[top] := act.type
1021                          stack.field[top] := act.field+1
1022                          stack.list[top] := act.list
1023                          stack.entry[top] := 2(BYTE)
1024
1025                  -------------------------------------------------------------
1026                  -- tuple field is to be read
1027                  -------------------------------------------------------------
1028                  2(BYTE)
1029                    SEQ
1030
1031                      -- copy tuple reference
1032                      Copy(act.list)
1033
1034                      -- push continuation on stack
1035                      top := top+1
1036                      stack.type[top]  := act.type
1037                      stack.field[top] := act.field
1038                      stack.list[top] := act.list
1039                      stack.entry[top] := 1(BYTE)
1040
1041                      -- push tuple element type on stack
1042                      top := top + 1
1043                      stack.type[top] :=
1044                        INT label[INT type[act.type+(act.field+1)]]
1045                      stack.entry[top] := 0(BYTE)

ADGENER.OCC                                                                Page  20
1046
1047                      -- construct read request for tuple field
1048                      [4]BYTE act RETYPES act.list:
1049                      BYTE    mod IS act[3]:
1050                      [3]BYTE ref IS [act FROM 0 FOR 3]:
1051                      SEQ
1052                        [4]BYTE toks RETYPES token[2]:
1053                        SEQ
1054                          toks[2] := mod
1055                          toks[1] := Read.MToken
1056                          toks[0] := BYTE act.field
1057                        [4]BYTE toks RETYPES token[0]:
1058                        SEQ
1059                          toks[3] := 0(BYTE)
1060                          [toks FROM 0 FOR 3] := ref
1061
1062                      -- send token into network
1063                      to.net ! net.token; token
1064
1065                      -- empty print message
1066                      len := 0
1067
1068              ---------------------------------------------------------------
1069              -- union value
1070              ---------------------------------------------------------------
1071              Union.Chr
1072                SEQ
1073
1074                  -- receive message
1075                  Receive(result, line, len, error)
1076                  act.list := result
1077
1078                  IF
1079                    error
1080                      SKIP
1081                    TRUE
1082                      SEQ
1083
1084                        -- copy union reference
1085                        Copy(act.list)
1086
1087                        -- construct request token for variant part
1088                        [4]BYTE act RETYPES act.list:
1089                        BYTE    mod IS act[3]:
1090                        [3]BYTE ref IS [act FROM 0 FOR 3]:
1091                        SEQ
1092                          [4]BYTE toks RETYPES token[2]:
1093                          SEQ
1094                            toks[2] := mod
1095                            toks[1] := Read.MToken
1096                            toks[0] := 0(BYTE)
1097                          [4]BYTE toks RETYPES token[0]:
1098                          SEQ
1099                            toks[3] := 0(BYTE)
1100                            [toks FROM 0 FOR 3] := ref

ADGENER.OCC                                                                Page  21
1101
1102                        -- send token to network
1103                        to.net ! net.token; token
1104
1105                        -- receive result token from network
1106                        Receive(result, line, len, error)
1107                        Assert(NOT error, union.err)
1108                        variant := INT(result >> 1)
1109                        SEQ i= act.type FOR variant
1110                          Assert(type[i]<>End.Type, union.err)
1111
1112                        -- construct print message
1113                        len := 0
1114                        append.int(len, line, variant, 0)
1115                        line[len] := '$'
1116                        len := len+1
1117
1118                        -- push continuation on stack
1119                        top := top+1
1120                        stack.type[top] :=
1121                          INT label[INT type[act.type+variant]]
1122                        stack.entry[top] := 0(BYTE)
1123
1124                        -- construct request token for element part
1125                        [4]BYTE act RETYPES act.list:
1126                        BYTE    mod IS act[3]:
1127                        [3]BYTE ref IS [act FROM 0 FOR 3]:
1128                        SEQ
1129                          [4]BYTE toks RETYPES token[2]:
1130                          SEQ
1131                            toks[2] := mod
1132                            toks[1] := Read.MToken
1133                            toks[0] := 1(BYTE)
1134                          [4]BYTE toks RETYPES token[0]:
1135                          SEQ
1136                            toks[3] := 0(BYTE)
1137                            [toks FROM 0 FOR 3] := ref
1138
1139                          -- send token to network
1140                          to.net ! net.token; token
1141
1142            -------------------------------------------------------------------
1143            -- send result message to inout interface
1144            -------------------------------------------------------------------
1145            to.inout ! is.result; len :: line
1146
1147    :
1148
1149    ---------------------------------------------------------------------------
1150    -- Body of Generator
1151    ---------------------------------------------------------------------------
1152    SEQ
1153
1154      -- loop forever
1155      WHILE TRUE

ADGENER.OCC                                                                Page  22
1156        SEQ
1157
1158          -- receive and compile type description
1159          Compile.Type(type, label, root, fun)
1160
1161          -- construct activation token
1162          [4]BYTE toks RETYPES token[2]:
1163          SEQ
1164            toks[2] := 1(BYTE)            -- address of first module
1165            toks[1] := FCall.PToken       -- fast call
1166          -- ignore context and offset fields (not required)
1167          token[0] := (INT32 fun)         -- function descriptor
1168          [4]BYTE toks RETYPES token[0]:
1169          toks[3] := 0(BYTE)              -- address of master module
1170
1171          -- wait for start signal from inout interface
1172          from.inout ? CASE start
1173
1174          -- send activation token to network
1175          to.net ! net.token; token
1176
1177          -- receive activation address from network (unused)
1178          from.net ? CASE net.token; token
1179
1180          -- receive result tokens, convert them into printable form and
1181          -- forward result to inout interface
1182          Result(type, label, root)
1183
1184          -- send end of result signal
1185          to.inout ! end.result
1186
1187          -- construct stop token
1188          [4]BYTE toks RETYPES token[2]:
1189          toks[1] := Stop.Token
1190
1191          -- send stop token to network
1192          to.net ! net.token; token
1193  :
1194
1195  -----------------------------------------------------------------------------
1196  -- End of ADGENER.OCC
1197  -----------------------------------------------------------------------------

ADINOUT.OCC                                                                Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADINOUT.OCC                                            Wolfgang Schreiner
 4  -- Input/Output Interface                                 (c) RISC-Linz 1990
 5  --
 6  -- Handles interaction of the ADAM architecture with the outside world.
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- External References
12  -----------------------------------------------------------------------------
13
14  -- include files
15  #INCLUDE "adglobal.inc" -- global constants
16  #INCLUDE "aderror.inc"  -- error messages
17  #INCLUDE "adnet.inc"    -- network constants and protocols
18  #INCLUDE "admaster.inc" -- master protocols and constants
19  #INCLUDE "hostio.inc"   -- input/output protocols and constants
20
21  -- compilation units
22  #USE "hostio.lib"       -- input/output interface to server
23  #USE "string.lib"       -- string handling facilities
24  #USE "convert.lib"      -- type conversion library
25
26  -----------------------------------------------------------------------------
27  -- Input/Output Interface to Server
28  --
29  --   Handles interaction with the outside world, i.e.
30  --
31  --     * reads program and forwards it to connector,
32  --     * reads type descriptor file and forwards it to result generator,
33  --     * receives result and prints it,
34  --     * receives execution statistics and prints it.
35  --
36  -- IN  from.net:    channel from that acknowledge signals are received
37  -- OUT to.net:      channel through that program is forwarded
38  -- OUT to.result:   channel through that type description is forwarded
39  -- IN  from.result: channel from that printable result is received
40  -- IN  from.stat:   channel from that statistics is received
41  -- IO  fs, ts:      input/output channels from/to server
42  -----------------------------------------------------------------------------
43  PROC Adam.In.Out(CHAN OF Net.Link from.net, to.net,
44    CHAN OF Result from.result, to.result, CHAN OF Stat from.stat,
45    CHAN OF SP fs, ts)
46
47    -- parameters for command line parsing
48    VAL [Opt.Num][]BYTE Opt.Str IS
49      [Prog.Opt, Expr.Opt, Out.Opt, Load.Opt, Batch.Opt, Stat.Opt]:
50    VAL [Opt.Num]INT Opt.Par IS
51      [spopt.maybe, spopt.always, spopt.always,
52        spopt.never, spopt.never, spopt.never]:
53
54    -- result values of command line parsing
55    [Cmd.Size]BYTE  command:   -- command line

ADINOUT.OCC                                                                Page   2
56    [Opt.Num]BOOL   opt.exist: -- which options do exist
57    [Opt.Num][2]INT opt.par:   -- position of option parameters in command line
58    INT             error:     -- length of error message
59
60    -- option flags
61    BOOL prog:  -- name of program file given
62    BOOL expr:  -- name of expression file given
63    BOOL out:   -- name of output file given
64    BOOL load:  -- program already loaded
65    BOOL batch: -- work in batch mode
66    BOOL stat:  -- print statistics
67
68    -- directory and file names
69    [Name.Size]BYTE dir.name:  -- directory name
70    [Name.Size]BYTE prog.file: -- name of program file
71    [Name.Size]BYTE expr.file: -- name of expression file
72    [Name.Size]BYTE type.file: -- name of type file
73    [Name.Size]BYTE out.file:  -- name of output file
74
75    -- lengths of file names
76    INT prog.len: -- length of program file name
77    INT expr.len: -- length of expression file name
78    INT out.len:  -- length of output file name
79    INT type.len: -- length of type file name
80
81    [so.temp.filename.length]BYTE tmp.file: -- name of temporary file
82
83    -- stream identifiers:
84    INT32 out.stream: -- output stream (protocol of session)
85    INT32 tmp.stream: -- temporary stream (statistics)
86
87    -- time related variables
88    TIMER clock: -- transputer clock
89    INT   time:  -- time between two events
90    INT   time0: -- time when program started
91    INT   time1: -- time when program returned result
92    INT   time2: -- time when program terminated
93
94    -- miscellaneous variables
95    INT  dir.len: -- length of directory name
96    BOOL going:   -- termination variable
97    BOOL loop:    -- termination variable
98    INT  len:     -- length of string
99    BYTE key:     -- input character
100    BYTE result:  -- success of last file operation
101
102    [Line.Size]BYTE string: -- output string
103
104    ---------------------------------------------------------------------------
105    -- Write String to Terminal and Output File
106    -- IN str: output string
107    ---------------------------------------------------------------------------
108    PROC Write.String(VAL []BYTE str)
109
110      BYTE result: -- success of operation

ADINOUT.OCC                                                                Page   3
111
112      -------------------------------------------------------------------------
113      -- Body of Write.String
114      -------------------------------------------------------------------------
115      SEQ
116        so.write.string(fs, ts, str)
117        so.fwrite.string(fs, ts, out.stream, str, result)
118    :
119
120    ---------------------------------------------------------------------------
121    -- Write New Line Character to Terminal and Output File
122    ---------------------------------------------------------------------------
123    PROC Write.Nl()
124
125      BYTE result: -- success of operation
126
127      -------------------------------------------------------------------------
128      -- Body of Write.Nl
129      -------------------------------------------------------------------------
130      SEQ
131        so.write.nl(fs, ts)
132        so.fwrite.nl(fs, ts, out.stream, result)
133    :
134
135    ---------------------------------------------------------------------------
136    -- Assert Condition
137    --
138    --   Checks condition and prints error message to screen and output file,
139    --   if neccessary.
140    --
141    -- IN cond: test condition
142    -- IN mess: error message
143    ---------------------------------------------------------------------------
144    PROC Assert(VAL BOOL cond, VAL []BYTE mess)
145
146      -------------------------------------------------------------------------
147      -- Body of Assert
148      -------------------------------------------------------------------------
149      SEQ
150        IF
151          cond
152            SKIP
153          TRUE
154            SEQ
155              Write.Nl()
156              Write.String(adam.assert)
157              Write.Nl()
158              Write.String(mess)
159              Write.Nl()
160              STOP
161    :
162
163    ---------------------------------------------------------------------------
164    -- Determine file names
165    --

ADINOUT.OCC                                                                Page   4
166    --   Parses the command lines and determine names of program, expression,
167    --   and output files. Names that are not explicitly given are determined
168    --   by default rules and by user input. Missing names in batch mode
169    --   result in an error break of the program. After the call of this
170    --   procedure, all flags for that file names have been determined
171    --   are set to TRUE.
172    --
173    -- IO  prog:      name of program file given in command string
174    -- IO  expr:      name of expression file given in command string
175    -- IO  out:       name of output file given in command string
176    -- OUT prog.file: name of program file
177    -- OUT expr.file: name of expression file
178    -- OUT out.file:  name of output file
179    -- OUT type.file: name of type file
180    -- OUT prog.len:  length of program file name
181    -- OUT expr.len:  length of expression file name
182    -- OUT out.len:   length of output file name
183    -- OUT type.len:  length of type file name
184    --
185    -- ATTENTION: References several global variables!
186    ---------------------------------------------------------------------------
187    PROC File.Names(BOOL prog, expr, out,
188      []BYTE prog.file, expr.file, out.file, type.file,
189      INT prog.len, expr.len, out.len, type.len)
190
191      BOOL  exists: -- file exists
192      BOOL  going:  -- termination variable
193      INT32 stream: -- stream identifier
194      BYTE  result: -- result of file operation
195
196      -------------------------------------------------------------------------
197      -- Append File Extension
198      --
199      --   Checks if file name contains extension. If not, a default
200      --   extension is appended to name and length of name is updated.
201      --   In any case, the default directory specification is inserted.
202      --
203      -- IO file: file name
204      -- IO len:  length of name
205      -- IN ext:  default extension
206      -------------------------------------------------------------------------
207      PROC Append.Ext([]BYTE file, INT len, VAL [Ext.Size]BYTE ext)
208
209        INT  start: -- position of first non-empty character
210        BYTE char:  -- first non-empty character
211        BOOL error: -- error occurred
212
213        -----------------------------------------------------------------------
214        -- Body of Append.Ext
215        -----------------------------------------------------------------------
216        SEQ
217          -- delete unused space
218          SEQ i = len FOR (SIZE file)-len
219            file[i] := Null
220          -- append extension

ADINOUT.OCC                                                                Page   5
221          IF
222            char.pos('.', file) = (-1)
223              SEQ
224                Assert((len+Ext.Size)<=Name.Size, file.len)
225                [file FROM len FOR Ext.Size] := ext
226                len := len+Ext.Size
227            TRUE
228              SKIP
229          -- search for first non-empty character
230          start, char := search.no.match([' ', Null], file)
231          -- insert directory specification
232          str.shift(file, start, len-start, dir.len-start, error)
233          Assert(NOT error, file.len)
234          [file FROM 0 FOR dir.len] := [dir.name FROM 0 FOR dir.len]
235          len := len+(dir.len-start)
236      :
237
238      -------------------------------------------------------------------------
239      -- Replace File Extension
240      --
241      --   Replaces the extension of a file name by another one.
242      --
243      -- IO file: file name with extension
244      -- IO len:  length of file name
245      -- IN ext:  new extension
246      -------------------------------------------------------------------------
247      PROC Replace.Ext([]BYTE file, INT len, VAL [Ext.Size]BYTE ext)
248
249        INT pos: -- position of dot in file name
250
251        -----------------------------------------------------------------------
252        -- Body of Replace.Ext
253        -----------------------------------------------------------------------
254        SEQ
255          pos := char.pos('.', file)
256          Assert(pos <> (-1), file.ext)
257          Assert((pos+Ext.Size)<=Name.Size, file.len)
258          [file FROM pos FOR Ext.Size] := ext
259          len := pos+Ext.Size
260      :
261
262      -------------------------------------------------------------------------
263      -- Test if File Exists
264      --
265      --   Tests if specified file exists and writes error message, if not.
266      --
267      -- IN  file:   name of file
268      -- IN  len:    length of file name
269      -- OUT exists: TRUE iff file exists
270      -------------------------------------------------------------------------
271      PROC File.Exists(VAL []BYTE file, VAL INT len, BOOL exists)
272
273        -----------------------------------------------------------------------
274        -- Body of File.Exists
275        -----------------------------------------------------------------------

ADINOUT.OCC                                                                Page   6
276        SEQ
277          so.test.exists(fs, ts, file, exists)
278          IF
279            exists
280              SKIP
281            TRUE
282              SEQ
283                so.write.string(fs, ts, "File ")
284                so.write.string(fs, ts, [file FROM 0 FOR len])
285                so.write.string.nl(fs, ts, " not found.")
286      :
287
288      -------------------------------------------------------------------------
289      -- Body of File.Names
290      -------------------------------------------------------------------------
291      SEQ
292
293        -----------------------------------------------------------------------
294        -- scan command line parameters
295        -----------------------------------------------------------------------
296
297        -- name of program file
298        IF
299          prog
300            SEQ
301              prog.len := opt.par[0][1]
302              Assert(prog.len<=Name.Size, file.len)
303              [prog.file FROM 0 FOR prog.len] :=
304                [command FROM opt.par[0][0] FOR prog.len]
305          TRUE
306            SKIP
307
308        -- name of expression file
309        IF
310          expr
311            SEQ
312              expr.len := opt.par[1][1]
313              Assert(expr.len<=Name.Size, file.len)
314              [expr.file FROM 0 FOR expr.len] :=
315                [command FROM opt.par[1][0] FOR expr.len]
316          TRUE
317            SKIP
318
319        -- name of output file
320        IF
321          out
322            SEQ
323              out.len  := opt.par[2][1]
324              Assert(out.len<=Name.Size, file.len)
325              [out.file FROM 0 FOR out.len] :=
326                [command FROM opt.par[2][0] FOR out.len]
327          TRUE
328            SKIP
329
330        -----------------------------------------------------------------------

ADINOUT.OCC                                                                Page   7
331        -- determine file names that are explicitly given
332        -- and add default extensions, if required
333        -----------------------------------------------------------------------
334
335        -- program file explicitly given and existing?
336        IF
337          prog
338            SEQ
339              Append.Ext(prog.file, prog.len, Prog.Ext)
340              File.Exists(prog.file, prog.len, prog)
341          TRUE
342            SKIP
343
344        -- expression file explicitly given and existing?
345        -- corresponding type file also existing?
346        IF
347          expr
348            SEQ
349              Append.Ext(expr.file, expr.len, Expr.Ext)
350              File.Exists(expr.file, expr.len, expr)
351              IF
352                expr
353                  SEQ
354                    type.file := expr.file
355                    type.len := expr.len
356                    Replace.Ext(type.file, type.len, Type.Ext)
357                    File.Exists(type.file, type.len, expr)
358                TRUE
359                  SKIP
360          TRUE
361            SKIP
362
363        -- output file explicitly given?
364        IF
365          out
366            Append.Ext(out.file, out.len, Out.Ext)
367          TRUE
368            SKIP
369
370        -----------------------------------------------------------------------
371        -- determine names of files that are not explicitly given
372        -- and add extensions if required
373        -----------------------------------------------------------------------
374
375        -- determine name of program file
376        IF
377          prog OR load
378            -- program file name found or not required
379            SKIP
380          batch
381            -- no program file means error when working in batch mode
382            Assert(FALSE, prog.miss)
383          TRUE
384            -- ask user for name of program file
385            SEQ

ADINOUT.OCC                                                                Page   8
386              exists := FALSE
387              WHILE (NOT exists) OR (result <> spr.ok)
388                SEQ
389                  -- ask for file name
390                  so.write.string(fs, ts, "Enter name of program file (")
391                  so.write.string(fs, ts, [Prog.Ext FROM 0 FOR SIZE Prog.Ext])
392                  so.write.string(fs, ts, "): ")
393                  so.read.echo.line(fs, ts, prog.len, prog.file, result)
394                  IF
395                    result = spr.ok
396                      SEQ
397                        so.write.nl(fs, ts)
398                        -- append extension, if required
399                        Append.Ext(prog.file, prog.len, Prog.Ext)
400                        -- test if file exists
401                        File.Exists(prog.file, prog.len, exists)
402                    TRUE
403                      SKIP
404              -- set flag
405              prog := TRUE
406
407        -- determine name of expression file
408        IF
409          expr
410            -- expression file name found
411            SKIP
412          batch
413            -- no expression file means error when working in batch mode
414            Assert(FALSE, expr.miss)
415          TRUE
416            -- ask user for name of expression file
417            SEQ
418              exists := FALSE
419              WHILE (NOT exists) OR (result <> spr.ok)
420                SEQ
421                  -- ask for file name
422                  so.write.string(fs, ts,
423                    "Enter name of expression file (")
424                  so.write.string(fs, ts, [Expr.Ext FROM 0 FOR SIZE Expr.Ext])
425                  so.write.string(fs, ts, "): ")
426                  so.read.echo.line(fs, ts, expr.len, expr.file, result)
427                  so.write.nl(fs, ts)
428                  IF
429                    result = spr.ok
430                      SEQ
431                        -- append extension, if required
432                        Append.Ext(expr.file, expr.len, Expr.Ext)
433                        -- test if file exists
434                        File.Exists(expr.file, expr.len, exists)
435                        IF
436                          exists
437                            SEQ
438                              -- construct name of type file
439                              type.file := expr.file
440                              type.len := expr.len

ADINOUT.OCC                                                                Page   9
441                              Replace.Ext(type.file, type.len, Type.Ext)
442                              -- test if file exists
443                              File.Exists(type.file, type.len, exists)
444                          TRUE
445                            SKIP
446                    TRUE
447                      SKIP
448              -- set flag
449              expr := TRUE
450
451        -- determine name of output file
452        IF
453          out
454            -- output file name given
455            SKIP
456          prog
457            -- take name of program file, add default extension
458            SEQ
459              out.file := prog.file
460              out.len := prog.len
461              Replace.Ext(out.file, out.len, Out.Ext)
462              out := TRUE
463          expr
464            -- take name of expression file, add default extension
465            SEQ
466              out.file := expr.file
467              out.len := expr.len
468              Replace.Ext(out.file, out.len, Out.Ext)
469              out := TRUE
470          batch
471            -- take default output file name
472            SEQ
473              [out.file FROM 0 FOR (SIZE Out.File)] := Out.File
474              out := TRUE
475          TRUE
476            SEQ
477              -- ask user for file name
478              going := TRUE
479              WHILE going
480                SEQ
481                  so.write.string(fs, ts, "Enter name of output file (")
482                  so.write.string(fs, ts, [Out.Ext FROM 0 FOR SIZE Out.Ext])
483                  so.write.string(fs, ts, "): ")
484                  so.read.echo.line(fs, ts, out.len, out.file, result)
485                  so.write.nl(fs, ts)
486                  IF
487                    result = spr.ok
488                      SEQ
489                        -- append extension, if required
490                        Append.Ext(out.file, out.len, Out.Ext)
491                        -- try to open output file
492                        so.open(fs, ts, out.file, spt.text, spm.output,
493                          stream, result)
494                        IF
495                          result = spr.ok

ADINOUT.OCC                                                                Page  10
496                            -- close file again
497                            SEQ
498                              so.close(fs, ts, stream, result)
499                              Assert(result=spr.ok, out.close)
500                              going := FALSE
501                          TRUE
502                            -- some error occurred when opening the file
503                            SEQ
504                              so.write.string(fs, ts, "File ")
505                              so.write.string(fs, ts,
506                                [out.file FROM 0 FOR out.len])
507                              so.write.string(fs, ts, " could not be opened.")
508                              so.write.nl(fs, ts)
509                              going := TRUE
510                    TRUE
511                      -- some error occurred when reading the file name
512                      going := TRUE
513              -- set flag
514              out := TRUE
515
516    :
517
518
519    ---------------------------------------------------------------------------
520    -- Load Program and Forward it to Network Connection
521    -- ATTENTION: References several global variables in In.Out!
522    ---------------------------------------------------------------------------
523    PROC Load.Prog()
524
525      INT32 stream: -- stream id of program file
526      BYTE  result: -- success of last file operation
527      BOOL  going:  -- termination variable
528      INT   len:    -- length of program cluster
529
530      [Cluster.Size]INT32 cluster: -- program cluster
531
532      -------------------------------------------------------------------------
533      -- Body of Load.Prog
534      -------------------------------------------------------------------------
535      SEQ
536
537        -- start status report
538        Write.String("(")
539        Write.String([prog.file FROM 0 FOR prog.len])
540
541        -- open program file
542        so.open(fs, ts, prog.file, spt.binary, spm.input, stream, result)
543        Assert(result=spr.ok, prog.open)
544
545        -- transmit program
546        going := TRUE
547        WHILE going
548          SEQ
549
550            -- read program cluster

ADINOUT.OCC                                                                Page  11
551            [Cluster.Size*4]BYTE clust RETYPES cluster:
552            so.read(fs, ts, stream, len, clust)
553
554            IF
555              len < (SIZE cluster)
556                SEQ
557                  -- eof must have occurred
558                  so.eof(fs, ts, stream, result)
559                  Assert(result=spr.ok,prog.read)
560                  going := FALSE
561              TRUE
562                SKIP
563
564            -- forward program cluster
565            to.net ! net.program; cluster
566
567        -- close program file
568        so.close(fs, ts, stream, result)
569        Assert(result=spr.ok, prog.close)
570
571        -- end status report
572        Write.String(")")
573    :
574
575    ---------------------------------------------------------------------------
576    -- Load Expression File to Network Connection
577    -- ATTENTION: References several global variables of Interface!
578    ---------------------------------------------------------------------------
579    PROC Load.Expr()
580
581      INT32 stream: -- stream id of expression file
582      BYTE  result: -- success of last file operation
583      BOOL  going:  -- termination variable
584      INT   len:    -- length of expression cluster
585
586      [Cluster.Size]INT32 cluster: -- expression cluster
587
588      -------------------------------------------------------------------------
589      -- Body of Load.Expr
590      -------------------------------------------------------------------------
591      SEQ
592
593        -- start status report
594        Write.String("(")
595        Write.String([expr.file FROM 0 FOR expr.len])
596
597        -- open expression file
598        so.open(fs, ts, expr.file, spt.binary, spm.input, stream, result)
599        Assert(result=spr.ok, expr.open)
600
601        -- send net signal for begin of transmission
602        to.net ! net.appendprog
603
604        -- transmit program
605        going := TRUE

ADINOUT.OCC                                                                Page  12
606        WHILE going
607          SEQ
608
609            -- read expression cluster
610            [Cluster.Size*4]BYTE clust RETYPES cluster:
611            so.read(fs, ts, stream, len, clust)
612
613            IF
614              len < (SIZE cluster)
615                SEQ
616                  -- eof must have occurred
617                  so.eof(fs, ts, stream, result)
618                  Assert(result=spr.ok,expr.read)
619                  going := FALSE
620              TRUE
621                SKIP
622
623            -- forward expression cluster
624            to.net ! net.program; cluster
625
626        -- close expression file
627        so.close(fs, ts, stream, result)
628        Assert(result=spr.ok, expr.close)
629
630        -- end status report
631        Write.String(")")
632
633    :
634
635    ---------------------------------------------------------------------------
636    -- Load Type Descriptor File to Result Generator
637    --
638    -- ATTENTION: References several global variables of Interface!
639    ---------------------------------------------------------------------------
640    PROC Load.Type()
641
642      INT32 stream: -- stream id of load descriptor file
643      BYTE  result: -- success of last file operation
644      INT   len:    -- length of last input line
645      BOOL  going:  -- termination variable
646      BOOL  okay:   -- success of type compilation
647
648      [Type.Size]BYTE string: -- line of type descriptor
649
650      -------------------------------------------------------------------------
651      -- Body of Load.Type
652      -------------------------------------------------------------------------
653      SEQ
654
655        -- start status report
656        Write.String("(")
657        Write.String([type.file FROM 0 FOR type.len])
658
659        -- open type descriptor file
660        so.open(fs, ts, type.file, spt.text, spm.input, stream, result)

ADINOUT.OCC                                                                Page  13
661        Assert(result=spr.ok, type.open)
662
663        -- read type descriptor file
664        going := TRUE
665        WHILE going
666          SEQ
667
668            -- read one line of file
669            so.gets(fs, ts, stream, len, string, result)
670
671            -- line valid?
672            going := result = spr.ok
673
674            IF
675              going
676                -- forward cluster to result generator
677                to.result ! is.type; len :: string
678              TRUE
679                SKIP
680
681        -- end of file occurred?
682        so.eof(fs, ts, stream, result)
683        Assert(result=spr.ok, type.read)
684
685        -- close type descriptor file
686        so.close(fs, ts, stream, result)
687        Assert(result=spr.ok, type.close)
688
689        -- terminate transmission
690        to.result ! end.type
691
692        -- end status report
693        Write.String(")")
694
695        -- determine success of type compilation
696        from.result ? CASE type.succ; okay
697        Assert(okay, type.err)
698
699    :
700
701    ---------------------------------------------------------------------------
702    -- Body of In.Out
703    ---------------------------------------------------------------------------
704    SEQ
705
706      -------------------------------------------------------------------------
707      -- initializations
708      -------------------------------------------------------------------------
709
710      -- hello message
711      so.write.string(fs, ts, "This is ADAM V")
712      so.write.string(fs, ts, Version)
713      so.write.string(fs, ts, " (")
714      so.write.int(fs, ts, Mod.Num, 0)
715      so.write.string(fs, ts, " modules used).")

ADINOUT.OCC                                                                Page  14
716      so.write.nl(fs, ts)
717
718      -- read environment variable for directory specification
719      so.getenv(fs, ts, Dir.Var, dir.len, dir.name, result)
720      IF
721        result = spr.ok
722          IF
723            (dir.len < (SIZE dir.name)) AND (dir.name[dir.len-1] <> '\')
724              SEQ
725                dir.name[dir.len] := '\'
726                dir.len := dir.len+1
727            TRUE
728              SKIP
729        TRUE
730          dir.len := 0
731
732      -- get command line parameters
733      so.parse.command.line(fs, ts,
734        Opt.Str, Opt.Par, opt.exist, opt.par, error, command)
735      Assert(error=0, [command FROM 0 FOR error])
736
737      -- fetch option flags
738      prog  := opt.exist[0] -- name of program file given
739      expr  := opt.exist[1] -- name of expression file given
740      out   := opt.exist[2] -- name of output file given
741      load  := opt.exist[3] -- program already loaded
742      batch := opt.exist[4] -- work in batch mode
743      stat  := opt.exist[5] -- print statistics
744
745      -- determine names of files
746      File.Names(prog, expr, out, prog.file, expr.file, out.file, type.file,
747        prog.len, expr.len, out.len, type.len)
748
749      -- open output file for protocol of session
750      so.open(fs, ts, out.file, spt.text, spm.output, out.stream, result)
751      Assert(result=spr.ok, out.open)
752
753      -- write hello message to output file
754      so.fwrite.string(fs, ts, out.stream, "This is ADAM V", result)
755      so.fwrite.string(fs, ts, out.stream, Version, result)
756      so.fwrite.string(fs, ts, out.stream, " (", result)
757      so.fwrite.int(fs, ts, out.stream, Mod.Num, 0, result)
758      so.fwrite.string(fs, ts, out.stream, " processors used).", result)
759      so.fwrite.nl(fs, ts, out.stream, result)
760
761      -------------------------------------------------------------------------
762      -- main loop
763      -------------------------------------------------------------------------
764      going := TRUE
765      WHILE going
766        SEQ
767
768          ---------------------------------------------------------------------
769          -- load files
770          ---------------------------------------------------------------------

ADINOUT.OCC                                                                Page  15
771
772          -- send net signal for begin of transmission
773          to.net ! net.startprog
774
775          -- forward program to network connection
776          IF
777            load
778              -- program is assumed to be loaded
779              SKIP
780            TRUE
781              -- load program
782              Load.Prog()
783
784          -- forward expression file to network connection
785          Load.Expr()
786
787          -- signal end of transmission
788          to.net ! net.endprog
789
790          -- wait for acknowledge signal
791          from.net ? CASE net.ack
792
793          -- forward type descriptor file to result generator
794          Load.Type()
795
796          ---------------------------------------------------------------------
797          -- receive results and statistics and write them to output file
798          ---------------------------------------------------------------------
799
800          -- open temporary file for statistics
801          IF
802            stat
803              SEQ
804                so.open.temp(fs, ts, spt.text, tmp.file, tmp.stream, result)
805                Assert(result=spr.ok,tmp.open)
806            TRUE
807              SKIP
808
809          -- make status report
810          Write.Nl()
811          Write.String("Program started.")
812          Write.Nl()
813
814          -- signal start of program execution
815          to.result ! start
816
817          -- remember time
818          clock ? time0
819
820          -- wait simultaneously for results and statistics and print them
821          loop := TRUE
822          WHILE loop
823            ALT
824              from.result ? CASE
825                is.result; len :: string    -- write result

ADINOUT.OCC                                                                Page  16
826                  Write.String([string FROM 0 FOR len])
827                end.result                  -- end result
828                  SEQ
829                    clock ? time1
830                    Write.Nl()
831                    loop := FALSE
832              from.stat ? len :: string     -- write statistics
833                IF
834                  stat
835                    SEQ
836                      so.fwrite.string.nl(fs, ts, tmp.stream,
837                        [string FROM 0 FOR len], result)
838                      Assert(result=spr.ok,tmp.write)
839                  TRUE
840                    SKIP
841
842          -- report status and time
843          Write.String("Result received (")
844          time := (10*(time1-time0))/Ticks
845          INTTOSTRING(len, string, time/10)
846          Write.String([string FROM 0 FOR len])
847          Write.String(".")
848          INTTOSTRING(len, string, time REM 10)
849          Write.String([string FROM 0 FOR len])
850          Write.String(" s).")
851          Write.Nl()
852
853          -- wait for termination signal
854          from.net ? CASE net.ack
855          clock ? time2
856
857          -- report status and time
858          Write.String("Program terminated (")
859          time := (10*(time2-time0))/Ticks
860          INTTOSTRING(len, string, time/10)
861          Write.String([string FROM 0 FOR len])
862          Write.String(".")
863          INTTOSTRING(len, string, time REM 10)
864          Write.String([string FROM 0 FOR len])
865          Write.String(" s).")
866          Write.Nl()
867
868          ---------------------------------------------------------------------
869          -- append statistics to output file
870          ---------------------------------------------------------------------
871          IF
872            stat
873              SEQ
874
875                -- close temporary file and open for reading
876                so.close(fs, ts, tmp.stream, result)
877                Assert(result=spr.ok, tmp.close)
878                so.open(fs, ts, tmp.file, spt.text, spm.input, tmp.stream,
879                  result)
880                Assert(result=spr.ok, tmp.open)

ADINOUT.OCC                                                                Page  17
881
882                -- read statistics from temporary file and write
883                -- to output file (not to screen!)
884                so.fwrite.string.nl(fs, ts, out.stream,
885                  "Program Statistics:", result)
886                loop := TRUE
887                WHILE loop
888                  SEQ
889                    so.read(fs, ts, tmp.stream, len, string)
890                    so.fwrite.string(fs, ts, out.stream,
891                      [string FROM 0 FOR len], result)
892                    loop := (len = (SIZE string))
893
894                -- close temporary file and delete it
895                so.close(fs, ts, tmp.stream, result)
896                Assert(result=spr.ok, tmp.close)
897                so.remove(fs, ts, tmp.file, result)
898                Assert(result=spr.ok, tmp.delete)
899
900            TRUE
901              SKIP
902
903          ---------------------------------------------------------------------
904          -- determine new file names
905          ---------------------------------------------------------------------
906          IF
907            batch
908
909              -- terminate program if in batch mode
910              going := FALSE
911
912            TRUE
913              SEQ
914
915                -- Ask for another expression file
916                loop := TRUE
917                WHILE loop
918                  SEQ
919                    so.write.string(fs, ts, "Another Expression (y/n)? ")
920                    so.getkey(fs, ts, key, result)
921                    Assert(result=spr.ok, key.read)
922                    so.write.char(fs, ts, key)
923                    so.write.nl(fs, ts)
924                    IF
925                      is.lower(key)
926                        key := BYTE ((INT key) - 32)
927                      TRUE
928                        SKIP
929                    loop := (key <> 'Y') AND (key <> 'N')
930
931                IF
932                  key = 'Y'
933                    -- reset expression flag
934                    expr := FALSE
935                  TRUE

ADINOUT.OCC                                                                Page  18
936                    SEQ
937                      -- Ask for another program file
938                      loop := TRUE
939                      WHILE loop
940                        SEQ
941                          so.write.string(fs, ts, "Another Program (y/n)? ")
942                          so.getkey(fs, ts, key, result)
943                          Assert(result=spr.ok, key.read)
944                          so.write.char(fs, ts, key)
945                          so.write.nl(fs, ts)
946                          IF
947                            is.lower(key)
948                              key := BYTE ((INT key) - 32)
949                            TRUE
950                              SKIP
951                          loop := (key <> 'Y') AND (key <> 'N')
952                      IF
953                        key = 'Y'
954                          -- reset program flag and expression flag
955                          prog, expr := FALSE, FALSE
956                        TRUE
957                          -- exit program
958                          going := FALSE
959
960                -- determine new file names
961                IF
962                  going
963                    File.Names(prog, expr, out,
964                      prog.file, expr.file, out.file, type.file,
965                      prog.len, expr.len, out.len, type.len)
966                  TRUE
967                    SKIP
968
969      -- farewell message
970      Write.String("Bye.")
971      Write.Nl()
972
973      -- close output file
974      so.close(fs, ts, out.stream, result)
975      Assert(result=spr.ok, out.close)
976
977      -- terminate server
978      so.exit(fs, ts, sps.success)
979
980  :
981
982  -----------------------------------------------------------------------------
983  -- End of ADINOUT.OCC
984  -----------------------------------------------------------------------------

ADSTAT.OCC                                                                 Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADSTAT.OCC                                             Wolfgang Schreiner
 4  -- Statistics Generator                                   (c) RISC-Linz 1990
 5  --
 6  -- Receives load messages from processors and memories and generates
 7  -- printable statistics.
 8  --
 9  -----------------------------------------------------------------------------
10
11  -----------------------------------------------------------------------------
12  -- External References
13  -----------------------------------------------------------------------------
14
15  -- include files
16  #INCLUDE "adglobal.inc" -- global constants
17  #INCLUDE "adnet.inc"    -- network constants and protocols
18  #INCLUDE "aderror.inc"  -- error messages
19  #INCLUDE "admaster.inc" -- master protocols and constants
20  #INCLUDE "adproc.inc"   -- processor unit protocols and constants
21  #INCLUDE "admemory.inc" -- memory unit protocols and constants
22  #INCLUDE "adstat.inc"   -- statistics constants
23  #INCLUDE "hostio.inc"   -- server constants and protocols
24
25  -- compilation units
26  #USE "convert.lib" -- type conversion facilities
27  #USE "string.lib"  -- string editing facilities
28  #USE "hostio.lib"  -- server interface
29
30  -----------------------------------------------------------------------------
31  -- Statistics Generator
32  --
33  --   Receives load tokens from all processor and memory units, computes
34  --   the relative load of these units and converts them into printable form
35  --   (i.e. generates a little diagram). The procedure does not terminate.
36  --
37  --   This procedure is just a first prototype and will be extended to
38  --   generate more expressive statistics.
39  --
40  -- IN  from.net: tokens from net
41  -- OUT to.inout: output channel to input/output interface
42  -- IO  fs, ts:   input/output channel for error messages
43  -----------------------------------------------------------------------------
44  PROC Adam.Statistics(CHAN OF Net.Link from.net, CHAN OF Stat to.inout,
45    CHAN OF SP fs, ts)
46
47    REAL32 rel.int.load: -- relative internal load
48    REAL32 rel.ext.load: -- relative external load
49    INT    len:          -- length of string
50    BOOL   error:        -- error flag
51
52    [Token.Size]INT32 token: -- network token
53    [Stat.Len]BYTE    str:   -- statistics string
54
55    ---------------------------------------------------------------------------

ADSTAT.OCC                                                                 Page   2
56    -- Assert Condition
57    --
58    --   Checks condition and prints error message to screen,
59    --   if neccessary.
60    --
61    -- IN cond: test condition
62    -- IN mess: error message
63    ---------------------------------------------------------------------------
64    PROC Assert(VAL BOOL cond, VAL []BYTE mess)
65
66      -------------------------------------------------------------------------
67      -- Body of Assert
68      -------------------------------------------------------------------------
69      SEQ
70        IF
71          cond
72            SKIP
73          TRUE
74            SEQ
75              so.write.string.nl(fs, ts, adam.assert)
76              so.write.string.nl(fs, ts, mess)
77              STOP
78    :
79
80    ---------------------------------------------------------------------------
81    -- Body of Adam.Statistics
82    ---------------------------------------------------------------------------
83    SEQ
84
85      -- loop forever
86      WHILE TRUE
87        SEQ
88
89          -- receive token
90          from.net ? CASE net.token; token
91
92          -- erase statistics line
93          SEQ i = 0 FOR Stat.Len
94            str[i] := ' '
95
96          -- extract processor address
97          [4]BYTE toks RETYPES token[2]:
98          BYTE    addr IS toks[0]:
99          INTTOSTRING(len, str, INT addr)
100
101          -- extract pieces of information
102          INT int.load RETYPES token[1]: -- internal load (frame or heap load)
103          INT ext.load RETYPES token[0]: -- external load (req. or exp. queue)
104
105          -- compute relative load
106          [4]BYTE toks RETYPES token[2]:
107          BYTE    type IS toks[1]:
108          CASE type
109            PLoad.PToken
110              SEQ

ADSTAT.OCC                                                                 Page   3
111                -- load of frame store and request queue
112                rel.int.load := (REAL32 TRUNC int.load)/(REAL32 TRUNC Frame.Len
     )
113                rel.ext.load := (REAL32 TRUNC ext.load)/(REAL32 TRUNC Requ.Len)
114                insert.string("P", len, str, 0, error)
115                Assert(NOT error, str.cat)
116            MLoad.MToken
117              SEQ
118                -- load of heap store and expensive request queue
119                rel.int.load := (REAL32 TRUNC int.load)/(REAL32 TRUNC Heap.Len)
120                rel.ext.load := (REAL32 TRUNC ext.load)/(REAL32 TRUNC Exp.Len)
121                insert.string("M", len, str, 0, error)
122                Assert(NOT error, str.cat)
123            ELSE
124              -- invalid token type
125              Assert(FALSE, load.exp)
126
127          -- insert relative load numbers
128          len := Int.Pos
129          append.real32(len, str, 100.0(REAL32) * rel.int.load, 3, 1)
130          len := Ext.Pos
131          append.real32(len, str, 100.0(REAL32) * rel.ext.load, 3, 1)
132
133          -- show relative load by two columns of stars
134          len := INT ROUND (rel.int.load * (REAL32 TRUNC Int.Len))
135          SEQ i = Int.Start FOR len
136            str[i] := '**'
137          len := INT ROUND (rel.ext.load * (REAL32 TRUNC Ext.Len))
138          SEQ i = Ext.Start FOR len
139            str[i] := '**'
140
141          -- send statistics to input/output interface
142          to.inout ! (SIZE str) :: str
143  :
144
145  -----------------------------------------------------------------------------
146  -- End of ADSTAT.OCC
147  -----------------------------------------------------------------------------

ADMASTER.OCC                                                               Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADMASTER.OCC                                           Wolfgang Schreiner
 4  -- Adam Master Module                                     (c) RISC-Linz 1990
 5  --
 6  -- Adam module serving as interface to the host.
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- External References
12  -----------------------------------------------------------------------------
13
14  -- include files
15  #INCLUDE "adglobal.inc" -- global constants
16  #INCLUDE "adnet.inc"    -- network constants and protocols
17  #INCLUDE "admaster.inc" -- master protocols and constants
18  #INCLUDE "hostio.inc"   -- input/output protocols and constants
19
20  -- compilation units
21  #USE "adconn.t8h"  -- network connector
22  #USE "adgener.t8h" -- result generator
23  #USE "adstat.t8h"  -- statistics generator
24  #USE "adinout.t8h" -- input/output interface
25  #USE "hostio.lib"  -- server interface
26
27  -----------------------------------------------------------------------------
28  -- Adam Master Module
29  --
30  --   Provides the interface to the host computer by performing several tasks:
31  --
32  --    * Loads basic program and distributes it over the network.
33  --    * Loads expression program and forwards it to first processor.
34  --    * Triggers start of execution (i.e. evaluation of expression).
35  --    * Requests result values, receives them and prints them.
36  --    * Receives load statistics, processes them and prints them.
37  --
38  -- IN  from.left:  input channel from left neighbour
39  -- OUT to.right:   output channel to right neighbour
40  -- IN  from.right: input channel from right neighbour
41  -- OUT to.left:    output channel to left neighbour
42  -- IO  fs, ts:     input/output channels from/to server
43  -----------------------------------------------------------------------------
44  PROC Adam.Master(CHAN OF Net.Link from.left, to.right, from.right, to.left,
45    CHAN OF SP from.server, to.server)
46
47    -- internal channels
48    CHAN OF Net.Link from.result: -- request tokens from result generator
49    CHAN OF Net.Link to.result:   -- result tokens to result generator
50    CHAN OF Net.Link to.stat:     -- load tokens to statistics generator
51    CHAN OF Net.Link from.inout:  -- program from interface to connector
52    CHAN OF Net.Link to.inout:    -- acknowledge signals to interface
53    CHAN OF Result   result.in:   -- type description to result generator
54    CHAN OF Result   result.out:  -- printable result to interface
55    CHAN OF Stat     stat.out:    -- printable statistics to interface

ADMASTER.OCC                                                               Page   2
56
57    -- input/output channels
58    [4]CHAN OF SP fs, ts:
59
60    ---------------------------------------------------------------------------
61    -- Body of Adam.Master
62    ---------------------------------------------------------------------------
63    PAR
64
65      -- connection to network
66      Adam.Connector(from.left, to.right, from.right, to.left,
67        from.result, to.result, to.stat, from.inout, to.inout, fs[0], ts[0])
68
69      -- result generator
70      Adam.Generator(to.result, from.result, result.out, result.in,
71        fs[1], ts[1])
72
73      -- statistics generator
74      Adam.Statistics(to.stat, stat.out, fs[2], ts[2])
75
76      -- input/output interface
77      Adam.In.Out(to.inout, from.inout, result.out, result.in, stat.out,
78        fs[3], ts[3])
79
80      -- input/output multiplexor
81      CHAN OF BOOL stopper: -- not used
82      so.multiplexor(from.server, to.server, ts, fs, stopper)
83
84  :
85  -----------------------------------------------------------------------------
86  -- End of ADMASTER.OCC
87  -----------------------------------------------------------------------------

ADSTAT.INC                                                                 Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADSTAT.INC                                             Wolfgang Schreiner
 4  -- Constants for Statistics Generator                     (c) RISC-Linz 1990
 5  --
 6  -- Defines Several Constants for the Presentation of Runtime Statistics
 7  -----------------------------------------------------------------------------
 8
 9  -- some parameters that control the format of the statics output
10  VAL INT Stat.Len  IS 80: -- length of a line of statistics
11  VAL INT Int.Pos   IS 3:  -- position for internal load number
12  VAL INT Int.Start IS 10: -- start of the column for the internal load
13  VAL INT Int.Len   IS 30: -- length of the column for the internal load
14  VAL INT Ext.Pos   IS 41: -- position for external load number
15  VAL INT Ext.Start IS 48: -- length of the column for the external load
16  VAL INT Ext.Len   IS 30: -- length of the column for the external load
17
18  -----------------------------------------------------------------------------
19  -- End of ADSTAT.INC
20  -----------------------------------------------------------------------------

ADMASTER.INC                                                               Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADMASTER.INC                                           Wolfgang Schreiner
 4  -- Adam Master Definitions                                (c) RISC-Linz 1990
 5  --
 6  -- Protocols and include files for Adam master module
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- Constants
12  -----------------------------------------------------------------------------
13
14  -- version number
15  VAL []BYTE Version IS "1.0":
16
17  -- maximal sizes
18  VAL INT Cmd.Size    IS 140: -- maximal size of command line
19  VAL INT Name.Size   IS 80:  -- maximal size of file name (inclusive path)
20  VAL INT Ext.Size    IS 4:   -- size of ext. (with leading dot)
21  VAL INT Line.Size   IS 80:  -- maximal size of output line
22  VAL INT Type.Size   IS 80:  -- maximal size of type descriptor line
23  VAL INT CType.Size  IS 128: -- maximal size of compiled type description
24  VAL INT CLabel.Size IS 16:  -- maximal number of type labels (< End.Type!!!)
25  VAL INT Stack.Size  IS 16:  -- maximal number of nested types
26
27  -- command line options
28  VAL INT    Opt.Num   IS 6:   -- number of options allowed
29  VAL []BYTE Prog.Opt  IS " ": -- name of program file
30  VAL []BYTE Expr.Opt  IS "E": -- name of expression file
31  VAL []BYTE Out.Opt   IS "O": -- name of output file
32  VAL []BYTE Load.Opt  IS "L": -- program is already loaded
33  VAL []BYTE Batch.Opt IS "B": -- work in batch mode
34  VAL []BYTE Stat.Opt  IS "S": -- print statistics
35
36  -- null character
37  VAL BYTE Null IS 0(BYTE):
38
39  -- default extensions
40  VAL [Ext.Size]BYTE Prog.Ext IS ".ADM": -- program files
41  VAL [Ext.Size]BYTE Expr.Ext IS ".ADE": -- expression files
42  VAL [Ext.Size]BYTE Type.Ext IS ".ADT": -- type files
43  VAL [Ext.Size]BYTE Out.Ext  IS ".LIS": -- output files
44
45  -- name of environment variable for directory specification
46  VAL []BYTE Dir.Var IS "ADDIR":
47
48  -- default output file name (include trailing blank)
49  VAL []BYTE Out.File IS "ADAM.LIS ":
50
51  -- special characters in type descriptor file
52  VAL BYTE Comm.Chr  IS ';': -- comment type
53  VAL BYTE Root.Chr  IS '#': -- number type
54  VAL BYTE Label.Chr IS ':': -- label type
55  VAL BYTE Int.Chr   IS 'I': -- integer type

ADMASTER.INC                                                               Page   2
56  VAL BYTE Bool.Chr  IS 'B': -- boolean type
57  VAL BYTE Char.Chr  IS 'C': -- character type
58  VAL BYTE Str.Chr   IS 'S': -- string type
59  VAL BYTE List.Chr  IS 'L': -- list type
60  VAL BYTE Tuple.Chr IS 'T': -- tuple type
61  VAL BYTE Union.Chr IS 'U': -- union type
62
63  VAL BYTE End.Type IS 255(BYTE): -- signals end of type in compiled type
64
65  -- some parameters that control the format of the statics output
66  VAL INT Stat.Len  IS 80: -- length of a line of statistics
67  VAL INT Int.Start IS 4:  -- start of the column for the internal load
68  VAL INT Int.Len   IS 35: -- length of the column for the internal load
69  VAL INT Ext.Start IS 40: -- length of the column for the external load
70  VAL INT Ext.Len   IS 35: -- length of the column for the external load
71
72  -----------------------------------------------------------------------------
73  -- Protocols
74  -----------------------------------------------------------------------------
75
76  -- connection between interface and result generator
77  PROTOCOL Result
78    CASE
79      is.type; INT :: []BYTE   -- type descriptor line
80      end.type                 -- end of type description
81      type.succ; BOOL          -- type description successfully compiled
82      start                    -- start signal
83      is.result; INT :: []BYTE -- result string
84      end.result               -- end of result signal
85  :
86
87  -- printable statistics string
88  PROTOCOL Stat IS INT :: []BYTE:
89
90  -----------------------------------------------------------------------------
91  -- End of ADMASTER.INC
92  -----------------------------------------------------------------------------

ADARBIT.OCC                                                                Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADARBIT.OCC                                            Wolfgang Schreiner
 4  -- The Adam Arbiter                                       (c) RISC-Linz 1990
 5  --
 6  -- Interpreter for the arbiter process of the ADAM worker module
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- External References
12  -----------------------------------------------------------------------------
13
14  -- include files
15  #INCLUDE "adglobal.inc" -- global constants and protocols
16  #INCLUDE "adnet.inc"    -- network constants and protocols
17  #INCLUDE "adarbit.inc"  -- arbiter constants and protocols
18  #INCLUDE "aderror.inc"  -- error messages
19
20  -----------------------------------------------------------------------------
21  -- The Adam Arbiter
22  --
23  --   Routes the incoming program clusters and network tokens
24  --   to their correct destinations.
25  --
26  -- IN  act.mod:   address of actual module
27  -- IN  boot.from: number of link from that program is received
28  -- IN  boot.to:   numbers of links to that program has to be forwarded
29  -- IN  route:     link addresses of all modules in the network (incl. master)
30  -- IN  in:        input channels from neighbour modules
31  -- OUT out:       output channels to neighbour modules
32  -- IN  from.proc: input channel from local processor unit
33  -- OUT to.proc:   output channel to local processor unit
34  -- IN  from.mem:  input channel from local memory unit
35  -- OUT to.mem:    output channel to local memory unit
36  -- OUT debug:     debugging channel
37  -----------------------------------------------------------------------------
38  PROC Adam.Arbiter(VAL BYTE act.mod, VAL INT boot.from,
39    VAL [Link.Num]BOOL boot.to, VAL [Mod.Num+1]INT route,
40    [Link.Num]CHAN OF Net.Link in, out,
41    CHAN OF Net.Link from.proc, CHAN OF Mod.Link to.proc,
42    CHAN OF Net.Link from.mem, CHAN OF Mod.Link to.mem,
43    CHAN OF Debug debug)
44
45    INT  count:   -- number of modules in boot path (< Link.Num)
46
47    [Link.Num]CHAN OF Permit   from.buff: -- channel for write permissions
48    [Link.Num]CHAN OF Net.Link to.buff:   -- channel for buffer network tokens
49
50    ---------------------------------------------------------------------------
51    -- Assert Condition
52    --
53    --   Checks condition and forwards error message to debugging channel,
54    --   if neccessary.
55    --

ADARBIT.OCC                                                                Page   2
56    -- IN cond: test condition
57    -- IN mess: error message
58    ---------------------------------------------------------------------------
59    PROC Assert(VAL BOOL cond, VAL []BYTE mess)
60
61      -------------------------------------------------------------------------
62      -- Body of Assert
63      -------------------------------------------------------------------------
64      SEQ
65        IF
66          cond
67            SKIP
68          TRUE
69            SEQ
70              debug ! (SIZE mess) :: mess
71              STOP
72    :
73
74    ---------------------------------------------------------------------------
75    -- Load Program
76    --
77    --   Receives program from link specified by 'boot.from' and
78    --   forwards it to local processor and to all neighbour modules
79    --   specified by 'boot.to'.
80    --
81    -- IN  boot.from: number of link from that program is to be received
82    -- IN  boot.to:   boot.to[i]=TRUE iff program is to be sent to link i
83    -- IN  in:        input channels
84    -- IN  permit:    channels by that write permission are received
85    -- OUT out:       output channels
86    ---------------------------------------------------------------------------
87    PROC Load.Prog(VAL INT boot.from, VAL [Link.Num]BOOL boot.to,
88      [Link.Num]CHAN OF Net.Link in, [Link.Num]CHAN OF Permit permit,
89      [Link.Num]CHAN OF Net.Link out)
90
91      BOOL going: -- termination variable
92
93      [Cluster.Size]INT32 program: -- program cluster
94
95      -------------------------------------------------------------------------
96      -- Body of Load.Prog
97      -------------------------------------------------------------------------
98      SEQ
99
100        -- load program and forward it
101        going := TRUE
102        WHILE going
103
104          in[boot.from] ? CASE
105
106            -- new program signal
107            net.startprog
108              SEQ
109                to.proc ! mod.startprog
110                SEQ i = 0 FOR Link.Num

ADARBIT.OCC                                                                Page   3
111                  IF
112                    boot.to[i]
113                      SEQ
114                        out[i] ! net.startprog
115                        permit[i] ? CASE write.permit
116                    TRUE
117                      SKIP
118
119            -- append program signal
120            net.appendprog
121              SEQ
122                to.proc ! mod.appendprog
123                SEQ i = 0 FOR Link.Num
124                  IF
125                    boot.to[i]
126                      SEQ
127                        out[i] ! net.appendprog
128                        permit[i] ? CASE write.permit
129                    TRUE
130                      SKIP
131
132            -- program cluster
133            net.program; program
134              SEQ
135                to.proc ! mod.program; program
136                SEQ i = 0 FOR Link.Num
137                  IF
138                    boot.to[i]
139                      SEQ
140                        out[i] ! net.program; program
141                        permit[i] ? CASE write.permit
142                    TRUE
143                      SKIP
144
145            -- end of program signal
146            net.endprog
147              SEQ
148                going := FALSE
149                to.proc ! mod.endprog
150                SEQ i = 0 FOR Link.Num
151                  IF
152                    boot.to[i]
153                      SEQ
154                        out[i] ! net.endprog
155                        permit[i] ? CASE write.permit
156                    TRUE
157                      SKIP
158
159    :
160
161    ---------------------------------------------------------------------------
162    -- Receive Acknowledge Signals
163    --
164    --   Waits for acknowledge signals from all links to that the program
165    --   has been forwarded and then send own acknowledge to boot link.

ADARBIT.OCC                                                                Page   4
166    --
167    -- IN  count:    number of expected acknowledge signals from links
168    -- IN  ack.to:   number of link to that acknowledge signal is to be sent
169    -- IN  ack.from: ack.from[i] = TRUE iff signal is expected from link i
170    -- IN  in:       input channels
171    -- IN  permit:   channels by that write permission are received
172    -- OUT out:      output channels
173    ---------------------------------------------------------------------------
174    PROC Acknowledge(VAL INT count, VAL INT ack.to, VAL [Link.Num]BOOL ack.from,
175      [Link.Num]CHAN OF Net.Link in, [Link.Num]CHAN OF Permit permit,
176      [Link.Num]CHAN OF Net.Link out)
177
178      [Link.Num]BOOL    wait:  -- on which links signal is still missing?
179      [Token.Size]INT32 token: -- network token
180
181      BOOL wait.proc: -- signal is missing from processor unit
182      BOOL wait.mem:  -- signal is missing from memory unit
183      INT n:          -- number of signals still missing
184
185      -------------------------------------------------------------------------
186      -- Body of Acknowledge
187      -------------------------------------------------------------------------
188      SEQ
189
190        -- all signals are missing
191        wait := ack.from
192        wait.proc := TRUE
193        wait.mem := TRUE
194        n := count+2
195
196        -- wait for acknowledge signals and skip all tokens received
197        WHILE n > 0
198          ALT
199
200            -- acknowledge from processor unit
201            wait.proc & from.proc ? CASE
202              net.ack
203                SEQ
204                  wait.proc := FALSE
205                  n := n-1
206              net.token; token
207                SKIP
208
209            -- acknowledge from memory unit
210            wait.mem & from.mem ? CASE
211              net.ack
212                SEQ
213                  wait.mem := FALSE
214                  n := n-1
215              net.token; token
216                SKIP
217
218            -- acknowledge from neighbour modules
219            ALT i = 0 FOR Link.Num
220              wait[i] & in[i] ? CASE

ADARBIT.OCC                                                                Page   5
221                net.ack
222                  SEQ
223                    wait[i] := FALSE
224                    n := n-1
225                net.token; token
226                  SKIP
227
228        -- send own acknowledge
229        out[ack.to] ! net.ack
230        permit[ack.to] ? CASE write.permit
231
232    :
233
234    ---------------------------------------------------------------------------
235    -- Transmit tokens
236    --
237    --   Receives tokens and stores them in an internal buffer until
238    --   token may be forwarded. If a stop token is received, this token
239    --   is forwarded to all links in the boot path. Afer the acknowledge
240    --   signals from all of these links have arrived, an acknowledge signal
241    --   is sent to the boot link.
242    --
243    -- IN  ack.to:   link to that acknowledges are to be sent
244    -- IN  stop.to:  stop.to[i] = TRUE iff stop token is to be sent to link i
245    -- IN  route:    route[i] is link number of tokens destined for module i
246    -- IN  in:       input channels
247    -- IN  permit:   channels by that write permission are received
248    -- OUT out:      output channels
249    ---------------------------------------------------------------------------
250    PROC Transmit(VAL INT ack.to,
251      VAL [Link.Num]BOOL stop.to, VAL [Mod.Num+1]INT route,
252      [Link.Num]CHAN OF Net.Link in, [Link.Num]CHAN OF Permit permit,
253      [Link.Num]CHAN OF Net.Link out)
254
255      BOOL going:   -- termination variable
256      BOOL waiting: -- waiting for arrival of new token
257      BOOL proc:    -- token destined for local processor unit
258      INT  free:    -- list of free buffer elements
259      INT  j:       -- auxiliary pointer
260      BYTE mod:     -- module address of token
261      BYTE type:    -- type of token
262      INT  link:    -- link number to send token through
263      INT  new:     -- reference to new token space
264
265      [Link.Num]BOOL wait:  -- waiting for send permission on link
266      [Link.Num]INT  first: -- first tokens in waiting queues
267      [Link.Num]INT  last:  -- last tokens in waiting queues
268
269      [Arbit.Size*Token.Size]INT32 buffer: -- token buffer
270      [Arbit.Size]INT              next:   -- link to next token
271      [Token.Size]INT32            token:  -- network token
272
273      -------------------------------------------------------------------------
274      -- Body of Transmit
275      -------------------------------------------------------------------------

ADARBIT.OCC                                                                Page   6
276      SEQ
277
278        -----------------------------------------------------------------------
279        -- initializations
280        -----------------------------------------------------------------------
281
282        -- link all buffer tokens into free list
283        free := 0
284        SEQ i = 0 FOR Arbit.Size
285          next[i] := i+1
286        next[Arbit.Size-1] := -1
287
288        -- initalize waiting queues
289        SEQ i = 0 FOR Link.Num
290          SEQ
291            wait[i] := FALSE
292            first[i] := -1
293            last[i] := -1
294
295        -----------------------------------------------------------------------
296        -- transmit tokens until stop token received
297        -----------------------------------------------------------------------
298        going := TRUE
299        WHILE going
300          SEQ
301
302            -------------------------------------------------------------------
303            -- wait for arrival of new token
304            -------------------------------------------------------------------
305            waiting := TRUE
306            WHILE waiting
307              ALT
308
309                -- receive send permission signal from some output channel
310                ALT i = 0 FOR Link.Num
311                  wait[i] & permit[i] ? CASE write.permit
312                    IF
313                      first[i] = (-1)
314                        -- no token for that link in buffer
315                        wait[i] := FALSE
316                      TRUE
317                        SEQ
318                          -- send token through that link
319                          out[i] ! net.token;
320                            [buffer FROM first[i]*Token.Size FOR Token.Size]
321                          -- unlink token space from linked list
322                          j := first[i]
323                          first[i] := next[j]
324                          IF
325                            first[i] = (-1)
326                              last[i] := -1
327                            TRUE
328                              SKIP
329                          -- collect buffer space in free list
330                          next[j] := free

ADARBIT.OCC                                                                Page   7
331                          free := j
332
333                -- receive token from processor unit
334                from.proc ? CASE net.token; token
335                  waiting := FALSE
336
337                -- receive token from memory unit
338                from.mem ? CASE net.token; token
339                  waiting := FALSE
340
341                -- receive token from some neighbour module
342                ALT i = 0 FOR Link.Num
343                  in[i] ? CASE
344                    net.token; token
345                      waiting := FALSE
346
347            -------------------------------------------------------------------
348            -- new token received
349            -------------------------------------------------------------------
350
351            -- extract destination address and type
352            [4]BYTE toks RETYPES token[2]:
353            mod, type  := toks[2], toks[1]
354
355            IF
356
357              -----------------------------------------------------------------
358              -- stop token encountered
359              -----------------------------------------------------------------
360              type = Stop.Token
361                going := FALSE
362
363              ---------------------------------------------------------------
364              -- token destined for actual module
365              ---------------------------------------------------------------
366              mod = act.mod
367                SEQ
368                  -- determine destination type of token
369                  proc  := ((INT16 type) /\ #E0(INT16)) = 0(INT16)
370                  IF
371                    proc
372                      -- token destined for local processor
373                      to.proc ! mod.token; token
374                    TRUE
375                      -- token destined for local memory
376                      to.mem ! mod.token; token
377
378              -----------------------------------------------------------------
379              -- token destined for another module
380              -----------------------------------------------------------------
381              TRUE
382                SEQ
383                  -- lookup link address in routing table
384                  link := route[INT mod]
385                  IF

ADARBIT.OCC                                                                Page   8
386                    -- arbiter has not send permission on that link
387                    wait[link]
388                      SEQ
389                        -- unlink buffer space from free list
390                        Assert(free>(-1), arbit.full)
391                        new := free
392                        free := next[free]
393                        -- store token in buffer
394                        [buffer FROM new*Token.Size FOR Token.Size] := token
395                        -- link token space into waiting queue
396                        next[new] := -1
397                        IF
398                          last[link] = (-1)
399                            SKIP
400                          TRUE
401                            next[last[link]] := new
402                        last[link] := new
403                        IF
404                          first[link] = (-1)
405                            first[link] := new
406                          TRUE
407                            SKIP
408                    -- arbiter has send permission on that link
409                    TRUE
410                      SEQ
411                        -- forward token through link
412                        out[link] ! net.token; token
413                        -- send permission lost
414                        wait[link] := TRUE
415
416        -----------------------------------------------------------------------
417        -- stop token encountered
418        -----------------------------------------------------------------------
419
420        -- forward stop token to local units
421        to.proc ! mod.token; token
422        to.mem ! mod.token; token
423
424        -- forward stop token to all modules in boot path
425        SEQ i = 0 FOR Link.Num
426          IF
427            stop.to[i]
428              SEQ
429                IF
430                  wait[i]
431                    permit[i] ? CASE write.permit
432                  TRUE
433                    SKIP
434                out[i] ! net.token; token
435                permit[i] ? CASE write.permit
436            TRUE
437              SKIP
438
439        -- get write permission on acknowledge link
440        IF

ADARBIT.OCC                                                                Page   9
441          wait[ack.to]
442            permit[ack.to] ? CASE write.permit
443          TRUE
444            SKIP
445
446    :
447
448    ---------------------------------------------------------------------------
449    -- Body of Adam.Arbiter
450    ---------------------------------------------------------------------------
451    SEQ
452
453      -------------------------------------------------------------------------
454      -- initializations
455      -------------------------------------------------------------------------
456
457      -- program must not be forwarded to link from that it has been received
458      IF
459        boot.to[boot.from]
460          SEQ
461            debug ! (SIZE boot.err) :: boot.err
462            STOP
463        TRUE
464          SKIP
465
466      -- count number of links through that program is to be forwarded
467      count := 0
468      SEQ i = 0 FOR Link.Num
469        IF
470          boot.to[i]
471            count := count + 1
472          TRUE
473            SKIP
474
475      -------------------------------------------------------------------------
476      -- let arbiter nucleus and link buffers work in parallel
477      -------------------------------------------------------------------------
478      PAR
479
480        -----------------------------------------------------------------------
481        -- nucleus of arbiter
482        -----------------------------------------------------------------------
483        WHILE TRUE
484          SEQ
485            -- boot program to network
486            Load.Prog(boot.from, boot.to, in, from.buff, to.buff)
487            -- receive acknowledge signals and send own to boot link
488            Acknowledge(count, boot.from, boot.to, in, from.buff, to.buff)
489            -- transmit tokens until stop signal received
490            Transmit(boot.from, boot.to, route, in, from.buff, to.buff)
491            -- receive acknowledge signals and send own to boot link
492            Acknowledge(count, boot.from, boot.to, in, from.buff, to.buff)
493
494        -----------------------------------------------------------------------
495        -- link buffers

ADARBIT.OCC                                                                Page  10
496        -----------------------------------------------------------------------
497        PAR i=0 FOR Link.Num
498          [Token.Size]INT32   token:   -- network token
499          [Cluster.Size]INT32 program: -- program cluster
500          WHILE TRUE
501            SEQ
502              to.buff[i] ? CASE
503                net.token; token
504                  out[i] ! net.token; token
505                net.startprog
506                  out[i] ! net.startprog
507                net.appendprog
508                  out[i] ! net.appendprog
509                net.program; program
510                  out[i] ! net.program; program
511                net.endprog
512                  out[i] ! net.endprog
513                net.ack
514                  out[i] ! net.ack
515              from.buff[i] ! write.permit
516
517  :
518  -----------------------------------------------------------------------------
519  -- End of ADARBIT.OCC
520  -----------------------------------------------------------------------------

ADMEMORY.OCC                                                               Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADMEMORY.OCC                                           Wolfgang Schreiner
 4  -- The Adam Memory Unit                                   (c) RISC-Linz 1990
 5  --
 6  -- Interpreter for all components of the Adam memory unit.
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- External References
12  -----------------------------------------------------------------------------
13
14  -- include files
15  #INCLUDE "adglobal.inc" -- global constants
16  #INCLUDE "adnet.inc"    -- network constants
17  #INCLUDE "aderror.inc"  -- error messages
18  #INCLUDE "admemory.inc" -- memory unit constants
19
20  -----------------------------------------------------------------------------
21  -- The Adam Memory Unit
22  --
23  --   The memory unit is responsible for the management of all non-atomic,
24  --   i.e. compound, data. The basic data structure is the 'tuple' i.e.
25  --   a data record of fixed size where each field of the tuple may contain
26  --   an (integer) atom or a reference to another tuple. Processor units
27  --   control creation of and access to tuples via 4 kinds of requests:
28  --
29  --     1. Allocation requests ask the memory unit to return a reference
30  --        to a new tuple of certain size with all tuple fields being empty.
31  --
32  --     2. Read requests ask the memory unit to return the component of some
33  --        particular tuple field. If that field has not yet been filled, the
34  --        request is deferred.
35  --
36  --     3. Write requests ask the memory unit to fill the components of some
37  --        tuple field with a particular value. If that field has already
38  --        been written, a runtime error is triggered. Otherwise, the value
39  --        is written, and all deferred read requests are resolved.
40  --
41  --     4. Submission requests ask the memory unit to submit the lambda-lifted
42  --        arguments of a higher order function stored in a tuple to a
43  --        particular activation of that function.
44  --
45  --   For reasons of program simplicity, tuples are internally represented by
46  --   linked lists of fixed size cells.
47  --
48  -- IN  act.mod:   actual module address
49  -- IN  mod.addr:  addresses of all neighbour modules
50  -- IN  link.addr: link addresses of actual module from view of neighbour
51  -- IN  in:        input channel from arbiter
52  -- OUT out:       output channel to arbiter
53  -- OUT debug:     debug channel to output process
54  -----------------------------------------------------------------------------
55  PROC Adam.Memory(VAL BYTE act.mod,

ADMEMORY.OCC                                                               Page   2
56    VAL [Link.Num]BYTE mod.addr, VAL [Link.Num]BYTE link.addr,
57    CHAN OF Mod.Link in, CHAN OF Net.Link out, CHAN OF Debug debug)
58
59    -- internal channels
60    CHAN OF Memory.Token to.exp, to.cheap:     -- transmit tokens to buffers
61    CHAN OF Buffer.Token from.exp, from.cheap: -- transmit tokens to manager
62    CHAN OF Demand demand.exp, demand.cheap:   -- transmit demands
63
64    -- buffers for expensive and cheap store
65    [Token.Size*Exp.Len]INT32 exp.store:     -- expensive store
66    [Token.Size*Cheap.Len]INT32 cheap.store: -- cheap store
67
68    [4]CHAN OF Debug error: -- internal debugging channels
69
70    --------------------------------------------------------------------------
71    -- The Memory Unit Router:
72    --
73    --   The router receives incoming data from the arbiter and routes them
74    --   as follows:
75    --
76    --     1. Allocation, read and submission requests to the expensive store.
77    --     2. Write requests and memory load reports to the cheap store.
78    --
79    --   IN  in:       channel from arbiter
80    --   OUT to.exp:   channel to expensive store
81    --   OUT to.cheap: channel to cheap store
82    --   OUT debug:    debugging channel
83    --------------------------------------------------------------------------
84    PROC Router(CHAN OF Mod.Link in,
85      CHAN OF Memory.Token to.exp, CHAN OF Memory.Token to.cheap,
86      CHAN OF Debug debug)
87
88      BYTE tag: -- actual token tag
89
90      [Token.Size]INT32 token: -- network token
91
92      ------------------------------------------------------------------------
93      -- Body of Router
94      ------------------------------------------------------------------------
95      SEQ
96
97        WHILE TRUE
98          SEQ
99            -- receive token and extract tag
100            in ? CASE mod.token; token
101            [4]BYTE toks RETYPES token[2]:
102            tag := toks[1]
103            -- forward token to appropriate store
104            CASE tag
105              Stop.Token                 -- stop token
106                SEQ
107                  to.cheap ! token
108                  to.exp ! token
109              Alloc.MToken, Read.MToken, Submit.MToken
110                to.exp ! token           -- expensive requests

ADMEMORY.OCC                                                               Page   3
111              ELSE
112                to.cheap ! token         -- cheap requests
113
114        -- never done (just to avoid compiler warnings)
115        debug ! 0 :: ""
116
117    :
118
119    --------------------------------------------------------------------------
120    -- The Memory Unit Buffer
121    --
122    --   Buffers incoming tokens in a queue and forwards them to the
123    --   memory manager on demand.
124    --
125    --   IN  in:     input channel from router
126    --   IN  demand: demand channel from memory manager
127    --   IN  buffer: store to hold tokens
128    --   OUT out:    output channel to memory unit
129    --   OUT debug:  debugging channel
130    --------------------------------------------------------------------------
131    PROC Buffer(CHAN OF Memory.Token in, CHAN OF Demand demand, []INT32 buffer,
132      CHAN OF Buffer.Token out, CHAN OF Debug debug)
133
134      VAL buffer.size IS SIZE buffer:
135
136      INT  first, last: -- indizes of the first and last queue elements
137      INT  num:         -- number of elements in queue
138      BOOL full, empty: -- denote the actual status of the queue
139      BOOL going:       -- termination flag
140      BYTE tag:         -- tag of actual token
141
142      [Token.Size]INT32 token: -- token value
143
144      ------------------------------------------------------------------------
145      -- Assert Condition
146      --
147      --   Checks condition and forwards error message to debugging channel,
148      --   if neccessary.
149      --
150      -- IN cond: test condition
151      -- IN mess: error message
152      ------------------------------------------------------------------------
153      PROC Assert(VAL BOOL cond, VAL []BYTE mess)
154        SEQ
155          IF
156            cond
157              SKIP
158            TRUE
159              SEQ
160                debug ! (SIZE mess) :: mess
161                STOP
162      :
163
164      -------------------------------------------------------------------------
165      -- Body of Buffer

ADMEMORY.OCC                                                               Page   4
166      -------------------------------------------------------------------------
167      WHILE TRUE
168        SEQ
169
170          -- buffer length must be a multiple of Token.Size
171          Assert((buffer.size REM Token.Size) = 0, buff.err)
172
173          -- initialize status variables
174          num, first, last := 0, 0, 0
175          full, empty := FALSE, TRUE
176
177          -- loop until stop token encountered
178          going := TRUE
179          WHILE going
180
181            -- full equals not empty
182            -- if first <> last
183            --   then first refers to the next free element
184            --        last refers to the last stored element
185            --   else if full
186            --          then first is not valid
187            --          else last is not valid
188
189            -- listen on both input channels
190            ALT
191
192              -- demand received
193              demand ? CASE demand.tag
194                SEQ
195
196                  -- submit token
197                  [Token.Size]INT32 token IS [buffer FROM last FOR Token.Size]:
198                  out ! num; token
199
200                  -- extract tag
201                  [Token.Size]INT32 token IS [buffer FROM last FOR Token.Size]:
202                  [4]BYTE toks RETYPES token[2]:
203                  tag := toks[1]
204
205                  IF
206                    tag = Stop.Token  -- stop token submitted, empty buffer
207                      going := FALSE
208                    empty             -- submitted token was undefined
209                      SKIP
210                    TRUE              -- update state variables
211                      SEQ
212                        num  := num-1
213                        last := (last + Token.Size) REM buffer.size
214                        full := FALSE
215                        empty := first = last
216
217              -- token received
218              in ? token
219                SEQ

ADMEMORY.OCC                                                               Page   5
221                  -- buffer must not be full
222                  Assert(NOT full, buff.full)
223
224                  -- store token
225                  [buffer FROM first FOR Token.Size] := token
226                  num   := num+1
227                  first := (first + Token.Size) REM buffer.size
228                  empty := FALSE
229                  full  := first = last
230
231    :
232
233    ---------------------------------------------------------------------------
234    -- The Memory Manager
235    --
236    --   Manages the heap store and handles incoming requests.
237    --
238    -- IN  from.exp:     input channel from expensive store
239    -- IN  from.cheap:   input channel from cheap store
240    -- OUT demand.exp:   demand channel to expensive store
241    -- OUT demand.cheap: demand channel to cheap store
242    -- OUT error:        error channel for debugging purposes
243    ---------------------------------------------------------------------------
244    PROC Manager(CHAN OF Buffer.Token from.exp, from.cheap,
245      CHAN OF Demand demand.exp, demand.cheap, CHAN OF Debug debug)
246
247      -- the basic stores
248      [Heap.Len]INT16 tag:    -- tag fields of heap cells
249      [Heap.Len]INT32 head:   -- head fields of heap cells
250      [Heap.Len]INT32 tail:   -- tail fields of heap cells
251      [Stack.Len]INT32 stack: -- deallocation stack
252
253      -- the load parameters
254      INT heap.load:  -- load of heap store
255      INT exp.load:   -- load of expensive store
256      INT cheap.load: -- load of cheap store
257      INT mLoad:      -- relative load of actual memory
258      INT lLoad:      -- last load reported to neighbours
259
260      -- global state variables
261      INT free: -- head of free list
262      INT top:  -- top of deallocation stack
263
264      -- network descriptors
265      [Link.Num]INT mem.load: -- relative load of neighbour memories
266
267      -- auxiliary variables
268      INT   action:  -- next action to be executed
269      BOOL  going:   -- termination flag
270
271      ------------------------------------------------------------------------
272      -- Assert Condition
273      --
274      --   Checks condition and forwards error message to debugging channel,
275      --   if neccessary.

ADMEMORY.OCC                                                               Page   6
276      --
277      -- IN cond: test condition
278      -- IN mess: error message
279      ------------------------------------------------------------------------
280      PROC Assert(VAL BOOL cond, VAL []BYTE mess)
281
282        ----------------------------------------------------------------------
283        -- Body of Assert
284        ----------------------------------------------------------------------
285        SEQ
286          IF
287            cond
288              SKIP
289            TRUE
290              SEQ
291                debug ! (SIZE mess) :: mess
292                STOP
293      :
294
295      -------------------------------------------------------------------------
296      -- Minimal Loaded Neighbour
297      --
298      --   Returns the address of that module among the neighbours of the
299      --   actual module whose memory unit is probably least loaded. If all
300      --   neighbours are probably more loaded than the actual module, the
301      --   actual module address is returned.
302      --
303      -------------------------------------------------------------------------
304      BYTE FUNCTION Min.Loaded()
305
306        BYTE addr: -- address of minimal loaded neighbour found up to now
307        INT  load: -- and its load
308
309        -----------------------------------------------------------------------
310        -- Body of Min.Loaded
311        -----------------------------------------------------------------------
312        VALOF
313          SEQ
314            addr, load := act.mod, mLoad  -- actual module is default
315            SEQ i = 0 FOR Link.Num        -- search for less loaded modules
316              VAL BYTE a IS mod.addr[i]:
317              VAL INT  l IS mem.load[i]:
318              IF
319                l < load
320                  addr, load := a, l
321                TRUE
322                  SKIP
323          RESULT addr
324      :
325
326      -------------------------------------------------------------------------
327      -- Compute Relative Load
328      --
329      --   Takes heap load and expensive store load and computes from these
330      --   values the relative memoire load, i.e. a value between 0 and 100

ADMEMORY.OCC                                                               Page   7
331      --   that (hopefully adequately) describes the overall working load of
332      --   the memory: a value of 0 should denote "the memory unit is not at
333      --   all occupied" and a value of 100 should denote "the memory unit is
334      --   not able to fulfill any further requests". Any value between should
335      --   in some way express a rough measure for the load of the memory.
336      --
337      -- IN heap: the heap load of the memory unit
338      -- IN exp:  the expensive store load of the memory unit
339      -------------------------------------------------------------------------
340      INT FUNCTION Load(VAL INT heap, exp)
341
342        -----------------------------------------------------------------------
343        -- Body of Load
344        -----------------------------------------------------------------------
345        VALOF
346          SKIP
347          RESULT ((100 * heap) / Heap.Len) + (0 * exp)
348      :
349
350      -------------------------------------------------------------------------
351      -- Determine Next Action
352      --
353      --   Determines the type of the next action to be executed.
354      --
355      -- IO action: type of action to be executed next
356      -------------------------------------------------------------------------
357      PROC Mem.Action(INT action)
358
359        -----------------------------------------------------------------------
360        -- Body of Mem.Action
361        -----------------------------------------------------------------------
362        IF
363          -- if load has significantly changed, report to neighbours
364          (mLoad > Mem.Trash) AND
365            (((100 * lLoad) < (Mem.Delay * mLoad)) OR
366             ((100 * mLoad) < (Mem.Delay * lLoad)))
367            action := Report.Act
368          -- expensive store empty, give it a chance for the next time
369          exp.load = 0
370            SEQ
371              exp.load := 1
372              action := Cheap.Act
373          -- expensive load too high and heap load not too high
374          (exp.load > Exp.Bound) AND (heap.load < Heap.Bound)
375            action := Exp.Act
376          -- heap load too low
377          heap.load  < Heap.Trash
378            action := Exp.Act
379          -- cheap store empty, give it a chance for the next time
380          cheap.load = 0
381            SEQ
382              cheap.load := 1
383              action := Exp.Act
384          -- cheap requests are always possible
385          TRUE

ADMEMORY.OCC                                                               Page   8
386            action := Cheap.Act
387
388      :
389      -------------------------------------------------------------------------
390      -- Check if Tuple Available on Heap
391      --
392      --   Returns TRUE iff a tuple with the specified number of fields can be
393      --   allocated on the heap.
394      --
395      -- IN num: number of heap cells to be allocated
396      -------------------------------------------------------------------------
397      BOOL FUNCTION Avail.Tuple(VAL INT num)
398
399        -----------------------------------------------------------------------
400        -- Body of Avail.Tuple
401        -----------------------------------------------------------------------
402        VALOF
403          SKIP
404          RESULT num <= (Heap.Len-heap.load)
405      :
406
407      -------------------------------------------------------------------------
408      -- Allocate Tuple
409      --
410      --   Allocates a tuple with the specified number of fields by
411      --   linking the required number of heap cells together. The
412      --   reference count field of the tuple is set to the maximal
413      --   reference weight, the 'available' bits of the tuple fields are
414      --   reset to FALSE, the heads of the associated deferred read lists
415      --   are reset to NIL. If the tuple cannot be allocated, the program
416      --   is terminated with an error message.
417      --
418      -- IN  num: number of tuple fields required
419      -- OUT new: address of new tuple allocated
420      -------------------------------------------------------------------------
421      PROC Alloc.Tuple(VAL INT num, INT new)
422
423        INT n:    -- auxiliary counter
424        INT next: -- auxiliary pointer
425
426        -----------------------------------------------------------------------
427        -- Body of Alloc.Tuple
428        -----------------------------------------------------------------------
429        SEQ
430
431          -- check if field number valid and required number of cells available
432          Assert((num > 1) AND (num <= (Heap.Len-heap.load)), alloc.err)
433
434          new := free                    -- address of new tuple
435
436          -- link open cells together
437          n := num
438          WHILE n > 2
439            SEQ
440              tag[free] := #0810(INT16)  -- reference count 8, cell is open

ADMEMORY.OCC                                                               Page   9
441              head[free] := 0(INT32)     -- deferred read list empty
442              free := INT tail[free]     -- unlink cell from free list
443              n := n-1                   -- one field less left
444
445          next := INT tail[free]         -- next free heap cell
446
447          tag[free] := #0800(INT16)      -- reference count 8, cell is closed
448          head[free] := 0(INT32)         -- deferred read list head empty
449          tail[free] := 0(INT32)         -- deferred read list tail empty
450
451          free := next                   -- new head of free list
452          heap.load := heap.load+(num-1) -- new heap load
453
454      :
455
456      -------------------------------------------------------------------------
457      -- Deallocate Tuple
458      --
459      --   Deallocates specified tuple and relinks its cells into the free
460      --   list. If not all tuple field have yet been filled, the program
461      --   is terminated with an error message. The addresses of all tuples
462      --   that were referenced by the deallocated tuple are put on the
463      --   deallocation stack (if they reside in the actual memory) or
464      --   decrease reference messages are sent to these tuples (if they
465      --   reside on other memory units).
466      --
467      -- IN addr: address of tuple to be deallocated
468      -------------------------------------------------------------------------
469      PROC Dealloc.Tuple(VAL INT addr)
470
471        BOOL open:   -- last tuple cell open?
472        BOOL open0:  -- actual tuple cell open?
473        BOOL avHd:   -- head field available?
474        BOOL avTl:   -- tail field available?
475        BOOL ptrHd:  -- head field pointer?
476        BOOL ptrTl:  -- tail field pointer?
477        BYTE mod:    -- module address
478        INT  wght:   -- weight field
479        INT  weight: -- reference weight
480        INT  addr0:  -- actual cell address
481        BOOL error:  -- error flag
482
483        [Token.Size]INT32 token: -- network token
484
485        -----------------------------------------------------------------------
486        -- Body of Dealloc.Tuple
487        -----------------------------------------------------------------------
488        SEQ
489
490          -- check tuple address
491          Assert(addr > 0, dealloc.err)
492
493          -- extract tuple tag bits
494          INT16 tags IS tag[addr]:
495          SEQ

ADMEMORY.OCC                                                               Page  10
496            open0 := (tags /\ #0010(INT16)) > 0(INT16)  -- cell open?
497            avHd  := (tags /\ #0008(INT16)) > 0(INT16)  -- head available?
498            ptrHd := (tags /\ #0004(INT16)) > 0(INT16)  -- head pointer?
499
500          -- save address
501          addr0 := addr
502
503          ---------------------------------------------------------------------
504          -- traverse head fields of tuple cells
505          ---------------------------------------------------------------------
506
507          -- enter loop at least one time
508          open := TRUE
509          WHILE open
510            SEQ
511
512              -- check that head available
513              Assert(avHd, dealloc.err)
514
515              -- actual pointer
516              INT32 ptr IS head[addr0]:
517
518              -- field contains reference to another tuple
519              IF
520                ptrHd
521                  SEQ
522
523                    -- error flag of actual pointer
524                    error := BOOL (ptr /\ #00000001(INT32))
525
526                    IF
527                      error
528                        SKIP
529
530                      TRUE
531                        SEQ
532
533                          -- extract module address
534                          [4]BYTE p RETYPES ptr:
535                          mod := p[3]
536
537                          IF -- tuple on other module or stack full
538                            (mod <> act.mod) OR (top = Stack.Len)
539                              SEQ
540
541                                -- construct decrease reference message
542                                [4]BYTE t RETYPES token[2]:
543                                SEQ
544                                  t[2] := mod
545                                  t[1] := DecRef.MToken
546                                token[0] := ptr
547
548                                -- send token into network
549                                out ! net.token; token

ADMEMORY.OCC                                                               Page  11
551                            TRUE -- tuple on actual module and stack not full
552                              SEQ
553
554                                -- push reference on stack
555                                INT32 next IS stack[top]:
556                                SEQ
557                                  next := ptr
558                                  [4]BYTE n RETYPES next:
559                                  n[3] := 0(BYTE)       -- delete mod address
560                                  next := next >> 1     -- delete error flag
561                                  wght :=               -- weight field
562                                    INT (next /\ #00000003(INT32))
563                                  next := next >> 2     -- tuple address
564                                  weight := 1           -- reference weight
565                                  SEQ i = 1 FOR wght
566                                    weight := weight * 2
567                                  [4]BYTE n RETYPES next:
568                                  n[3] := BYTE weight
569
570                                -- increase stack pointer
571                                top := top+1
572
573                -- field contains atomic (integer value)
574                TRUE
575                  SKIP
576
577              heap.load := heap.load-1 -- update load
578              open := open0            -- actual tuple closed?
579
580              -- extract tuple tags of next tuple field
581              IF
582                open
583                  SEQ
584                    tag[addr0] := 0(INT16)
585                    addr0 := INT tail[addr0]
586                    INT16 t IS tag[addr0]:
587                    SEQ
588                      open0 := (t /\ #0010(INT16)) > 0(INT16) -- cell open?
589                      avHd  := (t /\ #0008(INT16)) > 0(INT16) -- head available?
590                      ptrHd := (t /\ #0004(INT16)) > 0(INT16) -- head pointer?
591                TRUE
592                  SKIP
593
594          ---------------------------------------------------------------------
595          -- handle tail field of tuple cell
596          ---------------------------------------------------------------------
597
598          -- extract tuple tags
599          INT16 t IS tag[addr0]:
600          SEQ
601            avTl  := (t /\ #0002(INT16)) > 0(INT16)  -- tail available?
602            ptrTl := (t /\ #0001(INT16)) > 0(INT16)  -- tail pointer?
603
604          -- set reference count to 0
605          tag[addr0] := 0(INT16)

ADMEMORY.OCC                                                               Page  12
606
607          -- check that tail available
608          Assert(avTl, dealloc.err)
609
610          -- actual pointer
611          INT32 ptr IS tail[addr0]:
612
613          IF
614            ptrTl
615              SEQ
616
617                -- actual error bit
618                error := BOOL (ptr /\ #00000001(INT32))
619
620                IF
621                  error
622                    SKIP
623
624                  TRUE
625                    SEQ
626
627                      -- extract module address
628                      [4]BYTE p RETYPES ptr:
629                      mod := p[3]
630
631                      IF  -- tuple on other module or stack full
632                        (mod <> act.mod) OR (top = Stack.Len)
633                          SEQ
634
635                            -- construct decrease reference message
636                            [4]BYTE t RETYPES token[2]:
637                            SEQ
638                              t[2] := mod
639                              t[1] := DecRef.MToken
640                            token[0] := ptr
641
642                            -- send token into network
643                            out ! net.token; token
644
645                        TRUE -- tuple on actual module and stack not full
646                          SEQ
647
648                            -- push reference on stack
649                            INT32 next IS stack[top]:
650                            SEQ
651                              next := ptr
652                              [4]BYTE n RETYPES next:
653                              n[3] := 0(BYTE)           -- delete mod address
654                              next := next >> 1         -- delete error flag
655                              wght :=                   -- weight field
656                                INT (next /\ #00000003(INT32))
657                              next := next >> 2         -- tuple address
658                              weight := 1               -- reference weight
659                              SEQ i = 1 FOR wght
660                                weight := weight * 2

ADMEMORY.OCC                                                               Page  13
661                              [4]BYTE n RETYPES next:
662                              n[3] := BYTE weight
663
664                            -- increase stack pointer
665                            top := top+1
666
667            TRUE
668              SKIP
669
670          ---------------------------------------------------------------------
671          -- link tuple cells to free list
672          ---------------------------------------------------------------------
673
674          tail[addr0] := INT32 free
675          free := addr
676
677      :
678
679      -------------------------------------------------------------------------
680      -- Garbage Collection
681      --
682      --   Handle garbage collection using the weighted reference counting
683      --   method. Recursive deallocation of tuples is done by using an
684      --   explicit deallocation stack.
685      --
686      -- IN tuple: address of tuple to that reference has been deleted
687      -- IN wght:  weight field of reference deleted
688      -------------------------------------------------------------------------
689      PROC Garbage.Collection(VAL INT tuple, wght)
690
691        INT weight: -- actual reference weight
692        INT count:  -- reference count of tuple
693        INT addr:   -- address of actual tuple cell
694
695        -----------------------------------------------------------------------
696        -- Body of Garbage.Collection
697        -----------------------------------------------------------------------
698        SEQ
699
700          -- compute actual weight of reference
701          weight := 1
702          SEQ i = 1 FOR wght
703            weight := weight * 2
704
705          -- push reference weight and tuple address on stack
706          stack[0] := INT32 tuple
707          [4]BYTE s RETYPES stack[0]:
708          s[3] := BYTE weight
709          top := 1
710
711          -- recursively free tuples
712          WHILE top > 0
713            SEQ
714
715              -- pop reference weight and tuple address from stack

ADMEMORY.OCC                                                               Page  14
716              top := top-1
717              INT32 act IS stack[top]:
718              SEQ
719                [4]BYTE a RETYPES act:
720                SEQ
721                  weight := INT a[3]
722                  a[3] := 0(BYTE)
723                addr := INT act
724
725              -- compute reference count of tuple
726              [2]BYTE tags RETYPES tag[addr]:
727              count := INT tags[1]
728
729              -- update reference count
730              IF
731                count < weight
732                  Assert(FALSE, weight.err)
733                count = (Range.Count-1)      -- infinite reference count
734                  SKIP
735                count = weight               -- reference count decreases to 0
736                  Dealloc.Tuple(addr)
737                TRUE                         -- reference count stays positive
738                  [2]BYTE tags RETYPES tag[addr]:
739                  tags[1] := BYTE (count - weight)
740
741          -- update load
742          mLoad := Load(heap.load, exp.load)
743      :
744
745      -------------------------------------------------------------------------
746      -- Copy Values
747      --
748      --   Copies a tuple reference. If the weight field of the reference is 0,
749      --   the value remains unchanged and done is set to FALSE. Otherwise,
750      --   the weight field of the reference is updated.
751      --
752      -- IO  data: tuple reference to be copied
753      -- OUT done: TRUE iff copy could be done
754      -------------------------------------------------------------------------
755      PROC Copy(INT32 data, BOOL done)
756
757        BOOL error: -- error bit of reference
758        INT  wght:  -- weight field of reference
759
760        -----------------------------------------------------------------------
761        -- Body of Copy
762        -----------------------------------------------------------------------
763        SEQ
764
765          -- error bit and actual weight field
766          error := BOOL(data /\ #00000001(INT32))
767          wght := (INT (data >> 1)) /\ #00000003
768
769          IF
770            data = 0(INT32) -- nil pointer

ADMEMORY.OCC                                                               Page  15
771              done := TRUE
772            error           -- error bit set
773              done := TRUE
774            wght = 0        -- weight field 0
775              done := FALSE
776            TRUE            -- decrease weight field
777              SEQ
778                done := TRUE
779                data := (data /\ #FFFFFFF9(INT32)) \/ (INT32 ((wght - 1) << 1))
780      :
781
782
783      -------------------------------------------------------------------------
784      -- Handle Allocation Requests
785      --
786      --   Allocates a tuple with specified number of links by linking an
787      --   appropriate number of heap cells together. If the allocatin request
788      --   cannot be fulfilled, it is forwarded to the least loaded memory
789      --   unit among the neighbour modules.
790      --
791      -- IN alloc.tok: allocation request to be fulfilled
792      -------------------------------------------------------------------------
793      PROC Alloc.Request(VAL [Token.Size]INT32 alloc.tok)
794
795        INT  num: -- number of required tuple fields
796        INT  new: -- address of new tuple allocated
797
798        [Token.Size]INT32 token: -- network token
799
800        -----------------------------------------------------------------------
801        -- Body of Alloc.Request
802        -----------------------------------------------------------------------
803        SEQ
804
805          -- extract number of tuple fields
806          VAL [4]BYTE t RETYPES alloc.tok[2]:
807          num := INT t[0]
808
809          IF
810            Avail.Tuple(num)              -- tuple may be allocated
811              SEQ
812
813                -- allocate tuple
814                Alloc.Tuple(num, new)
815
816                -- construct message token with tuple address
817                [4]BYTE toks RETYPES token[2]:
818                VAL [4]BYTE t RETYPES alloc.tok[0]:
819                VAL BYTE mod IS t[3]:
820                SEQ
821                  toks[2] := mod          -- module address
822                  toks[1] := Mess.PToken  -- message token
823                token[1] :=  alloc.tok[1] -- context and offset of caller
824                token[0] :=               -- reference with maximal weight
825                  INT32 ((new << 3) \/ #00000006) -- and error flag not set

ADMEMORY.OCC                                                               Page  16
826                [4]BYTE toks RETYPES token[0]:
827                toks[3] := act.mod        -- module address in reference
828
829                -- send token into network
830                out ! net.token; token
831
832                -- update load
833                mLoad := Load(heap.load, exp.load)
834
835            TRUE                          -- forward allocation request
836              SEQ
837
838                -- token is to be forwarded
839                token := alloc.tok
840
841                -- replace module address by address of least loaded neighbour
842                [4]BYTE toks RETYPES token[2]:
843                toks[2] := Min.Loaded()
844
845                -- send token into network
846                out ! net.token; token
847
848      :
849
850      -------------------------------------------------------------------------
851      -- Handle Read Requests
852      --
853      --   Reads some particular field of a specified tuple and returns its
854      --   contents to the caller. However, if no value has yet been written
855      --   into the tuple field, the read request is queued into a waiting
856      --   list. As soon as the desired value has arrived, the read request is
857      --   fulfilled.
858      --
859      -- IN read.tok: read request to be fulfilled
860      -------------------------------------------------------------------------
861      PROC Read.Request(VAL [Token.Size]INT32 read.tok)
862
863        INT   num:     -- number of referenced tuple field
864        INT32 contoff: -- context and offset of caller
865        BYTE  mod:     -- module address of caller
866        INT   addr:    -- address of tuple
867        INT   wght:    -- weight field of tuple reference
868        INT   addr0:   -- address of actual tuple field
869        BOOL  error:   -- error flag of tuple reference
870        BOOL  open:    -- is actual tuple cell not the last?
871        INT   new:     -- address of new cell allocated
872        BOOL  avHd:    -- head value available
873        BOOL  avTl:    -- tail value available
874        INT16 t:       -- actual tag
875        BOOL  gc:      -- do garbage collection
876        BOOL  ptr:     -- is head/tail value pointer?
877        INT32 data:    -- data of actual tuple field
878        BOOL  done:    -- tuple reference could be copied
879
880        [Token.Size]INT32 token: -- network token

ADMEMORY.OCC                                                               Page  17
881
882        -----------------------------------------------------------------------
883        -- Body of Read.Request
884        -----------------------------------------------------------------------
885        SEQ
886
887          ---------------------------------------------------------------------
888          -- extract token components
889          ---------------------------------------------------------------------
890
891          VAL [4]BYTE t RETYPES read.tok[2]:
892          num := INT t[0]                   -- field number
893          contoff := read.tok[1]            -- activation context
894          VAL [4]BYTE t RETYPES read.tok[0]:
895          mod := t[3]                       -- module address
896          addr := INT read.tok[0]
897          [4]BYTE a RETYPES addr:
898          a[3] := 0(BYTE)
899          error := BOOL (addr /\ #00000001) -- error flag
900          addr := addr >> 1
901          wght := addr /\ #00000003         -- weight field
902          addr := addr >> 2                 -- tuple address
903
904          -- check tuple reference for validity
905          Assert((NOT error) AND (addr > 0) AND (tag[addr]>0(INT16)), ref.err)
906
907          ---------------------------------------------------------------
908          -- find specified tuple field
909          ---------------------------------------------------------------
910
911          -- actual tuple field
912          addr0 := addr
913
914          -- traverse tuple fields
915          open := (tag[addr0] /\ #0010(INT16)) > 0(INT16)
916          WHILE open AND (num > 0)
917            SEQ
918              num := num - 1
919              addr0 := INT tail[addr0]
920              open := (tag[addr0] /\ #0010(INT16)) > 0(INT16)
921
922          -- denoted tuple field found?
923          Assert(num < 2, field.err)
924
925          -- fetch actual tag
926          t := tag[addr0]
927
928          -- do garbage collection
929          gc := TRUE
930
931          IF
932            (num = 0) OR open
933              SEQ
934
935                ---------------------------------------------------------------

ADMEMORY.OCC                                                               Page  18
936                -- value stored in head field
937                ---------------------------------------------------------------
938
939                -- check if head value available and ptr
940                avHd := (t /\ #0008(INT16)) > 0(INT16)
941                ptr := (t /\ #0004(INT16)) > 0(INT16)
942
943                IF
944                  avHd AND (NOT ptr)       -- value available and not pointer
945                    SEQ
946
947                      -- construct message token
948                      [4]BYTE toks RETYPES token[2]:
949                      SEQ
950                        toks[2] := mod          -- module address
951                        toks[1] := Mess.PToken  -- message token
952                      token[1] :=  contoff      -- context and offset
953                      token[0] :=  head[addr0]  -- result value
954
955                      -- send token into network
956                      out ! net.token; token
957
958                      -- collect unreferenced cells
959                      Garbage.Collection(addr, wght)
960
961                  avHd AND ptr                   -- value available and pointer
962                    SEQ
963
964                      -- copy pointer value
965                      data := head[addr0]
966                      Copy(data, done)
967
968                      IF
969                        done                    -- pointer could be copied
970                          SEQ
971
972                            -- recopy data
973                            head[addr0] := data
974
975                            -- construct message token
976                            [4]BYTE toks RETYPES token[2]:
977                            SEQ
978                              toks[2] := mod          -- module address
979                              toks[1] := Mess.PToken  -- message token
980                            token[1] :=  contoff      -- context and offset
981                            token[0] :=  data         -- result value
982
983                            -- send token into network
984                            out ! net.token; token
985
986                            -- collect unreferenced cells
987                            Garbage.Collection(addr, wght)
988
989                        TRUE                 -- increase message neccessary
990                          SEQ

ADMEMORY.OCC                                                               Page  19
 991
 992                            -- construct message
 993                            [4]BYTE toks RETYPES token[2]:
 994                            [4]BYTE d RETYPES data:
 995                            SEQ
 996                              toks[2] := d[3]          -- module address
 997                              toks[1] := CopRef.MToken -- increase reference
 998                            [4]BYTE toks RETYPES token[1]:
 999                            VAL [4]BYTE r RETYPES read.tok[0]:
1000                            VAL [4]BYTE t RETYPES read.tok[2]:
1001                            SEQ                        -- node address
1002                              [toks FROM 0 FOR 3] := [r FROM 0 FOR 3]
1003                              toks[3] := t[0]          -- field number
1004                            [4]BYTE toks RETYPES token[0]:
1005                            [4]BYTE d RETYPES data:
1006                            SEQ
1007                              [toks FROM 0 FOR 3] := [d FROM 0 FOR 3]
1008                              toks[3] := act.mod
1009
1010                            -- send message into network
1011                            out ! net.token; token
1012
1013                            -- head not available
1014                            INT16 t IS tag[addr0]:
1015                            t := t /\ #FFF7(INT16)
1016                            head[addr0] := 0(INT32)
1017                            avHd := FALSE
1018
1019                            -- prohibit garbage collection
1020                            gc := FALSE
1021
1022                  TRUE
1023                    SKIP
1024
1025
1026                IF
1027                  NOT avHd                   -- head not available
1028
1029                    IF
1030                      free > 0               -- cell may be allocated
1031                        SEQ
1032
1033                          -- allocate new cell for deferred read request
1034                          new := free
1035                          free := INT tail[free]
1036                          heap.load := heap.load+1
1037
1038                          -- link request into list of deferred reads
1039                          [2]BYTE t RETYPES tag[new]:
1040                          t[0] := mod                 -- module address
1041                          head[new] := contoff        -- context and offset
1042                          INT32 hd IS head[addr0]:
1043                          SEQ
1044                            tail[new] := hd
1045                            hd := INT32 new

ADMEMORY.OCC                                                               Page  20
1046
1047                          -- collect unreferenced cells
1048                          IF
1049                            gc
1050                              Garbage.Collection(addr, wght)
1051                            TRUE
1052                              SKIP
1053
1054                      TRUE                   -- no heap space available
1055                        Assert(FALSE, space.err)
1056
1057                  TRUE
1058                    SKIP
1059
1060            TRUE
1061              SEQ
1062
1063                ---------------------------------------------------------------
1064                -- value stored in tail field
1065                ---------------------------------------------------------------
1066
1067                -- check if tail value available
1068                avTl := (t /\ #0002(INT16)) > 0(INT16)
1069                ptr := (t /\ #0001(INT16)) > 0(INT16)
1070
1071                IF
1072                  avTl AND (NOT ptr)       -- value available and not pointer
1073                    SEQ
1074
1075                      -- construct message token
1076                      [4]BYTE toks RETYPES token[2]:
1077                      SEQ
1078                        toks[2] := mod          -- module address
1079                        toks[1] := Mess.PToken  -- message token
1080                      token[1] :=  contoff      -- context and offset
1081                      token[0] :=  tail[addr0]  -- result value
1082
1083                      -- send token into network
1084                      out ! net.token; token
1085
1086                      -- collect unreferenced cells
1087                      Garbage.Collection(addr, wght)
1088
1089                  avTl AND ptr                   -- value available and pointer
1090                    SEQ
1091
1092                      -- copy pointer value
1093                      data := tail[addr0]
1094                      Copy(data, done)
1095
1096                      IF
1097                        done                    -- pointer could be copied
1098                          SEQ
1099
1100                            -- recopy data

ADMEMORY.OCC                                                               Page  21
1101                            tail[addr0] := data
1102
1103                            -- construct message token
1104                            [4]BYTE toks RETYPES token[2]:
1105                            SEQ
1106                              toks[2] := mod          -- module address
1107                              toks[1] := Mess.PToken  -- message token
1108                            token[1] :=  contoff      -- context and offset
1109                            token[0] :=  data         -- result value
1110
1111                            -- send token into network
1112                            out ! net.token; token
1113
1114                            -- collect unreferenced cells
1115                            Garbage.Collection(addr, wght)
1116
1117                        TRUE                 -- increase message neccessary
1118                          SEQ
1119
1120                            -- construct message
1121                            [4]BYTE toks RETYPES token[2]:
1122                            [4]BYTE d RETYPES data:
1123                            SEQ
1124                              toks[2] := d[3]          -- module address
1125                              toks[1] := CopRef.MToken -- increase reference
1126                            [4]BYTE toks RETYPES token[1]:
1127                            VAL [4]BYTE r RETYPES read.tok[0]:
1128                            VAL [4]BYTE t RETYPES read.tok[2]:
1129                            SEQ                        -- node address
1130                              [toks FROM 0 FOR 3] := [r FROM 0 FOR 3]
1131                              toks[3] := t[0]          -- field number
1132                            [4]BYTE toks RETYPES token[0]:
1133                            [4]BYTE d RETYPES data:
1134                            SEQ
1135                              [toks FROM 0 FOR 3] := [d FROM 0 FOR 3]
1136                              toks[3] := act.mod
1137
1138                            -- send message into network
1139                            out ! net.token; token
1140
1141                            -- tail not available
1142                            INT16 t IS tag[addr0]:
1143                            t := t /\ #FFFD(INT16)
1144                            tail[addr0] := 0(INT32)
1145                            avTl := FALSE
1146
1147                            -- prohibit garbage collection
1148                            gc := FALSE
1149
1150                  TRUE
1151                    SKIP
1152
1153                IF
1154                  NOT avTl                   -- tail not available

ADMEMORY.OCC                                                               Page  22
1156                    IF
1157                      free > 0               -- cell may be allocated
1158                        SEQ
1159
1160                          -- allocate new cell for deferred read request
1161                          new := free
1162                          free := INT tail[free]
1163                          heap.load := heap.load+1
1164
1165                          -- link request into list of deferred reads
1166                          [2]BYTE t RETYPES tag[new]:
1167                          t[0] := mod                 -- module address
1168                          head[new] := contoff        -- context and offset
1169                          tail[new] := tail[addr0]
1170                          tail[addr0] := INT32 new
1171
1172                          -- collect unreferenced cells
1173                          IF
1174                            gc
1175                              Garbage.Collection(addr, wght)
1176                            TRUE
1177                              SKIP
1178
1179                      TRUE                   -- no heap space available
1180                        Assert(FALSE, space.err)
1181
1182                  TRUE
1183                    SKIP
1184
1185      :
1186
1187      -------------------------------------------------------------------------
1188      -- Handle Submission Requests
1189      --
1190      --   Submits the lambda-lifted arguments of some higher-order function
1191      --   stored in a tuple to a specific activation of that function.
1192      --
1193      -- IN sub.tok: submission request token to be fulfilled
1194      -------------------------------------------------------------------------
1195      PROC Submit.Request(VAL [Token.Size]INT32 sub.tok)
1196
1197        BYTE  mod:     -- module address
1198        INT16 context: -- context of function activation to submit arguments to
1199        INT   addr:    -- tuple address
1200        INT   wght:    -- weight of tuple reference
1201        INT   addr0:   -- actual field address
1202        INT   num:     -- number of tuple field referenced
1203        BOOL  error:   -- error flag of reference
1204        BOOL  open:    -- last cell of tuple not encountered?
1205        BOOL  avHd:    -- head of cell available?
1206        BOOL  avTl:    -- tail of cell available?
1207        INT   new:     -- reference to new heap cell
1208        BOOL  ptr:     -- head/tail value pointer?
1209        INT32 data:    -- data of actual tuple field
1210        INT   count:   -- reference count of tuple

ADMEMORY.OCC                                                               Page  23
1211        BOOL  done:    -- tuple reference was copied
1212        INT   field:   -- actual number of tuple field
1213
1214        [Token.Size]INT32 token: -- network token
1215
1216        -----------------------------------------------------------------------
1217        -- Body of Submit.Request
1218        -----------------------------------------------------------------------
1219        SEQ
1220
1221          ---------------------------------------------------------------------
1222          -- extract token components
1223          ---------------------------------------------------------------------
1224
1225          VAL [2]INT16 t RETYPES sub.tok[1]:
1226          context := t[1]                   -- activation context
1227          VAL [4]BYTE t RETYPES sub.tok[0]:
1228          mod := t[3]                       -- module address
1229          addr := INT sub.tok[0]
1230          [4]BYTE a RETYPES addr:
1231          a[3] := 0(BYTE)
1232          error := BOOL (addr /\ #00000001) -- error flag
1233          addr := addr >> 1
1234          wght := addr /\ #00000003         -- weight field
1235          addr := addr >> 2                 -- tuple address
1236
1237          -- check tuple reference for validity
1238          Assert((NOT error) AND (addr > 0) AND (tag[addr]>0(INT16)), ref.err)
1239
1240          ---------------------------------------------------------------
1241          -- submit arguments in head fields
1242          ---------------------------------------------------------------
1243
1244          -- save tuple address
1245          addr0 := addr
1246
1247          -- extract tuple flags
1248          INT16 t IS tag[addr0]:
1249          SEQ
1250            open := (t /\ #0010(INT16)) > 0(INT16)  -- tuple not closed?
1251            avHd := (t /\ #0008(INT16)) > 0(INT16)  -- head available?
1252            [2]BYTE ts RETYPES t:
1253            count := INT ts[1]                      -- reference count
1254
1255          -- error flag and parameter number of function descriptor
1256          INT32 fun IS head[addr0]:
1257          SEQ
1258            error := BOOL (fun /\ #00000001(INT32)) -- error flag
1259            VAL INT32 funs IS fun >> 1:
1260            VAL [4]BYTE f RETYPES funs:
1261            num := INT f[2]                         -- parameter number
1262
1263          -- check if function descriptor available and not error
1264          Assert(avHd AND (NOT error), submit.err)

ADMEMORY.OCC                                                               Page  24
1266          -- actual number of tuple field
1267          field := 1
1268
1269          -- submit arguments or store deferred read request
1270          WHILE open
1271            SEQ
1272
1273              -- address of next tuple field
1274              addr0 := INT (tail[addr0])
1275
1276              -- extract field flags
1277              INT16 t IS tag[addr0]:
1278              SEQ
1279                open := (t /\ #0010(INT16)) > 0(INT16) -- last cell?
1280                avHd := (t /\ #0008(INT16)) > 0(INT16) -- head available?
1281                ptr  := (t /\ #0004(INT16)) > 0(INT16) -- head pointer?
1282
1283              IF
1284
1285                avHd AND (NOT ptr)     -- argument available and not pointer
1286                  SEQ
1287
1288                    -- construct message token
1289                    [4]BYTE toks RETYPES token[2]:
1290                    SEQ
1291                      toks[2] := mod          -- module address
1292                      toks[1] := Mess.PToken  -- message token
1293                    [2]INT16 toks RETYPES token[1]:
1294                    SEQ
1295                      toks[1] := context      -- context
1296                      toks[0] := INT16 num    -- offset (parameter number)
1297                    token[0] :=  head[addr0]  -- result value
1298
1299                    -- send token into network
1300                    out ! net.token; token
1301
1302                avHd AND ptr        -- value available and pointer
1303                  SEQ
1304
1305                    -- copy pointer value
1306                    data := head[addr0]
1307                    Copy(data, done)
1308
1309                    IF
1310                      done                    -- pointer could be copied
1311                        SEQ
1312
1313                          -- recopy data
1314                          head[addr0] := data
1315
1316                          -- construct message token
1317                          [4]BYTE toks RETYPES token[2]:
1318                          SEQ
1319                            toks[2] := mod          -- module address
1320                            toks[1] := Mess.PToken  -- message token

ADMEMORY.OCC                                                               Page  25
1321                          [2]INT16 toks RETYPES token[1]:
1322                          SEQ
1323                            toks[1] := context      -- context
1324                            toks[0] := INT16 num    -- offset (parameter)
1325                          token[0] :=  data         -- result value
1326
1327                          -- send token into network
1328                          out ! net.token; token
1329
1330                      TRUE                 -- increase message neccessary
1331                        SEQ
1332
1333                          -- construct message
1334                          [4]BYTE toks RETYPES token[2]:
1335                          [4]BYTE d RETYPES data:
1336                          SEQ
1337                            toks[2] := d[3]          -- module address
1338                            toks[1] := CopRef.MToken -- increase reference
1339                          [4]BYTE toks RETYPES token[1]:
1340                          VAL [4]BYTE s RETYPES sub.tok[0]:
1341                          SEQ                        -- node address
1342                            [toks FROM 0 FOR 3] := [s FROM 0 FOR 3]
1343                            toks[3] := BYTE field    -- field number
1344                          [4]BYTE toks RETYPES token[0]:
1345                          [4]BYTE d RETYPES data:
1346                          SEQ
1347                            [toks FROM 0 FOR 3] := [d FROM 0 FOR 3]
1348                            toks[3] := act.mod
1349
1350                          -- send message into network
1351                          out ! net.token; token
1352
1353                          -- head not available
1354                          INT16 t IS tag[addr0]:
1355                          t := t /\ #FFF7(INT16)
1356                          head[addr0] := 0(INT32)
1357                          avHd := FALSE
1358
1359                          -- update reference count
1360                          count := count+1
1361
1362                TRUE
1363                  SKIP
1364
1365
1366                IF
1367                  NOT avHd                   -- head not available
1368
1369                    IF
1370                      free > 0               -- cell may be allocated
1371                        SEQ
1372
1373                          -- allocate new cell for deferred read request
1374                          new := free
1375                          free := INT tail[free]

ADMEMORY.OCC                                                               Page  26
1376                          heap.load := heap.load+1
1377
1378                          -- link request into list of deferred reads
1379                          [2]BYTE t RETYPES tag[new]:
1380                          t[0] := mod                 -- module address
1381                          [2]INT16 heads RETYPES head[new]:
1382                          SEQ
1383                            heads[1] := context       -- context
1384                            heads[0] := INT16 num     -- offset (parameter)
1385                          INT32 hd IS head[addr0]:
1386                          SEQ
1387                            tail[new] := hd
1388                            hd := INT32 new
1389
1390                      TRUE                   -- no heap space available
1391                        Assert(FALSE, space.err)
1392
1393                  TRUE
1394                    SKIP
1395
1396              -- parameter and field number
1397              num := num+1
1398              field := field+1
1399
1400          ---------------------------------------------------------------------
1401          -- submit last argument in tail field
1402          ---------------------------------------------------------------------
1403
1404          -- check if tail value available
1405          INT16 t IS tag[addr0]:
1406          SEQ
1407            avTl := (t /\ #0002(INT16)) > 0(INT16)
1408            ptr := (t /\ #0001(INT16)) > 0(INT16)
1409
1410          IF
1411            avTl AND (NOT ptr)       -- value available and not pointer
1412              SEQ
1413
1414                -- construct message token
1415                [4]BYTE toks RETYPES token[2]:
1416                SEQ
1417                  toks[2] := mod          -- module address
1418                  toks[1] := Mess.PToken  -- message token
1419                [2]INT16 toks RETYPES token[1]:
1420                SEQ
1421                  toks[1] := context      -- context
1422                  toks[0] := INT16 num    -- offset (parameter)
1423                token[0] :=  tail[addr0]  -- result value
1424
1425                -- send token into network
1426                out ! net.token; token
1427
1428            avTl AND ptr                   -- value available and pointer
1429              SEQ

ADMEMORY.OCC                                                               Page  27
1431                -- copy pointer value
1432                data := tail[addr0]
1433                Copy(data, done)
1434
1435                IF
1436                  done                    -- pointer could be copied
1437                    SEQ
1438
1439                      -- recopy data
1440                      tail[addr0] := data
1441
1442                      -- construct message token
1443                      [4]BYTE toks RETYPES token[2]:
1444                      SEQ
1445                        toks[2] := mod          -- module address
1446                        toks[1] := Mess.PToken  -- message token
1447                      [2]INT16 toks RETYPES token[1]:
1448                      SEQ
1449                        toks[1] := context      -- context
1450                        toks[0] := INT16 num    -- offset (parameter)
1451                      token[0] :=  data         -- result value
1452
1453                      -- send token into network
1454                      out ! net.token; token
1455
1456                  TRUE                 -- increase message neccessary
1457                    SEQ
1458
1459                      -- construct message
1460                      [4]BYTE toks RETYPES token[2]:
1461                      [4]BYTE d RETYPES data:
1462                      SEQ
1463                        toks[2] := d[3]          -- module address
1464                        toks[1] := CopRef.MToken -- increase reference
1465                      [4]BYTE toks RETYPES token[1]:
1466                      VAL [4]BYTE s RETYPES sub.tok[0]:
1467                      SEQ                        -- node address
1468                        [toks FROM 0 FOR 3] := [s FROM 0 FOR 3]
1469                        toks[3] := BYTE field    -- field number
1470                      [4]BYTE toks RETYPES token[0]:
1471                      [4]BYTE d RETYPES data:
1472                      SEQ
1473                        [toks FROM 0 FOR 3] := [d FROM 0 FOR 3]
1474                        toks[3] := act.mod
1475
1476                      -- send message into network
1477                      out ! net.token; token
1478
1479                      -- tail not available
1480                      INT16 t IS tag[addr0]:
1481                      t := t /\ #FFFD(INT16)
1482                      tail[addr0] := 0(INT32)
1483                      avTl := FALSE
1484
1485                      -- update reference count

ADMEMORY.OCC                                                               Page  28
1486                      count := count+1
1487
1488            TRUE
1489              SKIP
1490
1491          IF
1492            NOT avTl                   -- head not available
1493
1494              IF
1495                free > 0               -- cell may be allocated
1496                  SEQ
1497
1498                    -- allocate new cell for deferred read request
1499                    new := free
1500                    free := INT tail[free]
1501                    heap.load := heap.load+1
1502
1503                    -- link request into list of deferred reads
1504                    [2]BYTE t RETYPES tag[new]:
1505                    t[0] := mod                 -- module address
1506                    [2]INT16 heads RETYPES head[new]:
1507                    SEQ
1508                      heads[1] := context       -- context
1509                      heads[0] := INT16 num     -- offset (parameter)
1510                    tail[new] := tail[addr0]
1511                    tail[addr0] := INT32 new
1512
1513                TRUE                            -- no heap space available
1514                  Assert(FALSE, space.err)
1515
1516            TRUE
1517              SKIP
1518
1519          -- update reference count field
1520          [2]BYTE t RETYPES tag[addr]:
1521          BYTE c IS t[1]:
1522          c := BYTE count
1523
1524          -- collect unreferenced cells
1525          Garbage.Collection(addr, wght)
1526
1527      :
1528
1529      -------------------------------------------------------------------------
1530      -- Handle Expensive Requests
1531      --
1532      --   Handles incoming allocation requests, read requests and
1533      --   submission requests.
1534      --
1535      -- ATTENTION:
1536      --
1537      --   This procedure REFERENCES and ALTERS global variables defined
1538      --   in the preamble of Manager.
1539      -------------------------------------------------------------------------
1540      PROC Exp.Request()

ADMEMORY.OCC                                                               Page  29
1541
1542        [Token.Size]INT32 token: -- network token
1543        BYTE type:               -- token tag
1544
1545        -----------------------------------------------------------------------
1546        -- Body of Exp.Request
1547        -----------------------------------------------------------------------
1548        SEQ
1549
1550          -- receive cheap request and update store load
1551          demand.exp ! demand.tag
1552          from.exp ? exp.load; token
1553
1554          IF
1555            exp.load = 0
1556
1557              -- store is empty
1558              SKIP
1559
1560            TRUE
1561              SEQ
1562
1563                -- extract token tag
1564                [4]BYTE toks RETYPES token[2]:
1565                type := toks[1]
1566
1567                -- perform specified action
1568                CASE type
1569                  Alloc.MToken                  -- allocation requests
1570                    Alloc.Request(token)
1571                  Read.MToken                   -- read requests
1572                    Read.Request(token)
1573                  Submit.MToken                 -- submit requests
1574                    Submit.Request(token)
1575                  Stop.Token                    -- stop token
1576                    WHILE going
1577                      SEQ
1578                        demand.cheap ! demand.tag
1579                        from.cheap ? cheap.load; token
1580                        [4]BYTE toks RETYPES token[2]:
1581                        BYTE type IS toks[1]:
1582                        going := type <> Stop.Token
1583
1584      :
1585
1586      -------------------------------------------------------------------------
1587      -- Handle Write Requests
1588      --
1589      --   Writes message value into particular tuple field. All deferred
1590      --   read requests waiting for this value are fulfilled by sending
1591      --   value messages to the specified operations.
1592      --
1593      -- IN write.tok: write request token to be fulfilled
1594      -------------------------------------------------------------------------
1595      PROC Write.Request(VAL [Token.Size]INT32 write.tok)

ADMEMORY.OCC                                                               Page  30
1596
1597        INT   num:    -- number of tuple field referenced
1598        INT32 data:   -- value to be written
1599        INT   addr:   -- tuple address
1600        INT   wght:   -- weight of tuple reference
1601        INT   addr0:  -- actual field address
1602        BOOL  error:  -- error flag of reference
1603        BOOL  open:   -- last cell of tuple not yet encountered?
1604        INT   next:   -- address of next tuple cell
1605        INT   next0:  -- auxiliary address
1606        BYTE  type:   -- type of write request
1607        BOOL  isHd:   -- head field contains data
1608        BOOL  isAtom: -- data is atomic
1609        BOOL  going:  -- auxiliary variable
1610        BOOL  done:   -- tuple reference could be copied
1611
1612        [Token.Size]INT32 token: -- network token
1613
1614        -----------------------------------------------------------------------
1615        -- Body of Write.Request
1616        -----------------------------------------------------------------------
1617        SEQ
1618
1619          ---------------------------------------------------------------------
1620          -- extract token components
1621          ---------------------------------------------------------------------
1622
1623          VAL [4]BYTE toks RETYPES write.tok[2]:
1624          SEQ
1625            type := toks[1]                 -- type of write request
1626            num  := INT toks[0]             -- number of tuple field
1627          data := write.tok[1]              -- value to be written
1628          addr := INT write.tok[0]          -- tuple reference
1629          [4]BYTE a RETYPES addr:
1630          a[3] := 0(BYTE)
1631          error := BOOL (addr /\ #00000001) -- error flag
1632          addr := addr >> 1
1633          wght := addr /\ #00000003         -- reference weight
1634          addr := addr >> 2                 -- tuple address
1635
1636          -- check tuple reference for validity
1637          Assert((NOT error) AND (addr > 0) AND (tag[addr]>0(INT16)), ref.err)
1638
1639          ---------------------------------------------------------------------
1640          -- find specified tuple field
1641          ---------------------------------------------------------------------
1642
1643          -- find specified tuple field
1644          addr0 := addr
1645          INT16 t IS tag[addr0]:
1646          open := (t /\ #0010(INT16)) > 0(INT16)
1647          WHILE open AND (num > 0)
1648            SEQ
1649              num := num - 1
1650              addr0 := INT tail[addr0]

ADMEMORY.OCC                                                               Page  31
1651              INT16 t IS tag[addr0]:
1652              open := (t /\ #0010(INT16)) > 0(INT16)
1653
1654          -- check field number
1655          Assert(num < 2, field.err)
1656
1657          ---------------------------------------------------------------------
1658          -- find deferred read requests and write value into tuple field
1659          ---------------------------------------------------------------------
1660
1661          -- head or data field, atomic value or pointer?
1662          isHd := (num = 0) OR open
1663          isAtom := (type = WrInt.MToken) OR (data = 0(INT32))
1664
1665          INT16 t IS tag[addr0]:            -- tag field
1666          IF
1667            isHd                            -- head field specified
1668              SEQ
1669                Assert((t/\#0008(INT16))=0(INT16), mult.write)
1670                next := INT head[addr0]
1671                head[addr0] := data
1672                IF
1673                  isAtom                    -- head available flag
1674                    t := t \/ #0008(INT16)
1675                  TRUE                      -- head available and pointer
1676                    t := t \/ #000C(INT16)
1677            TRUE                            -- tail field specified
1678              SEQ
1679                Assert((t/\#0002(INT16))=0(INT16), mult.write)
1680                next := INT tail[addr0]
1681                tail[addr0] := data
1682                IF
1683                  isAtom                    -- tail available flag
1684                    t := t \/ #0002(INT16)
1685                  TRUE                      -- tail available and pointer
1686                    t := t \/ #0003(INT16)
1687
1688          ---------------------------------------------------------------------
1689          -- fulfill deferred reads
1690          ---------------------------------------------------------------------
1691
1692          -- traverse list of deferred reads
1693          going := TRUE
1694          WHILE (next > 0) AND going
1695            IF
1696
1697              isAtom
1698                SEQ
1699
1700                  -- construct token
1701                  [2]BYTE tags RETYPES tag[next]:
1702                  [4]BYTE toks RETYPES token[2]:
1703                  SEQ
1704                    toks[2] := tags[0]             -- module address
1705                    toks[1] := Mess.PToken         -- message token

ADMEMORY.OCC                                                               Page  32
1706                  token[1] := head[next]           -- context and offset
1707                  token[0] := data                 -- message value
1708
1709                  -- send token to network
1710                  out ! net.token; token
1711
1712                  -- free heap cell
1713                  next0 := INT tail[next]
1714                  tag[next] := 0(INT16)
1715                  tail[next] := INT32 free
1716                  free := next
1717
1718                  -- update heap load
1719                  heap.load := heap.load-1
1720
1721                  -- next deferred read
1722                  next := next0
1723
1724              TRUE
1725                SEQ
1726
1727                  -- copy pointer value
1728                  Copy(data, done)
1729
1730                  IF
1731                    done                    -- pointer could be copied
1732                      SEQ
1733
1734                        -- recopy data
1735                        IF
1736                          isHd
1737                            head[addr0] := data
1738                          TRUE
1739                            tail[addr0] := data
1740
1741                        -- construct token
1742                        [2]BYTE tags RETYPES tag[next]:
1743                        [4]BYTE toks RETYPES token[2]:
1744                        SEQ
1745                          toks[2] := tags[0]           -- module address
1746                          toks[1] := Mess.PToken       -- message token
1747                        token[1] := head[next]         -- context and offset
1748                        token[0] := data               -- message value
1749
1750                        -- send token to network
1751                        out ! net.token; token
1752
1753                        -- free heap cell
1754                        next0 := INT tail[next]
1755                        tag[next] := 0(INT16)
1756                        tail[next] := INT32 free
1757                        free := next
1758
1759                        -- update heap load
1760                        heap.load := heap.load-1

ADMEMORY.OCC                                                               Page  33
1761
1762                        -- next deferred read
1763                        next := next0
1764
1765                    TRUE                 -- increase message neccessary
1766                      SEQ
1767
1768                        -- construct message
1769                        [4]BYTE toks RETYPES token[2]:
1770                        [4]BYTE d RETYPES data:
1771                        SEQ
1772                          toks[2] := d[3]          -- module address
1773                          toks[1] := CopRef.MToken -- increase reference
1774                        [4]BYTE toks RETYPES token[1]:
1775                        VAL [4]BYTE w RETYPES write.tok[0]:
1776                        VAL [4]BYTE t RETYPES write.tok[2]:
1777                        SEQ                        -- node address
1778                          [toks FROM 0 FOR 3] := [w FROM 0 FOR 3]
1779                          toks[3] := t[0]          -- field number
1780                        [4]BYTE toks RETYPES token[0]:
1781                        [4]BYTE d RETYPES data:
1782                        SEQ
1783                          [toks FROM 0 FOR 3] := [d FROM 0 FOR 3]
1784                          toks[3] := act.mod
1785
1786                        -- send message into network
1787                        out ! net.token; token
1788
1789                        -- head/tail not available
1790                        INT16 t IS tag[addr0]:
1791                        IF
1792                          isHd
1793                            SEQ
1794                              t := t /\ #FFF7(INT16)
1795                              head[addr0] := INT32 next
1796                          TRUE
1797                            SEQ
1798                              t := t /\ #FFFD(INT16)
1799                              tail[addr0] := INT32 next
1800
1801                        -- terminate loop
1802                        going := FALSE
1803
1804          -- collect unreferenced cells
1805          IF
1806            next = 0
1807              Garbage.Collection(addr, wght)
1808            TRUE
1809              SKIP
1810
1811      :
1812
1813      -------------------------------------------------------------------------
1814      -- Handle Reference Requests
1815      --

ADMEMORY.OCC                                                               Page  34
1816      --   Increase respectively decrease reference count field of some
1817      --   particular tuple. If the reference count is increased, a
1818      --   reference with an adapted weight field is sent to the caller.
1819      --   If the reference count decreases to zero, the tuple is reclaimed
1820      --   to the free list.
1821      --
1822      -- IN ref.tok: reference request token to be handled
1823      -------------------------------------------------------------------------
1824      PROC Ref.Request(VAL [Token.Size]INT32 ref.tok)
1825
1826        BYTE  type:    -- request type (increase or decrease)
1827        INT   addr:    -- tuple address
1828        INT   wght:    -- reference weight field
1829        BOOL  error:   -- tuple reference error bit
1830
1831        [Token.Size]INT32 token: -- network token
1832
1833        -----------------------------------------------------------------------
1834        -- Body of Ref.Request
1835        -----------------------------------------------------------------------
1836        SEQ
1837
1838          -- extract token components
1839          VAL [4]BYTE toks RETYPES ref.tok[2]:
1840          type := toks[1]
1841          addr := INT ref.tok[0]
1842          [4]BYTE a RETYPES addr:
1843          a[3] := 0(BYTE)
1844          error := BOOL (addr /\ #00000001) -- error flag
1845          addr := addr >> 1
1846          wght := addr /\ #00000003         -- reference weight
1847          addr := addr >> 2                 -- tuple address
1848
1849          -- check tuple reference for validity
1850          Assert((NOT error) AND (addr > 0) AND (tag[addr]>0(INT16)), ref.err)
1851
1852          -- increase or decrease reference count
1853          CASE type
1854
1855            -- increase reference count of tuple, send result to processor unit
1856            IncRef.MToken
1857              SEQ
1858
1859                -- update reference count
1860                [2]BYTE tags RETYPES tag[addr]:
1861                BYTE count IS tags[1]:
1862                SEQ
1863                  IF                       -- update reference count
1864                    (Range.Count-7) > (INT count)
1865                      count := BYTE((INT count) + 7)
1866                    TRUE                   -- reference count infinity
1867                      count := BYTE (Range.Count-1)
1868
1869                -- construct token
1870                VAL [4]BYTE t RETYPES ref.tok[0]:

ADMEMORY.OCC                                                               Page  35
1871                [4]BYTE toks RETYPES token[2]:
1872                SEQ
1873                  toks[2] := t[3]         -- module address
1874                  toks[1] := Mess.PToken  -- message token
1875                token[1] :=  ref.tok[1]   -- context and offset of caller
1876                token[0] :=               -- reference with maximal weight
1877                  INT32 ((addr << 3) \/ #00000006) -- and error bit not set
1878                [4]BYTE toks RETYPES token[0]:
1879                toks[3] := act.mod
1880
1881                -- send token into network
1882                out ! net.token; token
1883
1884            -- increase reference count of tuple, send result to memory unit
1885            CopRef.MToken
1886              SEQ
1887
1888                -- update reference count
1889                [2]BYTE tags RETYPES tag[addr]:
1890                BYTE count IS tags[1]:
1891                SEQ
1892                  IF                       -- update reference count
1893                    (Range.Count-7) > (INT count)
1894                      count := BYTE((INT count) + 7)
1895                    TRUE                   -- reference count infinity
1896                      count := BYTE (Range.Count-1)
1897
1898                -- construct token
1899                VAL [4]BYTE t1 RETYPES ref.tok[1]:
1900                VAL [4]BYTE t0 RETYPES ref.tok[0]:
1901                [4]BYTE toks RETYPES token[2]:
1902                SEQ
1903                  toks[2] := t0[3]        -- module address
1904                  toks[1] := WrPtr.MToken -- write token
1905                  toks[0] := t1[3]        -- field number
1906                token[1] :=               -- reference with maximal weight
1907                  INT32 ((addr << 3) \/ #00000006) -- and error bit not set
1908                [4]BYTE toks RETYPES token[1]:
1909                toks[3] := act.mod        -- actual module address
1910                token[0] :=  ref.tok[1]   -- tuple address of caller
1911
1912                -- send token into network
1913                out ! net.token; token
1914
1915            -- decrease reference count of tuple and perform garbage collection
1916            DecRef.MToken
1917              Garbage.Collection(addr, wght)
1918
1919      :
1920
1921      -------------------------------------------------------------------------
1922      -- Receive Memory Load Report
1923      --
1924      --   Update load description of neighbour memory.
1925      --

ADMEMORY.OCC                                                               Page  36
1926      -- IN token: load report token
1927      -------------------------------------------------------------------------
1928      PROC Load.Message(VAL [Token.Size]INT32 token)
1929
1930        -----------------------------------------------------------------------
1931        -- Body of Load.Message
1932        -----------------------------------------------------------------------
1933        SEQ
1934
1935          VAL [4]BYTE toks RETYPES token[2]:
1936          VAL BYTE link      IS      toks[0]:  -- link address of module
1937          VAL INT  heap.load RETYPES token[1]: -- its heap load
1938          VAL INT  exp.load  RETYPES token[0]: -- its frame load
1939
1940          -- update load description
1941          mem.load[INT link] := Load(heap.load, exp.load)
1942
1943      :
1944
1945      -------------------------------------------------------------------------
1946      -- Handle Cheap Requests
1947      --
1948      --   Handles incoming write requests and memory load reports.
1949      --
1950      -- ATTENTION:
1951      --
1952      --   This procedure REFERENCES and ALTERS global variables defined
1953      --   in the preamble of Manager.
1954      -------------------------------------------------------------------------
1955      PROC Cheap.Request()
1956
1957        [Token.Size]INT32 token: -- network token
1958        BYTE type:               -- token tag
1959
1960        -----------------------------------------------------------------------
1961        -- Body of Cheap.Request
1962        -----------------------------------------------------------------------
1963        SEQ
1964
1965          -- receive cheap request and update store load
1966          demand.cheap ! demand.tag
1967          from.cheap ? cheap.load; token
1968
1969          IF
1970            cheap.load = 0
1971
1972              -- store is empty
1973              SKIP
1974
1975            TRUE
1976              SEQ
1977
1978                -- extract token tag
1979                [4]BYTE toks RETYPES token[2]:
1980                type := toks[1]

ADMEMORY.OCC                                                               Page  37
1981
1982                -- perform specified action
1983                CASE type
1984                  WrInt.MToken, WrPtr.MToken    -- write requests
1985                    Write.Request(token)
1986                  IncRef.MToken, DecRef.MToken,
1987                  CopRef.MToken                 -- reference requests
1988                    Ref.Request(token)
1989                  MLoad.MToken                  -- load report messages
1990                    Load.Message(token)
1991                  Stop.Token                    -- stop token
1992                    WHILE going
1993                      SEQ
1994                        demand.exp ! demand.tag
1995                        from.exp ? exp.load; token
1996                        [4]BYTE toks RETYPES token[2]:
1997                        BYTE type IS toks[1]:
1998                        going := type <> Stop.Token
1999
2000      :
2001
2002      -------------------------------------------------------------------------
2003      -- Report Memory Load:
2004      --
2005      --   Tells the memories on all neighbour modules and the master
2006      --   module about the current workload of heap and expensive store.
2007      --
2008      -- ATTENTION:
2009      --
2010      --   This procedure REFERENCES and ALTERS global variables defined
2011      --   in the preamble of Manager.
2012      -------------------------------------------------------------------------
2013      PROC Load.Report()
2014
2015        [Token.Size]INT32 token: -- network token
2016
2017        -----------------------------------------------------------------------
2018        -- Body of Load.Report
2019        -----------------------------------------------------------------------
2020        SEQ
2021
2022          -- store load to be reported
2023          lLoad := mLoad
2024
2025          -- construct token
2026          [4]BYTE toks RETYPES token[2]:
2027          toks[1] := MLoad.MToken        -- memory load token
2028          token [1] := INT32 heap.load   -- heap load
2029          token [0] := INT32 exp.load    -- exprensive store load
2030
2031          -- send report to master
2032          [4]BYTE toks RETYPES token[2]:
2033          SEQ
2034            toks[2] := 0(BYTE)           -- address of master
2035            toks[0] := act.mod           -- actual module address

ADMEMORY.OCC                                                               Page  38
2036          out ! net.token; token
2037
2038          -- send report to neighbours
2039          SEQ i=0 FOR Link.Num
2040            SEQ
2041              [4]BYTE toks RETYPES token[2]:
2042              SEQ
2043                toks[2] := mod.addr[i]   -- address of neighbour
2044                toks[0] := link.addr[i]  -- unique memory idenitifier
2045              out ! net.token; token
2046
2047      :
2048
2049      -------------------------------------------------------------------------
2050      -- Body of Manager
2051      -------------------------------------------------------------------------
2052      SEQ
2053
2054        WHILE TRUE
2055          SEQ
2056
2057            -- create free list (cell 0 is unused since 0 denotes NIL)
2058            free := 1
2059            tag[0] := 0(INT16)
2060            SEQ i = 1 FOR Heap.Len-1
2061              SEQ
2062                tag[i] := 0(INT16)
2063                tail[i] := INT32 (i+1)
2064            tag[Heap.Len-1] := 0(INT16)
2065            tail[Heap.Len-1] := 0(INT32)
2066
2067            -- reset load variables
2068            heap.load, exp.load, cheap.load := 0, 0, 0
2069
2070            -- reset actual and last reported load
2071            mLoad, lLoad := 0, 0
2072
2073            -- reset load of all neighbour modules
2074            SEQ i = 0 FOR Link.Num
2075              mem.load[i] := 0
2076
2077            -- send acknowledge to arbiter
2078            out ! net.ack
2079
2080            -- manager main loop
2081            going := TRUE
2082            WHILE going
2083              SEQ
2084                Mem.Action(action)  -- determine next action
2085                CASE action
2086                  Exp.Act
2087                    Exp.Request()   -- fulfill expensive request
2088                  Cheap.Act
2089                    Cheap.Request() -- fulfill cheap request
2090                  Report.Act

ADMEMORY.OCC                                                               Page  39
2091                    Load.Report()   -- report actual load
2092
2093            -- send acknowledge to arbiter
2094            out ! net.ack
2095
2096        -- never done (just to avoid compiler warnings)
2097        debug ! 0 :: ""
2098
2099    :
2100
2101    ---------------------------------------------------------------------------
2102    -- Body of Adam.Memory
2103    --------------------------------------------------------------------------
2104    PAR
2105
2106      -- routes incoming tokens to expensive or cheap store
2107      Router(in, to.exp, to.cheap, error[0])
2108
2109      -- buffers incoming expensive requests
2110      Buffer(to.exp, demand.exp, exp.store, from.exp, error[1])
2111
2112      -- buffers incoming cheap requests
2113      Buffer(to.cheap, demand.cheap, cheap.store, from.cheap, error[2])
2114
2115      -- manages memory unit
2116      Manager(from.exp, from.cheap, demand.exp, demand.cheap, error[3])
2117
2118      -- multiplexor for error messages
2119      INT len:               -- length of message
2120      [Max.Debug]BYTE mess:  -- error message
2121      ALT i=0 FOR 4
2122        error[i] ? len :: mess
2123          debug ! len :: mess
2124
2125  :
2126
2127  -----------------------------------------------------------------------------
2128  -- End of ADMEMORY.OCC
2129  -----------------------------------------------------------------------------

ADPROC.OCC                                                                 Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADPROC.OCC                                             Wolfgang Schreiner
 4  -- The Adam Processor Unit                                (c) RISC-Linz 1990
 5  --
 6  -- Interpreter for all components of the Adam processor unit.
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- External References
12  -----------------------------------------------------------------------------
13
14  -- include files
15  #INCLUDE "adglobal.inc" -- global constants
16  #INCLUDE "adnet.inc"    -- network constants
17  #INCLUDE "aderror.inc"  -- error messages
18  #INCLUDE "adproc.inc"   -- processor constants
19
20  -----------------------------------------------------------------------------
21  -- The Adam Processor Unit
22  --
23  --   The processor unit receives
24  --     1. the executable program,
25  --     2. function call requests that are handled by the module,
26  --     3. a stop signal that breaks the execution of the unit.
27  --   The sequence 1-3 loops forever, i.e. the unit does not terminate.
28  --
29  --   IN  act.mod:   address of actual module
30  --   IN  mod.addr:  addresses of neighbour modules
31  --   IN  link.addr: link addresses of actual module from view of neighbour
32  --   IN  in:        input channels from arbiter
33  --   OUT out:       output channels to arbiter
34  --   OUT debug:     debug channel to output process
35  -----------------------------------------------------------------------------
36  PROC Adam.Processor(VAL BYTE act.mod,
37    VAL [Link.Num]BYTE mod.addr, VAL [Link.Num]BYTE link.addr,
38    CHAN OF Mod.Link in, CHAN OF Net.Link out, CHAN OF Debug debug)
39
40    -- internal channels
41    CHAN OF Cluster from.rout:                 -- transmit program clusters
42    CHAN OF Proc.Token to.requ, to.mess:       -- transmit tokens to buffers
43    CHAN OF Buffer.Token from.requ, from.mess: -- transmit tokens to execution
44    CHAN OF Demand demand.requ, demand.mess:   -- transmit demands
45
46    -- buffers for message and request store
47    [Token.Size*Mess.Len]INT32 mess.store: -- message store
48    [Token.Size*Requ.Len]INT32 requ.store: -- request store
49
50    [4]CHAN OF Debug error: -- internal debugging channels
51
52    --------------------------------------------------------------------------
53    -- The Processor Unit Router:
54    --
55    --   The Router receives incoming data from the arbiter and routes them

ADPROC.OCC                                                                 Page   2
56    --   as follows:
57    --     1. Programs to the execution unit,
58    --     2. Message tokens to the message store,
59    --     3. Request tokens to the request store.
60    --
61    --   IN  in:      channel from arbiter
62    --   OUT to.mess: channel to message store
63    --   OUT to.requ: channel to request store
64    --   OUT to.exec: channel to execution unit
65    --   OUT debug:   debugging channel
66    --------------------------------------------------------------------------
67    PROC Router(CHAN OF Mod.Link in,
68      CHAN OF Proc.Token to.mess, CHAN OF Proc.Token to.requ,
69      CHAN OF Cluster to.exec, CHAN OF Debug debug)
70
71      [Cluster.Size]INT32 prog:    -- program cluster
72      [Token.Size]INT32   token:   -- network token
73      BYTE                tag:     -- token tag
74      BOOL                going:   -- auxiliary variable
75
76      ------------------------------------------------------------------------
77      -- Body of Router
78      ------------------------------------------------------------------------
79      SEQ
80
81        -- loop forever
82        WHILE TRUE
83          SEQ
84
85            -- receive program and forward it to execution unit
86            going := TRUE
87            WHILE going
88              in ? CASE
89                mod.startprog
90                  to.exec ! start.cluster
91                mod.appendprog
92                  to.exec ! append.cluster
93                mod.program; prog
94                  to.exec ! cluster; prog
95                mod.endprog
96                  SEQ
97                    to.exec ! end.cluster
98                    going := FALSE
99
100            -- route tokens to appropriate stores until stop signal encountered
101            going := TRUE
102            WHILE going
103              SEQ
104                in ? CASE mod.token; token
105                [4]BYTE toks RETYPES token[2]:
106                tag := toks[1]
107                IF
108                  tag = Stop.Token         -- stop token
109                    SEQ
110                      to.mess ! token

ADPROC.OCC                                                                 Page   3
111                      to.requ ! token
112                      going := FALSE
113                  ((INT tag) /\ #F0) = 0   -- message token
114                    to.mess ! token
115                  TRUE                     -- request token
116                    to.requ ! token
117
118        -- never done (just to avoid compiler warnings)
119        debug ! 0 :: ""
120    :
121
122    --------------------------------------------------------------------------
123    -- The Processor Unit Buffer
124    --
125    --   Buffers incoming tokens in a queue and forwards them to the
126    --   execution unit on demand.
127    --
128    --   IN  in:     input channel from router
129    --   IN  demand: demand channel from execution unit
130    --   IN  buffer: store to hold tokens
131    --   OUT out:    output channel to execution unit
132    --   OUT debug:  debugging channel
133    --------------------------------------------------------------------------
134    PROC Buffer(CHAN OF Proc.Token in, CHAN OF Demand demand, []INT32 buffer,
135      CHAN OF Buffer.Token out, CHAN OF Debug debug)
136
137      VAL buffer.size IS SIZE buffer:
138
139      INT  first, last: -- indizes of the first resp. last queue elements
140      INT  num:         -- number of elements in queue
141      BOOL full, empty: -- denote the actual status of the queue
142      BOOL going:       -- termination flag
143      BYTE tag:         -- tag of actual token
144
145      [Token.Size]INT32 token: -- token value
146
147      ------------------------------------------------------------------------
148      -- Assert Condition
149      --
150      --   Checks condition and forwards error message to debugging channel,
151      --   if neccessary.
152      --
153      -- IN cond: test condition
154      -- IN mess: error message
155      ------------------------------------------------------------------------
156      PROC Assert(VAL BOOL cond, VAL []BYTE mess)
157        SEQ
158          IF
159            cond
160              SKIP
161            TRUE
162              SEQ
163                debug ! (SIZE mess) :: mess
164                STOP
165      :

ADPROC.OCC                                                                 Page   4
166
167      -------------------------------------------------------------------------
168      -- Body of Buffer
169      -------------------------------------------------------------------------
170      WHILE TRUE
171        SEQ
172
173          -- buffer length must be a multiple of Token.Size
174          Assert((buffer.size REM Token.Size) = 0, buff.err)
175
176          -- initialize status variables
177          num, first, last := 0, 0, 0
178          full, empty := FALSE, TRUE
179
180          -- loop until stop token encountered
181          going := TRUE
182          WHILE going
183
184            -- full equals not empty
185            -- if first <> last
186            --   then first refers to the next free element
187            --        last refers to the last stored element
188            --   else if full
189            --          then first is not valid
190            --          else last is not valid
191
192            -- listen on both input channels
193            ALT
194
195              -- demand received
196              demand ? CASE demand.tag
197                SEQ
198
199                  -- submit token
200                  [Token.Size]INT32 token IS [buffer FROM last FOR Token.Size]:
201                  out ! num; token
202
203                  -- extract tag
204                  [Token.Size]INT32 token IS [buffer FROM last FOR Token.Size]:
205                  [4]BYTE toks RETYPES token[2]:
206                  tag := toks[1]
207
208                  IF
209                    tag = Stop.Token  -- stop token submitted, empty buffer
210                      going := FALSE
211                    empty             -- submitted token was undefined
212                      SKIP
213                    TRUE              -- update state variables
214                      SEQ
215                        num  := num-1
216                        last := (last + Token.Size) REM buffer.size
217                        full := FALSE
218                        empty := first = last
219
220              -- token received

ADPROC.OCC                                                                 Page   5
221              in ? token
222                SEQ
223
224                  -- buffer must not be full
225                  Assert(NOT full, buff.full)
226
227                  -- store token
228                  [buffer FROM first FOR Token.Size] := token
229                  num   := num+1
230                  first := (first + Token.Size) REM buffer.size
231                  empty := FALSE
232                  full  := first = last
233
234    :
235
236    --------------------------------------------------------------------------
237    -- The Processor Execution Unit
238    --
239    --   Receives the addresses of the neighbour modules and the machine code
240    --   of the Adam program to be executed, then handles the incoming
241    --   messages and requests. Program execution is terminated by a stop
242    --   request that resets the execution unit to its initial state. The unit
243    --   then is ready to receive a new program.
244    --
245    --   The execution unit itself does not terminate.
246    --
247    --   IN from.rout:    channel from router transmitting the machine code
248    --   IN from.requ:    channel from request store
249    --   IN from.mess:    channel from message store
250    --   OUT demand.requ: demand channel to request store
251    --   OUT demand.mess: demand channel to message store
252    --   OUT debug:       debugging channel
253    ---------------------------------------------------------------------------
254    PROC Execution.Unit(CHAN OF Cluster from.rout,
255      CHAN OF Buffer.Token from.requ, from.mess,
256      CHAN OF Demand demand.requ, demand.mess, CHAN OF Debug debug)
257
258      -- the basic stores
259      [Prog.Len]INT32  prog.store:    -- the program store
260      [Frame.Len]INT32 frame.store:   -- the frame store
261      [Cont.Len]INT32  cont.store.f:  -- the context store (frame pointer)
262      [Cont.Len]INT32  cont.store.c:  -- the context store (code pointer)
263      [Trig.Len]INT32  trig.store:    -- the trigger store
264      [Used.Len]INT32  used.store:    -- used bits for frame store
265
266      -- the load parameters
267      INT frame.load: -- load of frame store
268      INT cont.load:  -- load of context store
269      INT trig.load:  -- load of trigger store
270      INT requ.load:  -- load of request store
271      INT mess.load:  -- load of message store
272
273      INT pLoad:   -- relative load of actual processor
274      INT lLoad:   -- last load reported to neighbours

ADPROC.OCC                                                                 Page   6
276      -- heap organizing variables
277      [Free.Step]INT frame.free:  -- heads of the frame store free lists
278      INT cont.free:              -- head of context store free list
279
280      -- network descriptors
281      [Link.Num]INT proc.load: -- relative load of processors
282
283      -- the following quantities are currently not used
284      -- [Link.Num]INT  proc.frame.load: -- load of neighbours' frame stores
285      -- [Link.Num]INT  proc.requ.load:  -- load of neighbours' request stores
286
287      -- auxiliary variables
288      INT32 null:    -- conversion auxiliary
289      INT   action:  -- next action to be executed
290      INT   pos:     -- actual program store position
291      INT   max.pos: -- actual length of program without appended fragment
292      BOOL  append:  -- program fragment is to be appended
293      BOOL  going:   -- termination flag
294
295      ------------------------------------------------------------------------
296      -- Auxiliary Procedures in Execution Unit
297      ------------------------------------------------------------------------
298
299      ------------------------------------------------------------------------
300      -- Assert Condition
301      --
302      --   Checks condition and forwards error message to debugging channel,
303      --   if neccessary.
304      --
305      -- IN cond: test condition
306      -- IN mess: error message
307      ------------------------------------------------------------------------
308      PROC Assert(VAL BOOL cond, VAL []BYTE mess)
309
310        ----------------------------------------------------------------------
311        -- Body of Assert
312        ----------------------------------------------------------------------
313        SEQ
314          IF
315            cond
316              SKIP
317            TRUE
318              SEQ
319                debug ! (SIZE mess) :: mess
320                STOP
321      :
322
323      ------------------------------------------------------------------------
324      -- Put Trigger into Store
325      --
326      --   Pushes trigger on stack-organized trigger store
327      --
328      -- IN trigger: instruction trigger
329      --
330      -- ATTENTION:

ADPROC.OCC                                                                 Page   7
331      --
332      --   Alters global variables trig.load and trig.store!
333      ------------------------------------------------------------------------
334      PROC Trig.Put(VAL INT32 trigger)
335
336        ----------------------------------------------------------------------
337        -- Body of Trig.Put
338        ----------------------------------------------------------------------
339        SEQ
340
341          -- stack must not be full
342          Assert(trig.load < Trig.Len, trig.full)
343
344          -- push trigger on stack
345          trig.store[trig.load] := trigger
346          trig.load := trig.load+1
347      :
348
349      ------------------------------------------------------------------------
350      -- Fetch Trigger from Store
351      --
352      --   Pops trigger from stack-organized trigger store
353      --
354      -- OUT trigger: instruction trigger
355      --
356      -- ATTENTION:
357      --
358      --   Alters global variables trig.load and trig.store!
359      ------------------------------------------------------------------------
360      PROC Trig.Fetch(INT32 trigger)
361
362        ----------------------------------------------------------------------
363        -- Body of Trig.Fetch
364        ----------------------------------------------------------------------
365        SEQ
366
367          -- store must not be empty
368          Assert(trig.load>0, trig.empt)
369
370          -- pop trigger from stack
371          trig.load := trig.load-1
372          trigger := trig.store[trig.load]
373      :
374
375      -------------------------------------------------------------------------
376      -- Compute Relative Load
377      --
378      --   Takes frame load and request load of some processor unit and
379      --   computes its relative load, i.e. a value between 0 and 10000
380      --   that (hopefully adequately) describes the overall working load of
381      --   the processor: a value of 0 should denote "the processor is
382      --   totally idle" and a value of 10000 should denote "the processor is
383      --   not able to fulfill any further call request". Any value between
384      --   should in some way express a rough measure for the load of the
385      --   processor.

ADPROC.OCC                                                                 Page   8
386      --
387      -- IN frame: the frame load of the processor
388      -- IN requ:  the request load of the processor
389      -------------------------------------------------------------------------
390      INT FUNCTION Load(VAL INT frame, requ)
391
392        -----------------------------------------------------------------------
393        -- Body of Load
394        -----------------------------------------------------------------------
395        VALOF
396          SKIP
397          RESULT ((100 * frame) / (Frame.Len / 100)) + (0 * requ)
398      :
399
400      ------------------------------------------------------------------------
401      -- Return Address of Minimal Loaded Processor
402      --
403      --   Returns the address of that processor that shall be considered as
404      --   minimal loaded. The local processor is preferred in the sense that
405      --   a neighbour processor must be significantly less loaded than the
406      --   local one in order to forward call requests to that processor.
407      --   The actual measure of 'significantly' is given by the constant
408      --   'Significance'.
409      --
410      -- ATTENTION: References global load descriptors!
411      ------------------------------------------------------------------------
412      BYTE FUNCTION Min.Loaded()
413
414        BYTE lmod: -- address of least loaded processor
415        INT  pl:   -- load of this processor
416
417        ----------------------------------------------------------------------
418        -- Body of Min.Loaded
419        ----------------------------------------------------------------------
420        VALOF
421
422          SEQ
423
424            -- default is actual module
425            lmod, pl := act.mod, (pLoad*Significance)/100
426
427            -- search for less loaded module among neighbours
428            SEQ i = 0 FOR Link.Num
429              IF
430                proc.load[i] <= pl
431                  SEQ
432                    lmod := mod.addr[i]
433                    pl := proc.load[i]
434                TRUE
435                  SKIP
436
437          RESULT lmod
438      :
439
440      ------------------------------------------------------------------------

ADPROC.OCC                                                                 Page   9
441      -- Determine Type of Processor Action
442      --
443      --   Using some strategy, this procedure returns the kind of processor
444      --   action to be executed next:
445      --   * execute instruction,
446      --   * receive message,
447      --   * fulfill request,
448      --   * report load.
449      --
450      -- IO action: last and next action to be executed
451      --
452      -- ATTENTION: references the global load descriptors!
453      -------------------------------------------------------------------------
454      PROC Proc.Action(INT action)
455
456        -----------------------------------------------------------------------
457        -- Body of Proc.Action
458        -----------------------------------------------------------------------
459        IF
460          trig.load > 0
461            -- prefer instruction execution, if possible
462            action := Execute.Act
463          (pLoad > Proc.Trash) AND
464            ((((100*lLoad) < (Rep.Delay*pLoad)) OR
465              ((100*pLoad) < (Rep.Delay*lLoad))))
466            -- let neighbours know, if load has significantly changed
467            action := Report.Act
468          mess.load = 0
469            -- fulfill requests, if message store empty but
470            -- give message store a chance for the next time
471            SEQ
472              action := Request.Act
473              mess.load := 1
474          requ.load = 0
475            -- receive message, if request store empty but
476            -- give request store a chance for the next time
477            SEQ
478              action := Receive.Act
479              requ.load := 1
480          (frame.load > Frame.Bound)
481            -- receive message, if frame load too high
482            action := Receive.Act
483          (frame.load < Frame.Trash) OR
484            ((frame.load < Frame.Bound) AND (requ.load > Requ.Bound))
485            -- fulfill requests, if frame load too low or
486            -- request store is quite filled
487            action := Request.Act
488          TRUE
489            -- receiving a message is always possible
490            action := Receive.Act
491
492      :
493
494      ------------------------------------------------------------------------
495      -- Context Heap:

ADPROC.OCC                                                                 Page  10
496      --
497      --   The following procedures manage the heap-organized context store
498      --   consisting of the arrays 'cont.store.f' and 'cont.store.c':
499      --
500      --     * 'cont.store.f' holds the address of the function's frame
501      --     * 'cont.store.c' holds the address of the function's code block
502      --
503      --   The two corresponding entries of each store represent one context,
504      --   i.e. one node on the heap. All free nodes on the heap are linked by
505      --   their 'cont.store.f' fields, the variable 'cont.free' references
506      --   the first element in the free list.
507      ------------------------------------------------------------------------
508
509      ------------------------------------------------------------------------
510      -- Allocate New Context
511      --
512      --   Allocates a new context in context store and returns its address.
513      --
514      -- OUT context: address of new context
515      -- OUT done:    context could be allocated
516      --
517      -- ATTENTION: references and alters the global variables
518      --            cont.store, cont.free, cont.load
519      ------------------------------------------------------------------------
520      PROC Alloc.Context(INT16 context, BOOL done)
521
522        ----------------------------------------------------------------------
523        -- Body of Alloc.Context
524        ----------------------------------------------------------------------
525        SEQ
526
527          -- context store must not be full
528          done := cont.free > (-1)
529
530          IF
531            done
532              SEQ
533                SEQ
534                  -- take first context in free list and update load
535                  context := INT16 cont.free
536                  cont.free := INT cont.store.f[cont.free]
537                  cont.load := cont.load+1
538            TRUE
539              SKIP
540      :
541
542      ------------------------------------------------------------------------
543      -- Deallocate Context
544      --
545      --   Dellocates a context and stores it into a free list.
546      --
547      -- IN context: address of context to deallocate
548      --
549      -- ATTENTION: references and alters the global variables
550      --            cont.store, cont.free, cont.load

ADPROC.OCC                                                                 Page  11
551      ------------------------------------------------------------------------
552      PROC Dealloc.Context(VAL INT16 context)
553
554        ----------------------------------------------------------------------
555        -- Body of Dealloc.Context
556        ----------------------------------------------------------------------
557        SEQ
558          -- store context in free list and update load
559          cont.store.f[INT context] := INT32 cont.free
560          cont.free := INT context
561          cont.load := cont.load-1
562      :
563
564      ------------------------------------------------------------------------
565      -- Frame Heap
566      --
567      --   The following procedures manage the heap-organized frame store
568      --   consisting of activation frames of variable size.
569      --
570      --   For heap management, the "buddy" algorithm is used that guarantees
571      --   a very fast allocation and deallocation of frames for the price that
572      --   every frame has a length that is a power of 2. In this particular
573      --   implementation, frame sizes may vary from at least 'Free.Min' words
574      --   to at most 'Free.Max' words.
575      --
576      --   Initially, the heap is split into blocks of 'Free.Max' words.
577      --   Vice versa, the reunification of deallocated blocks with their
578      --   buddies may lead to blocks of at most 'Free.Max' size.
579      ------------------------------------------------------------------------
580
581      -------------------------------------------------------------------------
582      -- Allocate Activation Frame
583      --
584      --   Allocates a new activation frame for a specified number of
585      --   synchronization events and registers.
586      --
587      -- IN events: number of words neccessary to represent the event bits
588      -- IN regs:   number of registers required
589      -- OUT frame: address of activation frame (only if done = TRUE)
590      -- OUT done:  activation frame could be allocated
591      -------------------------------------------------------------------------
592      PROC Alloc.Frame(VAL INT events, regs, INT frame, BOOL done)
593
594        INT i:  -- auxiliary index
595        INT i0: -- index of list of best fitting blocks
596        INT l:  -- required length of frame
597        INT n:  -- actual length of block
598        INT f:  -- frame address
599
600        INT  buddy: -- index of buddy block
601        INT  next:  -- index of successor of buddy block
602
603        -----------------------------------------------------------------------
604        -- Body of Alloc.Frame
605        -----------------------------------------------------------------------

ADPROC.OCC                                                                 Page  12
606        SEQ
607
608          -- required frame length (add two result address registers)
609          l := (events+regs)+2
610
611          -- length must not be to big
612          IF
613            l > Free.Max
614              Assert(FALSE, frame.size)
615            TRUE
616              SKIP
617
618          -- find frame of appropriate size
619          i, n := 0, Free.Min
620          WHILE (i < Free.Step) AND (n < l)
621            i, n := i+1, n*2
622
623          -- find fitting and free frame
624          i0 := i
625          WHILE (i < Free.Step) AND (frame.free[i] = (-1))
626            i, n := i+1, n*2
627
628          done := (i < Free.Step) AND (frame.free[i] > (-1))
629          IF
630            done
631
632              -- free frame found
633              SEQ
634
635                -- extract block from doubly linked list
636                frame := frame.free[i]
637                f := INT frame.store[frame]
638                frame.free[i] := f
639                IF
640                  f > (-1)
641                    frame.store[f+1] := -1(INT32)
642                  TRUE
643                    SKIP
644
645                WHILE i > i0
646                  SEQ
647
648                    -- split block
649                    i := i-1
650                    n := n/2
651
652                    -- compute address of second part
653                    buddy := frame + n
654
655                    -- link second part of block in free list
656                    next := frame.free[i]
657                    frame.free[i]        := buddy
658                    frame.store[buddy]   := INT32 next
659                    frame.store[buddy+1] := -1(INT32)
660                    frame.store[buddy+2] := INT32 n

ADPROC.OCC                                                                 Page  13
661                    IF
662                      next > (-1)
663                        frame.store[next+1] := INT32 buddy
664                      TRUE
665                        SKIP
666
667                -- mark block as used (32 bits/word, addresses multiples of 8)
668                INT32     slot IS used.store[frame / 256]:
669                VAL INT32 bit  IS bits[(frame / 8) REM 32]:
670                IF
671                  (slot /\ bit) = 0(INT32)
672                    slot := slot \/ bit
673                  TRUE
674                    STOP
675
676                -- reset synchronization bits
677                SEQ i = frame FOR events
678                  frame.store[i] := 0(INT32)
679
680                -- update load
681                frame.load := frame.load+n
682                pLoad := Load(frame.load, requ.load)
683
684                -- activation frame address is address of first register
685                frame := (frame+events)+2
686
687            TRUE
688
689              -- no fitting frame available
690              SKIP
691
692      :
693
694      -------------------------------------------------------------------------
695      -- Deallocate Activation Frame
696      --
697      --   Deallocates activation frame with specified address, number of
698      --   synchronization events and registers.
699      --
700      -- IN frame:  -- address of frame (= address of first register of frame)
701      -- IN events: -- number of words reserved for synchronization events
702      -- IN regs:   -- number of registers reserved in frame
703      -------------------------------------------------------------------------
704      PROC Dealloc.Frame(VAL INT frame, events, regs)
705
706        INT  block: -- base address of block
707        INT  buddy: -- address of buddy block
708        INT  next:  -- successor of buddy
709        INT  prev:  -- predecessor of buddy
710        INT  len:   -- length of frame
711        INT  i:     -- free list index
712        INT  n:     -- length of block
713        BOOL used:  -- buddy used?
714
715        -----------------------------------------------------------------------

ADPROC.OCC                                                                 Page  14
716        -- Body of Dealloc.Frame
717        -----------------------------------------------------------------------
718        SEQ
719
720          block := (frame-2)-events -- basis address of frame
721          len   := (events+regs)+2  -- length of frame
722
723          -- check if all synchronization events really occurred
724          SEQ i = block FOR events
725            IF
726              frame.store[i] = 0(INT32)
727                SKIP
728              TRUE
729                Assert(FALSE, synch.err)
730
731          -- compute actual length of block
732          i, n := 0, Free.Min
733          WHILE (i < Free.Step) AND (n < len)
734            i, n := i+1, n*2
735
736          -- update load
737          frame.load := frame.load - n
738          pLoad := Load(frame.load, requ.load)
739
740          -- block base address of block, n its length, i index in free list
741
742          -- mark block as unused
743          INT32     slot IS used.store[block / 256]:
744          VAL INT32 bit  IS bits[(block / 8) REM 32]:
745          IF
746            (slot /\ bit) = 0(INT32)
747              STOP
748            TRUE
749              slot := slot /\ (~bit)
750
751          used := FALSE
752          WHILE NOT used
753            SEQ
754
755              -- if buddy used, block has to be linked into free list
756              IF
757                i = (Free.Step-1)
758                  -- there is no buddy any more
759                  used := TRUE
760                TRUE
761                  SEQ
762                    -- address of buddy
763                    buddy := INT ((INT32 block) >< bits[i+Free.Min.B])
764                    -- buddy used?
765                    INT32     slot IS used.store[buddy / 256]:
766                    VAL INT32 bit  IS bits[(buddy / 8) REM 32]:
767                    used := ((slot /\ bit) <> 0(INT32)) OR
768                      (frame.store[buddy+2] <> (INT32 n))
769
770              IF

ADPROC.OCC                                                                 Page  15
771                used
772                  SEQ
773
774                    -- link block into free list
775                    next := frame.free[i]
776                    frame.free[i]     := block
777                    frame.store[block] := INT32 next
778                    frame.store[block+1] := -1(INT32)
779                    frame.store[block+2] := (INT32 n)
780                    IF
781                      next > (-1)
782                        frame.store[next+1] := INT32 block
783                      TRUE
784                        SKIP
785
786                TRUE
787                  SEQ
788
789                    -- unlink buddy from free list
790                    next := INT frame.store[buddy]
791                    prev := INT frame.store[buddy+1]
792                    IF
793                      prev > (-1)
794                        frame.store[prev] := INT32 next
795                      TRUE
796                        frame.free[i] := next
797                    IF
798                      next > (-1)
799                        frame.store[next+1] := INT32 prev
800                      TRUE
801                        SKIP
802
803                    -- join block with buddy
804                    IF
805                      buddy < block
806                        block := buddy
807                      TRUE
808                        SKIP
809
810                    -- next index in free list and length of block
811                    i := i+1
812                    n := n*2
813
814      :
815
816      ------------------------------------------------------------------------
817      -- Execute Instructions
818      --
819      --   This is the core of the interpreter. It fetches the actual
820      --   instruction from the program store, splits the instruction into
821      --   its operation code and its operand word and executes the denoted
822      --   operation. Then it continues with the successor instruction and
823      --   so on. The instruction sequence is interrupted, if a synchronization
824      --   event has not yet occurred or a request to the memory unit has
825      --   been submitted.

ADPROC.OCC                                                                 Page  16
826      --
827      -- ATTENTION:
828      --
829      --   Although REFERENCES and also ALTERS the global variables defined
830      --   in the preamble of 'Execution.Unit'!
831      -------------------------------------------------------------------------
832      PROC Instruction()
833
834        INT32 frame.base:  -- the base address of the actual activation frame
835        INT32 code.base:   -- the base address of the actual code block
836        INT32 trigger:     -- the actual trigger
837        INT32 operation:   -- the actual instruction word
838        INT   instruction: -- the actual instruction address
839        BOOL  going:       -- termination variable
840
841        -----------------------------------------------------------------------
842        -- Body of Instruction
843        -----------------------------------------------------------------------
844        SEQ
845
846          -- fetch trigger from trigger store
847          Trig.Fetch(trigger)
848
849          -- split it into its components context and offset
850          [2]INT16 trig RETYPES trigger:
851          INT16 context RETYPES trig[1]:
852          INT16 offset  RETYPES trig[0]:
853
854          SEQ
855
856            -- convert context field to signed integer
857            [2]INT16 n RETYPES null:
858            n[0] := context
859
860            -- fetch activation descriptors from context store
861            frame.base := cont.store.f[INT null]
862            code.base  := cont.store.c[INT null]
863
864            -- compute actual instruction address
865            instruction := (INT code.base) + (INT offset)
866
867            going := TRUE
868            WHILE going
869              SEQ
870
871                -- fetch instruction from program store
872                operation := prog.store[instruction]
873
874                -- split instruction into its components
875                [4]BYTE op      RETYPES operation:
876                BYTE    op.code IS op[3]:
877                [3]BYTE op.word IS [op FROM 0 FOR 3]:
878
879                -- execute instruction
880                #INCLUDE "adexec.inc"

ADPROC.OCC                                                                 Page  17
881
882      :
883
884      -------------------------------------------------------------------------
885      -- Receive Messages
886      --
887      --   Receives
888      --   * value messages, stores the transmitted values in the denoted
889      ---    activation frame registers and triggers the associated
890      --     instructions,
891      --   * context messages, stores the transmitted result addresses in the
892      --     denoted activation frame and triggers the first instruction of
893      --     the associated code block,
894      --   * load reports and updates the associated load variables.
895      --
896      -- ATTENTION:
897      --
898      --   This procedure REFERENCES and ALTERS the global variables defined
899      --   in the preamble of `Execution.Unit'!
900      -------------------------------------------------------------------------
901      PROC Reception()
902
903        [Token.Size]INT32 token:    -- token value
904
905        INT32 instruct:   -- instruction
906        INT32 frame.base: -- basis address of activation frame
907        INT32 code.base:  -- basis address of code block
908        INT32 trigger:    -- instruction trigger
909
910        -----------------------------------------------------------------------
911        -- Body of Reception
912        -----------------------------------------------------------------------
913        SEQ
914
915          -- fetch message from buffer
916          demand.mess ! demand.tag
917          from.mess ? mess.load; token
918
919          IF
920            mess.load = 0
921
922              -- buffer empty, no message received
923              SKIP
924
925            TRUE
926              SEQ
927
928                -- select token type
929                [4]BYTE  toks  RETYPES token[2]:
930                BYTE     mType  IS toks[1]:              -- message type
931                [2]INT32 mData IS [token FROM 0 FOR 2]:  -- message data
932
933                CASE mType
934
935                  ------------------------------------------------------------

ADPROC.OCC                                                                 Page  18
936                  -- stop token received
937                  ------------------------------------------------------------
938                  Stop.Token
939
940                    -- empty request buffer and terminate
941                    [Token.Size]INT32 token0:   -- auxiliary token
942                    WHILE going
943                      SEQ
944                        demand.requ ! demand.tag
945                        from.requ ? mess.load; token0
946                        [4]BYTE toks RETYPES token0[2]:
947                        BYTE    type IS toks[1]:
948                        going := type <> Stop.Token
949
950                  ------------------------------------------------------------
951                  -- value message received
952                  ------------------------------------------------------------
953                  Mess.PToken
954                    SEQ
955
956                      -- split token value into components
957                      [2]INT16 toks RETYPES mData[1]:
958                      INT16 context IS toks[1]:
959                      INT16 offset  IS toks[0]:
960                      INT32 data    IS mData[0]:
961                      SEQ
962
963                        -- convert context field to signed integer
964                        [2]INT16 n RETYPES null:
965                        n[0] := context
966
967                        -- fetch base register values
968                        frame.base := cont.store.f[INT null]
969                        code.base  := cont.store.c[INT null]
970
971                        -- convert offset field to signed integer
972                        [2]INT16 n RETYPES null:
973                        n[0] := offset
974
975                        -- fetch instruction word
976                        instruct := prog.store[(INT code.base)+(INT null)]
977
978                        -- write message value into register
979                        [4]BYTE inst RETYPES instruct:
980                        BYTE    op   IS inst[1]:
981                        frame.store[(INT frame.base) + (INT op)] := data
982
983                      -- put trigger into store
984                      Trig.Put(mData[1])
985
986                  ------------------------------------------------------------
987                  -- context message received
988                  ------------------------------------------------------------
989                  Cont.PToken
990                    SEQ

ADPROC.OCC                                                                 Page  19
 991
 992                      -- split token value into components
 993                      INT32    con.off IS mData[1]:
 994                      [2]INT16 toks    RETYPES mData[0]:
 995                      [2]BYTE  tok     RETYPES toks[1]:
 996                      BYTE     mod     IS tok[1]:
 997                      INT16    context IS toks[0]:
 998
 999                      SEQ
1000
1001                        -- convert context field to signed integer
1002                        [2]INT16 n RETYPES null:
1003                        n[0] := context
1004
1005                        -- fetch description of activation
1006                        frame.base := cont.store.f[INT null]
1007                        code.base  := cont.store.c[INT null]
1008
1009                        -- store return address in activation frame
1010                        frame.store[(INT frame.base)-1] := con.off
1011                        frame.store[(INT frame.base)-2] := INT32 mod
1012
1013                        -- put trigger into store
1014                        [2]INT16 trig RETYPES trigger:
1015                        SEQ
1016                          trig[1] := context
1017                          trig[0] := 0(INT16)
1018                        Trig.Put(trigger)
1019
1020                  -------------------------------------------------------------
1021                  -- processor load message received
1022                  -------------------------------------------------------------
1023                  PLoad.PToken
1024
1025                    -- update load variables
1026                    BYTE link IS toks[0]:
1027                    INT32 fload IS mData[1]: -- frame load
1028                    INT32 rload IS mData[0]: -- request load
1029                    SEQ
1030
1031                      -- store relative processor load
1032                      proc.load[INT link] := Load(INT fload, INT rload)
1033
1034                      -- currently not used
1035                      -- proc.frame.load[link] := fload
1036                      -- proc.requ.load[link]  := rload
1037
1038      :
1039
1040      -------------------------------------------------------------------------
1041      -- Fulfill Call Requests
1042      --
1043      --   Fulfills the next call request in request store, if possible.
1044      --   Allocates activation frame and context and triggers start of
1045      --   the execution, if required.

ADPROC.OCC                                                                 Page  20
1046      --
1047      --   If a stop request is encountered, the termination variable 'going'
1048      --   is set to FALSE. By this, the Execution.Unit is reset to its
1049      --   initial state ready to receive a new program.
1050      --
1051      -- ATTENTION:
1052      --
1053      --   This procedure REFERENCES and ALTERS variables that have been
1054      --   globally defined in the preamble of Execution.Unit!
1055      -------------------------------------------------------------------------
1056      PROC Request()
1057
1058        INT32 descr:   -- function descriptor
1059        INT32 code:    -- code block address
1060        INT32 ret:     -- return address
1061        INT32 trig:    -- instruction trigger
1062        INT16 context: -- context address
1063        INT   frame:   -- activation frame address
1064        BOOL  done:    -- allocation successful
1065
1066        [Token.Size]INT32 token:  -- input token
1067        [Token.Size]INT32 token0: -- output token
1068
1069        -----------------------------------------------------------------------
1070        -- Body of Request
1071        -----------------------------------------------------------------------
1072        SEQ
1073
1074          -- fetch request from buffer
1075          demand.requ ! demand.tag
1076          from.requ ? requ.load; token
1077          token0 := token
1078
1079          -- update processor load
1080          pLoad := Load(frame.load, requ.load)
1081
1082          IF
1083
1084            requ.load = 0
1085
1086              -- buffer empty, no request received
1087              SKIP
1088
1089            TRUE
1090
1091              -- request received
1092              SEQ
1093
1094                -- select token type
1095                [4]BYTE  toks  RETYPES token[2]:
1096                BYTE     rType IS toks[1]:               -- request type
1097                [2]INT32 rData IS [token FROM 0 FOR 2]:  -- request data
1098
1099                IF

ADPROC.OCC                                                                 Page  21
1101                  -- stop request encountered
1102                  rType = Stop.Token
1103
1104                    -- empty message buffer and terminate
1105                    WHILE going
1106                      SEQ
1107                        demand.mess ! demand.tag
1108                        from.mess ? requ.load; token0
1109                        [4]BYTE toks RETYPES token0[2]:
1110                        BYTE    type IS toks[1]:
1111                        going := type <> Stop.Token
1112
1113                  -- call request encountered
1114                  TRUE
1115
1116                    -- select arguments
1117                    INT32   contoff IS rData[1]:              -- return address
1118                    [4]BYTE modfun  RETYPES rData[0]:
1119                    BYTE    mod     IS modfun[3]:             -- return module
1120                    [2]BYTE fun     IS [modfun FROM 0 FOR 2]: -- function
1121
1122                    SEQ
1123
1124                      -- construct function descriptor
1125                      descr := 0(INT32)
1126                      [4]BYTE d RETYPES descr:
1127                      [d FROM 0 FOR 2] := fun
1128
1129                      -- fetch code block address
1130                      code := prog.store[INT descr]
1131
1132                      -- fetch resource descriptors
1133                      [4]BYTE evreg  RETYPES prog.store[(INT code)-1]:
1134                      VAL INT events IS INT evreg[1]:
1135                      VAL INT regs   IS INT evreg[0]:
1136
1137                      -- Allocate context and frame
1138                      SEQ
1139                        Alloc.Context(context, done)
1140                        IF
1141                          done
1142                            SEQ
1143                              Alloc.Frame(events, regs, frame, done)
1144                              IF
1145                                done
1146                                  SKIP
1147                                TRUE
1148                                  Dealloc.Context(context)
1149                          TRUE
1150                            SKIP
1151
1152                      IF
1153
1154                        -- allocation okay
1155                        done

ADPROC.OCC                                                                 Page  22
1156
1157                          -- activate function
1158                          SEQ
1159
1160                            -- initialize context
1161                            cont.store.f[INT context] := INT32 frame
1162                            cont.store.c[INT context] := code
1163
1164                            CASE rType
1165
1166                              -- fast call
1167                              FCall.PToken
1168
1169                                -- result address is available
1170                                SEQ
1171
1172                                  -- construct result address
1173                                  [2]INT16 conts RETYPES contoff:
1174                                  [2]INT16 rets  RETYPES ret:
1175                                  SEQ
1176                                    rets[1] := conts[1]           -- context
1177                                    rets[0] := conts[0]+1(INT16)  -- offset
1178
1179                                  -- store result address
1180                                  frame.store[(INT frame)-2] := INT32 mod
1181                                  frame.store[(INT frame)-1] := ret
1182
1183                                  -- construct trigger
1184                                  [2]INT16 trigs RETYPES trig:
1185                                  SEQ
1186                                    trigs[1] := context          -- context
1187                                    trigs[0] := 0(INT16)         -- offset
1188
1189                                  -- release trigger
1190                                  Trig.Put(trig)
1191
1192                              -- tail call
1193                              TCall.PToken
1194
1195                                -- result address will be sent later
1196                                SKIP
1197
1198                              -- error in token type
1199                              ELSE
1200
1201                                Assert(FALSE, requ.err)
1202
1203                            -- return context address to caller
1204
1205                            -- construct token
1206                            [4]BYTE toks RETYPES token0[2]:
1207                            SEQ
1208                              toks[2] := mod                -- module address
1209                              toks[1] := Mess.PToken        -- message token
1210                            token0[1] := contoff            -- return address

ADPROC.OCC                                                                 Page  23
1211                            [4]BYTE toks RETYPES token0[0]:
1212                            toks[2] := act.mod              -- actual module
1213                            [2]INT16 toks RETYPES token0[0]:
1214                            toks[0] := context              -- context address
1215                            token0[0] := token0[0] << 1     -- error flag
1216
1217                            -- send token to processor unit
1218                            out ! net.token; token0
1219
1220                        -- too little memory for function activation
1221                        TRUE
1222
1223                          -- forward call request
1224                          SEQ
1225
1226                            -- enter address of minimal loaded module
1227                            [4]BYTE toks RETYPES token0[2]:
1228                            toks[2] := Min.Loaded()
1229
1230                            -- send token to processor unit
1231                            out ! net.token; token0
1232
1233      :
1234
1235      -------------------------------------------------------------------------
1236      -- Report Processor Load:
1237      --
1238      --   Tells the processors on all neighbour modules and the master
1239      --   module about the current workload of frame store and request store.
1240      --
1241      -- ATTENTION:
1242      --
1243      --   This procedure REFERENCES and ALTERS global variables defined
1244      --   in the preamble of Execution.Unit
1245      -------------------------------------------------------------------------
1246      PROC Report()
1247
1248        [Token.Size]INT32 token: -- network token
1249
1250        -----------------------------------------------------------------------
1251        -- Body of Report
1252        -----------------------------------------------------------------------
1253        SEQ
1254
1255          -- store load to be reported
1256          lLoad := pLoad
1257
1258          -- construct token
1259          [4]BYTE toks RETYPES token[2]:
1260          toks[1] := PLoad.PToken        -- processor load token
1261          token [1] := INT32 frame.load  -- frame store load
1262          token [0] := INT32 requ.load   -- request store load
1263
1264          -- send report to master
1265          [4]BYTE toks RETYPES token[2]:

ADPROC.OCC                                                                 Page  24
1266          SEQ
1267            toks[2] := 0(BYTE)           -- address of neighbour
1268            toks[0] := act.mod           -- actual module address
1269          out ! net.token; token
1270
1271          -- send report to neighbours
1272          SEQ i=0 FOR Link.Num
1273            SEQ
1274              [4]BYTE toks RETYPES token[2]:
1275              SEQ
1276                toks[2] := mod.addr[i]   -- address of neighbour
1277                toks[0] := link.addr[i]  -- unique processor idenitifier
1278              out ! net.token; token
1279
1280      :
1281
1282      -------------------------------------------------------------------------
1283      -- Body of Execution.Unit
1284      -------------------------------------------------------------------------
1285      SEQ
1286
1287        -- initialize global conversion auxiliary
1288        null := 0(INT32)
1289
1290        -- loop forever
1291        WHILE TRUE
1292          SEQ
1293
1294            -------------------------------------------------------------------
1295            -- initializations
1296            -------------------------------------------------------------------
1297
1298            -- initialize load descriptors of neighbour processors
1299            SEQ i=0 FOR Link.Num
1300              SEQ
1301                proc.load[i] := 0
1302                -- currently not used
1303                -- proc.frame.load[i], proc.requ.load[i] := 0, 0
1304
1305            -- initialize local load descriptors
1306            frame.load, cont.load, trig.load, requ.load, mess.load :=
1307              0, 0, 0, 0, 0
1308
1309            -- initialize relative processor load
1310            pLoad, lLoad := 0, 0
1311
1312            -- initialize context store heap
1313            cont.free := 0
1314            SEQ i = 0 FOR Cont.Len
1315              cont.store.f[i] := INT32(i+1)
1316            cont.store.f[Cont.Len-1] := -1(INT32)
1317
1318            -- initialize frame store heap
1319            SEQ i = 0 FOR Used.Len
1320              used.store[i] := 0(INT32)         -- no activation frame used

ADPROC.OCC                                                                 Page  25
1321            SEQ i = 0 FOR Free.Step-1
1322              frame.free[i] := -1               -- all free lists empty
1323            frame.free[Free.Step-1] := 0        --   except the last one
1324            SEQ i = 0 FOR Frame.Len / Free.Max  -- split store in frames
1325              VAL j IS i*Free.Max:              --   and link them in
1326              SEQ                               --   both directions
1327                frame.store[j]   := INT32 (j+Free.Max)
1328                frame.store[j+1] := INT32 (j-Free.Max)
1329                frame.store[j+2] := INT32 Free.Max
1330            frame.store[0+1] := -1(INT32)       -- handle bounds of frame store
1331            frame.store[Frame.Len-Free.Max] := -1(INT32)
1332
1333            -- assume start signal
1334            pos, max.pos, append := 0, 0, FALSE
1335
1336            -------------------------------------------------------------------
1337            -- receive program
1338            -------------------------------------------------------------------
1339
1340            -- receive program clusters
1341            going := TRUE
1342            WHILE going
1343              from.rout ? CASE
1344                start.cluster            -- a new program will arrive
1345                  SEQ
1346                    pos := 0
1347                    max.pos := 0
1348                    append := FALSE
1349                append.cluster           -- a program part is to be appended
1350                  SEQ                    -- store address of appended part
1351                    prog.store[0] := INT32 (max.pos+1)
1352                    pos := max.pos
1353                    append := TRUE       -- receive program cluster
1354                cluster; [prog.store FROM pos FOR Cluster.Size]
1355                  SEQ
1356                    pos := pos+Cluster.Size
1357                    Assert(pos<Prog.Len, prog.full)
1358                    IF
1359                      append
1360                        SKIP
1361                      TRUE
1362                        max.pos := max.pos+Cluster.Size
1363                end.cluster              -- end of program signalled
1364                  going := FALSE
1365
1366            -- send acknowledge signal
1367            out ! net.ack
1368
1369            -------------------------------------------------------------------
1370            -- interpretation loop
1371            -------------------------------------------------------------------
1372            going := TRUE
1373            WHILE going
1374              SEQ

ADPROC.OCC                                                                 Page  26
1376                --------------------------------------------------------------
1377                -- ATTENTION:
1378                --
1379                --   Although the following procedures are not given explicit
1380                --   arguments, they REFERENCE the variables defined in the
1381                --   preamble of procedure 'Execution.Unit' and also ALTER
1382                --   their values!
1383                --------------------------------------------------------------
1384
1385                -- choose next action
1386                Proc.Action(action)
1387
1388                CASE action
1389                  Execute.Act  -- Execute Instruction
1390                    Instruction()
1391                  Receive.Act  -- Receive Message
1392                    Reception()
1393                  Request.Act  -- Fulfill Request
1394                    Request()
1395                  Report.Act   -- Report Processor Load
1396                    Report()
1397
1398            -- send acknowledge signal
1399            out ! net.ack
1400
1401    :
1402
1403    ---------------------------------------------------------------------------
1404    -- Body of Adam.Processor
1405    ---------------------------------------------------------------------------
1406    PAR
1407
1408      -- routes incoming tokens to request or to message store,
1409      -- the incoming program to execution unit
1410      Router(in, to.mess, to.requ, from.rout, error[0])
1411
1412      -- buffers incoming processor requests and forwards them
1413      -- on demand to execution unit
1414      Buffer(to.requ, demand.requ, requ.store, from.requ, error[1])
1415
1416      -- buffers incoming processor messages and forwards them
1417      -- on demand to execution unit
1418      Buffer(to.mess, demand.mess, mess.store, from.mess, error[2])
1419
1420      -- receives program and executes it,
1421      -- handles requests and messages
1422      Execution.Unit(from.rout, from.requ, from.mess,
1423        demand.requ, demand.mess, error[3])
1424
1425      -- multiplexor for error messages
1426      INT len:               -- length of message (< Max.Debug!)
1427      [Max.Debug]BYTE mess:  -- error message
1428      ALT i=0 FOR 4
1429        error[i] ? len :: mess
1430          debug ! len :: mess

ADPROC.OCC                                                                 Page  27
1431
1432  :
1433  -----------------------------------------------------------------------------
1434  -- End of ADPROC.OCC
1435  -----------------------------------------------------------------------------

ADMODULE.OCC                                                               Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADMODULE.OCC                                           Wolfgang Schreiner
 4  -- A Module of the AdaM Network                           (c) RISC-Linz 1990
 5  --
 6  -- Connect the units within an Adam module
 7  --
 8  -----------------------------------------------------------------------------
 9
10  -----------------------------------------------------------------------------
11  -- External References
12  -----------------------------------------------------------------------------
13
14  -- include files
15  #INCLUDE "aderror.inc"  -- error messages
16  #INCLUDE "adglobal.inc" -- global constants and protocols
17  #INCLUDE "adnet.inc"    -- network constants and protocols
18
19  -- compilation units
20  #USE "adarbit.t8h"  -- arbiter unit
21  #USE "adproc.t8h"   -- processor unit
22  #USE "admemory.t8h" -- memory unit
23
24  -----------------------------------------------------------------------------
25  -- The Adam Module:
26  --
27  --   The arbiter routes an incoming network token (with respect to its
28  --   destination address) either to the processor respectively the memory
29  --   unit or forwards it to one of the neighbour modules. It is also
30  --   responsible for the correct distribution of the program before the
31  --   start of the execution.
32  --
33  --   The processor performs the actual program execution by receiving
34  --   call requests, activating the desired functions, computing the results
35  --   of this activation and delivering them to the sender of the request.
36  --
37  --   The memory holds all structured data in a heap organized store. The
38  --   processor has no direct access to this unit. It has to send requests
39  --   to the store and will then receive the desired results.
40  --
41  --   IN  act.mod:   address of actual module
42  --   IN  mod.addr:  addresses of neighbour modules
43  --   IN  link.addr: link addresses of actual module from view of neighbours
44  --   IN  route:     link addresses of all modules in the network (inc. master)
45  --   IN  boot.from: link address from which program is received
46  --   IN  boot.to:   signals to which links program is to be forwarded
47  --   IN  in:        input channels from neighbour modules
48  --   OUT out:       output channels to neighbour modules
49  -----------------------------------------------------------------------------
50  PROC Adam.Module(VAL BYTE act.mod, VAL [Link.Num]BYTE mod.addr,
51    VAL [Link.Num]BYTE link.addr, VAL [Mod.Num+1]INT route,
52    VAL INT boot.from, VAL[Link.Num]BOOL boot.to,
53    [Link.Num]CHAN OF Net.Link in, out)
54
55    CHAN OF Net.Link from.proc, from.mem: -- arbiter channels from local units

ADMODULE.OCC                                                               Page   2
56    CHAN OF Mod.Link to.proc, to.mem:     -- arbiter channels to local units
57
58    [3]CHAN OF Debug debug: -- debug channels
59
60    ---------------------------------------------------------------------------
61    -- Body of Adam.Module
62    ---------------------------------------------------------------------------
63    PAR
64
65      -- the arbiter
66      Adam.Arbiter(act.mod, boot.from, boot.to, route,
67        in, out, from.proc, to.proc, from.mem, to.mem, debug[0])
68
69      -- the processor unit
70      Adam.Processor(act.mod, mod.addr, link.addr,
71        to.proc, from.proc, debug[1])
72
73      -- the memory unit
74      Adam.Memory(act.mod, mod.addr, link.addr,
75        to.mem, from.mem, debug[2])
76
77      -- debug process
78      INT len:
79      [80]BYTE error:
80      ALT i=0 FOR 3
81        debug[i] ? len :: error
82          STOP
83
84  :
85
86  -----------------------------------------------------------------------------
87  -- End of ADMODULE.OCC
88  -----------------------------------------------------------------------------

ADARBIT.INC                                                                Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADARBIT.INC                                            Wolfgang Schreiner
 4  -- Arbiter Constants and Protocols                        (c) RISC-Linz 1990
 5  --
 6  -- Defines constants and protocols that are used in the arbiter
 7  -- of the Adam module.
 8  --
 9  -----------------------------------------------------------------------------
10
11  --constants
12  VAL INT Arbit.Size IS 5000: -- length of arbiter buffer in tokens
13
14  -- protocols
15  PROTOCOL Permit
16    CASE
17      write.permit           -- write permission signal
18  :
19
20  -----------------------------------------------------------------------------
21  -- End of ADARBIT.INC
22  -----------------------------------------------------------------------------

ADEXEC.INC                                                                 Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADEXEC.INC                                             Wolfgang Schreiner
 4  -- Execution of Adam Machine Code                         (c) RISC-Linz 1990
 5  --
 6  -- Contains the piece of code that interprets the various instructions of
 7  -- the ADAM machine language.
 8  --
 9  -- For reasons of efficiency, this piece of code is textually included into
10  -- the procedure `Instruction' (file "ADPROC.OCC") and not compiled as a
11  -- seperate compilation unit.
12  --
13  -----------------------------------------------------------------------------
14
15  -----------------------------------------------------------------------------
16  -- Integer Absolute Value (only works for x values > MOSTNEG!)
17  -----------------------------------------------------------------------------
18  INT FUNCTION IABS(VAL INT x)
19    INT y:
20    VALOF
21      IF
22        x < 0
23          y := - x  -- no overflow, since only called for x > MOSTNEG
24        TRUE
25          y := x
26      RESULT y
27  :
28
29  -----------------------------------------------------------------------------
30  -- Select Instruction
31  -----------------------------------------------------------------------------
32  CASE op.code
33
34
35    ---------------------------------------------------------------------------
36    -- Instruction ADD
37    ---------------------------------------------------------------------------
38    Add.OpC
39
40      -- components of operand word
41      BYTE regL IS op.word[2]: -- left register operand
42      BYTE regR IS op.word[1]: -- right register operand
43      BYTE succ IS op.word[0]: -- successor instruction
44
45      -- local variables
46      INT   opL, opR:   -- operand adresses
47      INT32 infL, infR: -- operand values
48      INT   datL, datR: -- data parts of operands
49      BOOL  errL, errR: -- error flags of operands
50      INT32 result:     -- result of operation
51
52      SEQ -- instruction ADD
53
54        -- select operands
55        opL,  opR  := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)

ADEXEC.INC                                                                 Page   2
56        infL, infR := frame.store[opL], frame.store[opR]
57        datL, datR := ASHIFTRIGHT(INT infL, 1), ASHIFTRIGHT(INT infR, 1)
58        errL, errR := BOOL(infL /\ 1(INT32)), BOOL(infR /\ 1(INT32))
59
60        -- compute results
61        IF
62          errL
63            -- left operand invalid, keep error code
64            result := infL
65          errR
66            -- right operand invalid, keep error code
67            result := infR
68          TRUE
69            SEQ
70              -- add values (no overflow may occur!)
71              result := INT32 (datL + datR)
72              IF
73                (result < Min.Int) OR (result > Max.Int)
74                  -- integer overflow
75                  result := Error.Overflow
76                TRUE
77                  -- generate error bit 0
78                  result := result << 1
79
80        -- store results
81        frame.store[opL] := result
82
83        -- go to successor instruction
84        instruction := instruction + (INT succ)
85
86    ---------------------------------------------------------------------------
87    -- Instruction ADDC
88    ---------------------------------------------------------------------------
89    AddC.OpC
90
91      -- components of operand word
92      BYTE con  IS op.word[2]: -- constant operand
93      BYTE reg  IS op.word[1]: -- register operand
94      BYTE succ IS op.word[0]: -- successor instruction
95
96      -- local variables
97      INT   op:     -- operand adress
98      INT32 inf:    -- operand value
99      INT   dat:    -- data parts of operand
100      BOOL  err:    -- error flag of operand
101      INT32 result: -- result of operation
102      INT   scon:   -- signed constant operand
103
104      SEQ -- instruction ADDC
105
106        -- select register operand
107        op  := (INT frame.base)+(INT reg)
108        inf := frame.store[op]
109        dat := ASHIFTRIGHT(INT inf, 1)
110        err := BOOL(inf /\ 1(INT32))

ADEXEC.INC                                                                 Page   3
111
112        -- compute results
113        IF
114          err
115            -- operand invalid, keep error code
116            result := inf
117          TRUE
118            SEQ
119              -- sign constant
120              IF
121                con > (BYTE Max.Con)
122                  scon := (INT con) - Range.Con
123                TRUE
124                  scon := INT con
125              -- add values (no overflow may occur!)
126              result := INT32 (dat + scon)
127              IF
128                (result < Min.Int) OR (result > Max.Int)
129                  -- integer overflow
130                  result := Error.Overflow
131                TRUE
132                  -- generate error bit 0
133                  result := result << 1
134
135        -- store results
136        frame.store[op] := result
137
138        -- go to successor instruction
139        instruction := instruction + (INT succ)
140
141
142    ---------------------------------------------------------------------------
143    -- Instruction SUB
144    ---------------------------------------------------------------------------
145    Sub.OpC
146
147      -- components of operand word
148      BYTE regL IS op.word[2]: -- left register operand
149      BYTE regR IS op.word[1]: -- right register operand
150      BYTE succ IS op.word[0]: -- successor instruction
151
152      -- local variables
153      INT   opL, opR:   -- operand adresses
154      INT32 infL, infR: -- operand values
155      INT   datL, datR: -- data parts of operands
156      BOOL  errL, errR: -- error flags of operands
157      INT32 result:     -- result of operation
158
159      SEQ -- instruction SUB
160
161        -- select operands
162        opL,  opR  := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
163        infL, infR := frame.store[opL], frame.store[opR]
164        datL, datR := ASHIFTRIGHT(INT infL, 1), ASHIFTRIGHT(INT infR, 1)
165        errL, errR := BOOL(infL /\ 1(INT32)), BOOL(infR /\ 1(INT32))

ADEXEC.INC                                                                 Page   4
166
167        -- compute results
168        IF
169          errL
170            -- left operand invalid, keep error code
171            result := infL
172          errR
173            -- right operand invalid, keep error code
174            result := infR
175          TRUE
176            SEQ
177              -- subtract values (no overflow may occur!)
178              result := INT32 (datL - datR)
179              IF
180                (result < Min.Int) OR (result > Max.Int)
181                  -- integer overflow
182                  result := Error.Overflow
183                TRUE
184                  -- generate error bit 0
185                  result := result << 1
186
187        -- store results
188        frame.store[opL] := result
189
190        -- go to successor instruction
191        instruction := instruction + (INT succ)
192
193
194    ---------------------------------------------------------------------------
195    -- Instruction SUBC
196    ---------------------------------------------------------------------------
197    SubC.OpC
198
199      -- components of operand word
200      BYTE con  IS op.word[2]: -- constant operand
201      BYTE reg  IS op.word[1]: -- register operand
202      BYTE succ IS op.word[0]: -- successor instruction
203
204      -- local variables
205      INT   op:     -- operand adress
206      INT32 inf:    -- operand value
207      INT   dat:    -- data parts of operand
208      BOOL  err:    -- error flag of operand
209      INT   scon:   -- signed constant operand
210      INT32 result: -- result of operation
211
212      SEQ -- instruction SUBC
213
214        -- select register operand
215        op  := (INT frame.base)+(INT reg)
216        inf := frame.store[op]
217        dat := ASHIFTRIGHT(INT inf, 1)
218        err := BOOL(inf /\ 1(INT32))
219
220        -- compute results

ADEXEC.INC                                                                 Page   5
221        IF
222          err
223            -- operand invalid, keep error code
224            result := inf
225          TRUE
226            SEQ
227              -- sign constant
228              IF
229                con > (BYTE Max.Con)
230                  scon := (INT con) - Range.Con
231                TRUE
232                  scon := INT con
233              -- subtract values (no overflow may occur!)
234              result := INT32 (dat - scon)
235              IF
236                (result < Min.Int) OR (result > Max.Int)
237                  -- integer overflow
238                  result := Error.Overflow
239                TRUE
240                  -- generate error bit 0
241                  result := result << 1
242
243        -- store results
244        frame.store[op] := result
245
246        -- go to successor instruction
247        instruction := instruction + (INT succ)
248
249
250    ---------------------------------------------------------------------------
251    -- Instruction SUBRC
252    ---------------------------------------------------------------------------
253    SubRC.OpC
254
255      -- components of operand word
256      BYTE con  IS op.word[2]: -- constant operand
257      BYTE reg  IS op.word[1]: -- register operand
258      BYTE succ IS op.word[0]: -- successor instruction
259
260      -- local variables
261      INT   op:     -- operand adress
262      INT32 inf:    -- operand value
263      INT   dat:    -- data parts of operand
264      BOOL  err:    -- error flag of operand
265      INT   scon:   -- signed constant operand
266      INT32 result: -- result of operation
267
268      SEQ -- instruction SUBRC
269
270        -- select register operand
271        op  := (INT frame.base)+(INT reg)
272        inf := frame.store[op]
273        dat := ASHIFTRIGHT(INT inf, 1)
274        err := BOOL(inf /\ 1(INT32))

ADEXEC.INC                                                                 Page   6
276        -- compute results
277        IF
278          err
279            -- operand invalid, keep error code
280            result := inf
281          TRUE
282            SEQ
283              -- sign constant
284              IF
285                con > (BYTE Max.Con)
286                  scon := (INT con) - Range.Con
287                TRUE
288                  scon := INT con
289              -- subtract values (no overflow may occur!)
290              result := INT32 (scon - dat)
291              IF
292                (result < Min.Int) OR (result > Max.Int)
293                  -- integer overflow
294                  result := Error.Overflow
295                TRUE
296                  -- generate error bit 0
297                  result := result << 1
298
299        -- store results
300        frame.store[op] := result
301
302        -- go to successor instruction
303        instruction := instruction + (INT succ)
304
305
306    ---------------------------------------------------------------------------
307    -- Instruction MUL
308    ---------------------------------------------------------------------------
309    Mul.OpC
310
311      -- components of operand word
312      BYTE regL IS op.word[2]: -- left register operand
313      BYTE regR IS op.word[1]: -- right register operand
314      BYTE succ IS op.word[0]: -- successor instruction
315
316      -- local variables
317      INT   opL, opR:   -- operand adresses
318      INT32 infL, infR: -- operand values
319      INT   datL, datR: -- data parts of operands
320      BOOL  errL, errR: -- error flags of operands
321      INT   high, low:  -- result words of multiplication
322      INT32 result:     -- result of operation
323
324      SEQ -- instruction MUL
325
326        -- select operands
327        opL,  opR  := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
328        infL, infR := frame.store[opL], frame.store[opR]
329        datL, datR := ASHIFTRIGHT(INT infL, 1), ASHIFTRIGHT(INT infR, 1)
330        errL, errR := BOOL(infL /\ 1(INT32)), BOOL(infR /\ 1(INT32))

ADEXEC.INC                                                                 Page   7
331
332        -- compute results
333        IF
334          errL
335            -- left operand invalid, keep error code
336            result := infL
337          errR
338            -- right operand invalid, keep error code
339            result := infR
340          TRUE
341            SEQ
342              -- multiply operands
343              high, low := LONGPROD(IABS(datL), IABS(datR), 0)
344              IF
345                high <> 0
346                  -- product out of range
347                  result := Error.Overflow
348                ((datL > 0) AND (datR > 0)) OR ((datL < 0) AND (datR < 0))
349                  -- product is positive
350                  IF
351                    (INT32 low) > Max.Int
352                      result := Error.Overflow
353                    TRUE
354                      result := (INT32 low) << 1
355                TRUE
356                  -- product is negative
357                  IF
358                    (INT32 low) > Min.Int.Abs
359                      result := Error.Overflow
360                    TRUE
361                      result := (INT32 (- low)) << 1
362
363        -- store results
364        frame.store[opL] := result
365
366        -- go to successor instruction
367        instruction := instruction + (INT succ)
368
369
370    ---------------------------------------------------------------------------
371    -- Instruction MULC
372    ---------------------------------------------------------------------------
373    MulC.OpC
374
375      -- components of operand word
376      BYTE con  IS op.word[2]: -- constant operand
377      BYTE reg  IS op.word[1]: -- register operand
378      BYTE succ IS op.word[0]: -- successor instruction
379
380      -- local variables
381      INT   op:         -- operand adress
382      INT32 inf:        -- operand value
383      INT   dat:        -- data part of operand
384      BOOL  err:        -- error flag of operand
385      INT   scon:       -- signed constant operand

ADEXEC.INC                                                                 Page   8
386      INT   high, low:  -- result words of multiplication
387      INT32 result:     -- result of operation
388
389      SEQ -- instruction MULC
390
391        -- select operands
392        op  := (INT frame.base)+(INT reg)
393        inf := frame.store[op]
394        dat := ASHIFTRIGHT(INT inf, 1)
395        err := BOOL(inf /\ 1(INT32))
396
397        -- compute results
398        IF
399          err
400            -- operand invalid, keep error code
401            result := inf
402          TRUE
403            SEQ
404              -- sign constant
405              IF
406                con > (BYTE Max.Con)
407                  scon := (INT con) - Range.Con
408                TRUE
409                  scon := INT con
410              -- multiply operands
411              high, low := LONGPROD(IABS(dat), IABS(scon), 0)
412              IF
413                high <> 0
414                  -- product out of range
415                  result := Error.Overflow
416                ((dat > 0) AND (scon > 0)) OR ((dat < 0) AND (scon < 0))
417                  -- product is positive
418                  IF
419                    (INT32 low) > Max.Int
420                      result := Error.Overflow
421                    TRUE
422                      result := (INT32 low) << 1
423                TRUE
424                  -- product is negative
425                  IF
426                    (INT32 low) > Min.Int.Abs
427                      result := Error.Overflow
428                    TRUE
429                      result := (INT32 (- low)) << 1
430
431        -- store results
432        frame.store[op] := result
433
434        -- go to successor instruction
435        instruction := instruction + (INT succ)
436
437
438    ---------------------------------------------------------------------------
439    -- Instruction DIV
440    ---------------------------------------------------------------------------

ADEXEC.INC                                                                 Page   9
441    Div.OpC
442
443      -- components of operand word
444      BYTE regL IS op.word[2]: -- left register operand
445      BYTE regR IS op.word[1]: -- right register operand
446      BYTE succ IS op.word[0]: -- successor instruction
447
448      -- local variables
449      INT   opL, opR:   -- operand adresses
450      INT32 infL, infR: -- operand values
451      INT   datL, datR: -- data parts of operands
452      BOOL  errL, errR: -- error flags of operands
453      INT32 result:     -- result of operation
454
455      SEQ -- instruction DIV
456
457        -- select operands
458        opL,  opR  := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
459        infL, infR := frame.store[opL], frame.store[opR]
460        datL, datR := ASHIFTRIGHT(INT infL, 1), ASHIFTRIGHT(INT infR, 1)
461        errL, errR := BOOL(infL /\ 1(INT32)), BOOL(infR /\ 1(INT32))
462
463        -- compute results
464        IF
465          errL
466            -- left operand invalid, keep error code
467            result := infL
468          errR
469            -- right operand invalid, keep error code
470            result := infR
471          datR = 0
472            -- division by zero
473            result := Error.ZeroDiv
474          TRUE
475            SEQ
476              -- divide operands (no overflow may occur!)
477              result := INT32 (datL / datR)
478              IF
479                (result < Min.Int) OR (result > Max.Int)
480                  -- integer overflow
481                  result := Error.Overflow
482                TRUE
483                  -- generate error bit 0
484                  result := result << 1
485
486        -- store results
487        frame.store[opL] := result
488
489        -- go to successor instruction
490        instruction := instruction + (INT succ)
491
492
493    ---------------------------------------------------------------------------
494    -- Instruction DIVC
495    ---------------------------------------------------------------------------

ADEXEC.INC                                                                 Page  10
496    DivC.OpC
497
498      -- components of operand word
499      BYTE con  IS op.word[2]: -- constant operand
500      BYTE reg  IS op.word[1]: -- register operand
501      BYTE succ IS op.word[0]: -- successor instruction
502
503      -- local variables
504      INT   op:         -- operand adress
505      INT32 inf:        -- operand value
506      INT   dat:        -- data part of operand
507      BOOL  err:        -- error flag of operand
508      INT   scon:       -- signed constant operand
509      INT32 result:     -- result of operation
510
511      SEQ -- instruction DIVC
512
513        -- select operands
514        op  := (INT frame.base)+(INT reg)
515        inf := frame.store[op]
516        dat := ASHIFTRIGHT(INT inf, 1)
517        err := BOOL(inf /\ 1(INT32))
518
519        -- compute results
520        IF
521          err
522            -- operand invalid, keep error code
523            result := inf
524          TRUE
525            SEQ
526              -- constant must not be zero
527              Assert(con > 0(BYTE), const.zero)
528              -- sign constant
529              IF
530                con > (BYTE Max.Con)
531                  scon := (INT con) - Range.Con
532                TRUE
533                  scon := INT con
534              -- divide operands (no overflow may occur!)
535              result := INT32 (dat / scon)
536              IF
537                (result > Max.Int)
538                  -- integer overflow (can only happen in divisions by -1)
539                  result := Error.Overflow
540                TRUE
541                  -- generate error bit 0
542                  result := result << 1
543
544        -- store results
545        frame.store[op] := result
546
547        -- go to successor instruction
548        instruction := instruction + (INT succ)
549

ADEXEC.INC                                                                 Page  11
551    ---------------------------------------------------------------------------
552    -- Instruction DIVRC
553    ---------------------------------------------------------------------------
554    DivRC.OpC
555
556      -- components of operand word
557      BYTE con  IS op.word[2]: -- constant operand
558      BYTE reg  IS op.word[1]: -- register operand
559      BYTE succ IS op.word[0]: -- successor instruction
560
561      -- local variables
562      INT   op:         -- operand adress
563      INT32 inf:        -- operand value
564      INT   dat:        -- data part of operand
565      BOOL  err:        -- error flag of operand
566      INT   scon:       -- signed constant operand
567      INT32 result:     -- result of operation
568
569      SEQ -- instruction DIVRC
570
571        -- select operands
572        op  := (INT frame.base)+(INT reg)
573        inf := frame.store[op]
574        dat := ASHIFTRIGHT(INT inf, 1)
575        err := BOOL(inf /\ 1(INT32))
576
577        -- compute results
578        IF
579          err
580            -- operand invalid, keep error code
581            result := inf
582          dat = 0
583            -- division by zero
584            result := Error.ZeroDiv
585          TRUE
586            SEQ
587              -- sign constant
588              IF
589                con > (BYTE Max.Con)
590                  scon := (INT con) - Range.Con
591                TRUE
592                  scon := INT con
593              -- divide operands (no overflow may occur!)
594              result := INT32 (scon / dat)
595              -- no integer overflow may occur
596              -- generate error bit 0
597              result := result << 1
598
599        -- store results
600        frame.store[op] := result
601
602        -- go to successor instruction
603        instruction := instruction + (INT succ)
604

ADEXEC.INC                                                                 Page  12
606    ---------------------------------------------------------------------------
607    -- Instruction MOD
608    ---------------------------------------------------------------------------
609    Mod.OpC
610
611      -- components of operand word
612      BYTE regL IS op.word[2]: -- left register operand
613      BYTE regR IS op.word[1]: -- right register operand
614      BYTE succ IS op.word[0]: -- successor instruction
615
616      -- local variables
617      INT   opL, opR:   -- operand adresses
618      INT32 infL, infR: -- operand values
619      INT   datL, datR: -- data parts of operands
620      BOOL  errL, errR: -- error flags of operands
621      INT32 result:     -- result of operation
622
623      SEQ -- instruction MOD
624
625        -- select operands
626        opL,  opR  := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
627        infL, infR := frame.store[opL], frame.store[opR]
628        datL, datR := ASHIFTRIGHT(INT infL, 1), ASHIFTRIGHT(INT infR, 1)
629        errL, errR := BOOL(infL /\ 1(INT32)), BOOL(infR /\ 1(INT32))
630
631        -- compute results
632        IF
633          errL
634            -- left operand invalid, keep error code
635            result := infL
636          errR
637            -- right operand invalid, keep error code
638            result := infR
639          datR = 0
640            -- remainder modulo zero
641            result := Error.ZeroDiv
642          TRUE
643            -- compute remainder and generate error bit 0
644            result := (INT32 (infL REM infR)) << 1
645
646        -- store results
647        frame.store[opL] := result
648
649        -- go to successor instruction
650        instruction := instruction + (INT succ)
651
652
653    ---------------------------------------------------------------------------
654    -- Instruction MODC
655    ---------------------------------------------------------------------------
656    ModC.OpC
657
658      -- components of operand word
659      BYTE con  IS op.word[2]: -- constant operand
660      BYTE reg  IS op.word[1]: -- register operand

ADEXEC.INC                                                                 Page  13
661      BYTE succ IS op.word[0]: -- successor instruction
662
663      -- local variables
664      INT   op:     -- operand adress
665      INT32 inf:    -- operand value
666      INT   dat:    -- data part of operand
667      BOOL  err:    -- error flag of operand
668      INT   scon:   -- signed constant operand
669      INT32 result: -- result of operation
670
671      SEQ -- instruction MODC
672
673        -- select operands
674        op  := (INT frame.base)+(INT reg)
675        inf := frame.store[op]
676        dat := ASHIFTRIGHT(INT inf, 1)
677        err := BOOL(inf /\ 1(INT32))
678
679        -- compute results
680        IF
681          err
682            -- register operand invalid, keep error code
683            result := inf
684          TRUE
685            SEQ
686              -- constant must not be zero
687              Assert(con > 0(BYTE), const.zero)
688              -- sign constant
689              IF
690                con > (BYTE Max.Con)
691                  scon := (INT con) - Range.Con
692                TRUE
693                  scon := INT con
694              -- compute remainder and generate error bit 0
695              result := (INT32 (dat REM scon)) << 1
696
697        -- store results
698        frame.store[op] := result
699
700        -- go to successor instruction
701        instruction := instruction + (INT succ)
702
703
704    ---------------------------------------------------------------------------
705    -- Instruction MODRC
706    ---------------------------------------------------------------------------
707    ModRC.OpC
708
709      -- components of operand word
710      BYTE con  IS op.word[2]: -- constant operand
711      BYTE reg  IS op.word[1]: -- register operand
712      BYTE succ IS op.word[0]: -- successor instruction
713
714      -- local variables
715      INT   op:     -- operand adress

ADEXEC.INC                                                                 Page  14
716      INT32 inf:    -- operand value
717      INT   dat:    -- data part of operand
718      BOOL  err:    -- error flag of operand
719      INT   scon:   -- signed constant operand
720      INT32 result: -- result of operation
721
722      SEQ -- instruction MODRC
723
724        -- select operands
725        op  := (INT frame.base)+(INT reg)
726        inf := frame.store[op]
727        dat := ASHIFTRIGHT(INT inf, 1)
728        err := BOOL(inf /\ 1(INT32))
729
730        -- compute results
731        IF
732          err
733            -- register operand invalid, keep error code
734            result := inf
735          dat = 0
736            -- remainder by zero
737            result := Error.ZeroDiv
738          TRUE
739            SEQ
740              -- sign constant
741              IF
742                con > (BYTE Max.Con)
743                  scon := (INT con) - Range.Con
744                TRUE
745                  scon := INT con
746              -- compute remainder and generate error bit 0
747              result := (INT32 (scon REM dat)) << 1
748
749        -- store results
750        frame.store[op] := result
751
752        -- go to successor instruction
753        instruction := instruction + (INT succ)
754
755
756    ---------------------------------------------------------------------------
757    -- Instruction AND
758    ---------------------------------------------------------------------------
759    And.OpC
760
761      -- components of operand word
762      BYTE regL IS op.word[2]: -- left register operand
763      BYTE regR IS op.word[1]: -- right register operand
764      BYTE succ IS op.word[0]: -- successor instruction
765
766      -- local variables
767      INT   opL, opR:   -- operand adresses
768      INT32 infL, infR: -- operand values
769      INT   datL, datR: -- data parts of operands
770      BOOL  errL, errR: -- error flags of operands

ADEXEC.INC                                                                 Page  15
771      INT32 result:     -- result of operation
772
773      SEQ -- instruction AND
774
775        -- select operands
776        opL,  opR  := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
777        infL, infR := frame.store[opL], frame.store[opR]
778        datL, datR := ASHIFTRIGHT(INT infL, 1), ASHIFTRIGHT(INT infR, 1)
779        errL, errR := BOOL(infL /\ 1(INT32)), BOOL(infR /\ 1(INT32))
780
781        -- compute results
782        IF
783          errL
784            -- left operand invalid, keep error code
785            result := infL
786          errR
787            -- right operand invalid, keep error code
788            result := infR
789          TRUE
790            -- compute conjunction and generate error bit 0
791            result := (INT32 (datL /\ datR)) << 1
792
793        -- store results
794        frame.store[opL] := result
795
796        -- go to successor instruction
797        instruction := instruction + (INT succ)
798
799
800    ---------------------------------------------------------------------------
801    -- Instruction OR
802    ---------------------------------------------------------------------------
803    Or.OpC
804
805      -- components of operand word
806      BYTE regL IS op.word[2]: -- left register operand
807      BYTE regR IS op.word[1]: -- right register operand
808      BYTE succ IS op.word[0]: -- successor instruction
809
810      -- local variables
811      INT   opL, opR:   -- operand adresses
812      INT32 infL, infR: -- operand values
813      INT   datL, datR: -- data parts of operands
814      BOOL  errL, errR: -- error flags of operands
815      INT32 result:     -- result of operation
816
817      SEQ -- instruction OR
818
819        -- select operands
820        opL,  opR  := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
821        infL, infR := frame.store[opL], frame.store[opR]
822        datL, datR := ASHIFTRIGHT(INT infL, 1), ASHIFTRIGHT(INT infR, 1)
823        errL, errR := BOOL(infL /\ 1(INT32)), BOOL(infR /\ 1(INT32))
824
825        -- compute results

ADEXEC.INC                                                                 Page  16
826        IF
827          errL
828            -- left operand invalid, keep error code
829            result := infL
830          errR
831            -- right operand invalid, keep error code
832            result := infR
833          TRUE
834            -- compute disjunction and generate error bit 0
835            result := (INT32 (datL \/ datR)) << 1
836
837        -- store results
838        frame.store[opL] := result
839
840        -- go to successor instruction
841        instruction := instruction + (INT succ)
842
843
844    ---------------------------------------------------------------------------
845    -- Instruction EQUAL
846    ---------------------------------------------------------------------------
847    Equal.OpC -- Instruction EQUAL
848
849      -- components of operand word
850      BYTE regL IS op.word[2]: -- left register operand
851      BYTE regR IS op.word[1]: -- right register operand
852      BYTE succ IS op.word[0]: -- successor instruction
853
854      -- local variables
855      INT   opL, opR:   -- operand adresses
856      INT32 infL, infR: -- operand values
857      INT   datL, datR: -- data parts of operands
858      BOOL  errL, errR: -- error flags of operands
859      INT32 result:     -- result of operation
860
861      SEQ
862
863        -- select operands
864        opL,  opR  := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
865        infL, infR := frame.store[opL], frame.store[opR]
866        datL, datR := ASHIFTRIGHT(INT infL, 1), ASHIFTRIGHT(INT infR, 1)
867        errL, errR := BOOL(infL /\ 1(INT32)), BOOL(infR /\ 1(INT32))
868
869        -- compute results
870        IF
871          errL
872            -- left operand invalid, keep error code
873            result := infL
874          errR
875            -- right operand invalid, keep error code
876            result := infR
877          TRUE
878            -- test equality and generate error bit 0
879            result := (INT32 (datL = datR)) << 1

ADEXEC.INC                                                                 Page  17
881        -- store results
882        frame.store[opL] := result
883
884        -- go to successor instruction
885        instruction := instruction + (INT succ)
886
887
888    ---------------------------------------------------------------------------
889    -- Instruction EQUALC
890    ---------------------------------------------------------------------------
891    EqualC.OpC -- Instruction EQUALC
892
893      -- components of operand word
894      BYTE con  IS op.word[2]: -- constant operand
895      BYTE reg  IS op.word[1]: -- register operand
896      BYTE succ IS op.word[0]: -- successor instruction
897
898      -- local variables
899      INT   op:     -- operand adress
900      INT32 inf:    -- operand value
901      INT   dat:    -- data part of operand
902      BOOL  err:    -- error flag of operand
903      INT   scon:   -- signed constant operand
904      INT32 result: -- result of operation
905
906      SEQ
907
908        -- select operands
909        op  := (INT frame.base)+(INT reg)
910        inf := frame.store[op]
911        dat := ASHIFTRIGHT(INT inf, 1)
912        err := BOOL(inf /\ 1(INT32))
913
914        -- compute results
915        IF
916          err
917            -- register operand invalid, keep error code
918            result := inf
919          TRUE
920            SEQ
921              -- sign constant
922              IF
923                con > (BYTE Max.Con)
924                  scon := (INT con) - Range.Con
925                TRUE
926                  scon := INT con
927              -- test equality and generate error bit 0
928              result := (INT32 (dat = scon)) << 1
929
930        -- store results
931        frame.store[op] := result
932
933        -- go to successor instruction
934        instruction := instruction + (INT succ)

ADEXEC.INC                                                                 Page  18
936
937    ---------------------------------------------------------------------------
938    -- Instruction LESS
939    ---------------------------------------------------------------------------
940    Less.OpC
941
942      -- components of operand word
943      BYTE regL IS op.word[2]: -- left register operand
944      BYTE regR IS op.word[1]: -- right register operand
945      BYTE succ IS op.word[0]: -- successor instruction
946
947      -- local variables
948      INT   opL, opR:   -- operand adresses
949      INT32 infL, infR: -- operand values
950      INT   datL, datR: -- data parts of operands
951      BOOL  errL, errR: -- error flags of operands
952      INT32 result:     -- result of operation
953
954      SEQ -- instruction LESS
955
956        -- select operands
957        opL,  opR  := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
958        infL, infR := frame.store[opL], frame.store[opR]
959        datL, datR := ASHIFTRIGHT(INT infL, 1), ASHIFTRIGHT(INT infR, 1)
960        errL, errR := BOOL(infL /\ 1(INT32)), BOOL(infR /\ 1(INT32))
961
962        -- compute results
963        IF
964          errL
965            -- left operand invalid, keep error code
966            result := infL
967          errR
968            -- right operand invalid, keep error code
969            result := infR
970          TRUE
971            -- test order and generate error bit 0
972            result := (INT32 (datL < datR)) << 1
973
974        -- store results
975        frame.store[opL] := result
976
977        -- go to successor instruction
978        instruction := instruction + (INT succ)
979
980
981    ---------------------------------------------------------------------------
982    -- Instruction LESSC
983    ---------------------------------------------------------------------------
984    LessC.OpC
985
986       -- components of operand word
987      BYTE con  IS op.word[2]: -- constant operand
988      BYTE reg  IS op.word[1]: -- register operand
989      BYTE succ IS op.word[0]: -- successor instruction

ADEXEC.INC                                                                 Page  19
 991      -- local variables
 992      INT   op:     -- operand adress
 993      INT32 inf:    -- operand value
 994      INT   dat:    -- data part of operand
 995      BOOL  err:    -- error flag of operand
 996      INT   scon:   -- signed constant operand
 997      INT32 result: -- result of operation
 998
 999      SEQ -- instruction LESSC
1000
1001        -- select operands
1002        op  := (INT frame.base)+(INT reg)
1003        inf := frame.store[op]
1004        dat := ASHIFTRIGHT(INT inf, 1)
1005        err := BOOL(inf /\ 1(INT32))
1006
1007        -- compute results
1008        IF
1009          err
1010            -- register operand invalid, keep error code
1011            result := inf
1012          TRUE
1013            SEQ
1014              -- sign constant
1015              IF
1016                con > (BYTE Max.Con)
1017                  scon := (INT con) - Range.Con
1018                TRUE
1019                  scon := INT con
1020              -- test order and generate error bit 0
1021              result := (INT32 (dat < scon)) << 1
1022
1023        -- store results
1024        frame.store[op] := result
1025
1026        -- go to successor instruction
1027        instruction := instruction + (INT succ)
1028
1029
1030    ---------------------------------------------------------------------------
1031    -- Instruction LESSRC
1032    ---------------------------------------------------------------------------
1033    LessRC.OpC
1034
1035       -- components of operand word
1036      BYTE con  IS op.word[2]: -- constant operand
1037      BYTE reg  IS op.word[1]: -- register operand
1038      BYTE succ IS op.word[0]: -- successor instruction
1039
1040      -- local variables
1041      INT   op:     -- operand adress
1042      INT32 inf:    -- operand value
1043      INT   dat:    -- data part of operand
1044      BOOL  err:    -- error flag of operand
1045      INT   scon:   -- signed constant operand

ADEXEC.INC                                                                 Page  20
1046      INT32 result: -- result of operation
1047
1048      SEQ -- instruction LESSRC
1049
1050        -- select operands
1051        op  := (INT frame.base)+(INT reg)
1052        inf := frame.store[op]
1053        dat := ASHIFTRIGHT(INT inf, 1)
1054        err := BOOL(inf /\ 1(INT32))
1055
1056        -- compute results
1057        IF
1058          err
1059            -- register operand invalid, keep error code
1060            result := inf
1061          TRUE
1062            SEQ
1063              -- sign constant
1064              IF
1065                con > (BYTE Max.Con)
1066                  scon := (INT con) - Range.Con
1067                TRUE
1068                  scon := INT con
1069              -- test order and generate error bit 0
1070              result := (INT32 (scon < dat)) << 1
1071
1072        -- store results
1073        frame.store[op] := result
1074
1075        -- go to successor instruction
1076        instruction := instruction + (INT succ)
1077
1078
1079    ---------------------------------------------------------------------------
1080    -- Instruction LESSEQ
1081    ---------------------------------------------------------------------------
1082    LessEq.OpC
1083
1084      -- components of operand word
1085      BYTE regL IS op.word[2]: -- left register operand
1086      BYTE regR IS op.word[1]: -- right register operand
1087      BYTE succ IS op.word[0]: -- successor instruction
1088
1089      -- local variables
1090      INT   opL, opR:   -- operand adresses
1091      INT32 infL, infR: -- operand values
1092      INT   datL, datR: -- data parts of operands
1093      BOOL  errL, errR: -- error flags of operands
1094      INT32 result:     -- result of operation
1095
1096      SEQ -- instruction LESSEQ
1097
1098        -- select operands
1099        opL,  opR  := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
1100        infL, infR := frame.store[opL], frame.store[opR]

ADEXEC.INC                                                                 Page  21
1101        datL, datR := ASHIFTRIGHT(INT infL, 1), ASHIFTRIGHT(INT infR, 1)
1102        errL, errR := BOOL(infL /\ 1(INT32)), BOOL(infR /\ 1(INT32))
1103
1104        -- compute results
1105        IF
1106          errL
1107            -- left operand invalid, keep error code
1108            result := infL
1109          errR
1110            -- right operand invalid, keep error code
1111            result := infR
1112          TRUE
1113            -- test order and generate error bit 0
1114            result := (INT32 (datL <= datR)) << 1
1115
1116        -- store results
1117        frame.store[opL] := result
1118
1119        -- go to successor instruction
1120        instruction := instruction + (INT succ)
1121
1122
1123    ---------------------------------------------------------------------------
1124    -- Instruction LESSEQC
1125    ---------------------------------------------------------------------------
1126    LessEqC.OpC
1127
1128      -- components of operand word
1129      BYTE con  IS op.word[2]: -- constant operand
1130      BYTE reg  IS op.word[1]: -- register operand
1131      BYTE succ IS op.word[0]: -- successor instruction
1132
1133      -- local variables
1134      INT   op:     -- operand adress
1135      INT32 inf:    -- operand value
1136      INT   dat:    -- data part of operand
1137      BOOL  err:    -- error flag of operand
1138      INT   scon:   -- signed constant operand
1139      INT32 result: -- result of operation
1140
1141      SEQ -- instruction LESSEQC
1142
1143        -- select operands
1144        op  := (INT frame.base)+(INT reg)
1145        inf := frame.store[op]
1146        dat := ASHIFTRIGHT(INT inf, 1)
1147        err := BOOL(inf /\ 1(INT32))
1148
1149        -- compute results
1150        IF
1151          err
1152            -- register operand invalid, keep error code
1153            result := inf
1154          TRUE
1155            SEQ

ADEXEC.INC                                                                 Page  22
1156              -- sign constant
1157              IF
1158                con > (BYTE Max.Con)
1159                  scon := (INT con) - Range.Con
1160                TRUE
1161                  scon := INT con
1162              -- test order and generate error bit 0
1163              result := (INT32 (dat <= scon)) << 1
1164
1165        -- store results
1166        frame.store[op] := result
1167
1168        -- go to successor instruction
1169        instruction := instruction + (INT succ)
1170
1171
1172    ---------------------------------------------------------------------------
1173    -- Instruction LESSEQRC
1174    ---------------------------------------------------------------------------
1175    LessEqRC.OpC
1176
1177      -- components of operand word
1178      BYTE con  IS op.word[2]: -- constant operand
1179      BYTE reg  IS op.word[1]: -- register operand
1180      BYTE succ IS op.word[0]: -- successor instruction
1181
1182      -- local variables
1183      INT   op:     -- operand adress
1184      INT32 inf:    -- operand value
1185      INT   dat:    -- data part of operand
1186      BOOL  err:    -- error flag of operand
1187      INT   scon:   -- signed constant operand
1188      INT32 result: -- result of operation
1189
1190      SEQ -- instruction LESSEQRC
1191
1192        -- select operands
1193        op  := (INT frame.base)+(INT reg)
1194        inf := frame.store[op]
1195        dat := ASHIFTRIGHT(INT inf, 1)
1196        err := BOOL(inf /\ 1(INT32))
1197
1198        -- compute results
1199        IF
1200          err
1201            -- register operand invalid, keep error code
1202            result := inf
1203          TRUE
1204            SEQ
1205              -- sign constant
1206              IF
1207                con > (BYTE Max.Con)
1208                  scon := (INT con) - Range.Con
1209                TRUE
1210                  scon := INT con

ADEXEC.INC                                                                 Page  23
1211              -- test order and generate error bit 0
1212              result := (INT32 (scon <= dat)) << 1
1213
1214        -- store results
1215        frame.store[op] := result
1216
1217        -- go to successor instruction
1218        instruction := instruction + (INT succ)
1219
1220
1221    ---------------------------------------------------------------------------
1222    -- Instruction NEG
1223    ---------------------------------------------------------------------------
1224    Neg.OpC
1225
1226      -- components of operand word
1227      BYTE regL IS op.word[2]: -- left register operand
1228      BYTE regR IS op.word[1]: -- right register operand
1229      BYTE succ IS op.word[0]: -- successor instruction
1230
1231      -- local variables
1232      INT   opL, opR: -- operand adresses
1233      INT32 infR:     -- value of right operand
1234      INT   datR:     -- data part of right operand
1235      BOOL  errR:     -- error flag of right operands
1236      INT32 result:   -- result of operation
1237
1238      SEQ -- instruction NEG
1239
1240        -- select operands
1241        opL, opR := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
1242        infR     := frame.store[opR]
1243        datR     := ASHIFTRIGHT(INT infR, 1)
1244        errR     := BOOL(infR /\ 1(INT32))
1245
1246        -- compute results
1247        IF
1248          errR
1249            -- right operand invalid, keep error code
1250            result := infR
1251          TRUE
1252            SEQ
1253              -- invert sign
1254              result := INT32 (- datR)
1255              IF
1256                (result > Max.Int)
1257                  -- only positive integer overflow possible
1258                  result := Error.Overflow
1259                TRUE
1260                  result := result << 1
1261
1262        -- store results
1263        frame.store[opL] := result
1264
1265        -- go to successor instruction

ADEXEC.INC                                                                 Page  24
1266        instruction := instruction + (INT succ)
1267
1268
1269    ---------------------------------------------------------------------------
1270    -- Instruction NOT
1271    ---------------------------------------------------------------------------
1272    Not.OpC
1273
1274      -- components of operand word
1275      BYTE regL IS op.word[2]: -- left register operand
1276      BYTE regR IS op.word[1]: -- right register operand
1277      BYTE succ IS op.word[0]: -- successor instruction
1278
1279      -- local variables
1280      INT   opL, opR: -- operand adresses
1281      INT32 infR:     -- value of right operand
1282      INT   datR:     -- data part of right operand
1283      BOOL  errR:     -- error flag of right operands
1284      INT32 result:   -- result of operation
1285
1286      SEQ -- instruction NOT
1287
1288        -- select operands
1289        opL, opR := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
1290        infR     := frame.store[opR]
1291        datR     := ASHIFTRIGHT(INT infR, 1)
1292        errR     := BOOL(infR /\ 1(INT32))
1293
1294        -- compute results
1295        IF
1296          errR
1297            -- right operand invalid, keep error code
1298            result := infR
1299          TRUE
1300            -- compute negation
1301            result := (INT32 (~ datR)) << 1
1302
1303        -- store results
1304        frame.store[opL] := result
1305
1306        -- go to successor instruction
1307        instruction := instruction + (INT succ)
1308
1309
1310    ---------------------------------------------------------------------------
1311    -- Instruction FAILED
1312    ---------------------------------------------------------------------------
1313    Failed.OpC
1314
1315      -- components of operand word
1316      BYTE regL IS op.word[2]: -- left register operand
1317      BYTE regR IS op.word[1]: -- right register operand
1318      BYTE succ IS op.word[0]: -- successor instruction
1319
1320      -- local variables

ADEXEC.INC                                                                 Page  25
1321      INT   opL, opR: -- operand adresses
1322      INT32 infR:     -- value of right operand
1323      BOOL  errR:     -- error flag of right operands
1324
1325      SEQ -- instruction FAILED
1326
1327        -- select operands
1328        opL, opR := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
1329        infR     := frame.store[opR]
1330        errR     := BOOL(infR /\ 1(INT32))
1331
1332        -- compute and store result
1333        frame.store[opL] := (INT32 errR) << 1
1334
1335        -- go to successor instruction
1336        instruction := instruction + (INT succ)
1337
1338
1339    ---------------------------------------------------------------------------
1340    -- Instruction COPYI
1341    ---------------------------------------------------------------------------
1342    CopyI.OpC
1343
1344      -- components of operand word
1345      BYTE regL IS op.word[2]: -- left register operand
1346      BYTE regR IS op.word[1]: -- right register operand
1347      BYTE succ IS op.word[0]: -- successor instruction
1348
1349      SEQ -- instruction COPYI
1350
1351        -- copy integer value
1352        frame.store[(INT frame.base)+(INT regL)] :=
1353          frame.store[(INT frame.base)+(INT regR)]
1354
1355        -- go to successor instruction
1356        instruction := instruction + (INT succ)
1357
1358
1359    ---------------------------------------------------------------------------
1360    -- Instruction COPYP
1361    ---------------------------------------------------------------------------
1362    CopyP.OpC
1363
1364      -- components of operand word
1365      BYTE regL IS op.word[2]: -- left register operand
1366      BYTE regR IS op.word[1]: -- right register operand
1367      BYTE succ IS op.word[0]: -- successor instruction
1368
1369      -- local variables
1370      INT   op:   -- operand address
1371      INT32 ptr:  -- pointer value
1372      BOOL  err:  -- error flag
1373      INT   wght: -- reference weigth
1374
1375      [Token.Size]INT32 token: -- network token

ADEXEC.INC                                                                 Page  26
1376
1377      SEQ -- instruction COPYP
1378
1379        -- fetch pointer value
1380        op  := (INT frame.base) + (INT regR)
1381        ptr := frame.store[op]
1382
1383        -- decompose pointer into its components
1384        err  := BOOL (ptr /\ 1(INT32))       -- error flag
1385        wght := INT ((ptr >> 1) /\ 3(INT32)) -- reference weight
1386
1387        IF
1388          (ptr = 0(INT32)) OR err
1389            SEQ
1390
1391              -- nil or error flag set, pointer may be copied
1392              frame.store[(INT frame.base)+(INT regL)] := ptr
1393
1394              -- go to successor instruction
1395              instruction := instruction + (INT succ)
1396
1397          wght > 0
1398            SEQ
1399
1400              -- decrease weight
1401              wght := (wght - 1) << 1
1402              ptr := (ptr /\ #FFFFFFF9(INT32)) \/ (INT32 wght)
1403
1404              -- copy pointer
1405              frame.store[op] := ptr
1406              frame.store[(INT frame.base)+(INT regL)] := ptr
1407
1408              -- go to successor instruction
1409              instruction := instruction + (INT succ)
1410
1411          TRUE
1412            SEQ
1413
1414              -- construct token with reference request
1415              [4]BYTE ptrs RETYPES ptr:
1416              [4]BYTE toks RETYPES token[2]:
1417              SEQ
1418                toks[2] := ptrs[3]                         -- module address
1419                toks[1] := IncRef.MToken                   -- increase reference
1420              [2]INT16 toks RETYPES token[1]:
1421              SEQ
1422                toks[1] := context                         -- actual context
1423                toks[0] :=                                 -- actual offset
1424                  INT16 (instruction - (INT code.base))
1425              [4]BYTE ptrs RETYPES ptr:
1426              [4]BYTE toks RETYPES token[0]:
1427              SEQ
1428                toks[3] := act.mod                         -- actual module
1429                [toks FROM 0 FOR 3] := [ptrs FROM 0 FOR 3] -- node reference

ADEXEC.INC                                                                 Page  27
1431              -- send token to memory unit
1432              out ! net.token; token
1433
1434              -- break instruction sequence
1435              going := FALSE
1436
1437
1438    ---------------------------------------------------------------------------
1439    -- Instruction DELP
1440    ---------------------------------------------------------------------------
1441    DelP.OpC
1442
1443      -- components of operand word
1444      BYTE reg  IS op.word[1]: -- register operand
1445      BYTE succ IS op.word[0]: -- instruction offset
1446
1447      -- local variables
1448      INT32 ptr: -- pointer value
1449      BOOL  err: -- error flag
1450
1451      [Token.Size]INT32 token: -- network token
1452
1453      SEQ -- instruction DELP
1454
1455        -- fetch pointer value and extract error flag
1456        ptr := frame.store[(INT frame.base) + (INT reg)]
1457        err := BOOL (ptr /\ 1(INT32))
1458
1459        IF
1460          (ptr = 0(INT32)) OR err
1461
1462            -- nil or error flag set, go to successor instruction
1463            instruction := instruction + (INT succ)
1464
1465          TRUE
1466            SEQ
1467
1468              -- send decrease reference request
1469              [4]BYTE ptrs RETYPES ptr:
1470              SEQ
1471                -- construct token
1472                [4]BYTE toks RETYPES token[2]:
1473                SEQ
1474                  toks[2] := ptrs[3]            -- module address
1475                  toks[1] := DecRef.MToken      -- decrease reference
1476                [4]BYTE toks RETYPES token[0]:
1477                [toks FROM 0 FOR 3] :=          -- node reference
1478                  [ptrs FROM 0 FOR 3]
1479
1480              -- send token to memory unit
1481              out ! net.token; token
1482
1483              -- go to successor instruction
1484              instruction := instruction + (INT succ)

ADEXEC.INC                                                                 Page  28
1486    ---------------------------------------------------------------------------
1487    -- Instruction SPLIT
1488    ---------------------------------------------------------------------------
1489    Split.OpC
1490
1491      -- components of operand word
1492      BYTE succ1 IS op.word[1]: -- first successor instruction
1493      BYTE succ2 IS op.word[0]: -- second successor instruction
1494
1495      -- local variables
1496      INT32 trigger: -- instruction trigger
1497
1498      SEQ -- instruction SPLIT
1499
1500        -- construct trigger with first successor instruction
1501        [2]INT16 i RETYPES trigger:
1502        INT16 cont IS i[1]: -- context field
1503        INT16 off  IS i[0]: -- offset field
1504        SEQ
1505          cont := context
1506          off  := (INT16 (instruction - (INT code.base))) + (INT16 succ1)
1507
1508        -- put it into trigger store
1509        Trig.Put(trigger)
1510
1511        -- go to second successor instruction
1512        instruction := instruction + (INT succ2)
1513
1514
1515    ---------------------------------------------------------------------------
1516    -- Instruction SWITCH
1517    ---------------------------------------------------------------------------
1518    Switch.OpC
1519
1520      -- components of operand word
1521      BYTE reg   IS op.word[2]: -- register operand
1522      BYTE succ1 IS op.word[1]: -- first successor instruction
1523      BYTE succ2 IS op.word[0]: -- second successor instruction
1524
1525      -- local variables
1526      INT32 inf:  -- test operand
1527      INT32 dat: -- value of operand
1528      BOOL  err: -- error flag of operand
1529
1530      SEQ -- instruction SWITCH
1531
1532        -- select operands
1533        inf := frame.store[(INT frame.base)+(INT reg)]
1534        dat := inf >> 1
1535        err := BOOL(inf /\ 1(INT32))
1536
1537        IF
1538          err
1539            -- test value invalid, go to next instruction
1540            instruction := instruction + 1

ADEXEC.INC                                                                 Page  29
1541          dat = (INT32 TRUE)
1542            -- test value true, go to first successor
1543            instruction := instruction + (INT succ1)
1544          dat = (INT32 FALSE)
1545            -- test value false, go to second successor
1546            instruction := instruction + (INT succ2)
1547          TRUE
1548            -- fatal error occurred
1549            Assert(FALSE, true.false)
1550
1551
1552    ---------------------------------------------------------------------------
1553    -- Instruction JUMP
1554    ---------------------------------------------------------------------------
1555    Jump.OpC
1556
1557      -- operand word denotes SIGNED offset to successor instruction
1558
1559      -- local variables
1560      INT32 off: -- instruction offset
1561
1562      SEQ -- instruction JUMP
1563
1564        -- increase instruction by SIGNED integer equivalent of op.word
1565        [4]BYTE o RETYPES off:
1566        [o FROM 1 FOR 3] := op.word
1567        instruction := instruction + ASHIFTRIGHT(INT off,8)
1568
1569
1570    ---------------------------------------------------------------------------
1571    -- Instruction WAIT
1572    ---------------------------------------------------------------------------
1573    Wait.OpC
1574
1575      -- components of operand word
1576      [2]BYTE ev IS [op.word FROM 1 FOR 2]: -- event number
1577      BYTE  succ IS op.word[0]:             -- successor instruction
1578
1579      -- local variables
1580      INT  slot.num: -- number of event slot
1581      INT  bit.num:  -- number of event bit
1582      INT  addr:     -- address of slot
1583      BOOL synch:    -- synchronization neccessary
1584
1585      SEQ -- instruction WAIT
1586
1587        -- convert event number to integer
1588        [4]BYTE e RETYPES null:
1589        [e FROM 0 FOR 2] := ev
1590
1591        -- compute slot number and bit number of event
1592        slot.num, bit.num := (INT null) / 32, (INT null) REM 32
1593
1594        -- compute effective slot address
1595        addr := ((INT frame.base) - 3) - slot.num

ADEXEC.INC                                                                 Page  30
1596
1597        -- get bit and invert it
1598        INT32     slot IS frame.store[addr]:
1599        VAL INT32 bit  IS bits[bit.num]:
1600        SEQ
1601          synch := (slot /\ bit) = 0(INT32)
1602          slot  := slot >< bit
1603
1604        IF
1605          synch
1606            -- event bit not set, break execution
1607            going := FALSE
1608          TRUE
1609            -- event bit set, continue execution
1610            instruction := instruction + (INT succ)
1611
1612
1613    ---------------------------------------------------------------------------
1614    -- Instruction CONST
1615    ---------------------------------------------------------------------------
1616    Const.OpC
1617
1618      -- components of operand word
1619      BYTE reg  IS op.word[1]: -- register operand
1620      BYTE succ IS op.word[0]: -- successor instruction
1621
1622      SEQ -- instruction CONST
1623
1624        -- copy long constant from program store into register
1625        frame.store[(INT frame.base)+(INT reg)] := prog.store[instruction+1]
1626
1627        -- go to successor instruction
1628        instruction := instruction + (INT succ)
1629
1630
1631    ---------------------------------------------------------------------------
1632    -- Instruction FASTCALL
1633    ---------------------------------------------------------------------------
1634    FastCall.OpC
1635
1636      -- components of operand word
1637      [2]BYTE fun  IS [op.word FROM 1 FOR 2]: -- function descriptor
1638      BYTE    succ IS op.word[0]:             -- successor instruction
1639
1640      -- local variables
1641      [Token.Size]INT32 token: -- network token
1642
1643      SEQ -- instruction FASTCALL
1644
1645        -- construct token
1646        [4]BYTE toks RETYPES token[2]:
1647        SEQ
1648          toks[2] := Min.Loaded()       -- address of minimal loaded module
1649          toks[1] := FCall.PToken       -- fast call
1650        [2]INT16 toks RETYPES token[1]:

ADEXEC.INC                                                                 Page  31
1651        SEQ
1652          toks[1] := context            -- actual context
1653          toks[0] :=                    -- successor offset
1654            (INT16 (instruction - (INT code.base))) + (INT16 succ)
1655        [4]BYTE toks RETYPES token[0]:
1656        SEQ
1657          toks[3] := act.mod            -- actual module
1658          [toks FROM 0 FOR 2] := fun    -- function descriptor
1659
1660        -- send token to processor unit
1661        out ! net.token; token
1662
1663        -- break instruction sequence
1664        going := FALSE
1665
1666
1667    ---------------------------------------------------------------------------
1668    -- Instruction TAILCALL
1669    ---------------------------------------------------------------------------
1670    TailCall.OpC
1671
1672      -- components of operand word
1673      [2]BYTE fun  IS [op.word FROM 1 FOR 2]: -- function descriptor
1674      BYTE    succ IS op.word[0]:             -- successor instruction
1675
1676      -- local variables
1677      [Token.Size]INT32 token: -- network token
1678
1679      SEQ -- instruction TAILCALL
1680
1681        -- construct token
1682        [4]BYTE toks RETYPES token[2]:
1683        SEQ
1684          toks[2] := Min.Loaded()       -- address of minimal loaded module
1685          toks[1] := TCall.PToken       -- fast call
1686        [2]INT16 toks RETYPES token[1]:
1687        SEQ
1688          toks[1] := context            -- actual context
1689          toks[0] :=                    -- successor offset
1690            (INT16 (instruction - (INT code.base))) + (INT16 succ)
1691        [4]BYTE toks RETYPES token[0]:
1692        SEQ
1693          toks[3] := act.mod            -- actual module
1694          [toks FROM 0 FOR 2] := fun    -- function descriptor
1695
1696        -- send token to processor unit
1697        out ! net.token; token
1698
1699        -- break instruction sequence
1700        going := FALSE
1701
1702
1703    ---------------------------------------------------------------------------
1704    -- Instruction CONTEXT
1705    ---------------------------------------------------------------------------

ADEXEC.INC                                                                 Page  32
1706    Context.OpC
1707
1708      -- components of operand word
1709      BYTE reg  IS op.word[1]: -- register operand
1710      BYTE succ IS op.word[0]: -- successor instruction
1711
1712      -- local variables
1713      INT32 dat:               -- register operand
1714      [Token.Size]INT32 token: -- network token
1715
1716      SEQ -- instruction CONTEXT
1717
1718        -- select operand
1719        dat := frame.store[(INT frame.base)+(INT reg)] >> 1
1720
1721        -- components of activation address
1722        [2]INT16 dati RETYPES dat:
1723        INT16    cont IS dati[0]:
1724        [2]BYTE  datb RETYPES dati[1]:
1725        BYTE     mod  IS datb[0]:
1726
1727        -- components of result address
1728        [2]INT16 addr0 RETYPES frame.store[(INT frame.base)-1]:
1729        INT16 res.cont IS addr0[1]:
1730        INT16 res.off  IS addr0[0]:
1731        [4]BYTE addr1 RETYPES frame.store[(INT frame.base)-2]:
1732        BYTE  res.mod  IS addr1[0]:
1733
1734        -- construct token
1735        SEQ
1736          [4]BYTE toks RETYPES token[2]:
1737          SEQ
1738            toks[2] := mod                -- module address
1739            toks[1] := Cont.PToken        -- context message
1740          [2]INT16 toks RETYPES token[1]:
1741          SEQ
1742            toks[1] := res.cont           -- result context
1743            toks[0] := res.off            -- result offset
1744          [4]BYTE toks RETYPES token[0]:
1745          toks[3] := res.mod              -- result module
1746          [2]INT16 toks RETYPES token[0]:
1747          toks[0] := cont                 -- context
1748
1749        -- send token to processor unit
1750        out ! net.token; token
1751
1752        -- jump to successor instruction
1753        instruction := instruction + (INT succ)
1754
1755
1756    ---------------------------------------------------------------------------
1757    -- Instruction APPLY
1758    ---------------------------------------------------------------------------
1759    Apply.OpC

ADEXEC.INC                                                                 Page  33
1761      -- components of operand word
1762      BYTE reg  IS op.word[1]: -- register operand
1763      BYTE succ IS op.word[0]: -- successor instruction
1764
1765      -- local variables
1766      INT32 inf: -- operand
1767      INT16 fun: -- function descriptor of operand
1768      BOOL  err: -- error flag of operand
1769
1770      [Token.Size]INT32 token: -- network token
1771
1772      SEQ -- instruction APPLY
1773
1774        -- select operand
1775        inf := frame.store[(INT frame.base)+(INT reg)]
1776        fun := INT16 ((inf >> 1) /\ #FFFF(INT32))
1777        err := BOOL(inf /\ 1(INT32))
1778
1779        IF
1780          err
1781            SEQ
1782              -- error flag of function descriptor set
1783
1784              -- replace activation address by error value
1785              [4]BYTE next RETYPES prog.store[instruction+(INT succ)]:
1786              BYTE    nreg IS next[1]:
1787              frame.store[(INT frame.base)+(INT nreg)] := inf
1788
1789              -- replace result value by error value
1790              [4]BYTE next RETYPES prog.store[(instruction+(INT succ))+1]:
1791              BYTE    nreg IS next[1]:
1792              frame.store[(INT frame.base)+(INT nreg)] := inf
1793
1794          TRUE
1795            SEQ
1796              -- function descriptor valid
1797
1798              -- construct token
1799              [4]BYTE toks RETYPES token[2]:
1800              SEQ
1801                toks[2] := Min.Loaded()       -- minimal loaded module
1802                toks[1] := FCall.PToken       -- fast call
1803              [2]INT16 toks RETYPES token[1]:
1804              SEQ
1805                toks[1] := context            -- actual context
1806                toks[0] :=                    -- successor offset
1807                  (INT16 (instruction - (INT code.base))) + (INT16 succ)
1808              [2]INT16 toks RETYPES token[0]:
1809              [2]BYTE  tokb RETYPES toks[1]:
1810              SEQ
1811                tokb[1] := act.mod -- actual module
1812                toks[0] := fun     -- function descriptor
1813
1814              -- send token to processor unit
1815              out ! net.token; token

ADEXEC.INC                                                                 Page  34
1816
1817              -- break instruction sequence
1818              going := FALSE
1819
1820
1821    ---------------------------------------------------------------------------
1822    -- Instruction SEND
1823    ---------------------------------------------------------------------------
1824    Send.OpC
1825
1826      -- components of operand word
1827      BYTE regL IS op.word[2]: -- left register operand
1828      BYTE regR IS op.word[1]: -- right register operand
1829      BYTE par  IS op.word[0]: -- parameter number
1830
1831      -- local variables
1832      INT32 inf: -- operand
1833      INT32 dat: -- data field of operand
1834      BOOL  err: -- error flag of operand
1835
1836      [Token.Size]INT32 token: -- network token
1837
1838      SEQ -- instruction SEND
1839
1840        -- select operand
1841        inf := frame.store[(INT frame.base)+(INT regR)]
1842        dat := inf >> 1
1843        err := BOOL(inf /\ 1(INT32))
1844
1845        IF
1846          err
1847
1848            -- error flag of activation adress set, do nothing
1849            SKIP
1850
1851          TRUE
1852            SEQ
1853
1854              -- components of activation address
1855              [2]INT16 dati RETYPES dat:
1856              INT16    cont IS dati[0]:
1857              [2]BYTE  datb RETYPES dati[1]:
1858              BYTE     mod  IS datb[0]:
1859
1860              -- construct token
1861              SEQ
1862                [4]BYTE toks RETYPES token[2]:
1863                SEQ
1864                  toks[2] := mod                -- module address of callee
1865                  toks[1] := Mess.PToken        -- message token
1866                [2]INT16 toks RETYPES token[1]:
1867                SEQ
1868                  toks[1] := cont               -- context of callee
1869                  toks[0] := (INT16 par)        -- parameter number
1870                token[0] :=                     -- argument value

ADEXEC.INC                                                                 Page  35
1871                  frame.store[(INT frame.base) + (INT regL)]
1872
1873              -- send token to processor unit
1874              out ! net.token; token
1875
1876        -- go to immediate next instruction
1877        instruction := instruction + 1
1878
1879
1880    ---------------------------------------------------------------------------
1881    -- Instruction SENDC
1882    ---------------------------------------------------------------------------
1883    SendC.OpC
1884
1885      -- components of operand word
1886      BYTE con IS op.word[2]: -- constant argument
1887      BYTE reg IS op.word[1]: -- register operand
1888      BYTE par IS op.word[0]: -- parameter number
1889
1890      -- local variables
1891      INT32 inf: -- operand
1892      INT32 dat: -- data field of operand
1893      BOOL  err: -- error flag of operand
1894      INT   arg: -- argument value
1895
1896      [Token.Size]INT32 token: -- network token
1897
1898      SEQ -- instruction SENDC
1899
1900        -- select operand
1901        inf := frame.store[(INT frame.base)+(INT reg)]
1902        dat := inf >> 1
1903        err := BOOL(inf /\ 1(INT32))
1904
1905        IF
1906          err
1907
1908            -- error flag of activation adress set, do nothing
1909            SKIP
1910
1911          TRUE
1912            SEQ
1913
1914              -- argument value is signed constant with error flag
1915              IF
1916                con > (BYTE Max.Con)
1917                  arg := ((INT con) - Range.Con) << 1
1918                TRUE
1919                  arg := (INT con) << 1
1920
1921              -- components of activation address
1922              [2]INT16 dati RETYPES dat:
1923              INT16    cont IS dati[0]:
1924              [2]BYTE  datb RETYPES dati[1]:
1925              BYTE     mod  IS datb[0]:

ADEXEC.INC                                                                 Page  36
1926
1927              -- construct token
1928              SEQ
1929                [4]BYTE toks RETYPES token[2]:
1930                SEQ
1931                  toks[2] := mod                -- module address of callee
1932                  toks[1] := Mess.PToken        -- message token
1933                [2]INT16 toks RETYPES token[1]:
1934                SEQ
1935                  toks[1] := cont               -- context of callee
1936                  toks[0] := (INT16 par)        -- parameter number
1937                token[0] := INT32 arg           -- argument value
1938
1939              -- send token to processor unit
1940              out ! net.token; token
1941
1942        -- go to immediate next instruction
1943        instruction := instruction + 1
1944
1945
1946    ---------------------------------------------------------------------------
1947    -- Instruction SUBMIT
1948    ---------------------------------------------------------------------------
1949    Submit.OpC
1950
1951      -- components of operand word
1952      BYTE regL IS op.word[2]: -- left register operand
1953      BYTE regR IS op.word[1]: -- right register operand
1954      BYTE succ IS op.word[0]: -- successor instruction
1955
1956      -- local variables
1957      INT   opL, opR:   -- operand adresses
1958      INT32 infL, infR: -- operand values
1959      BOOL  errL, errR: -- error flags of operands
1960      INT32 datR:       -- data value of operand
1961
1962      [Token.Size]INT32 token: -- network token
1963
1964      SEQ -- instruction SUBMIT
1965
1966        -- select operands
1967        opL,  opR  := (INT frame.base)+(INT regL), (INT frame.base)+(INT regR)
1968        infL, infR := frame.store[opL], frame.store[opR]
1969        errL, errR := BOOL(infL /\1(INT32)), BOOL(infR /\ 1(INT32))
1970
1971        IF
1972          errL OR errR
1973
1974            -- error flag set, do nothing
1975            SKIP
1976
1977          TRUE
1978            SEQ
1979
1980              -- data value of right operand

ADEXEC.INC                                                                 Page  37
1981              datR := infR >> 1
1982
1983              -- components of pointer to curried function
1984              [4]BYTE  infs RETYPES infL:
1985              BYTE     rmod IS infs[3]:             -- module address of tuple
1986              [3]BYTE  ref  IS [infs FROM 0 FOR 3]: -- tuple reference
1987
1988              -- components of activation address
1989              [2]INT16  dati RETYPES datR:
1990              INT16     cont IS dati[0]:            -- context of activation
1991              [2]BYTE   datb RETYPES dati[1]:
1992              BYTE      mod  IS datb[0]:            -- module address
1993
1994              -- construct token
1995              SEQ
1996                [4]BYTE toks RETYPES token[2]:
1997                SEQ
1998                  toks[2] := rmod               -- module address of tuple
1999                  toks[1] := Submit.MToken      -- submission token
2000                [2]INT16 toks RETYPES token[1]:
2001                toks[1] := cont                 -- context of activation
2002                [4]BYTE toks RETYPES token[0]:
2003                SEQ
2004                  toks[3] := mod                 -- module of activation
2005                  [toks FROM 0 FOR 3] := ref     -- tuple reference
2006
2007              -- send token to processor unit
2008              out ! net.token; token
2009
2010        -- go to successor instruction
2011        instruction := instruction + (INT succ)
2012
2013
2014    ---------------------------------------------------------------------------
2015    -- Instruction RETURN
2016    ---------------------------------------------------------------------------
2017    Return.OpC
2018
2019      -- components of operand word
2020      BYTE reg  IS op.word[1]: -- register operand
2021      BYTE succ IS op.word[0]: -- successor instruction
2022
2023      [Token.Size]INT32 token: -- network token
2024
2025      SEQ -- instruction RETURN
2026
2027        -- select return address
2028        [2]INT16 addr0 RETYPES frame.store[(INT frame.base)-1]:
2029        INT16    cont  IS addr0[1]:
2030        INT16    off   IS addr0[0]:
2031        [4]BYTE  addr1 RETYPES frame.store[(INT frame.base)-2]:
2032        BYTE     mod   IS addr1[0]:
2033
2034        -- construct token
2035        SEQ

ADEXEC.INC                                                                 Page  38
2036          [4]BYTE toks RETYPES token[2]:
2037          SEQ
2038            toks[2] := mod                -- module address
2039            toks[1] := Mess.PToken        -- message token
2040          [2]INT16 toks RETYPES token[1]:
2041          SEQ
2042            toks[1] := cont               -- return context
2043            toks[0] := off                -- return offset
2044          token[0] :=                     -- result value
2045            frame.store[(INT frame.base)+(INT reg)]
2046
2047        -- send token to processor unit
2048        out ! net.token; token
2049
2050        -- go to successor instruction
2051        instruction := instruction + (INT succ)
2052
2053
2054    ---------------------------------------------------------------------------
2055    -- Instruction RETURNC
2056    ---------------------------------------------------------------------------
2057    ReturnC.OpC
2058
2059      -- components of operand word
2060      BYTE con  IS op.word[1]: -- constant operand
2061      BYTE succ IS op.word[0]: -- successor instruction
2062
2063      INT res:                 -- result value
2064
2065      [Token.Size]INT32 token: -- network token
2066
2067      SEQ -- instruction RETURNC
2068
2069        -- argument value is signed constant with error flag
2070        IF
2071          con > (BYTE Max.Con)
2072            res := ((INT con) - Range.Con) << 1
2073          TRUE
2074            res := (INT con) << 1
2075
2076        -- select return address
2077        [2]INT16 addr0 RETYPES frame.store[(INT frame.base)-1]:
2078        INT16    cont  IS addr0[1]:
2079        INT16    off   IS addr0[0]:
2080        [4]BYTE  addr1 RETYPES frame.store[(INT frame.base)-2]:
2081        BYTE     mod   IS addr1[0]:
2082
2083        -- construct token
2084        SEQ
2085          [4]BYTE toks RETYPES token[2]:
2086          SEQ
2087            toks[2] := mod                -- module address
2088            toks[1] := Mess.PToken        -- message token
2089          [2]INT16 toks RETYPES token[1]:
2090          SEQ

ADEXEC.INC                                                                 Page  39
2091            toks[1] := cont               -- return context
2092            toks[0] := off                -- return offset
2093          token[0] := INT32 res           -- result value
2094
2095        -- send token to processor unit
2096        out ! net.token; token
2097
2098        -- go to successor instruction
2099        instruction := instruction + (INT succ)
2100
2101
2102    ---------------------------------------------------------------------------
2103    -- Instruction TERMINATE
2104    ---------------------------------------------------------------------------
2105    Terminate.OpC
2106
2107      [4]BYTE inst RETYPES prog.store[(INT code.base)-1]:
2108      BYTE    evts IS inst[1]:
2109      BYTE    regs IS inst[0]:
2110
2111      SEQ -- instruction TERMINATE
2112
2113        -- deallocate frame
2114        Dealloc.Frame((INT frame.base), (INT evts), (INT regs))
2115
2116        -- deallocate context
2117        Dealloc.Context(context)
2118
2119        -- break execution sequence
2120        going := FALSE
2121
2122
2123    ---------------------------------------------------------------------------
2124    -- Instruction ALLOC
2125    ---------------------------------------------------------------------------
2126    Alloc.OpC
2127
2128      -- components of operand word
2129      BYTE const IS op.word[1]: -- constant operand
2130      BYTE succ  IS op.word[0]: -- successor instruction
2131
2132      [Token.Size]INT32 token: -- network token
2133
2134      SEQ -- instruction ALLOC
2135
2136        -- construct token
2137        SEQ
2138          [4]BYTE toks RETYPES token[2]:
2139          SEQ
2140            toks[2] := act.mod            -- module address (actual module)
2141            toks[1] := Alloc.MToken       -- allocation token
2142            toks[0] := const              -- number of tuple fields
2143          [2]INT16 toks RETYPES token[1]:
2144          SEQ
2145            toks[1] := context            -- return context

ADEXEC.INC                                                                 Page  40
2146            toks[0] :=                    -- return offset
2147              (INT16 (instruction - (INT code.base))) + (INT16 succ)
2148          [4]BYTE toks RETYPES token[0]:
2149          toks[3] := act.mod              -- return module
2150
2151        -- send token to memory unit on actual module
2152        out ! net.token; token
2153
2154        -- break execution sequence
2155        going := FALSE
2156
2157
2158    ---------------------------------------------------------------------------
2159    -- Instruction READ
2160    ---------------------------------------------------------------------------
2161    Read.OpC
2162
2163      -- components of operand word
2164      BYTE const IS op.word[2]: -- constant operand
2165      BYTE reg   IS op.word[1]: -- register operand
2166      BYTE succ  IS op.word[0]: -- successor instruction
2167
2168      INT32 dat: -- tuple reference
2169
2170      [Token.Size]INT32 token: -- network token
2171
2172      SEQ -- instruction READ
2173
2174        dat := frame.store[(INT frame.base)+(INT reg)]
2175
2176        IF
2177
2178          -- reference is nil
2179          dat = 0(INT32)
2180            SEQ
2181
2182              -- replace result value by error value
2183              [4]BYTE next RETYPES prog.store[instruction+(INT succ)]:
2184              BYTE    nreg IS next[1]:
2185              frame.store[(INT frame.base)+(INT nreg)] := Error.Read
2186
2187              -- go to successor instruction
2188              instruction := instruction + (INT succ)
2189
2190          -- reference is error
2191          (dat /\ 1(INT32)) > 0(INT32)
2192            SEQ
2193
2194              -- replace result value by propagated error value
2195              [4]BYTE next RETYPES prog.store[instruction+(INT succ)]:
2196              BYTE    nreg IS next[1]:
2197              frame.store[(INT frame.base)+(INT nreg)] := dat
2198
2199              -- go to successor instruction
2200              instruction := instruction + (INT succ)

ADEXEC.INC                                                                 Page  41
2201
2202          -- reference is valid
2203          TRUE
2204
2205            -- select components of tuple reference
2206            [4]BYTE dats RETYPES dat:
2207            BYTE    mod  IS dats[3]:
2208            [3]BYTE ref  IS [dats FROM 0 FOR 3]:
2209
2210            SEQ
2211
2212              -- construct token
2213              [4]BYTE toks RETYPES token[2]:
2214              SEQ
2215                toks[2] := mod                -- module address of tuple
2216                toks[1] := Read.MToken        -- read token
2217                toks[0] := const              -- number of tuple field to read
2218              [2]INT16 toks RETYPES token[1]:
2219              SEQ
2220                toks[1] := context            -- return context
2221                toks[0] :=                    -- return offset
2222                  (INT16 (instruction - (INT code.base))) + (INT16 succ)
2223              [4]BYTE toks RETYPES token[0]:
2224              SEQ
2225                toks[3] := act.mod            -- return module
2226                [toks FROM 0 FOR 3] := ref    -- tuple reference
2227
2228              -- send token to memory unit
2229              out ! net.token; token
2230
2231              -- break execution sequence
2232              going := FALSE
2233
2234
2235    ---------------------------------------------------------------------------
2236    -- Instruction WRITEI
2237    ---------------------------------------------------------------------------
2238    WriteI.OpC
2239
2240      -- components of operand word
2241      BYTE regL  IS op.word[2]: -- left register operand (integer value)
2242      BYTE regR  IS op.word[1]: -- right register operand (tuple reference)
2243      BYTE const IS op.word[0]: -- constant value (number of tuple field)
2244
2245      INT32 ptr: -- tuple reference
2246      BOOL  err: -- error flag
2247
2248      [Token.Size]INT32 token: -- network token
2249
2250      SEQ -- instruction WRITEI
2251
2252        -- fetch tuple reference
2253        ptr := frame.store[(INT frame.base)+(INT regR)]
2254        err := (ptr /\ 1(INT32)) <> 0(INT32)

ADEXEC.INC                                                                 Page  42
2256        -- select components of tuple reference
2257        [4]BYTE  ptrs RETYPES ptr:
2258        BYTE     mod  IS ptrs[3]:             -- module address
2259        [3]BYTE  ref  IS [ptrs FROM 0 FOR 3]: -- tuple address
2260
2261        -- construct token
2262        SEQ
2263
2264          -- tuple reference must be valid
2265          Assert(NOT err, ref.err)
2266
2267          [4]BYTE toks RETYPES token[2]:
2268          SEQ
2269            toks[2] := mod                -- module address of tuple
2270            toks[1] := WrInt.MToken       -- write integer request
2271            toks[0] := const              -- number of tuple field
2272          token[1] :=                     -- value to be written
2273            frame.store[(INT frame.base) + (INT regL)]
2274          [4]BYTE toks RETYPES token[0]:
2275          [toks FROM 0 FOR 3] := ref      -- tuple reference
2276
2277        -- send token to memory unit
2278        out ! net.token; token
2279
2280        -- continue execution with immediately next instruction
2281        instruction := instruction + 1
2282
2283
2284    ---------------------------------------------------------------------------
2285    -- Instruction WRITEP
2286    ---------------------------------------------------------------------------
2287    WriteP.OpC
2288
2289      -- components of operand word
2290      BYTE regL  IS op.word[2]: -- left register operand (pointer value)
2291      BYTE regR  IS op.word[1]: -- right register operand (tuple reference)
2292      BYTE const IS op.word[0]: -- constant value (number of tuple field)
2293
2294      INT32 ptr: -- tuple reference
2295      BOOL  err: -- error flag
2296
2297      [Token.Size]INT32 token: -- network token
2298
2299      SEQ -- instruction WRITEP
2300
2301        -- fetch tuple reference
2302        ptr := frame.store[(INT frame.base)+(INT regR)]
2303        err := (ptr /\ 1(INT32)) <> 0(INT32)
2304
2305        -- select components of tuple reference
2306        [4]BYTE  ptrs RETYPES ptr:
2307        BYTE     mod  IS ptrs[3]:             -- module address
2308        [3]BYTE  ref  IS [ptrs FROM 0 FOR 3]: -- tuple address
2309
2310        -- construct token

ADEXEC.INC                                                                 Page  43
2311        SEQ
2312
2313          -- tuple reference must be valid
2314          Assert(NOT err, ref.err)
2315
2316          [4]BYTE toks RETYPES token[2]:
2317          SEQ
2318            toks[2] := mod                -- module address of tuple
2319            toks[1] := WrPtr.MToken       -- write pointer request
2320            toks[0] := const              -- number of tuple field
2321          token[1] :=                     -- value to be written
2322            frame.store[(INT frame.base) + (INT regL)]
2323          [4]BYTE toks RETYPES token[0]:
2324          [toks FROM 0 FOR 3] := ref      -- tuple reference
2325
2326        -- send token to memory unit
2327        out ! net.token; token
2328
2329        -- continue execution with immediately next instruction
2330        instruction := instruction + 1
2331
2332
2333    ---------------------------------------------------------------------------
2334    -- Instruction WRITEC
2335    ---------------------------------------------------------------------------
2336    WriteC.OpC
2337
2338      -- components of operand word
2339      BYTE con   IS op.word[2]: -- constant argument value
2340      BYTE reg   IS op.word[1]: -- register operand (tuple reference)
2341      BYTE const IS op.word[0]: -- constant value (number of tuple field)
2342
2343      -- local variables
2344      INT32 ptr: -- tuple reference
2345      BOOL  err: -- error flag
2346      INT   arg: -- signed integer value
2347
2348      [Token.Size]INT32 token: -- network token
2349
2350      SEQ -- instruction WRITEC
2351
2352        -- argument value is signed constant with error flag
2353        IF
2354          con > (BYTE Max.Con)
2355            arg := ((INT con) - Range.Con) << 1
2356          TRUE
2357            arg := (INT con) << 1
2358
2359        -- fetch tuple reference
2360        ptr := frame.store[(INT frame.base)+(INT reg)]
2361        err := (ptr /\ 1(INT32)) <> 0(INT32)
2362
2363        -- select components of tuple reference
2364        [4]BYTE  ptrs RETYPES ptr:
2365        BYTE     mod  IS ptrs[3]:             -- module address

ADEXEC.INC                                                                 Page  44
2366        [3]BYTE  ref  IS [ptrs FROM 0 FOR 3]: -- tuple address
2367
2368        SEQ
2369
2370          -- tuple reference must be valid
2371          Assert(NOT err, ref.err)
2372
2373          -- construct token
2374          [4]BYTE toks RETYPES token[2]:
2375          SEQ
2376            toks[2] := mod                -- module address of tuple
2377            toks[1] := WrInt.MToken       -- write integer request
2378            toks[0] := const              -- number of tuple field
2379          token[1] := INT32 arg           -- value to be written
2380          [4]BYTE toks RETYPES token[0]:
2381          [toks FROM 0 FOR 3] := ref      -- tuple reference
2382
2383        -- send token to memory unit
2384        out ! net.token; token
2385
2386        -- continue execution with immediately next instruction
2387        instruction := instruction + 1
2388
2389
2390    ---------------------------------------------------------------------------
2391    -- Instruction Invalid
2392    ---------------------------------------------------------------------------
2393    ELSE
2394      Assert(FALSE, inst.err)
2395
2396
2397  -----------------------------------------------------------------------------
2398  -- End of ADEXEC.INC
2399  -----------------------------------------------------------------------------

ADMEMORY.INC                                                               Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADMEMORY.INC                                           Wolfgang Schreiner
 4  -- Memory Constants and Protocols                         (c) RISC-Linz 1990
 5  --
 6  -- Defines all the constants and protocols that are used within the
 7  -- memory unit of an ADAM module.
 8  --
 9  -----------------------------------------------------------------------------
10
11  VAL INT K IS 1024: -- unit of measure
12
13  VAL INT Heap.Len  IS  64*K:  -- length of heap in cells
14  VAL INT Exp.Len   IS   4*K:  -- length of expensive request store in tokens
15  VAL INT Cheap.Len IS   4*K:  -- length of cheap request store in tokens
16  VAL INT Stack.Len IS   4*K:  -- length of deallocation stack in tokens
17
18  VAL INT Range.Count IS 256: -- range of reference count field (excl.)
19
20  -----------------------------------------------------------------------------
21  -- protocols (within memory unit)
22  -----------------------------------------------------------------------------
23
24  -- token protocol
25  PROTOCOL Memory.Token IS [Token.Size]INT32:
26
27  -- demand protocol (just a tag for synchronization)
28  PROTOCOL Demand
29    CASE
30      demand.tag
31  :
32
33  -- buffer protocol (number of buffered elements and delivered token)
34  PROTOCOL Buffer.Token IS INT; [Token.Size]INT32:
35
36  -----------------------------------------------------------------------------
37  -- action codes
38  -----------------------------------------------------------------------------
39
40  VAL INT Exp.Act    IS 0: -- fulfill expensive request
41  VAL INT Cheap.Act  IS 1: -- fulfill cheap request
42  VAL INT Report.Act IS 2: -- report actual load
43
44  -----------------------------------------------------------------------------
45  -- The following variables control the behaviour of the procedure
46  -- 'Mem.Action' determining the next action of the memory unit:
47  --
48  --   * Mem.Trash denotes the minimal memory load for sending a load report.
49  --   * Mem.Delay denotes 100 minus the tolerable relative difference between
50  --     the actual processor load and the last reported one.
51  --   * Heap.Trash denotes a lower bound for the load of the heap store.
52  --     If the actual load falls below this bound, the memory fulfills more
53  --     expensive requests.
54  --   * Heap.Bound denotes an upper bound for the load of the bound store.
55  --     If the actual load exceedes this bound, the memory fulfills less

ADMEMORY.INC                                                               Page   2
56  --     expensive requests.
57  --   * Exp.Bound denotes an upper bound for the load of the expensive store.
58  --     If the actual load exceedes this bound, the processor will fulfill
59  --     more expensive requests.
60  -----------------------------------------------------------------------------
61
62  VAL INT Mem.Trash  IS 1:  -- lower bound memory load (>0 <= 100)
63  VAL INT Mem.Delay  IS 90: -- report delay (>0 <= 100)
64
65  VAL INT Heap.Trash IS Heap.Len * (25/100): -- lower bound heap store
66  VAL INT Heap.Bound IS Heap.Len * (75/100): -- upper bound heap store
67  VAL INT Exp.Bound  IS Exp.Len  * (50/100): -- bound request store
68
69  -----------------------------------------------------------------------------
70  -- End of ADMEMORY.INC
71  -----------------------------------------------------------------------------

ADPROC.INC                                                                 Page   1
 1  -----------------------------------------------------------------------------
 2  --
 3  -- ADPROC.INC                                             Wolfgang Schreiner
 4  -- Processor Constants and Protocols                      (c) RISC-Linz 1990
 5  --
 6  -- Defines all the constants and protocols that are used within the
 7  -- processor unit of an ADAM module.
 8  --
 9  -----------------------------------------------------------------------------
10
11  -----------------------------------------------------------------------------
12  -- constants
13  -----------------------------------------------------------------------------
14
15  VAL INT K IS 1024: -- storage unit in words
16
17  VAL INT Prog.Len  IS   8*K: -- length of program store in words
18  VAL INT Frame.Len IS  64*K: -- length of frame store (must be a power of 2)
19  VAL INT Cont.Len  IS   8*K: -- length of context store in words
20  VAL INT Trig.Len  IS   1*K: -- length of trigger store in words
21  VAL INT Mess.Len  IS   4*K: -- length of message store in tokens
22  VAL INT Requ.Len  IS   4*K: -- length of request store in tokens
23
24  ----------------------------------------------------------------------------
25  -- constants for managing the heap-oriented frame store (buddy method)
26  ----------------------------------------------------------------------------
27
28  -- frame blocks may be from 8 to 256 words long (in factors of 2)
29  VAL INT Free.Min   IS 8:   -- length of smallest activation frame
30  VAL INT Free.Max   IS 256: -- length of largest activation frame
31  VAL INT Free.Min.B IS 3:   -- binary logarithm of Free.Min
32  VAL INT Free.Step  IS 6:   -- number of different sizes of activation frames
33
34  VAL INT Used.Len   IS (Frame.Len/256): -- length of array of used bits
35
36  -----------------------------------------------------------------------------
37  -- 'Significance' must have a value between 0 (excl.) and 100 (incl).
38  -- It denotes the factor by that the load of a neighbour must be
39  -- smaller than the load of the actual processor in order to forward
40  -- activation requests to that processor.
41  -----------------------------------------------------------------------------
42
43  VAL INT Significance IS 100:  -- load significance
44
45  -----------------------------------------------------------------------------
46  -- The following variables control the behaviour of the procedure
47  -- 'Proc.Action' determining the next action of the processor unit:
48  --
49  --   * Proc.Trash denotes the minimal processor load for sending a load
50  --     report.
51  --   * Rep.Delay denotes 100 minus the tolerable relative difference
52  --     between the actual processor load and the last reported one.
53  --   * Frame.Trash denotes a lower bound for the load of the frame store.
54  --     If the actual load falls below this bound, the processor fulfills more
55  --     call requests.

ADPROC.INC                                                                 Page   2
56  --   * Frame.Bound denotes an upper bound for the load of the frame store.
57  --     If the actual load exceedes this bound, the processor fulfills less
58  --     call requests.
59  --   * Requ.Bound denotes an upper bound for the load of the request store.
60  --     If the actual load exceedes this bound, the processor will fulfill
61  --     more call requests.
62  -----------------------------------------------------------------------------
63
64  VAL INT Proc.Trash  IS 1:                    -- lower bound processor load
65  VAL INT Rep.Delay   IS 90:                   -- report delay (>0 <= 100)
66  VAL INT Frame.Trash IS Frame.Len * (25/100): -- lower bound frame store
67  VAL INT Frame.Bound IS Frame.Len * (75/100): -- upper bound frame store
68  VAL INT Requ.Bound  IS Requ.Len  * (50/100): -- upper bound request store
69
70  -----------------------------------------------------------------------------
71  -- protocols (within processor unit)
72  -----------------------------------------------------------------------------
73  -- token protocol
74  PROTOCOL Proc.Token IS [Token.Size]INT32:
75
76  -- cluster protocol (for transmitting the program to the execution unit)
77  PROTOCOL Cluster
78    CASE
79      start.cluster                    -- start of new program
80      append.cluster                   -- append program fragment
81      cluster;    [Cluster.Size]INT32  -- program cluster
82      end.cluster                      -- end of cluster signal
83  :
84
85  -- demand protocol (just a tag for synchronization)
86  PROTOCOL Demand
87    CASE
88      demand.tag
89  :
90
91  -- buffer protocol (number of buffered elements and delivered token)
92  PROTOCOL Buffer.Token IS INT; [Token.Size]INT32:
93
94  -----------------------------------------------------------------------------
95  -- action codes
96  -----------------------------------------------------------------------------
97
98  VAL INT Execute.Act IS 0: -- execute instruction
99  VAL INT Receive.Act IS 1: -- receive message
100  VAL INT Request.Act IS 2: -- fulfill request
101  VAL INT Report.Act  IS 3: -- report load
102
103  -----------------------------------------------------------------------------
104  -- instruction codes
105  -----------------------------------------------------------------------------
106
107  -- arithmetical-logical instructions
108  VAL BYTE Add.OpC       IS #00(BYTE): -- integer addition
109  VAL BYTE AddC.OpC      IS #01(BYTE): -- integer addition with constant
110  VAL BYTE Sub.OpC       IS #02(BYTE): -- integer subtraction

ADPROC.INC                                                                 Page   3
111  VAL BYTE SubC.OpC      IS #03(BYTE): -- integer subtraction with constant
112  VAL BYTE SubRC.OpC     IS #04(BYTE): -- integer subtraction reverse with c.
113  VAL BYTE Mul.OpC       IS #05(BYTE): -- integer multiplication
114  VAL BYTE MulC.OpC      IS #06(BYTE): -- integer multiplication with constant
115  VAL BYTE Div.OpC       IS #07(BYTE): -- integer division
116  VAL BYTE DivC.OpC      IS #08(BYTE): -- integer division with constant
117  VAL BYTE DivRC.OpC     IS #09(BYTE): -- integer division reverse with const.
118  VAL BYTE Mod.OpC       IS #0A(BYTE): -- integer remainder
119  VAL BYTE ModC.OpC      IS #0B(BYTE): -- integer remainder with constant
120  VAL BYTE ModRC.OpC     IS #0C(BYTE): -- integer remainder reverse with const.
121  VAL BYTE And.OpC       IS #0D(BYTE): -- boolean conjunction
122  VAL BYTE Or.OpC        IS #0E(BYTE): -- boolean disjunction
123  VAL BYTE Equal.OpC     IS #0F(BYTE): -- integer equality
124  VAL BYTE EqualC.OpC    IS #10(BYTE): -- integer equality with constant
125  VAL BYTE Less.OpC      IS #11(BYTE): -- integer strict order
126  VAL BYTE LessC.OpC     IS #12(BYTE): -- integer strict order with constant
127  VAL BYTE LessRC.OpC    IS #13(BYTE): -- integer strict order reverse with c.
128  VAL BYTE LessEq.OpC    IS #14(BYTE): -- integer reflexive order
129  VAL BYTE LessEqC.OpC   IS #15(BYTE): -- integer reflexive order with const.
130  VAL BYTE LessEqRC.OpC  IS #16(BYTE): -- integer reflexive order rev. with c.
131  VAL BYTE Neg.OpC       IS #17(BYTE): -- integer negation
132  VAL BYTE Not.OpC       IS #18(BYTE): -- boolean negation
133  VAL BYTE Failed.OpC    IS #19(BYTE): -- test for error value
134
135  -- control instructions
136  VAL BYTE CopyI.OpC     IS #20(BYTE): -- copy integer value
137  VAL BYTE CopyP.OpC     IS #21(BYTE): -- copy pointer value
138  VAL BYTE DelP.OpC      IS #22(BYTE): -- delete pointer value
139  VAL BYTE Split.OpC     IS #23(BYTE): -- split control flow
140  VAL BYTE Switch.OpC    IS #24(BYTE): -- switch control flow
141  VAL BYTE Jump.OpC      IS #25(BYTE): -- route control flow
142  VAL BYTE Wait.OpC      IS #26(BYTE): -- wait for synchronization
143  VAL BYTE Const.OpC     IS #27(BYTE): -- generate long constant
144
145  -- function call instructions
146  VAL BYTE FastCall.OpC  IS #30(BYTE): -- standard function call
147  VAL BYTE TailCall.OpC  IS #31(BYTE): -- tail-recursive function call
148  VAL BYTE Context.OpC   IS #32(BYTE): -- submit own result address
149  VAL BYTE Apply.OpC     IS #33(BYTE): -- request activation
150  VAL BYTE Send.OpC      IS #34(BYTE): -- send argument to callee
151  VAL BYTE SendC.OpC     IS #35(BYTE): -- send constant argument to callee
152  VAL BYTE Submit.OpC    IS #36(BYTE): -- request submission of curried arg.
153  VAL BYTE Return.OpC    IS #37(BYTE): -- return result value
154  VAL BYTE ReturnC.OpC   IS #38(BYTE): -- return constant result value
155  VAL BYTE Terminate.OpC IS #39(BYTE): -- terminate activation
156
157  -- storage instructions
158  VAL BYTE Alloc.OpC     IS #40(BYTE): -- allocate tuple
159  VAL BYTE Read.OpC      IS #41(BYTE): -- read tuple field
160  VAL BYTE WriteI.OpC    IS #42(BYTE): -- write integer into tuple field
161  VAL BYTE WriteP.OpC    IS #43(BYTE): -- write pointer into tuple field
162  VAL BYTE WriteC.OpC    IS #44(BYTE): -- write integer constant into tuple f.
163
164  -----------------------------------------------------------------------------
165  -- End of ADPROC.INC

ADPROC.INC                                                                 Page   4
 166  -----------------------------------------------------------------------------

References

402

Bibliography

[Abelson and Sussman, 1985] Harold Abelson and Gerald J. Sussman. Structure and Interpretation of Computer Programs. The MIT Press, Cambridge, Massachusetts, 1985.

[Aho et al., 1986] Alfred A. Aho, Ravi Sethi, and Jeffrey D. Ullman. Compilers — Principles, Techniques and Tools. Addison-Wesley, 1986.

[Arvind and Culler, 1986] Arvind and D. E. Culler. Dataflow Architectures. Annual Reviews in Computer Science, 1:225–253, 1986.

[Arvind and Culler, 1987] Arvind and David E. Culler. Resource Requirements of Dataflow Programs. Computation Structures Group Memo 280, Laboratory for Computer Science, Massachusetts Institute of Technology, Cambridge, MA, December 1987.

[Arvind and Ekanadham, 1987] Arvind and Kattamuri Ekanadham. Future Scientific Programming on Parallel Machines. Computation Structures Group Memo 272, Laboratory for Computer Science, Massachusetts Institute of Technology, Cambridge, MA, March 1987. Revised February 1988.

[Arvind and Iannucci, 1987] Arvind and Robert A. Iannucci. Two Fundamental Issues in Multiprocessing. Computation Structures Group Memo 226-6, Laboratory for Computer Science, Massachusetts Institute of Technology, Cambridge, MA, May 1987. Also in: Proceedings of the DVLR-Conference on Parallel Processing in Science and Engineering, Bonn/Bad Godesberg, June 25–26, 1987.

[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.

[Arvind et al., 1988a] Arvind, David E. Culler, and Gino K. Maa. Assessing the Benefits of Finegrained Parallelism in Dataflow Programs. Computation Structures Group Memo 279, Laboratory for Computer Science, Massachusetts Institute of Technology, Cambridge, MA, March 1988. Revised June 1988. To appear in the International Journal of Supercomputer Applications.

[Arvind et al., 1988b] Arvind, Steve Heller, and Rishiyur S. Nikhil. Programming Generality and Parallel Computers. Computation Structures Group Memo 287, Laboratory for Computer Science, Massachusetts Institute of Technology, Cambridge, MA, May 1988. Also: Fourth International Symposium on Biological and Artificial Intelligence Systems, Trento, Italy, September 18–22, 1988.

[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, 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.

[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.

[Bjornson et al., 1987] Robert Bjornson, Nicholas Carriero, David Gelernter, and Jerrold S. Leichter. Linda, the Portable Parallel. Research Report YALE/DCS/RR-520, Department of Computer Science, Yale University, New Haven, Connecticut, February 1987. Revised January 1988.

[Boyle, 1989] James M. Boyle. Deriving Parallel Programs from Pure Lisp Specifications by Program Transformation. Lecture held at RISC-Linz, Johannes Kepler University, Linz, Austria, June 1989.

[Buchberger, 1978] Bruno Buchberger. Computer-Trees and their Programming. In Dauchet, editor, 4th Colloquium “Trees in algebra and programming”, pages 1–18, University of Lille, February 16–18, 1978. Also: Technical Report 78-11, RISC-Linz, Johannes Kepler University, Linz, Austria, 1978.

[Buchberger, 1985] Bruno Buchberger. The L-Machine: An Attempt at Parallel Hardware for Symbolic Computation. In J. Calmet, editor, 3rd International Conference on Algebraic Algorithms and Error-Correcting Codes, volume 229 of Lecture Notes in Computer Science, pages 333–347, Grenoble, France, July 15–19, 1985. Springer. Also: Technical Report 85-24, RISC-Linz, Johannes Kepler University, Linz, Austria, 1985.

[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.

[Church, 1941] A. Church. The Calculi of Lambda-Conversion. Princeton University Press, Princeton, New Jersey, 1941.

[Cousineau et al., 1985] Guy Cousineau, Gèrard Huet, and Larry Paulson. The ML Handbook. University of Cambridge, May 1985.

[Dennis, 1984] Jack B. Dennis. Data Flow Computation. In Control Flow and Data Flow — Concepts of Distributed Programming, International Summer School, pages 343–398, Marktoberdorf, West Germany, July 31 – August 12, 1984. Springer, Berlin.

[Foster and Taylor, 1989] Ian Foster and Stephen Taylor. Strand — New Concepts in Parallel Programming. Prentice-Hall, Englewood Cliffs, New Jersey, 1989.

[Funk, 1989] Gerhard Funk. A Verification System for Total Correctness of the Parallel L-Language. PhD thesis, Johannes Kepler University, Linz, Austria, June 1989. Also: Technical Report 89-26, RISC-Linz, Johannes Kepler University, Linz, Austria, 1989.

[Gao et al., 1988] Guang R. Gao, Rene Tio, and Herbert H. J. Hum. Design of an Efficient Dataflow Architecture without Data Flow. In Proceedings of the International Conference on Fifth Generation Computer Systems, pages 861–868, Japan, 1988. ICOT.

[Goldberg, 1988] Benjamin F. Goldberg. Multiprocessor Execution of Functional Programs. Research Report YALEU/DCS/RR-618, Department of Computer Science, Yale University, April 1988.

[Gurd et al., 1985] J. R. Gurd, C. C. Kirkham, and I. Watson. The Manchester Prototype Dataflow Computer. Communications of the ACM, 28(1):34–52, January 1985.

[Harper et al., 1988] R. Harper, R. Milner, and M. Tofte. The Definition of Standard ML — Version 2. Technical Report LFCS-88-62, Laboratory for Foundations of Computer Science, Department of Computer Science, University of Edinburgh, UK, 1988.

[Herath et al., 1985] Jayantha Herath, Nobuo Saito, Kenji Toda, Yoshinori Yamaguchi, and Toshitsugu Yuba. NOT(OPERATION) for High Speed Dataflow Computer Systems. In Proceedings of the First International Conference on Supercomputing Systems, pages 524–533, St. Petersburg, Florida, December 16–20, 1985. IEEE Computer Society.

[Hiraki et al., 1984] Kei Hiraki, Toshio Shimada, and Kenji Nishida. A Hardware Design of the SIGMA-1, a Data Flow Computer for Scientific Computations. In Proceedings of the 1984 International Conference on Parallel Processing, pages 524–531. IEEE Computer Society, August 1984.

[Hiraki et al., 1986] Kei Hiraki, Kenji Nishida, Satoshi Sekiguchi, and Toshio Shimada. Maintenance Architecture and its LSI Implementation of a Dataflow Computer with a Large Number of Processors. In Proceedings of the 1986 International Conference on Parallel Processing, pages 584–591. IEEE Computer Society, 1986.

[Hoare, 1985] C. A. R. Hoare. Communicating Sequential Processes. Series in Computer Science. Prentice Hall, Englewood Cliffs, NJ, 1985.

[Hudak et al., 1990] Paul Hudak, Philip Wadler, Arvind, Brian Boutel, John Fairbairn, Joseph Fasel, Kevin Hammond, John Hughes, Thomas Johnsson, Dick Kieburtz, Rishiyur Nikhil, Simon Peyton Jones, Mike Reeve, David Wise, and Jonathan Young. Report on the Programming Language Haskell – A Non-strict, Purely Functional Language (Version 1.0). Research report, Department of Computing Science, University of Glasgow, Glasgow, UK, April 1990.

[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, 1988a] Inmos. Occam 2 Reference Manual. Prentice Hall, New York, 1988.

[Inmos, 1988b] Inmos Limited, Bristol, UK. The Transputer Databook, November 1988.

[Jensen, 1988] Jensen & Partners International. TopSpeed Modula-2 for IBM Personal Computer and Compatibles, User’s Manual, 1988.

[Johnson, 1985] Thomas Johnson. Lambda Lifting: Transforming Programs to Recursive Equations. In Jean-Pierre Jouannaud, editor, Functional Programming Languages and Computer Architecture, volume 201 of Lecture Notes in Computer Science, pages 190–203, Nancy, France, September 16–19, 1985. Springer, Berlin.

[Johnsson, 1987] Thomas Johnsson. Compiling Lazy Functional Languages, Part I. PhD thesis, Department of Computer Sciences, Chalmers University of Technology, Göteborg, Sweden, 1987.

[Kusche, 1988] Klaus Kusche. LL User Manual. Master’s thesis, Johannes Kepler University, Linz, Austria, 1988. Also: Technical Report 88-25, RISC-Linz, Johannes Kepler University, Linz, Austria, 1988.

[Landin, 1966] P. J. Landin. The Next 700 Programming Languages. Communications of the ACM, 9(3):157–166, March 1966.

[Loidl, 1992] Hans Wolfgang Loidl. Compiling Functional Programs to Dataflow Code. In Proceedings of the Workshop on Parallel Computers and Programming Languages, number 10 in Communications of the German Computer Society, Group for Parallel Algorithms, Computer Architectures and System Software, pages 78–86, Dagstuhl Castle, Germany, February 26–28, 1992. Also: Technical Report ACPC/TR 92-10, ACPC Technical Report Series, Austrian Center for Parallel Computation, July 1992.

[McCarthy, 1960] J. McCarthy. Recursive Functions of Symbolic Expressions and their Computation by Machines. Communications of the ACM, 3(4):184–195, April 1960.

[McGraw et al., 1985] James McGraw, Stephen Skedzielewski, Stephen Allan, Rod Oldehoeft, John Glauert, Chris Kirkham, Bill Noyce, and Robert Thomas. SISAL — Streams and Iteration in a Single Assignment Language, Language Reference Manual, Version 1.2. Technical Report M-146, Rev. 1, Lawrence Livermore National Laboratory, University of California, March 1985.

[Nikhil, 1987] Rishiyur S. Nikhil. Id World Reference Manual (for Lisp Machines). Computation Structures Group Memo 280, Laboratory for Computer Science, Massachusetts Institute of Technology, Cambridge, Massachusetts, December 1987.

[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, 1987] Simon L. Peyton Jones. The Implementation of Functional Programming Languages. Prentice-Hall, New York, 1987.

[Rudalics, 1988a] Martin Rudalics. Multiprocessor List Memory Management. PhD thesis, Johannes Kepler University, Linz, Austria, 1988. Also: Technical Report 88-87, RISC-Linz, Johannes Kepler University, Linz, Austria, 1988.

[Rudalics, 1988b] Martin Rudalics. Multiprocessor Reference Counting. In Chris Jesshope and K. D. Reinartz, editors, CONPAR 88, pages 32–41, Manchester, England, September 12–16, 1988. Cambridge University Press, Cambridge, UK.

[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.

[Schreiner, 1989] Wolfgang Schreiner. Report on the Eve Programming Language. Technical Report 89-43, RISC-Linz, Johannes Kepler University, Linz, Austria, November 1989.

[Schreiner, 1990] Wolfgang Schreiner. ADAM — An Abstract Dataflow Machine and Its Transputer Implementation (Extended Abstract). In Workshop on Parallel Computation, Schloß Seggau at Graz, Austria, June 24–30, 1990.

[Schreiner, 1991] Wolfgang Schreiner. ADAM — An Abstract Dataflow Machine and Its Transputer Implementation. In Distributed Memory Computing, 2nd European Conference, EDMCC2, pages 392–401, Munich, Germany, April 22–24, 1991. Volume 487 of Lecture Notes in Computer Science, Springer, Berlin. Also: Technical Report 90-53, RISC-Linz, Johannes Kepler University, Linz, Austria, November 1990. Also: Technical Report 91-3, Austrian Center for Parallel Computation, January 1991.

[Shapiro, 1987] Ehud Shapiro, editor. Concurrent Prolog, Collected Papers, Volumes 1 and 2. MIT Press, Cambridge, Massachusetts, 1987.

[Shimada et al., 1986] Toshio Shimada, Kei Hiraki, Kenji Nishida, and Satosi Sekiguchi. Evaluation of a Prototype Data Flow Processor of the SIGMA-1 for Scientific Computation. In Proceedings of the 13th Annual International Symposium on Computer Architecture, pages 226– 234, Tokyo, Japan, June 2–5, 1986. IEEE Computer Society.

[Siegl, 1990a] Kurt Siegl. A Parallel Version of Buchberger’s Algorithm in STRAND88. Technical Report 90-17.0, RISC-Linz, Johannes Kepler University, Linz, Austria, April 1990.

[Siegl, 1990b] 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.

[Tevanian Jr. et al., 1987] Avadis Tevanian Jr., Richard F. Rashid, David B. Golub, David L. Black, Eric Cooper, and Michael W. Young. Mach Threads and the Unix Kernel: The Battle for Control. Technical Report CMU-CS-87-149, Computer Science Department, Carnegie Mellon University, Pittsburgh, PA 15213, USA, August 1987.

[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.

[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.

[Yamaguchi et al., 1983] Yoshinori Yamaguchi, Kenji Toda, and Toshitsugu Yuba. A Performance Evaluation of a Lisp-Based Data-Driven Machine (EM-3). In Proceedings of the 10th Annual International Symposium on Computer Architecture, pages 363–369, Stockholm, Sweden, 1983. IEEE Computer Society.

[Yamaguchi et al., 1984] Yoshinori Yamaguchi, Kenji Toda, Jayanta Herath, and Toshitsugu Yuba. EM-3: A Lisp-Based Data-Driven Machine. Future Generation Computer Systems, pages 524– 532, November 1984.

[Yuba et al., 1983] Toshitsugu Yuba, Yoshinori Yamaguchi, and Toshio Shimada. A Control Mechanism of a Lisp-Based Data-Driven Machine. Information Processing Letters, pages 139–143, April 1983.

[Yuba et al., 1985] Toshitsugu Yuba, Toshio Shimada, Kei Hiraki, and Hiroshi Kashiwagi. SIGMA-1: A Dataflow Computer for Scientific Computations. Computer Physics Communications, pages 141–148, 1985.

[Yuba, 1986] Toshitsugu Yuba. Research and Development Efforts on a Dataflow Computer Architecture in Japan. Journal of Information Processing, 9(2):51–60, September 1986.