Sie sind auf Seite 1von 55

Diplomarbeit

Konzeption und Entwicklung eines neuen


Compiler „CESC“ zur Implementierung
von Prozeduren als atomare Tasks.

zur Erlangung des akademischen Grades


Diplom-Informatiker (FH)

vorgelegt dem Fachbereich Mathematik,


Naturwissenschaften und Informatik der
Fachhochschule Gießen-Friedberg

Sven Wagner

im August 2007

Referent: Prof. Dr. Hellwig Geisse


Korreferent: Prof. Dr. Thomas Letschert
Inhaltsverzeichnis

1. Zusammenfassung 6

2. Einleitung 8
2.1. Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.2. Zielsetzung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2.3. Aufbau . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10

3. Grundlagen 11
3.1. Die Prozedur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.2. Der Task . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.3. Der Frame Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.4. Der Current Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.5. Programmbeispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

4. Die Sprache CES 20


4.1. Einführung in die Sprache von CES . . . . . . . . . . . . . . . . . . . . . . 20
4.1.1. Eigenschaften von CES . . . . . . . . . . . . . . . . . . . . . . . . 20
4.1.2. Einordnung der Programmiersprache CES . . . . . . . . . . . . . . 21
4.1.3. Lexikalische Konventionen . . . . . . . . . . . . . . . . . . . . . . . 21
4.1.4. Beispielprogramme . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
4.1.5. Datentypen und Variablen . . . . . . . . . . . . . . . . . . . . . . . 23
4.1.6. Der Präprozessor . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.2. Die CES Prozedur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23
4.2.1. Definition einer CES Prozedur . . . . . . . . . . . . . . . . . . . . 23
4.2.2. Die Parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.3. Der CES Aufruf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.3.1. Vorsicht Falle! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.4. Die CES Variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26

5. Das Backend 27
5.1. Die Run Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.2. Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.2.1. Der Task Frame . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.2.2. Der Storage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.3. Die Header Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
5.4. Der von CESC generierte C Code . . . . . . . . . . . . . . . . . . . . . . . 35
5.4.1. C Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

2
5.4.2. CES Prozedur Aufruf . . . . . . . . . . . . . . . . . . . . . . . . . 38
5.4.3. Verwendung einer CES Variablen . . . . . . . . . . . . . . . . . . . 39
5.4.4. Den Storage erzeugen . . . . . . . . . . . . . . . . . . . . . . . . . 40
5.4.5. Kopie des Current Stack auf den Frame Stack . . . . . . . . . . . . 41
5.4.6. Die Print Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

6. Der CES Compiler 43


6.1. Der Scanner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
6.2. Der Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
6.3. Der abstrakte Syntaxbaum . . . . . . . . . . . . . . . . . . . . . . . . . . 44
6.4. Die semantische Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
6.5. Die Codegenerierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
6.6. Der Pretty Printer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45

7. Ausblick 47

A. CESC Handbuch 50
A.1. Dateien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
A.1.1. Makefile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
A.1.2. CESC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
A.1.3. Scanner . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
A.1.4. Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
A.1.5. Run Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
A.1.6. Pretty Printer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
A.1.7. Beispielprogramm . . . . . . . . . . . . . . . . . . . . . . . . . . . 52

B. Testumegbung 53
B.1. Dateien im Verzeichnis CESC/Tests . . . . . . . . . . . . . . . . . . . . . 53

3
Abbildungsverzeichnis

2.1. Activation Frames[1] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9


2.2. Task Frames[1] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

3.1. Frame Stack mit Tasks und Storages . . . . . . . . . . . . . . . . . . . . . 13


3.2. Frame Stack während der Ausführung von TaskA . . . . . . . . . . . . . . 14
3.3. Frame Stack nach der Ausführung von TaskA . . . . . . . . . . . . . . . . 14
3.4. Frame Stack während der Ausführung des CES Programms . . . . . . . . 16
3.5. Pretty Printer Ausgabe . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

4.1. Ablaufdiagramm eines Kompiliervorgangs . . . . . . . . . . . . . . . . . . 20


4.2. Informationsaustausch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24

5.1. Pretty Printer Ausgabe mit dem „ancestor“ Task program(;;) . . . . . . . 31


5.2. Frame Stack nach der Ausführung von program(;;) . . . . . . . . . . . . . 33
5.3. Pretty Printer Ausgabe mit dem „ancestor“ Task program(;;) . . . . . . . 34
5.4. Frame Stack nach der Ausführung von program(;;) . . . . . . . . . . . . . 36

6.1. Der abstrakte Syntaxbaum . . . . . . . . . . . . . . . . . . . . . . . . . . 45

4
Listings

2.1. Pseudoprogrammbeispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8

3.1. Beispiel if Anweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12


3.2. TaskA wird ersetzt durch TaskB und TaskC . . . . . . . . . . . . . . . . . 13
3.3. C Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.4. CES Programm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

4.1. CES Programm Hallo, Welt! . . . . . . . . . . . . . . . . . . . . . . . . . . 22


4.2. CES Programm Euklid’scher Algorithmus . . . . . . . . . . . . . . . . . . 22
4.3. Beispiel mit Fehlerquelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

5.1. Funktion aus der Run Time zur Ausfürung eines Task . . . . . . . . . . . 27
5.2. TASK_FRAME Datenstruktur aus der run_time.h . . . . . . . . . . . . . 27
5.3. Beispiel einer von CESC generierten Task Struktur im Header File . . . . 28
5.4. Beispiel zur Parameterprüfung . . . . . . . . . . . . . . . . . . . . . . . . 29
5.5. Datenstruktur des Tasks euclid(b,c;;double erg2) . . . . . . . . . . . . . . 29
5.6. STORAGE_FRAME Datenstruktur . . . . . . . . . . . . . . . . . . . . . 31
5.7. Storage Funktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.8. CES Programm zur Storage Erzeugung . . . . . . . . . . . . . . . . . . . . 32
5.9. Datenstruktur eines Storage für double a . . . . . . . . . . . . . . . . . . . 33
5.10. CES Prozedur printVar(double a;;) . . . . . . . . . . . . . . . . . . . . . . 34
5.11. Datenstruktur des Tasks print_Var(double a;;) . . . . . . . . . . . . . . . 35
5.12. CES Programmbeispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35

6.1. C Quellcode des Pretty Printers . . . . . . . . . . . . . . . . . . . . . . . . 45

5
1. Zusammenfassung
Die vorliegende Diplomarbeit mit dem Titel Konzeption und Entwicklung eines
neuen Compiler „CESC“ zur Implementierung von Prozeduren als atomare
Tasks beschreibt eine Neuimplementierung von Prozeduren als atomare Tasks.
Als Grundlage für diese Diplomarbeit dient die Veröffentlichung Task Frames [1] von
Dr. Burkhard Steinmacher-Burow (IBM Deutschland Entwicklung GmbH) sowie die Po-
werPoint Präsentation The Execution of Computer Applications[2] (bei Interesse bitte
Email an steinmac@de.ibm.com). In der Arbeit Task Frames[1] wird das Konzept von
Task Frames beschrieben sowie eine grobe Implementierung von Prozeduren als atomare
Tasks. Diese Diplomarbeit greift das von Dr. Steinmacher-Burow entwickelte Konzept
auf und zeigt, dass es möglich ist die beschriebene Theorie und den praktischen Ansatz
für das Execution System in eine weiterbenutzbare Neuimplementierung umzusetzen.
Der entwickelte Compiler CESC (C with Execution System Compiler) basiert auf der
Implementierung des Compilers für die Sprache SPL von Dr. Hellwig Geisse, Professor an
der Fachhochschule Giessen-Fiedberg, der zu Lehrzwecken entwickelt wurde. Ziel war es
den Aufwand für die Entwicklung des Compilers zu minimieren, deswegen wurde auf eine
altbewährte Entwicklung im Compilerbau zurückgegriffen und eine getestete Struktur des
SPL Compilers benutzt.
Die Programmiersprache C wurde durch ein neues Sprachkonzept erweitert und als
neue Sprache CES definiert. Das C in CES steht für die Programmiersprache C, da
diese als Basis dient, um die Sprache CES zu entwickeln. Es ist weiterhin möglich die
Programmiersprache C zu benutzen. ES bedeutet Execution System. Das Execution Sys-
tem steuert die Ausführung einer Prozedur als Task, hierzu ist für die Ausführung der
atomaren Tasks eine Verwaltung nötig, die ebenfalls vom ES übernommen wird.
Der im Rahmen dieser Arbeit entwickelte Compiler CESC generiert aus CES Quelltext
einen C Quelltext. Alle in CES definierten Prozeduren werden als atomare Tasks von dem
CES Compiler übersetzt, das heisst sie werden als C Funktion implementiert. Durch eine
eigens entwickelte Run Time, die zusammen mit dem vom CES Compiler generierten C
File zum ausführbaren Programm kompiliert wird, werden die erzeugten Tasks als neu
implementierte Funktionen ausgeführt.
Nach einer Einführung in das Thema in Kapitel 2 beinhaltet die Diplomarbeit in Kapi-
tel 3 eine Beschreibung der neu definierten und verwendeten Begriffe für die Entwicklung
von CES und dem Compiler CESC. Eine genaue Beschreibung der Erweiterung von C
zu CES erfolgt in Kapitel 4. Das Kapitel 5 ist der Kern dieser Diplomarbeit. In diesem
Kapitel wird genaustens beschrieben wie die Ausführung der Tasks erfolgt und welche
Strukturen dazu nötig sind. Kapitel 6 umfasst wie der CES Compiler entwickelt wur-
de und welche Phasen zur Übersetztung nötig sind. Zusätzlich wird in diesem Kapitel
beschrieben welche Konstrukte nötig waren um das beschriebene Konzept der Neuimple-

6
mentierung durch CESC zu ermöglichen.
Das Ziel dieser Diplomarbeit ist nicht eine neue Programmiersprache zu entwickeln,
sondern das Prinzip einer Neuimplementierung einer Prozedur als atomarer Task zu
verdeutlichen.

7
2. Einleitung
Programmiersprachen beinhalten in ihrem Quellcode mitunter Befehle, Definitionen von
Funktionen und Aufrufe von Funktionen. Zur Komplexitätsbewältigung wird das Kon-
zept der Modularisierung verwendet. Das heisst, dass ein komplexes Programm durch
kleinere Teilprogramme (Funktionen) beschrieben wird. Diese können unabhängig von-
einander definiert werden, funktionieren aber als Ganzes. CES benutzt genau diese Art
von Vereinfachung. In CES ist es wie in C möglich aus mehreren Dateien ein Programm
zu erstellen. Das Teilprogramm wird in CES als CES Prozedur definiert. Die Implemen-
tierung der CES Prozedur erfolgt durch eine C Funktion.
Ein CES Quelltext ist ähnlich wie ein C Quelltext. Der Unterschied liegt in der Ausfüh-
rung von CES Prozeduren gegenüber Prozeduren oder Funktionen aus anderen Sprachen.
In anderen Sprachen wird ein Ausfruf einer Prozedur sofort ausgeführt. In CES, innerhalb
der ausführenden Prozedur, werden alle CES Prozedur Aufrufe gesammelt. Am Ende der
ausführenden Prozedur werden die gesammelten Aufurfe dem Execution System überge-
ben. Das Execution System führt dann den nächsten Aufruf aus. Der Ablauf einer CES
Prozedur ist somit ein atomarer Task, da er nicht von dem Ablauf anderer Prozeduren
abhängig ist.
Eine CES Prozedur wird ausgeführt durch einen Task Frame. Eine Prozedur aus einer
anderen Programmiersprache wird durch ein Activation Frame ausgeführt. Ein Beispiel
[1] soll nochmal den Unterschied der Ausführung zwischen dem Konzept der Activation
Frames und dem der Task Frames verdeutlichen.
1 a () { ... }
2 b() { ... }
3 c () { ... a () . . . }
4 d() { ... b() . . . c () . . . }
Listing 2.1: Pseudoprogrammbeispiel

Die Prozedur „d“ wird ausgeführt. Bei dem bekannten Konzept der Activation Frames
ist die Prozedur „d“ von Anfang bis Ende auf dem Prozedurstack vorhanden. Betrachtet
man das Konzept der Task Frames, so kann man sagen, dass ein Task durch andere
„ersetzt“ wird. Tatsächlich ist es aber so, dass das Execution System speichert in welcher
Reihenfolge die Tasks ausgeführt werden müssen. Erst wenn ein Task beendet ist, wird
der nächste Task ausgeführt. In dem aufgeführten Beispiel wird der Task d durch die
Tasks b und c „ersetzt“.

8
Abbildung 2.1.: Activation Frames[1]

Abbildung 2.2.: Task Frames[1]

2.1. Motivation
Bewährte Systeme wie zum Beispiel Seti@home oder auch diverse Datenbanken zeigen,
dass es bereits Anwendungsgebiete gibt, die das Konzept von atomaren Tasks verwenden
und erfolgreich einsetzen.
Im Hinblick auf die aktuelle Entwicklung zu Mehrprozessorsystemen entwickelt die
nachfolgende Diplomarbeit ein Execution System, dass die einzelnen Task Frames parallel
auf mehreren CPUs ausführt.

2.2. Zielsetzung
Ziel ist es Prozeduren als atomare Tasks zu implementieren. Es wurde die Program-
miersprache C erweitert und verwendet. Hierbei ist darauf geachtet worden, dass die
Programmiersprache C weiterhin verwendet werden kann. Dies war Vorrausetzung für
diese Arbeit, da es nicht Ziel ist in der vorgegebenen Zeit eine neue Sprache zu entwi-
ckeln, sondern Vorteile der Programmiersprache C nutzen zu können. Zum Beispiel soll
es weiterhin möglich sein C Bibliotheken per Include-Anleitung für eine seperate Kompi-
lierung benutzten zu können. C biete außerdem die Möglichkeit der Zeigerarithemtik mit
der einfach Referenzen gebildet werden können. Es wurde auch bewusst eine dem Autor
bekannte Programmiersprache gewählt damit ein zügiges Implementieren garantiert ist.
Zur Präsentation des Programmablaufs soll ein Pretty Printer implementiert werden, der

9
den Stack mit allen wichtigen Informationen zum Verstehen des aktuellen Status und der
zukünftigen Ausführung des Programms ausgibt. Ergebnisse dieser Diplomarbeit dienen
als Basis für die Weiterentwicklung von CES und dem Execution System (Kapitel „Das
Backend“), um einmal atomare Tasks parallel auf mehreren Prozessoren ausführen zu
können.

2.3. Aufbau
Zu Beginn der Diplomarbeit wurde ein Beispielprogramm entwickelt, welches die Aus-
gabe des CES Compilers darstellt. Hierzu wurde aus der Veröffentlichung Task Frames
[1] von Dr. Steinmacher-Burow die doch sehr einfach gehaltene Implementierung aufge-
griffen und als Basis für eine erste Testimplementierung benutzt. Es sollte klar werden
welche Codefragmente für die Neuimplementierung von Prozeduren als Tasks vom CES
Compiler generiert werden sollen. Aus diesem Beispielprogramm wurde ersichtlich wie die
Run Time implementiert werden muss, um die gleiche Funktion zu beinhalten. Daraufhin
erfolgte die Entwicklung des CES Compilers zur Generierung eines C Files, dass zusam-
men mit der Run Time von einem C Compiler übersetzt, ein ausführbares Programm
erzeugt, welches das gleiche Ergebnis liefert.
Im Konzept der Activation Frames dient der Activation Frame zur Übermittlung von
Informationen zwischen den Subfunktionen. Für diesen Mechanismus mußte eine Alter-
native geschaffen werden. Im CES dienen sogenannte Storages als Vermittler zwischen
den Subtasks.
Der Aufbau der Stacks zum Speichern und verwalten der Tasks wurde entwickelt. Die-
ser Stack ist ähnlich dem Funktionsstack in C. Passende Strukturen für den Stack und die
Storages wurden anschliessend entwickelt. Es mussten Datenstrukturen definiert werden
und welche Daten diese Strukturen enthalten müssen, um die gewünschte Funktionalität
erbringen zu können.
Der nächste Schwerpunkt der Diplomarbeit beinhaltet die Entwicklung des CES Com-
pilers. Dieser umfasst folgende Phasen: Der Scanner dient dazu den CES-Quelltext ein-
zulesen und sogenannte Tokens für den Parser zu generieren. Die im Parser definierte
kontexfreie Grammatik dient zur Überprüfung des Tokenstroms aus dem Scanner. Bei Zu-
treffen eines eingelesenen Tokenstroms in den endlichen Automaten des Parsers wird ein
vordefinierte Aktion ausgeführt. Folge hiervon ist die Erstellung eines abstrakten Syntax-
baums. Parallel kommt es zu einer Überprüfung der Semantik. Mit Hilfe des Syntaxbau-
mes werden zuerst Symboltabellen für die einzelnen Prozeduren erstellt, um anschliessend
die Codegenerierung in C zu ermöglichen. Abschließend wird das vom CES-Compiler ge-
nerierte C-File zusammen mit der Runtime als Eingabedatum für den C-Compiler einge-
geben. Das Endprodukt ist ein ausführbares Programm, dass jeden Aufruf einer Prozedur
als atomarer Task ausführt.

10
3. Grundlagen
Diese Diplomarbeit baut auf altbewährte und bekannte Grundlagen aus der Informatik
auf. Die Begriffe Prozedur und Task sind typische Begriffe aus der Informatik. In diesem
Kapitel werden die in der Arbeit verwendeten Begriffe beschrieben und erklärt um die
neu definierten Konzepte von CES verständlicher zu machen. Am Ende dieses Kapitels
verdeutlicht ein Programmbeispiel die beschriebenen Begriffe aus der vorliegenden Arbeit
in dem dazugehörigen Kontext.

3.1. Die Prozedur


Eine Prozedur ist eine Spezifikation von aufeinanderfolgenden Befehlen, welche in ei-
nem zusammenhängenden Kontext bearbeitet werden sollen und bei gleicher Eingabe
dieselbe Ausgabe liefern. In der Informatik ist dies einem Unterprogramm gleichzuset-
zen. Funktionen sind ähnlich definiert, haben aber im Vergleich zu Prozeduren einen
Rückgabewert. Ein definierter CES Task entspricht einer Prozedur. Diese Prozedur ist
transparent, eigenständig und kann durch die Neuimplementierung unabhängiger aus-
geführt werden als Prozeduren aus bekannten Programmiersprachen. Diese Eigenschaf-
ten bezeichnen eine atomare Prozedur. Eine Prozedur hat die Aufgabe ein vorliegendes
Programm in Module zu unterteilen. Diese Module fassen Teile eines Programms unter
eigenem Namen zusammen. Prozeduren arbeiten nach dem EVA Prinzip; sie können Pa-
rameter als Eingabe erhalten, verarbeiten diese und liefern Ergebnisse. Diese Ergebnisse
können berechnete Werte sein, sowie Ausgaben auf dem Bildschirm oder in einer Da-
tei. Eine Prozedur wird aktiviert indem ihr definierter Name aufgerufen wird. Beinhaltet
eine Prozedur weitere Prozeduraufrufe, so werden diese je nach Implementierung der
Prozeduren sofort ausgeführt. Bei konventionellen Programmiersprachen, die nach dem
Prinzip der Sequentialisierung arbeiten, sind Funktionen Vermittler zwischen anderen
Funktionen. In der Sprache C bedeutet diese eine starke Abhängigkeit der Funktionen
untereinander.

3.2. Der Task


Ein Task im herkömmlichen Sinne ist eine Zusammenfassung von Befehlen, die aus dem
Speicher geladen und ausgeführt werden. In der Sprache CES ist ein Task eine Prozedur
mit Befehlen. Ein Befehl kann ein Aufruf eines anderen Task sein. Der Task wird in den
Speicher geladen und kann als Ganzes ausgeführt werden. Beinhaltet ein Task andere
Tasks, so wird von dem Excution System gespeichert welche Tasks als nächstes ausgeführt
werden müssen. Es kann immer nur ein Task ausgeführt werden. Dies bedeutet es ist nicht

11
wie bei Activation Frames möglich, dass ein Task einen anderen Task sofort aktiviert und
ausgeführt wird. Betrachtet man den Ablauf auf dem Stack so gewinnt man den Eindruck,
dass der aktuelle Task durch seine Aufrufe ersetzt wird.

3.3. Der Frame Stack


Der Frame Stack ist ein Stapel mit dem LIFO Prinzip. Dieser Stapel beinhaltet sogenann-
te Frames. Ein Frame hat immer die gleiche Speichergrösse. Dies wurde zur Vereinfachung
der Implementierung des Frame Stacks gewählt. Die Verarbeitung der Frames auf dem
Frame Stack wurde somit vereinfacht. Es wird immer nur der oberste Frame auf dem Fra-
me Stack von der Run Time ausgeführt. Dies ist die gewöhnliche depth-first Ausführung
von Prozeduren. Ist der oberste Frame ein Storage, so wird der Frame Stack Pointer nur
auf den nächsten Task gesetzt. Die Abbildung 3.1 zeigt ein Beispiel des Frame Stacks mit
Task Frames und Storage Frames. fsp ist der Frame Stack Pointer, der auf den obersten
Frame zeigt. fsp_bottom zeigt immer auf den „Boden“ des Frame Stacks und dient zur
Berechnung der Indizes der einzelnen Frames auf dem Stack.

3.4. Der Current Stack


In der einfachen Implementierung dieser Diplomarbeit wurde ein zusätzlicher Stack er-
zeugt, der sogenannte Current Stack. Der Current Stack ist ein Top Down Stack, d.h.
jeder neue Task wird unter dem letzten Task erzeugt.
Der Current Stack dient dazu Aufrufe von CES Prozeduren als Task Frames zu sam-
meln, solange der Eltern Task ausgeführt wird. Ist der Eltern Task beendet werden alle
gesammelten Kinder Task Frames auf den Frame Stack kopiert. Der Current Stack wird
eingesetzt, da durch Verzweigungen, zum Beispiel if Anweisungen, nicht bekannt ist wie-
viele Tasks erzeugt werden.
Im folgenden Beispiel soll TaskA entweder durch TaskB oder TaskC ersetzt werden, da
diese in TaskA enthalten sind. Zum Zeitpunkt der Übersetzung ist nicht bekannt welchen
Wert a zur Laufzeit annehmen wird.
1 int a ;
2 a = ...;
3 TaskA ( ; ; ) { // E l t e r n Task
4 if (a) {
5 TaskB ( ; ; ) ; // Kind Task
6 TaskC ( ; ; ) ; // Kind Task
7 } else
8 TaskD ( ; ; ) ; // Kind Task
9 }
Listing 3.1: Beispiel if Anweisung
Ein zusätzliches Beispiel soll verdeutlichen wie der Kopiervorgang vom Current Stack
auf den Frame Stack erfolgt.

12
f sp /8 Task E(;x;)

7 Storage(x)

6 Task D

5 Task C

4 Task B(;;a,b)

3 Storage(b)

2 Storage(a)

f sp_bottom /1 Task A

Abbildung 3.1.: Frame Stack mit Tasks und Storages

1 TaskA ( ; ; ) { // E l t e r n Task
2 TaskB ( ; ; ) ; // Kind Task
3 TaskC ( ; ; ) ; // Kind Task
4 }
Listing 3.2: TaskA wird ersetzt durch TaskB und TaskC

Zuerst wird TaskB und dann TaskC auf dem Current Stack erzeugt.
Danach wird der komplette Current Stack auf den Frame Stack kopiert.

3.5. Programmbeispiel
Das in diesem Abschnitt gezeigte Programmbeispiel verdeutlicht die Ausführung eines
CES Programms. Es sollte eindeutig zu erkennen sein, wo der wesentliche Unterschied

13
Frame Stack Current Stack

2 1 2 TaskB

1 TaskA 2 1 TaskC

Abbildung 3.2.: Frame Stack während der Ausführung von TaskA

Frame Stack Current Stack

q
2 TaskB 1 TaskB

q
1 TaskC 2 TaskC

Abbildung 3.3.: Frame Stack nach der Ausführung von TaskA

zum Ablauf eines vergleichbaren C Programms liegt. Beide Programme haben die gleiche
Definition und das gleiche Ergebnis: Den Wert 100.0 der Variable a auf dem Monitor
auszugeben. Die Ausführung des Programms ist allerdings unterschiedlich.
Bei dem bekannten C Programm wird zuerst die Funktion main() ausgeführt. In der
main() wird eine Variable mit dem Bezeichner a definiert und an die Funktion setVal(;;)
per Call by Reference übergeben. In der Funktion setVal wird dann a der Wert 100.0
zugewiesen. Danach wird die Funktion printVar() aufgerufen, zu beachten ist, dass die
Funktion main weiterhin im Arbeitsspeicher vorhanden ist. Die printf Anweisung wird
also ausgeführt und die Funktion printVar wird beendet. Danach wird die Funktion
main() wieder reaktiviert um sich letztendlich mit dem return Befehl zu beenden.
1 #include <s t d i o . h>
2

3 void s e t V a l ( double ∗a ) {
4 p r i n t f ( " w e i s e ␣ a ␣ den ␣ wert ␣ 1 0 0 . 0 ␣ zu \n" ) ;
5 ∗a = 1 0 0 ;
6 }
7

8 void p r i n t V a r ( double a ) {
9 p r i n t f ( " Die ␣ V a r i a b l e ␣a ␣ hat ␣ den ␣Wert␣%f \n" , a ) ;
10 }
11

14
12 int main ( void ) {
13

14 double a ;
15

16 s e t V a l (&a ) ; // c a l l by r e f e r e n c e
17 p r i n t V a r ( a ) ; // c a l l by v a l u e
18

19 return 0 ;
20 }
Listing 3.3: C Programm
Bei dem CES Programm ist der Ablauf ähnlich, aber es gibt doch einen wesentlichen
Unterschied. Der CES Compiler generiert ein C File, dass die definierten Prozeduren als
Task implementiert. Das vorliegende Beispiel verdeutlicht den Ablauf bei der Ausführung
des Programms.
1 #include <s t d i o . h>
2

3 $program ( ; ; ) {
4 $ s e t V a l ( ; ; double a ) ; $
5 $printVar ( a ; ; ) ; $
6 }$
7

8 $ s e t V a l ( ; ; double a ) {
9 p r i n t f ( " w e i s e ␣ a ␣ den ␣ wert ␣ 1 0 0 . 0 ␣ zu \n" ) ;
10 $a$ = 1 0 0 . 0 ;
11 }$
12

13 $ p r i n t V a r ( double a ; ; ) {
14 p r i n t f ( " Die ␣CES␣ V a r i a b l e ␣a␣ hat ␣ den ␣Wert␣%f \n" , $a$ ) ;
15 }$
Listing 3.4: CES Programm
Abbildung 3.4 zeigt nochmal den Frame Stack während der Ausführung des CES Pro-
gramms.
Die Run Time erzeugt auf dem Frame Stack den ersten Task program(;;) und beginnt
mit der Ausführung der Prozedur program(;;).

F rameStack CurrentStack

program(; ; )
Der Task program(;;) wird auf dem Frame Stack erzeugt

15
Abbildung 3.4.: Frame Stack während der Ausführung des CES Programms

Nun wird der Task program(;;) ausgeführt. Der Kinder Task setVal(;;a) wird auf dem
Current Stack erzeugt.

F rameStack CurrentStack

program(; ; ) setV al(; ; a)


Der Task setVal(;;a) wird auf dem Current Stack angelegt

Aber es erfolgt keine Ausführung von setVal(;;a)! Der in dem Task definierte
Output Parameter erzeugt als nächstes ein Storage Frame a auf dem Frame Stack der
diesen Parameter beinhaltet. Dieser Outputparameter referneziert nun auf den Storage
a. Dieser Storage ist mit dem Wert 0 initialisiert, damit die Ausgabe des Pretty Printers
keine undefinierten Zeichenketten ausgibt.

F rameStack CurrentStack

Storage(a) = 0
hQQQ
QQQ
QQQ
QQQ

program(; ; ) setV al(; ; a)


Der Storage für die variable a wird auf dem Frame Stack erzeugt

Als nächstes wird der Task printVar(a;;) auf dem Current Stack erzeugt und alle Refe-
renzen werden aufgelöst, d.h. in diesem Fall wird eine Referenz von dem Input Parameter
a auf den Storage a gebildet.

16
F rameStack CurrentStack

Storage(a) = 0 / printV ar(a; ; )


hQQQ
QQQ
QQQ
QQQ
program setV al(; ; a)
Der Task printVar(a;;) wird auf dem Current Stack erzeugt

Zuletzt werden alle von program(;;) erzeugten Tasks, die auf dem Current Stack liegen,
auf den Frame Stack kopiert. Man kann sagen, dass program(;;) durch setVal(;;a) und
printVar(a;;) ersetzt wird. Der Current Stack ist nun leer.

F rameStack CurrentStack

setV al(; ; a)

printV ar(a; ; )
O

y
Storage(a) = 0
Der Current Stack wurde auf den Frame Stack kopiert

Der Task program(;;) ist somit zu Ende und es kann der zuvor belegte Speicherbereich
freigegeben werden. Die Run Time führt den nächsten Task aus. In diesem Beispiel ist
das der Task setVal(;;a). Beim Ausführen von setVal(;;a) wird nun dem Storage a der
Wert 100.0 zugewiesen.

17
F rameStack CurrentStack

setV al(; ; a)

printV ar(a; ; )
O

y
Storage(a) = 100.0
Der Task setVal(;;a) wird ausgeführt

Danach wird der Task setVal(;;a) beendet und printVar(a;;) wird als nächstes ausge-
führt.

F rameStack CurrentStack

printV ar(a; ; )
O

Storage(a) = 100.0
Der Task printVar(a;;) wird ausgeführt.

Der Task printVar(a;;) erzeugt die Ausgabe auf dem Monitor und endet.

F rameStack CurrentStack

Storage(a) = 100.0
Der Task printVar(a;;) erzeugt die Ausgabe und endet.

Der Storage hat keine weitere Bedeutung bzw. wird nicht mehr von einem anderen
Task benötigt und kann somit freigegeben werden. Das Programm ist somit beendet. Der
Pretty Printer erzeugt die Ausgabe aus der folgenden Abbildung 3.5. Die Nummerierung
an der linken Seite der Ausgabe bezeichnet die Nummer des Frames auf dem Frame Stack.
Es kann somit nochmal der genaue Ablauf des CES Programms nachvollzogen werden.

18
*****************EXECUTE 1****************
1 program(;;)
*****************EXECUTE 2****************
3 setVal(;;double a_1=0.000000)
2 printVar(double a_1=0.000000;;)
weise a den wert 100.0 zu
*****************EXECUTE 3****************
2 printVar(double a_1=100.000000;;)
Die CES Variable a hat den Wert 100.000000

Abbildung 3.5.: Pretty Printer Ausgabe

19
4. Die Sprache CES
Die Sprache CES ist eine Erweiterung der Programmiersprache C, die als neues Feature
eine Prozedur als Task implementiert. Für diese neu implementierten Prozeduren wurde
eine neue Syntax eingeführt. Es kann weiterhin die Programmiersprache C benutzt wer-
den. Im folgenden werden die neueingeführten Sprachkonzepte von CES aufgeführt und
anhand einiger Beispiele deutlich gemacht.

4.1. Einführung in die Sprache von CES


4.1.1. Eigenschaften von CES
Die Sprache CES unterstützt die getrennte Kompilierung von Programmeinheiten wie
in C bekannt. Es ist somit möglich bereits geschriebene Bibliotheken einzubinden und
zu benutzen. Der in CES geschriebene Quellcode eines Programms kann aus mehreren
Dateien bestehen. Die generierten C Dateien werden dann zusammen mit der runTime.c
und der debug.c mit einem C Compiler kompiliert.

program.ces f oo.ces

CES Compiler CES Compiler


QQQ
QQQ mmm
QQQ mmmmm
QQQ mm
 Q( mv mm 
program.c HeaderF iles f oo.c
PPP oo
PPP ooo
PPP oo
PPP oo
'  ow oo
Präprozessor o runTime.c
CESP rogramm o
C Compiler debug.c

Abbildung 4.1.: Ablaufdiagramm eines Kompiliervorgangs

Die getrennte Kompilierbarkeit bietet die üblichen Vorteile, zum Beispiel:


• Bei Änderung müssen nur bestimmte Dateien bearbeitet werden, alle anderen Da-
teien bleiben stabil.
• Außerdem kann der Kompilierlauf des gesamten Programms verkürzt werden, in-
dem nur die veränderten Dateien kompiliert werden.

20
4.1.2. Einordnung der Programmiersprache CES
CES gehört zu den imperativen und deklarativen Programmiersprachen, wie auch die
Programmiersprache C.

4.1.3. Lexikalische Konventionen


Lexikalischer Zeichenvorrat
Der Zeichenvorrat in CES umfasst alle benutzbaren Zeichen aus C und zusätzlich das
Dollarzeichen $. Dieses Zeichen wurde zur Vereinfachung des CES Compilers eingeführt.
Der Scanner, der Teil des CES Compilers ist, kann somit leichter CES Code und C
Code getrennt voneinander erkennen. Das Dollarzeichen wir am Anfag einer Prozedur
vorrangestellt und am Ende mit einer geschweiften Klammer }$ beendet. Bei einem
Aufruf einer CES Prozedur muss am Anfang ein Dollarzeichen vorhanden sein und am
Ende des Aufrufs muss ein Semikolon ;$ stehen.

Lexikalische Einheiten
Zu Beginn der Kompilierung eines CES Quellprogrammes liegt das Programm in Form
der anschließenden lexikalischen Einheiten, auch Token genannt vor.

• Namen (Bezeichner, speziell für CES Prozedur Definition und Aufruf, sowie Varia-
blennamen)

• reservierte Wörter

• C Code

Reservierte Wörter
program Zur Vereinfachung der Implementierung beginnt ein CES Programm mit der
Ausführung der Prozedur program(;;). Andere Prozeduren können mit selbstge-
wähltem Namen bezeichnet werden. Hierbei sind gewisse Konventionen für die
Namensbildung einzuhalten (siehe Abschnitt Die CES Prozedur ). Der Name pro-
gram(;;) ist natürlich exklusiv für das Hauptprogramm reserviert und darf in einem
CES Programm nicht ein zweites Mal als Prozedur definiert werden. Die Prozedur
program(;;) dient als „Einsprung“ in das CES Programm und wird von der Run
Time im Backend erzeugt. Diese Prozedur ist der Anfangspunkt wie die Funktion
main() aus der Programmiersprache C.

parallel Dieses Schlüsselwort dient dazu Prozeduraufrufe als parallel ausführbar zu mar-
kieren. Die Möglichkeit der Funktion hierzu wurde noch nicht umgesetzt; alle CES
Programme laufen somit noch sequentiell ab. Die technische Umsetzung ist für eine
erweiterte Version von CESC und dem Execution System geplant.

21
4.1.4. Beispielprogramme
Aufbau eines CES-Programmes
Ein CES Programm besteht aus CES Prozeduren, deren Aufrufen, Variablen und C Code.
Die Prozeduren nehmen die Rolle von Haupt- und Unterprogrammen ein. Sie haben dabei
die Aufgabe Teile eines Programmes unter eigenem Namen zusammenzufassen. Mit Hilfe
des Prozedurnamens werden CES Prozeduren innerhalb eines Programmes aufgerufen.
Dabei besteht die Möglichkeit der Prozedur beim Aufruf Argumente zu übergeben. Der
Austausch von Informationen kann zum Beispiel in den CES Ausgabeparametern erfolgen
oder auch über globale C Variablen. Bei der Ausführung wird der Task durch seine Kinder
Tasks ersetzt. Wie bei C Funktionen können in CES die Prozeduren nicht geschachtelt
definiert werden. Dies bedeutet, dass es in CES nicht möglich ist eine Prozedur innerhalb
einer Prozedur zu definieren.

Programmbeispiel 1 Hallo, Welt!

1 #include <s t d i o . h>


2

3 $program ( ; ; ) {
4 p r i n t f ( " Hallo , Welt ! " ) ; /∗ C Code ∗/
5 }$
Listing 4.1: CES Programm Hallo, Welt!

Programmbeispiel 2 Euklid’scher Algorithmus

1 #include <s t d i o . h>


2

3 $program ( ; ; ) {
4 int a ;
5 int b ;
6 int e r g e b n i s = 0 ;
7 p r i n t f ( "\ t E u c l i d ( a , b ) \n" ) ;
8 p r i n t f ( "␣ a ␣ e i n g e b e n ␣ " ) ;
9 s c a n f ( "%d" , &a ) ;
10 p r i n t f ( "␣b␣ e i n g e b e n ␣ " ) ;
11 s c a n f ( "%d" , &b ) ;
12 $ e u c l i d ( int a , int b ; int e r g e b n i s ; ) ; $
13 $ p r i n t R e s u l t ( int e r g e b n i s ; ; ) ; $
14 }$
15 $ p r i n t R e s u l t ( int e r g ; ; ) {
16 p r i n t f ( "␣ E r g e b n i s ␣=␣%d\n" , $ e r g $ ) ;
17 }$

22
18 $ e u c l i d ( int a , int b ; int e r g ; ) {
19 i f ( $b$ == 0 )
20 $ e r g $ = $a$ ;
21 else {
22 int c = $a$ % $b$ ;
23 $ e u c l i d ( b , int c ; e r g ; ) ; $
24 }
25 }$
Listing 4.2: CES Programm Euklid’scher Algorithmus

4.1.5. Datentypen und Variablen


Datentypen, die zur Definition von Parameter oder Argumenten benutzt werden dürfen
nur aus einem Bezeichner bestehen. Es ist also nicht direkt möglich den Datentyp unsi-
gned char zu benutzten. Eine Lösung wäre zuvor einen zusammengesetzten Datentyp zu
definieren:
1 typedef unsigned char UCHAR;

Alle Datentypen aus C, die aus einem Bezeichner bestehen können verwendet wer-
den, zum Beispiel char, int, float, double, sowie zusammengesetzte bzw. selbstdefinierte
Datentypen aus C.
Variabeln können als Parameter oder C-Variable definiert werden. Die Sichtbarkeit
und Lebensdauer einer CES-Variable bezieht sich immer auf die CES Prozedur in der die
Variable definiert wurde. Somit vergleichbar mit dem Prinzip der lokalen Variablen aus
diversen anderen Programmiersprachen.

4.1.6. Der Präprozessor


Das Präprozessing wird von dem C Präprozessor übernommen. Unter anderem werden bei
der Übersetzung vom C Compiler die von CESC generierten Header Files eingebunden.

4.2. Die CES Prozedur


Eine CES Prozedur wird als ein Task Frame auf dem Frame Stack aufgerufen. Benötigt
werden ein Bezeichner für den Task, Parameter und einen Task Body. Zur Vereinfachung
der Implementierung ist es nicht möglich einen Prototypen zu deklarieren. Der Austausch
von Informationen zwischen den Prozeduren erfolgt über die Parameter oder auch über
Möglichkeiten, die C bietet wie z.B. globale Variablen.

4.2.1. Definition einer CES Prozedur


Eine CES Prozedur ist definiert durch das Zeichen „$“ am Anfang der Definition, den
Namen der Prozedur und die Parameter. Der Name der CES Prozedur darf nur aus

23
Buchstaben, Ziffern und einem „Unterstrich“ bestehen (diese Konvention wurde aus der
Programmiersprache C übernommen). Der reguläre Ausdruck hierfür ist in der scanner.l
zu finden. Die drei Arten von Parameter Input, InOutput und Output müssen durch
Semikolons getrennt werden. Durch diese Trennung können Abhängigkeiten zwischen
den Prozeduren besser deutlich gemacht werden. Beinhaltet zum Beispiel ein Task A
einen Parameter x als Ausgabeparameter, so erkennt man bei anderen definierten Tasks
welche diesen Parameter als Eingabeparameter benutzen (siehe Abbildung 4.2).

$ProzedurName(inputParamListe;inOutputParamListe;outputParamListe){...}$

Zu beachten sind die Dollarzeichen am Anfang und Ende der Prozedurdefinition.

4.2.2. Die Parameter


Ein Parameter ist definiert durch seinen Typ und Bezeichner. Die Deklaration eines
Parameters sieht wie in C aus:

int parameter

Die folgende Abbildung 4.2 soll deutlich machen in welche Richtungen Informationen
ausgetauscht werden können und welche Parameter dazu benutzt werden müssen.

C Code P rozedurA P rozedurB

OutputA / InputB

C Variable / InOutputA / InputB

C Variable o InputP arameterA

Abbildung 4.2.: Informationsaustausch

Input Parameter
Die hier definierten Parameter dürfen nur lesend benutzt werden. Es ist möglich mehrere
Parameter als Liste zu definieren.

InOutput Parameter
Auf die hier definierten Parameter darf lesend und schreibend zugegriffen werden, das
heißt es ist möglich innerhalb dieser CES Prozedur den Wert der Variablen zu verändern.

24
Output Parameter
Die hier definierten Parameter dürfen nur schreibend benutzt werden.

4.3. Der CES Aufruf


Der Aufruf eines CES Prozedur erfolgt durch folgende Syntax.

$ProzedurName(inputArgListe;inOutputArgListe;outputArgListe);$

Zu beachten sind die Dollarzeichen am Anfang und Ende des Prozeduraufrufs. In der
einfachen CESC Implementierung ist es nur möglich eine Variable als Argument zu über-
geben. Es ist also nicht möglich einen Ausdruck zu übergeben. Der CES Aufruf kann auch
ohne Argumente erfolgen. Sollte der Fall eintreten, dass mehr oder weniger Argumente
übergeben werden, als in der CES Prozedur definiert, so wird es eine Fehlermeldung vom
C Compiler geben. Die einfache CESC Implementierung überprüft dies nicht.
Wird dem CES Aufruf eine C Variable als Argument übergeben so muss darauf geachtet
werden den zugehörigen Datentyp davor zu setzen um dem CES Compiler mitzuteilen
welchen Datentyp diese C Variable hat. Handelt es sich bei dem Argument um eine zuvor
definierte CES Variable so ist dies nicht nötig, da dem CES Compiler diese Variable (
als Symbol) bekannt ist.

4.3.1. Vorsicht Falle!


Ein Programmierer, der das Konzept der Activation Frames gewohnt ist, wird wahr-
scheinlich nicht daran denken, dass ein CES Aufruf nicht sofort ausgeführt wird. Ein
kleines Beispiel soll verdeutlichen welche Umgewöhnung nötig ist.
1 $program ( ; ; ) {
2 $ s e t V a l ( ; ; int a ) $ ;
3 p r i n t f ( " Der ␣Wert␣ von ␣a ␣ i s t ␣%d\n" , $a$ ) ; // u n d e f i n i e r t e
Ausgabe ! ! !
4 }$
5

6 $ s e t V a l ( ; ; int a ) {
7 $a$ = 1 0 0 ;
8 }$
Listing 4.3: Beispiel mit Fehlerquelle

Die Ausgabe der printf Anweisung in Zeile 3 ist undefiniert. Der CES Aufruf setval(;;a)
in Zeile 6 bewirkt nur ein Erzeugen des Task Frames auf dem Current Stack. setVal(;;a)
wird nicht ausgeführt! Dies ist zu beachten.

25
4.4. Die CES Variable
Als CES Variable werden Variablen bezeichnet, die als InOutput oder nur Output Ar-
gument oder Parameter deklariert sind. Soll eine CES Variable benutzt werden, müssen
vor und nach dem Bezeichner Dollarzeichen gesetzt werden. Es ist möglich eine CES
Variable wie eine C Variable im C Code zu benutzen. Beispielsweise ist es möglich durch
Zuweisungen Werte zwischen C und CES Variablen auszutauschen oder CES Variablen
können auch als Argumente an eine C Funktion übergeben werden. Die Syntax sieht
folgendermaßen aus.

$var$

In den vorgehenden CES Quelltexten sind viele Beispiele, die eine CES Variable in C
Code benutzten.

26
5. Das Backend
Das Backend setzt sich zusammen aus der Run Time, das vom CES Compiler generierte
C Files, einem C Compiler und anderen C Entwicklungstools. Der CES Compiler wird
im nächsten Kapitel im Detail besprochen.

5.1. Die Run Time


Die Run Time beinhaltet eine Funktion zur Ausführung des obersten Tasks auf dem
Frame Stack. Es wird immer nur ein Task von der Run Time ausgeführt. Erst wenn
dieser beendet ist wird der nächste Task ausgeführt.
1 void exec_top_of_fs ( ) {
2 while ( f s p >= frame_stack ) {
3 i f ( f s p −>a n c e s t o r == 0 && f s p −>func_ptr != f u n c _ s t o r a g e ) {
4 i f ( show_stack == 1 ) {
5 show_fs ( ) ;
6 }
7 ( ∗ ( ( void ( ∗ ∗ ) ( void ) ) f s p ) ) ( ) ; /∗ e x e c u t e t o p o f s t a c k ∗/
8 } else {
9 f s p −= 1 ; /∗ s e t frame s t a c k p o i n t e r t o n e x t t a s k ∗/
10 }
11 }
12 }
Listing 5.1: Funktion aus der Run Time zur Ausfürung eines Task

Der ausführbare Task ist immer eine C Funktion ohne Parameter mit dem Rückgabe-
wert void. Diese C Funktion wurde vom CES Compiler generiert. Die Codegenerierung
wird in einem späteren Abschnitt dieses Kapitels erklärt.

5.2. Datenstrukturen
5.2.1. Der Task Frame
Die Datenstruktur eines Task Frame auf dem Stack ist in der run_time.h folgendermassen
definiert:
1 typedef struct {
2 void ( ∗ func_ptr ) ( void ) ;
3 void ( ∗ p r i n t _ f u n c ) ( void ) ;

27
4 int numIn ;
5 int numInOut ;
6 int numOut ;
7 void ∗ parameter [ ARG_SIZE ] ;
8 int a n c e s t o r ; // b e n u t z t von d e r Run Time
9 char ∗name ; // b e n u t z t vom P r e t t y P r i n t e r
10 int p a r a l l e l ; // b e n u t z t vom P r e t t y P r i n t e r ( nur f ü r Ausgabe ,
k e i n e I m p l e m e n t a t i o n bzw Funktion )
11 } TASK_FRAME;
Listing 5.2: TASK_FRAME Datenstruktur aus der run_time.h

Für jeden Task wird ein taskspezifischer Frame generiert, der auf der aufgeführten
TASK_FRAME Datenstruktur aufbaut. Dieser taskspezifische Frame wird speziell in
einem eigenen Header File <Task Name>.h generiert. Da für diese taskspezifische Struk-
tur die Variablen ancestor, parallel und *name nicht verwendet werden, sind diese nicht
im eigenen Header File deklariert.
1 typedef struct {
2 void ( ∗ func_foo ) ( void ) ; // Neuimplementierung d e s Task a l s
Prozedur bzw C Funktion
3 void ( ∗ p r i n t _ f u n c ) ( void ) ; // P r i n t Funktion z u r
S e l b s t d a r s t e l l u n g f ü r den P r e t t y P r i n t e r
4 int numIn2 ; // Anzahl d e r I n p u t Parameter
5 int numInOut1 ; // Anzahl d e r InOutput Parameter
6 int numOut0 ; // Anzahl d e r Output Parameter
7 int ∗ i n 1 ; // 1 . I n p u t Parameter
8 . . . // e v e n t u e l l e z u s ä t z l i c h e I n p u t Parameter
9 int ∗ inOut1 ; // 1 . InOutput Parameter
10 . . . // e v e n t u e l l e z u s ä t z l i c h e InOutput Parameter
11 int ∗ out1 ; // 1 . Output Parameter
12 . . . // e v e n t u e l l e z u s ä t z l i c h e Output Parameter
13 // maximal ARG_SIZE Parameter
14 } Struct_foo ;
Listing 5.3: Beispiel einer von CESC generierten Task Struktur im Header File

Da Struct_foo auf der Struktur von TASK_FRAME aufbaut, kann die Run Time diese
als Task Frame TASK_FRAME benutzten.
Alle Frames auf dem Frame Stack haben die gleiche Grösse. Die Strukturgrösse wurde
konstant gehalten um eine Vereinfachung bei der Implementierung zu haben. Diese ge-
wählte Lösung bietet den Vorteil Schwierigkeiten bei der Zeigerarithmetik zu vermeiden.
Es ist also zu jedem Zeitpunkt einfach bekannt wo sich alle Informationen über einen
Task im Speicher befinden. Da der Frame Stack wie ein Array aufgebaut ist kann über
einen Index direkt auf jeden Frame zugegriffen werden. Ein weiterer Vorteil ist beim De-
bugging zu erkennen. Der Pretty Printer kann sehr leicht alle Frames ausgeben. Ziel ist es

28
nicht an Ressourcen und Performance zu sparen sondern das Prinzip und die Abfolge im
Speicher klar und deutlich zu präsentieren. Daher wurde eine einfache Struktur gewählt.

Der Funktionszeiger func_ptr


Dieser Funktionszeiger ist der eigentliche Kern eines Task Frames. Der in Zeile 2 definierte
Funktionszeiger zeigt auf die auszuführende C Funktion in dem generierten C File, der
den CES Task als Prozedur repräsentiert. Die Namen des Task und der C Funktion sind
identisch. Da die Parameter im Frame sind, wurde der Rückgabewert und die Parameter
des Funktionszeigers als void definiert.

Der Funktionszeiger print_func


Zu Debugzwecken wurde ein zweiter Funktionszeiger print_func definiert. Dieser zeigt
auf die generierte Print Funktion. Jeder Task beinhaltet eine eigene Print Funktion, die
den Task im Pretty Printer ausgibt. Die Anzahl und Typen der Parameter ist von Task
zu Task unterschiedlich. Der Pretty Printer führt dann diese spezielle Print Funktion
aus.

Überprüfung der Parameteranzahl


Die drei Integervariablen numIn, numInOut und numOut in Zeile 4, 5 und 6 dienen
zur Überprüfung der Anzahl von Parametern in dem definierten Task Frame. Allerdings
wird der Wert der Variablen nicht benutzt, sondern der Name der Variable gibt an wieviel
Parameter definiert sind.
Hier ein Beispiel um die genaue Funktion der Parameterüberprüfung zu verdeutlichen.
Der Task

$euclid(int a, int b; int erg){...}

1 $ e u c l i d ( int a , int b ; int e r g ; ) {


2 int c ;
3 i f ( $b$ == 0 )
4 $ e r g $ = $a$ ;
5 else {
6 c = $a$ % $b$ ;
7 $euclid (b , c ; erg ; ) ; $
8 }
9 }$
Listing 5.4: Beispiel zur Parameterprüfung

erzeugt folgende Struktur.


1 typedef struct {

29
2 void ( ∗ f u n c _ e u c l i d ) ( void ) ; // Neuimplementierung d e s Task a l s
Prozedur bzw C Funktion
3 void ( ∗ p r i n t _ f u n c ) ( void ) ; // P r i n t Funktion z u r
S e l b s t d a r s t e l l u n g f ü r den P r e t t y P r i n t e r
4 int numIn2 ; // Anzahl d e r I n p u t Parameter
5 int numInOut1 ; // Anzahl d e r InOutput Parameter
6 int numOut0 ; // Anzahl d e r Output Parameter
7 int ∗ i n 1 ; // 1 . I n p u t Parameter
8 int ∗ i n 2 ; // 2 . I n p u t Parameter
9 int ∗ i n o u t 1 ; // 1 . InOutput Parameter
10 } Struct_euclid ;
Listing 5.5: Datenstruktur des Tasks euclid(b,c;;double erg2)
Da der Task euclid(a,b;;c) 2 Input Parameter definiert hat befindet sich hinter der
Variablen numIn die Zahl 2. So kann später bei einem Task Aufruf durch den C Com-
piler festgestellt werden ob die Definition mit der tatsächlichen Anzahl an Parameter
übereinstimmt. Würde dem Task Aufruf ein Argument fehlen $euclid(int c;erg ;) ;$ so
hätte dies folgende Fehlermeldung zur folge.
ces_program.c: In function ‘euclid’:
ces_program.c:168: structure has no member named ‘numIn1’
make: *** [gcc] Error 1
Warum diese doch etwas umständliche Art der Überprüfung von Parameter? Um die
Implementierung des CES Compiler zu vereinfachen wurde die Überprüfung an den C
Compiler weitergegeben. Die nötigen Informationen, die der C Compiler braucht ist je-
weils der Bezeichner der drei Variablen numIn, numInOut und numOut.

Das Zeigerarray parameter[ARG_SIZE]


Die hier definierten Zeiger zeigen auf die im Task definierten Parameter, diese referenzie-
ren auf ein vorher angelegtes Storage. ARG_SIZE wurde in der run_time.h deklariert
und ist eine Konstante die die Anzahl der maximalen Parameter oder Argumente in
einem Task vorgibt. Für das Parameter Array wurde bewusst eine konstanten Grösse
gewählt, da eine Struktur mit variabler Grösse einen zu grossen Aufwand bei der CESC
Implementierung mit sich bringen würde. Zum Beispiel bei der Ausgabe im Pretty Prin-
ter müsste jedesmal der Offset im Frame Stack berechnet werden. Bei konstanter Grösse
der Frames im Frame Stack ist es einfacher zu berechnen an welcher Position sich der
Zeiger auf den Parameter befindet. Da nicht bekannt ist welcher Typ von Parametern
definiert werden, muss hier ein void Zeiger verwendet werden. So ist gesichert, dass die
Zeiger in dem Array auf jeden Parametertyp zeigen kann.

Die Variable ancestor


Um ausgeführte Tasks auf dem Frame Stack anzeigen zu können, wurde eine Variable
ancestor angelegt, die mit dem Wert 1 einen noch auszufühenden Task repräsentiert

30
und mit dem Wert 0 einen schon ausgeführten Task anzeigt. Dies dient lediglich zur
Anzeige beim Pretty Printer. ancestor wird nicht im Frame, dass im Header File für
den zugehörigen Task generiert wurde, definiert. ancestor hat keine Auswirkung auf das
Programm, sondern dient lediglich der Ausgabe des Pretty Printers.
*****************EXECUTE 1****************
1 program(;;)
*****************EXECUTE 2****************
4 setVal(;;double a_2=0.000000)
3 printVar(double a_2=0.000000;;)
1 ANCESTOR program(;;)
*****************EXECUTE 3****************
3 printVar(double a_2=100.000000;;)
1 ANCESTOR program(;;)
Die CES Variable a hat den Wert 100.000000

Abbildung 5.1.: Pretty Printer Ausgabe mit dem „ancestor“ Task program(;;)

Der String name


Der String name beinhaltet den Namen des Tasks. Diese ist für die Ausgabe des Pretty
Printers wichtig.

Die Variable parallel


Steht parallel vor einem Task Auruf so kann dieser unabhängig von anderen CES Proze-
dur Aufrufen ausgeführt werden. Diese Funktion wurde allerdings noch nicht umgesetzt.
Die Variable parallel enthält bei der Ausführung des Programms den Wert 1, wenn der
CES Aufrufmit dem Schlüsselwort parallel gekennzeichnet wurde. Ansonsten ist der Wert
0. Der Pretty Printer zeigt welche Task Frames auf verschiedenen Prozessoren ausgeführt
werden könnten.

5.2.2. Der Storage


Eine Speichereinheit für eine CES Variable auf dem Frame Stack wird als Storage bezeich-
net. Dieser beinhaltet den Wert und Namen der Variable sowie einen Funktionszeiger auf
die Storage C Funktion. Der Wert der Variable, die der Storage verwaltet, wird in dem
generierten Member value gespeichert. Der Datentyp von value wird automatisch vom
CES Compiler generiert und ist somit von der Definition der Variablen abhängig.
Ein Storage hat folgende Struktur.
1 typedef struct {
2 void ( ∗ f u n c _ s t o r a g e ) ( void ) ; // F u n k t i o n s z e i g e r a u f d i e s t o r a g e
Funktion

31
3 char ∗name ; // Name d e r V a r i a b l e
4 <type> v a l u e ; // Datentyp <t y p e > a b h ä n g i g von d e r D e f i n i t i o n
der Variable
5 } STORAGE_FRAME;
Listing 5.6: STORAGE_FRAME Datenstruktur

Die maximale Grösse eines selbstdefinierten Datentyps ist 4 Byte * ARG_SIZE. Diese
Grösse darf nicht überschritten werden. Ansonsten würden Daten vom nächsten Frame
überschrieben werden. Dieses Problem ist allerdings abhängig von der Implementierung
und nicht fundamental.

Der Funktionszeiger func_storage


Der Zeiger func_storage zeigt auf die C Funktion storage, die in der run_time.c imple-
mentiert ist. Wird die storage Funktion ausgeführt, so wird der Frame Stack Pointer auf
den nächsten Frame gesetzt.
1 void f u n c _ s t o r a g e ( ) {
2 f s p −= 1 ; // f s p a u f n ä c h s t e n Frame s e t z e n
3 }
Listing 5.7: Storage Funktion

Der String name


Der String name beinhaltet den Namen der CES Variable. Diese ist für die Ausgabe des
Pretty Printers wichtig. Die Variable *name wird nicht in der taskspezifischen Struktur
im Header File erzeugt.

Die Variable value


Ein Storage wird bei der Ausführung eines Task angelegt, sobald eine CES Variable in
einer Prozedur deklariert wurde und der Task auf den Frame Stack kommt. Die Rei-
henfolge ist hier sehr wichtig, damit alle nachkommenden Prozeduren auf den Storage
zugreifen können. Bei der Ausführung eines Tasks werden immer zuerst alle Storages
auf dem Stack erzeugt und dann die Task Frames der Kinder Tasks. Der CES Compiler
generiert somit C Code, der ein Storage erzeugt. Erst bei der Ausführung der Prozedur,
die den Storage erzeugt bzw. benutzt wird der Wert geschrieben oder ausgelesen. Diese
Lösung wurde gewählt um eine Vereinfachung der Implementierung im CES Compiler zu
bekommen.
Im folgendem Beispiel soll ersichtlich werden wie ein Storage erstellt wird.
1 #include <s t d i o . h>
2

3 $program ( ; ; ) {
4 $ s e t V a l ( ; ; double a ) ; $

32
F rameStack CurrentStack

setV al(; ; a)

printV ar(a; ; )
O

y
Storage(a) = 100.0

Abbildung 5.2.: Frame Stack nach der Ausführung von program(;;)

5 $printVar ( a ; ; ) ; $
6 }$
7

8 $ s e t V a l ( ; ; double a ) {
9 $a$ = 1 0 0 . 0 ;
10 }$
11

12 $ p r i n t V a r ( double a ; ; ) {
13 p r i n t f ( " Die ␣CES␣ v a r i a b l e ␣a␣ hat ␣ den ␣Wert␣%f \n" , $a$ ) ;
14 }$
Listing 5.8: CES Programm zur Storage Erzeugung

CESC generiert aus dem CES Quelltext folgenden C Code als Storage Struktur.
1 /∗ CREATE STORAGE FOR OUTPUT ARGUMENT ’ a ’ ∗/
2 typedef struct {
3 void ( ∗ f u n c _ s t o r a g e ) ( void ) ;
4 char ∗name ;
5 double v a l u e ;
6 } Struct_storage_a_ptr ;
Listing 5.9: Datenstruktur eines Storage für double a

CESC erzeugt einen Storage für die CES Variable a. Der Scope dieser Struktur be-
zieht sich nur auf die C Funktion, in der die Struktur definiert wurde. Die Struktur ist
somit lokal. Dies ermöglicht bei verschiedenen CES Prozeduren gleiche Bezeichner für
Parameter zu benutzen. Innerhalb einer CES Prozedur dürfen keine gleichen Bezeichner
für Variablen benutzt werden; auch nicht wenn sie unterschiedliche Typen hätten.
Die Abbildung 5.3 zeigt die Ausgabe des Pretty Printers. Die Nummerierung an der
linken Seite der Ausgabe bezeichnet die Nummer des Frames auf dem Frame Stack.

33
*****************EXECUTE 1****************
1 program(;;)
*****************EXECUTE 2****************
4 setVal(;;double a_2=0.000000)
3 printVar(double a_2=0.000000;;)
1 ANCESTOR program(;;)
*****************EXECUTE 3****************
3 printVar(double a_2=100.000000;;)
1 ANCESTOR program(;;)
Die CES Variable a hat den Wert 100.000000

Abbildung 5.3.: Pretty Printer Ausgabe mit dem „ancestor“ Task program(;;)

Frame 2, der nicht vom Pretty Printer angezeigt wird, beinhaltet den Storage für die
CES Variable a. Erst bei Ausführung der Prozedur setVal bekommt a den Wert 100
zugewiesen.
Damit die Ausgabe vom Pretty Printer keine undefinierten Werte in den Storage Fra-
mes anzeigt wurde bei der Codegenerierung der Wert auf 0 initialisiert.

5.3. Die Header Files


Der CES Compiler erzeugt ein Header File für jede definierte CES Prozedur. Jedes gene-
rierte Header Files beinhaltet die taskspezifische Struktur. Diese Struktur ist notwendig,
um einen Task, der aufgerufen wurde, auf dem Stack zu erzeugen, sowie Operationen
bezüglich der Parameter durchführen zu können. Diese Struktur ist auf die Struktur
TASK_FRAME (siehe Abschnitt Datenstruktur) aufgebaut.
Das nächste Beispiel zeigt im CES Quellcode die definierte Prozedur printVar(double
a;;). Zur Vereinfachung der Implementierung werden die Namen der Parameter hier nicht
mehr verwendet, da zu diesem Zeitpunkt der Übersetztung die Header Files bei fremden
Prozeduren vom CES Compiler gescannt werden müssten, um den Namen der Parameter
benutzen zu könenn. Als einfache Lösung werden die Parameter vom CES Compiler
durchnummeriert. Ein Beispiel zum besseren Verständnis: der erste Input Parameter hat
den Namen in1, der dritte Output Parameter out3 und der sechste InOutput Parameter
den Namen inOut6. Die Struktur bekommt den Typennamen Struct_<NameDesTask>.
Es ist nun möglich mit diesem Datentyp z.B. einen Zeiger zu definieren. Die Anweisung
1 $ p r i n t V a r ( double a ; ; ) {
2 p r i n t f ( " Die ␣CES␣ v a r i a b l e ␣a␣ hat ␣ den ␣Wert␣%f \n" , $a$ ) ;
3 }$
Listing 5.10: CES Prozedur printVar(double a;;)

im CES Quelltext führt bei der Codegenerierung durch den CES Compiler zu fol-
gendem Eintrag in der ces_print.h. Der Name des Header Files wird definiert durch

34
ces_<NameDesTask>.h.
1 #i f n d e f CES_printVar_H
2 #define CES_printVar_H

4 typedef struct {
5 void ( ∗ func_printVar ) ( void ) ;
6 void ( ∗ p r i n t _ f u n c ) ( void ) ;
7 int numIn1 ;
8 int numInOut0 ;
9 int numOut0 ;
10 double ∗ i n 1 ;
11 } Struct_printVar ;
12

13 void p r i n t V a r ( void ) ;
14

15 #endif
Listing 5.11: Datenstruktur des Tasks print_Var(double a;;)

5.4. Der von CESC generierte C Code


Das vom CES Compiler generierte C File ergibt zusammen mit der Run Time das aus-
führbare Programm. Ist die Kompilierung des CES Source Code Files durch den CES
Compiler efolgreich gewesen, so wird ein C File generiert welches die Tasks als Prozeduren
implementiert und den C Code so übernimmt, wie er im CES Quellcode definiert wurde.
In diesem Abschnitt werden die Inhalte des vom CES Compiler generierten C Files und
deren Zwecke erläutert und im Detail besprochen. Das C File ist nur zusammen mit der
Run Time ausführbar.
Die in diesem Kapitel beschriebene Codegenerierung bezieht sich auf folgendes CES
Beispiel:
1 #include <s t d i o . h>
2

3 $program ( ; ; ) {
4 $ s e t V a l ( ; ; double a ) ; $
5 $printVar ( a ; ; ) ; $
6 }$
7

8 $ s e t V a l ( ; ; double a ) {
9 $a$ = 1 0 0 . 0 ;
10 }$
11

12 $ p r i n t V a r ( double a ; ; ) {

35
13 p r i n t f ( " Die ␣CES␣ v a r i a b l e ␣a␣ hat ␣ den ␣Wert␣%f \n" , $a$ ) ;
14 }$
Listing 5.12: CES Programmbeispiel

F rameStack CurrentStack

setV al(; ; a)

printV ar(a; ; )
O

y
Storage(a) = 100.0

Abbildung 5.4.: Frame Stack nach der Ausführung von program(;;)

Im generierten C File werden zuerst alle benötigten Standard C Bibliotheken und Hea-
der Files aus der Run Time eingebunden. Diese sind für spätere Anwendungen notwendig.
1 #include <s t d i o . h>
2 #include <s t r i n g . h>
3 #include " debug . h"
4 #include " run_time . h"

Die extern definierten Variablen aus der Run Time werden zusätzlich gebraucht. Dazu
gehören der Frame Stack Zeiger zusammen mit dem Frame Stack, Current Stack Zeiger
zusammen mit dem Current Stack und zusätzliche Zeiger auf den Anfang der beiden
Stacks.
1 extern TASK_FRAME ∗ f s p ; /∗ frame s t a c k p o i n t e r ∗/
2 extern TASK_FRAME ∗ c s p ; /∗ c u r r e n t s t a c k p o i n t e r ∗/
3 extern TASK_FRAME ∗ p t r ; /∗ t h e a c t u a l t a s k frame ∗/ \TODO
4 extern TASK_FRAME ∗ fs_bottom ; /∗ bottom o f t h e frame s t a c k ∗/
5 extern TASK_FRAME ∗ cs_top ; /∗ t o p o f t h e c u r r e n t s t a c k ∗/

Der oben gezeigte Code wird unabhängig vom CES Quelltext erzeugt.
Danach werden alle Prototypen der Print Funktionen einer jeden definierten CES Pro-
zedur erzeugt. Diese sind für die Ausgabe des Tasks durch den Pretty Printer nötig.
Die Print Funktion wird an den dazugehörigen Funktionszeiger print_func in der Task
Struktur, die im Header File erzeugt wurde, übergeben.
1 void ces_print_program ( void ) ;

36
2 void c e s _ p r i n t _ s e t V a l ( void ) ;
3 void c e s _ p r i n t _ p r i n t V a r ( void ) ;

Dann wird C Code aus dem CES Quelltext in das C File kopiert bis die erste CES
Prozedur erreicht ist. Danach wird das Header File der erreichten Prozedur durch ei-
ne include Anweisung eingebunden. Im Anschluss folgen die Header Files der von der
erreichten Prozedur aufgerufenen Prozeduren.
1 //CES Q u e l l t e x t
2 $program ( ; ; ) {
3 setVal ( . . . ) ;
4 printVar ( . . . ) ;
5 }$

1 // g e n e r i e r t e r C Code zum CES Q u e l l t e x t


2 ...
3 #include " program . h"

4 #include " s e t V a l . h"

5 #include " p r i n t V a r . h"

6 void program ( void ) {

7 . . .

8 }

9 . . .

Nun wird für die erreichte CES Prozedur die C Funktion generiert. Jede CES Proze-
durdefinition erzeugt eine gleichnamige C Funktionen.
In der gleichnamigen C Funktion wird zuerst der Frame Stack Zeiger kopiert. Hierzu
wird ein Zeiger definiert, der auf die passende Datenstruktur des Tasks zeigen kann. Der
Wert des Zeigers wird auf die Adresse des Frame Stack Zeigers gesetzt. Dieser Zeiger
wird später benötigt, um die Referenzen auf die Parameter des Tasks, die auf dem Frame
Stack vorhanden sind, setzen zu können.
1

2 void program ( void ) {


3 /∗ d i e Funktion program b r a u c h t k e i n e S i c h e r u n g d e s f s p ∗/
4 ...
5 }
6

7 #include " c e s _ s e t V a l . h"


8

9 void s e t V a l ( void ) {
10 /∗ SAVE ME s e t V a l ∗/
11 S t r u c t _ s e t V a l ∗me = ( S t r u c t _ s e t V a l ∗ ) f s p ; // Kopie d e r Adresse
a u f dem Frame S t a c k
12 ...

37
13 }
14

15 #include " c e s _ p r i n t . h"


16

17 void p r i n t V a r ( void ) {
18 /∗ SAFE ME p r i n t V a r ∗/
19 S t r u c t _ p r i n t V a r ∗me = ( S t r u c t _ p r i n t V a r ∗ ) f s p ;
20 ...
21 }

Danach wird aus dem CES Quelltext weiter C Code kopiert bis die nächste CES Pro-
zedur erreicht wird. Diese Prozedur wird gleich behandelt wie die Erste. Dieser Vorgang
wiederholt sich bis zum Ende des CES quelltextes.
Zuletzt werden die Print Funktionen für die definierten Tasks generiert. Der hier aufge-
führte Quellcode ist abhängig vom Prozedurnamen und deren definierten Parameter. Auf
die Berechnung der Storage Frames für die Ausgabe wird hier nicht genauer eingegangen.
1 void c e s _ p r i n t _ s e t V a l ( void ) {
2 int ∗ value_out1 = ( ( ( S t r u c t _ s e t V a l ∗ ) p t r )−>out1 ) ;
3 int storage_index_out1 = ( ( ( char ∗ ) value_out1 −(char ∗ ) fs_bottom
) / s i z e o f (TASK_FRAME) ) +1;
4

5 p r i n t f ( " s e t V a l ( " ) ; // B e z e i c h n e r d e r CES Prozedur


6 printf (" ; ") ;
7 printf (" ; ") ;
8 p r i n t f ( "%s_%d" , ∗ ( char ∗ ∗ ) ( ( char ∗ ) ( ( ( S t r u c t _ s e t V a l ∗ ) p t r )−>
out1 ) −((( char ∗ ) &( s t o r a g e . v a l u e ) ) −((char ∗ ) &( s t o r a g e . name ) )
) ) , storage_index_out1 ) ; // Berechnung d e s S t o r a g e f ü r d i e
e r s t e Output V a r i a b l e
9 p r i n t f ( "=%d" , ∗ ( ( S t r u c t _ s e t V a l ∗ ) p t r )−>out1 ) ;
10 printf (")") ;
11 }

5.4.1. C Code
Das oben beschriebene Kopieren des C Codes in das generierte C File bewirkt, dass die
Reihenfolge und die Semantik des kopierten C Codes nicht beeinflusst ist von generierten
Codefragmenten des CES Compilers.

5.4.2. CES Prozedur Aufruf


Ist im CES Quelltext ein CES Aufruf vorhanden, so wird daraus folgender C Code ge-
neriert:
1 //CES Q u e l l t e x t

38
2 $program ( ; ; ) {
3 ...
4 s e t V a l ( ; ; double a ) ;
5 ...
6 }$

1 void program ( void ) {


2 ...
3 /∗ PUSH TASK TO CURRENT STACK ∗/
4 c s p −= 1 ;
5 csp−>a n c e s t o r = 0 ;
6 csp−>p a r a l l e l = 0 ;
7 csp−>name = " s e t V a l " ;
8 ( ( S t r u c t _ s e t V a l ∗ ) c s p )−>numIn0 = 0 ;
9 ( ( S t r u c t _ s e t V a l ∗ ) c s p )−>numInOut0 = 0 ;
10 ( ( S t r u c t _ s e t V a l ∗ ) c s p )−>numOut1 = 1 ;
11 ( ( S t r u c t _ s e t V a l ∗ ) c s p )−>fu nc_setVal = &s e t V a l ; //
F u n k t i o n s z e i g e r a u f d i e C Funktion
12

13 ( ( S t r u c t _ s e t V a l ∗ ) c s p )−>p r i n t _ f u n c = &c e s _ p r i n t _ s e t V a l ; //
F u n k t i o n s z e i g e r a u f d i e P r i n t Funktion
14

15 /∗ CREATE REFERENCE FROM OUTPUT ARG ’ a ’ TO STORAGE ∗/


16 ( ( S t r u c t _ s e t V a l ∗ ) c s p )−>out1 = &(storage_a_ptr−>v a l u e ) ;
17 ...
18 }

Zuerst wird der Current Stack Zeiger auf den nächsten Frame gesetzt. Die Member
ancestor und parallel werden initialsiert. ancestor mit dem Wert 0 bedeutet dass der
task nocht nicht ausgeführt wurde. parallel mit dem Wert 0 bedeutet, dass der Task
nicht parallel ausgeführt werden kann. Der Name des Tasks wird in den Member name
geschrieben. Nun werden die Variablen für die Argumentenanzahl des Task initialisiert.
Die Funktionszeiger func_setVal und print_func werden auf die Adressen der C Funk-
tionen gesetzt. Zuletzt wird C Code generiert der Referenzen für die Argumente auf die
Storages erzeugt. Output Argument out1 auf den Storage für die Variable a. Dies ist
abhängig der Parameterdefinitionen in der CES Prozedur.

5.4.3. Verwendung einer CES Variablen


Tritt der Fall auf, dass auf eine CES Variable schreibend zugegriffen wird,
1 // CES Q u e l l t e x t
2 $ s e t V a l ( ; ; double a ) {
3 p r i n t f ( " w e i s e ␣ a ␣ den ␣ wert ␣ 1 0 0 . 0 ␣ zu \n" ) ;
4 $a$ = 1 0 0 . 0 ;

39
5 }$

so sieht der generierte Code folgendermaßen aus:


1 // g e n e r i e r t e r C Code
2 void s e t V a l ( void ) {
3 ...
4 p r i n t f ( " w e i s e ␣ a ␣ den ␣ wert ␣ 1 0 0 . 0 ␣ zu \n" ) ;
5 ∗ (me−>out1 ) = 1 0 0 . 0 ;
6 ...
7 }

Die Variable out1 ist das erste Output Argument aus dem dazugehörigen CES Task.
Diese wurde im dazugehörigen Header File setVal.h definiert. Bei lesendem Zugriff wird
dieser Code generiert.
1 void p r i n t V a r ( void ) {
2 ...
3 p r i n t f ( " Die ␣CES␣ V a r i a b l e ␣a␣ hat ␣ den ␣Wert␣%f \n" , ∗ (me−>i n 1 ) ) ;
4 ...
5 }

Die Variable in1 ist das erste Input Argument aus dem dazugehörigen CES Task setVal.
Diese Variable ist ebenfalls im Header File definiert worden.Der Zeiger me zeigt auf den
aktuellen Task Frame auf dem Frame Stack.

5.4.4. Den Storage erzeugen


Gibt es CES Aufrufe deren Argumente eine CES Variable erzeugen sollen so muss ein
Storage angelegt werden. Hierzu wird vom CES Compiler die Datenstruktur für das
passende Storage generiert. Der Name der Struktur des Storages und der Name des
Zeigers der auf den Storage Frame sind folgendermaßen definiert.
Name der Datenstruktur Struct_storage_<Name der CES Variable>_ptr
Name des Zeiger *storage_<Name der CES Variable>_ptr
Die Referenzen können danach aufgelöst werden. Zuerst wird der Storage Zeiger auf
den Storage Frame gesetzt. Der Funktionszeiger func_storage wird der Storage Funktion
aus der Run Time zugewiesen. Diese Storage Funktion sorgt dafür, dass der Frame Stack
Pointer bei der Ausführung auf den nächsten Frame gesetzt wird. Der Wert der Member
Variable value des Storages wird mit 0 initialisiert. Diese verhindert bei der Ausgabe
durch den Pretty Printer unnötig lange Ausgaben. Der Name des Storage wird an den
Member name der Struktur übergeben. Zuletzt wird der Frame Stack Pointer auf den
nächsten Frame gesetzt.
Der generierte Code im C File für die Datenstruktur des Storages ist folgendermaßen
aufgebaut:
1 void program ( void ) {
2 ...

40
3 /∗ CREATE STORAGE FOR OUTPUT ARGUMENT ’ a ’ ∗/
4 typedef struct {
5 void ( ∗ f u n c _ s t o r a g e ) ( void ) ;
6 char ∗name ;
7 double v a l u e ; // Datentyp a b h ä n g i g von d e r D e f i n i t i o n
8 } Struct_storage_a_ptr ;
9

10 /∗ SET STORAGE PTR TO STORAGE FRAME ON FRAME STACK ∗/


11 Struct_storage_a_ptr ∗ storage_a_ptr = (
Struct_storage_a_ptr ∗ ) f s p ;
12 storage_a_ptr−>f u n c _ s t o r a g e = &f u n c _ s t o r a g e ;
13 storage_a_ptr−>v a l u e = 0 ;
14 storage_a_ptr−>name = " d o u b l e ␣a" ;
15 f s p += 1 ;
16 ...
17 }

5.4.5. Kopie des Current Stack auf den Frame Stack


Als letztes generiert der CES Compiler in der C Funktion C Code um den gesamten
Current Stack auf den Frame Stack zu kopieren.
1 /∗ COPY CURRENT STACK TO FRAME STACK ∗/
2 memcpy( f s p , csp , ( cs_top − c s p ) ∗ s i z e o f (TASK_FRAME) ) ;
3 f s p += ( cs_top−c s p ) −1;
4 c s p = cs_top ;

5.4.6. Die Print Funktion


Für die Ausgabe der Tasks auf dem Frame Stack generiert CESC C Code, der die Print
Funktion für den Pretty Printer darstellt. Die Anzahl der Parameter ist hier Abhängig von
der Definition in der CES Prozedur. Zusätzlich wurde jedem Frame ein Index zugewiesen.
Die Variable heisst storage_index_<Name_des_Parameters>. Auf die Berechnung wird
hier nicht näher eingegangen.
1 void c e s _ p r i n t _ s e t V a l ( void ) {
2 int ∗ value_out1 = ( ( ( S t r u c t _ s e t V a l ∗ ) p t r )−>out1 ) ; // Z e i g e r a u f
Parameter
3 int storage_index_out1 = ( ( ( char ∗ ) value_out1 −(char ∗ ) fs_bottom
) / s i z e o f (TASK_FRAME) ) +1; // I n d e x d e s Frames a u f dem Frame
Stack
4

5 p r i n t f ( " s e t V a l ( " ) ; // B e z e i c h n e r d e r CES Prozedur


6 printf (" ; ") ;

41
7 printf (" ; ") ;
8 p r i n t f ( "%s_%d" , ∗ ( char ∗ ∗ ) ( ( char ∗ ) ( ( ( S t r u c t _ s e t V a l ∗ ) p t r )−>
out1 ) −((( char ∗ ) &( s t o r a g e . v a l u e ) ) −((char ∗ ) &( s t o r a g e . name ) )
) ) , storage_index_out1 ) ; // Berechnung d e s S t o r a g e f ü r d i e
e r s t e Output V a r i a b l e
9 p r i n t f ( "=%d" , ∗ ( ( S t r u c t _ s e t V a l ∗ ) p t r )−>out1 ) ;
10 printf (")") ;
11 }

42
6. Der CES Compiler
Der CES Compiler übersetzt den CES Quelltext in Form einer einzigen Eingabedatei
in eine C Datei, die alle definierten Prozeduren als Task implementiert. Der Compiler
durchläuft folgenden Phasen:

lexikalische Analyse Der Scanner liest die Eingabedatei, den CES Quelltext, ein und
erzeugt einen Tokenstrom für den Parser.

syntaktische Analyse Der Parser überprüft den Tokenstrom vom Scanner und erzeugt
daraus einen abstrakten Syntaxbaum.

semantische Analyse Der abstrakte Syntaxbaum wird durchlaufen und prüft auf se-
mantische Korrektheit. Zusätzlich wird eine Symboltabelle für die Verwaltung der
definierten Variablen im Quelltext erzeugt.

Codegenerierung Aus dem abstrakten Syntaxbaum und der Symboltabelle wird C Code
generiert der die CES Prozeduren als Tasks implementiert.

Nachdem der CES Compiler das C File generiert hat wird dieses zusammen mit der Run
Time von einem C Compiler übersetzt und in ein ausführbares Programm erstellt. Es ist
möglich mit mehreren vom CESC generierten C Files und der Run Time ein ausführbares
Programm zu erzeugen.
Der Compiler CESC wurde unter dem Betriebssystem Windows XP Professional mit
Hilfe von MinGW (www.mingw.org), flex 2.5.4, bison 1.28, gcc 3.2.3 entwickelt. make
3.78.1 wurde verwendet, um die Kompilierung durch den C und CES Compiler zu steuern
und zu automatisieren.

6.1. Der Scanner


Ziel des Scanners ist es Tokens zu erzeugen, die an den Parser weitergereicht werden. Die
strikte Trennung zwischen C und CES stand dabei im Vordergrund. Der C Code wird
nur an den Parser weitergereicht. Der Scanner überprüft nur CES auf die Richtigkeit
der Syntax. Der Scanner beinhaltet zum Einen die regulären Ausdrücke zur Erkennung
der CES spezifischen Code Fragmente in der CES Quellcodedatei, die auch C spezifische
Code Fragmente beinhalten kann und zum Anderen reguläre Ausdrücke die den als nicht
erkannten CES Code als C Code Token an den Parser weiterreicht. Damit der Scanner
leichter zwischen C und CES unterscheiden kann, ist ein zusätzliches Zeichen „$“ benutzt
worden, um den Anfang und das Ende von CES Code anzuzeigen. Das benutzte Tool
heisst flex. Dieses generiert aus der eigens erstellten scanner.l ein C File lex.yy.c . Bei

43
dem Kompiliervorgang wird die Eingabedatei, also der CES Quelltext, analysiert und die
regulären Ausdrücke verwendet, um die Tokens für den Parser zu erzeugen. Die Datei
scanner.l ist in den Anlagen zu finden. Auf den Code der scanner.l wird in dieser Diplom-
arbeit nicht weiter eingegangen, da der Quelltext der scanner.l bekannte Konstrukte aus
dem Compilerbau aufweist und somit keine neue Konzepte beinhaltet.
CESC bietet die Möglichkeit den Tokenstrom ausgeben zu lassen (siehe Anhang A).

6.2. Der Parser


Der Parser beinhaltet Regeln, welche Aktionen bei einem bestimmten Tokenstrom durch-
geführt werden. Die sogenannten Aktionen sind C Funktionen, die den abstrakten Syn-
taxbaum generieren. Die Datei parser.y beschreibt alle definierten Tokens und Aktionen.
Hierzu wurde das Konzept verschachtelte Listen benutzt. Das verwendete Tool heisst
bison.

6.3. Der abstrakte Syntaxbaum


Der abstrakte Syntaxbaum repräsentiert den Quelltext der Eingabedatei als Baum. Die
Blätter sind Parameter, Argumente, C Code, Variablen oder Typinformationen. Der ab-
strakten Syntaxbaum dient später zur Generierung des gewünschten C Codes, der dem
CES Quelltext als C Quelltext entspricht. Die entwickelten Funktionen sind in der Datei
absyn.c definiert und implementiert. Es können Knoten erzeugt werden, die C oder CES
Konstrukte beinhalten können. Ein C Code Knoten beinhaltet den im CES Quelltext ge-
schriebenen C Code ohne irgendwelche Veränderungen. Die definierten CES Definitionen
oder Aufrufe werden als eigens angelegte Knoten im abstrakten Syntaxbaum gespeichert.
CESC bietet die Möglichkeit den abstrakten Syntaxbaum ausgeben zu lassen (siehe
Anhang A). Die Abbildung 6.1 zeigt nochmal ein Beispiel einer Baumstruktur der Im-
plementierung.

6.4. Die semantische Analyse


Bei der semantischen Analyse wird der abstrakte Syntaxbaum systematisch von der Wur-
zel bis zu allen Blättern auf semantische Fehler geprüft. Zusätzlich wird die Symboltabelle
erstellt.

6.5. Die Codegenerierung


Die Codegenerierung erfolgt in der letzten Phase des Kompiliervorgangs. C Code wird
generiert, der den vorhandenen CES Quellcode als C Programm darstellt und die definier-
ten Prozeduren als Task implementiert. Die Codegenerierung erfolgt mittels der fprintf
Funktion in das generierte C File. C Code zu generieren ist relativ einfach und portierbar

44
 
 ast 

_ _ _ _ _  _ _ _ _ _ 
 listN ode 
next / listN ode 
_ _ _ _ _ _ _ _ _ _
value value
  _ _ _ _ _  _ _ _ _ _ 
body
/ listN ode  / listN ode 
next
cCodeN ode cesDef N ode
_ _ _ _ _ _ _ _ _ _
params value value
  
paramN ode o paramsN ode cesCallN ode cCodeN ode
in
nn
nnnnn
nn out args
w nn inOut
n  
paramN ode paramN ode argsN ode
out / argN ode
PPP
PPinOut
PPP
type in PPP
  P'
typeN ode typeN ode o type
argN ode argN ode

Abbildung 6.1.: Der abstrakte Syntaxbaum

gegenüber Assembler. Welche Codefragmente erzeugt werden, ist im Kapitel 5 Abschnitt


„Das generierte C File“ zu finden.

6.6. Der Pretty Printer


Der Pretty Printer dient zur Ausgabe des Frame Stack, zum Beispiel zum Debuggen und
um die neue Implementierung von Prozeduren darzustellen. Somit ist es möglich den
aktuellen Status eines Tasks auszugeben und die Referenzen auf Storages und andere
Tasks nachvollziehen zu können. Hierfür wurden in der Codegenerierung Print Funktionen
erzeugt, die dann die Ausgabe des Tasks erzeugen. Die Generierung des Quelltextes der
Print Funktionen ist im Kapitel 5 zu finden.
1 #i n c l u d e " run_time . h"
2

3 extern TASK_FRAME ∗ fs_bottom ;


4 extern TASK_FRAME ∗ f s p ;
5

6 extern TASK_FRAME ∗ cs_top ;


7 extern TASK_FRAME ∗ c s p ;
8

9 TASK_FRAME ∗ p t r ;
10

45
11 extern int show_stack ;
12

13 void show_fs ( ) {
14 int frame_index ;
15

16 i f ( show_stack == 0 ) return ;
17

18 f o r ( p t r = f s p ; p t r >= fs_bottom ; ptr −−) {


19

20 i f ( !SHOW_ANCESTOR && ptr−>a n c e s t o r == 1 ) continue ;


21 i f ( ptr−>func_ptr == f u n c _ s t o r a g e ) continue ;
22

23 frame_index = ( ( ( char ∗ ) ptr −(char ∗ ) fs_bottom ) / s i z e o f (


TASK_FRAME) ) +1;
24

25 p r i n t f ( "\ t%3d␣ " , frame_index ) ;


26

27 i f ( ptr−>a n c e s t o r == 1 ) p r i n t f ( "\tANCESTOR␣" ) ;
28 ( void ) ( ptr−>p r i n t _ f u n c ) ( ) ; /∗ e x e c u t e t a s k ∗/
29 i f ( ptr−>p a r a l l e l == 1 ) p r i n t f ( "\tPARALLEL␣" ) ;
30 p r i n t f ( "\n" ) ;
31 }
32 }
Listing 6.1: C Quellcode des Pretty Printers

46
7. Ausblick
Die Ergebnisse dieser Diplomarbeit zeigen, dass der theoretische Ansatz von Dr. Burk-
hard Steinmacher-Burow in eine funktionierende Implementierung umgesetzt werden
konnte. Die zu Anfangs gesteckten Ziele konnten erfolgreich erreicht werden. Der entwi-
ckelte CES Compiler erzeugt die geforderten Daten zur Implementierung von Prozeduren
als atomare Tasks. Auftretende Probleme wurden gelöst und trugen bei der Erkenntnis-
findung des Themas bei. Mit Hilfe der Veröffentlichung Task Frames [1] wurde ein leichter
Einstieg in das Thema ermöglicht. Die gewonnene Erkenntnisse dieser Arbeit bieten die
Möglichkeit Tasks parallel ausführen zu können. Um dies zu erreichen muss ein erwei-
tertes Execution System entwickelt werden, dass die Verwaltung der Tasks übernimmt.
Diese Diplomarbeit bietet ebenfalls einen pädagogischen Aspekt zur Verdeutlichung des
Themas. Die Strukturen wurden einfach gehalten. Die Pretty Printer Ausgabe des Frame
Stack kann leicht nachvollzogen werden und verdeutlicht mit einfachen Details den Ablauf
eines CES Programms. Der modulare Aufbau gibt die Möglichkeit die Implementierung
zu erweitern und zukünftige auftretende Probleme zu lösen.
Ich hoffe, dass die vorliegende Arbeit viele Menschen inspiriert und auch motiviert das
Konzept der Task Frames weiterzuentwickeln und zu nutzen.

47
Literaturverzeichnis
[1] Dr. Burkhard Steinmacher-Burow. Task Frames, 2000.
http://www-zeus.desy.de/~funnel/TSIA/index.htm.

[2] Dr. Burkhard Steinmacher-Burow. The execution of computer applications. Kontakt:


steinmac@de.ibm.com, 2006.

48
Benutzte Internetquellen
www.wikipedia.org

49
A. CESC Handbuch
A.1. Dateien
A.1.1. Makefile
folgende Regeln sind möglich:

all kompiliert das ausführbare Programm aus dem CES Quelltext

cesc erzeugt das Programm cesc, den Compiler

compile benutzt das Programm cesc und erzeugt aus der Datei program.ces das C Fi-
le ces_program.c. Der Name der Eingabedatei (CES Quelltext) muss immer pro-
gram.ces lauten, möchte man dieses Makefile benutzen. Der Name der Ausgabedatei
wird immer ces_program.c heissen.

print gibt das Beispielprogramm program.ces als Text auf der Konsole aus (das Pro-
gramm cat muss vorhanden sein)

gcc der C Compiler kompiliert zusammen mit der Run Time das ausführbare Programm
ces_program

exe führt das kompilierte Programm ces_program aus

fs gibt den Frame Stack während der Ausführung des Programms durch den Pretty
Printer auf der Konsole aus

tokens, absyn, tables siehe Optionen von CESC

ancestor zeigt zusätzlich die bereits ausgeführten Tasks auf dem Frame Stack

fstests führt nacheinander alle Testprogramme aus dem Verzeichnis Tests aus und gibt
zusätzlich den Frame Stack auf der Konsole aus

tests führt nacheinander alle Testprogramme aus dem Verzeichnis Tests aus

clean löscht alle vom C Compiler erzeugten Dateien

dist-clean löscht alle vom C und CES Compiler erzeugten Dateien

50
A.1.2. CESC
abstrakter Syntaxbaum
absyn.c Funktionen zur Erzeugung des abstrakten Syntaxbaumes
absyn.h Datenstrukturen für den Syntaxbaum, Prototypen

Codegenerierung
codegen.c Funktionen zur Codegenerierung
codegen.h Prototypen

Hauptprogramm CESC
main.c Das Hauptprogramm von CESC
folgende Optionen bietet CESC:

• —tokens zeigt den Tokenstrom

• —absyn zeigt den abstrakten Syntaxbaum

• —version zeigt die Compilerversion

• —ancestor zeigt bei der Ausgabe die schon ausgeführten Tasks

• —help zeigt die Hilfe für CESC Optionen

Symbole
symbol.c Funktionen zur Erzeugung von Symbolen und deren Verwaltung in einer Hash
Tabelle
symbol.h Datenstruktur eines Symbols für die Symboltabelle

Symboltabelle
table.c Funktionen zur Erstellung der Symboltabelle und zur Ausgabe der Symboltabel-
le
table.h Datenstrukturen der Symboltabelle und Definitionen von Konstanten, Prototy-
pen

Utils
utils.c Funktionen zur Fehlerausgabe, Speicherreservierung und Speicherfreigabe
utils.h Prototypen
common.h Definitionen für den eigenen Datentyp boolean

51
Semantiküberprüfung
semant.c Funktionen zur Überprüfung der Semantik
semant.h Prototypen

A.1.3. Scanner
scanner.l Scanner Eingabedatei für flex
scanner.h Datenstrukturen zur Erzeugung der Tokens

A.1.4. Parser
parser.y Parser Eingabedatei für bison
parser.h Prototypen

A.1.5. Run Time


run_time.c Die Run Time
run_time.h Datenstrukturen für Task Frame sund Storage Frames

A.1.6. Pretty Printer


debug.c Der Pretty Printer
debug.h Definitionen für Konstanten zum Debuggen

A.1.7. Beispielprogramm
program.ces Ein einfaches Beispielprogramm

52
B. Testumegbung
B.1. Dateien im Verzeichnis CESC/Tests
Alle Dateien von test01.ces bis test26.ces testen die richtige Definition von CES Proez-
duren und deren Verwendung durch CES Aufrufe. In der Excel Tabelle ist aufgelistet wie
der Informationsaustausch zwischen verschiedenen Prozeduren möglich ist.

53
Danksagung
Ich danke besonders Dr. Burkhard Steinmacher-Burow, der mir die Möglichkeit gegeben
hat diese sehr interessante Arbeit durchführen zu können. Ausserdem danke ich Ihm
für seine Unterstützung und den vielen wissenschaftlichen Ratschlägen zur Fertigstel-
lung dieser Diplomarbeit. Ebenfalls bedanke ich mich bei Prof. Dr. Hellwig Geisse für
seine persönliche Hilfe bei Fragen bezüglich dieser Arbeit. Der Firma IBM Deutschland
Entwicklung GmbH danke ich für die Bereitstellung der Ressourcen und der Räumlichkei-
ten. Meinem Nachfolger Jens Remus danke ich für wichtige Tipps und das Korrekturlesen
bezüglich der technischen Korrektheit dieser Arbeit. Meiner Freundin Maike Schmelter
danke ich für die Motivierung und Unterstützung bei dieser Arbeit, ohne Sie wäre es nie
möglich gewesen diese Arbeit in dem angesetzten Zeitraum zu Ende zu bringen.

54
Eidesstattliche Erklärung
Hiermit versichere ich, die vorliegende Arbeit selbstständig und unter ausschließlicher
Verwendung der angegebenen Literatur und Hilfsmittel erstellt zu haben.

Die Arbeit wurde bisher in gleicher oder ähnlicher Form keiner anderen Prüfungsbehörde
vorgelegt und auch nicht veröffentlicht.

Böblingen, 8. August 2007


Unterschrift

55