Der in das Betriebssystem L3 integrierte ELAN Compiler übersetzt ELAN Programme in ausführbaren 80386 Code. Er ist nicht nur ein Werkzeug zum Übersetzen von ELAN Quelltexten, sondern auch der Verwalter der ausführbaren ELAN Programme einer Task.
Stellung und Funktionsweise des ELAN Compilers sind Resultat des L3 Taskkonzepts. Eine Task wird angelegt, indem der L3 Systemkern den Auftrag erhält, einen Prozeß zu erzeugen. Dabei wird insbesondere ein Datenraum benötigt, der der Standarddatenraum (STDDS) des neuen Prozesses sein soll. Dieser Datenraum muß, zumindest in Teilen, ausführbaren Code enthalten. Durch Angabe der Adresse in diesem Standarddatenraum, auf die der Befehlszähler zu Beginn zeigen soll, kann eine »Startprozedur« festgelegt werden, die unmittelbar nach Erzeugung des Prozesses gestartet wird.
Im »Normalbetrieb« wird der Standarddatenraum einer neuen Task durch faules Kopieren des STDDS der Vatertask erzeugt. Die Startprozedur wird kann explizit vorgegeben werden, das System bietet auch eine Voreinstellung an. Wenn im Terminalmenü der Punkt neue Task erzeugen angewählt wird und als Vatertask PUBLIC bestätigt wird, so wird der STDDS von PUBLIC faul kopiert und eine Prozedur gestartet, die einen Kommandodialog anbietet. Offensichtlicher wird der Mechanismus durch die ELAN Syntax, wenn eine Task mit einer besonderen Startprozedur in einem Programm erzeugt wird: begin ("teilnehmer", PROC startproc, tvar). Hier wird eine Sohntask mit der (nachträglich erzeugten) Prozedur »startproc« angelegt. Näheres dazu siehe Kap. 5.
In den im System vorhandenen und weiteren durch das ELAN System erzeugten Tasks ist der Standarddatenraum wie folgt aufgebaut:
Standarddatenraum
Der STDDS einer Task enthält also nicht etwa nur den ausführbaren Code von ELAN Programmen, sondern auch den Compiler, um weitere ELAN Programme zu übersetzen, und die Modulbibliothek (Compilerdatenbank), die der Compiler zur Verwaltung bereits übersetzter Programme braucht. Da die Moduln im STDDS, der für diese Zwecke hinreichend groß ist, immer zur Verfügung stehen, ist ein besonderer Dateityp »ausführbare Datei« und ein »Linker«, über den Referenzen eines in Übersetzung befindlichen Programms auf andere Moduln gesetzt werden, nicht erforderlich. Die im STDDS vom Compiler verwalteten Moduln werden direkt über ihren Namen adressiert. Die Bereiche »ELAN Programmcode« und »ELAN Paketdaten« des STDDS enthalten zunächst Code und Daten der bei der Systemerzeugung übersetzten ELAN Pakete. Die Programmdaten des aktuell laufenden Programms sind auf dem Stack gespeichert.
Die Startprozedur bei Anlage einer neuen Task kann nun dergestalt sein, daß sie eine »Commandshell« folgender Systematik anbietet:
REPEAT prompt ausgeben; zeichenkette empfangen; zeichenkette an compiler; IF fehlerfrei THEN ausführen ELSE fehlermeldung ausgeben FI END REPEAT
Der Compiler prüft, ob die Zeichenkette ein syntaktisch korrektes ELAN Programm darstellt und führt es im Falle der Korrektheit aus. Da gerade Namen von Objekten, die in der Schnittstelle eines ELAN Paketes stehen, als korrekte ELAN Programme erkannt und via Modulbibliothek direkt adressiert und ausgeführt werden können, steht der ELAN Compiler als »Command Interpreter« zur Verfügung. Alle sichtbaren Objekte (Prozeduren, Typen etc.) in den Schnittstellen der vorübersetzten ELAN Pakete im STDDS sind damit in Kommandos verwendbar. Die Liste der sichtbaren Objekte kann durch das Kommando bulletin angezeigt werden.
Desweiteren kann der Compiler natürlich auch als »normaler« Compiler benutzt werden, um ELAN Quellprogramme, die in einer Textdatei stehen, zu übersetzen. Ein solches Programm wird entweder übersetzt und, falls korrekt, direkt ausgeführt (run) oder insertiert (insert).
Insertieren bedeutet, daß das Programm, das mindestens ein PACKET enthalten muß, übersetzt wird und der Programmcode, wiederum die Korrektheit vorausgesetzt, in den Codebereich der Task eingetragen wird. Der Packetcode, also alle Anweisungen vor, zwischen und hinter PROC/OP Deklarationen wird unmittelbar nach der Übersetzung eines PACKETs ausgeführt. Die Objekte in der Schnittstelle des PACKET sind dann über die Modulbibliothek sichtbar. Auf diese Weise kann der »Kommandovorrat« einer Task erweitert werden.
Die Sichtbarkeitsregeln für die insertierten Objekte ergeben sich nach kurzer Betrachtung der L3 Kopierlogik: bei Erschaffung einer Task wird eine Kopie des STDDS der erzeugenden Task als STDDS für die neue Task genommen. Die neue Task »erbt« also den Kommandovorrat. Wenn in dieser neuen Task nun insertiert wird, erfolgt ein Schreibzugriff auf den STDDS. Dabei wird neuer Programmcode in den STDDS geschrieben, neue Seiten werden entkoppelt. Damit sind die neu insertierten Objekte nur in der Task sichtbar, in der sie insertiert wurden, und in deren Nachkommen, die diesen erweiterten STDDS erben.
PROC run (TEXT CONST filename)
PROC insert (TEXT CONST filename)
Der ELAN Compiler kann mit ein- oder ausgeschaltetem Checkmodus, Protokollmodus und Warnmodus benutzt werden. Die Einstellungen sind tasklokal und werden beim Anlegen einer neuen Task vererbt.
Die tiefgreifendsten Auswirkungen hat der Checkmodus. Ist dieser Modus eingeschaltet, werden bei der Übersetzung Zeilennummern im Code des übersetzten Programms mitgeführt. Bei Auftreten eines Laufzeitfehlers kann dann die Zeilennummer in der Quelldatei ausgegeben werden, sowie der Name des PACKETs, in der/dem der Fehler bemerkt wurde.
Durch Einfügen von Zeilennummern wird der erzeugte Code um ca. 25% größer. Es ist deshalb eine übliche Vorgehensweise, bei Entwicklung und Test mit check on zu arbeiten und das PACKET mit den fertig ausgetesteten Dateien mit check off zu insertieren.
Die Zeilennummern des ELAN Programms, die von check und prot gesetzt werden, sind dieselben, die auch beim Ausdruck einer ELAN Programmdatei im Listing erscheinen.
PROC prot (TEXT CONST filename)
PROC bulletin (TEXT CONST packet name)
PROC help (TEXT CONST pattern)
Der Compiler übersetzt ELAN Programme in 80386 Code. Die elementaren (denotierbaren) Datentypen der Sprache ELAN sind BOOL, INT, REAL und TEXT. Weiterhin kennt ELAN zusammengesetzte Datentypen (ROW und STRUCT), die wiederum aus einfachen oder zusammengesetzten Typen bestehen.
Die elementaren Datentypen des INTEL 80386 sind Bytes, Worte und Doppelworte. Dementsprechend sind auch die Typen BYTE, WORD, DWORD als (quasi-elementare) Typen verfügbar.
Die interne Darstellung der REALs ist die binäre 8 Byte Fließkommadarstellung nach IEEE 754.
Jedes Datenobjekt vom Typ TEXT besteht aus einem festen Teil von 16 Bytes und ggf. einem flexiblen Teil auf dem Textheap. Texte mit einer Länge bis zu 11 Byte werden im festen Teil gespeichert. Der feste Teil enthält im ersten Byte die Textlänge, gefolgt vom Text selbst. Die letzten vier Byte sind in diesem Fall ohne Bedeutung. Längere Texte werden auf dem Textheap des Datenraums gespeichert. Im ersten Byte des Textes steht dann 0xff, das zweite ist ohne Bedeutung. Die Bytes drei und vier enthalten die Länge des Textes auf dem Textheap, die anschließenden 8 Byte sind ohne Bedeutung. In den letzten vier Byte steht der Heaplink, also die Adresse des Textes im Textheap.
Aufbau des Textheap im Standarddatenraum
Im Standarddatenraum liegt der Textheap in den oberen 14 MB des (32 MB großen) Standarddatenraums (Adreßbereich 0x1200000 bis 0x1ffffff). Der Textheap wird vom ELAN Laufzeitsystem verwaltet. Der Textheap ist in Container verschiedener Größe eingeteilt. Es wird stets versucht, einen Text in einem jeweils minimalen passenden Container zu speichern:
Backlink ist der Zeiger auf den festen Textteil zu dem der Container gehört.
Aufbau des Textheap in anderen Datenräumen
In Datenräumen, die benutzereigene Struktur aufweisen, wird der Heap hinter dem statischen Teil (siehe unten) aufgebaut. Der Datenraum enthält zwei Zeiger auf Heapstart und aktuellen Heaptop. Platz auf dem Heap wird in Vielfachen von 16 Byte vergeben.
Benutzerdef. Datenraum: Zeiger auf ersten freien Container Zeiger auf ersten Container ... Statischer Teil des Datenraums --------------- Erster Textcontainer x.Textcontainer ... y.Textcontainer erster freier Container
Jeder Container hat in seinen ersten beiden Byte eine Kapazitätsangabe c (1 <= c <= 2048). Die Kapazität des Containers errechnet sich durch (16 * c) - 2. Falls ein Text über die Größe seines Containers hinaus wächst, wird ein neuer Container an der Heapspitze zugewiesen. Eine »Müllabfuhr« auf dem Textheap findet nicht statt. Es kann also nützlich sein, Daten in einen neuen Datenraum umzukopieren, wenn sehr viel Speicherplatz durch »alte« Texte verbraucht wird.
Die Mindestgröße des Textheap in einem Datenraum kann mit der Prozedur set min heapsize festgesetzt werden.
ROW und STRUCT Objekte werden gemäß ihrer Länge ausgerichtet. Jedes ROW oder STRUCT Datenobjekt, dessen Länge mehr als 5 Byte beträgt, wird auf durch 8 teilbare Adressen ausgerichtet.
Um vorgegebene komplexe Datenstrukturen in ELAN exakt nachbilden zu können, kann man die Ausrichtungsstrategie des ELAN-Compilers beeinflussen. Wird einer Struktur- oder Row-Deklaration das Schlüsselwort PACKED vorangestellt, so werden
LET MCB = STRUCT (BYTE marker , WORD owner, length, ROW 11 BYTE reserved) ;
Der Compiler richtet WORDs auf eine geradzahlige Adresse aus, Objekte mit einer Länge >= 8 auf eine durch 4 teilbare Länge. Ein MCB wird also wie folgt abgelegt:
Gesamtlänge 24 Bytes.
LET MCB = PACKED STRUCT (BYTE marker , WORD owner, length, ROW 11 BYTE reserved) ;
Der Compiler richtet innerhalb der Struktur nicht mehr aus:
Gesamtlänge 21 Byte.
LET MCB = PACKED STRUCT (BYTE marker , WORD owner, length, PACKED ROW 11 BYTE reserved) ;
Jede Ausrichtung ist unterdrückt:
Gesamtlänge 16 Byte.
Vor allen anderen Typbezeichnern darf PACKED stehen, hat aber keine Wirkung:
BYTE VAR byte ; PACKED INT VAR i; BYTE VAR byte ; INT VAR i;
Beide Zeilen sind äquivalent.
Bei der systemnahen Programmierung entsteht oft die Notwendigkeit, Typen explizit umzubetrachten. Hierzu dient der Forcer
TYPE forcer :: TYPE declarer, square bracket open token, TYPE1 expression, square bracket close token .
Die Notation ist der des Konstruktors ähnlich, im Gegensatz dazu liefert der Forcer jedoch ein Objekt und keinen Wert. Wie im folgenden Beispiel gezeigt wird, findet keine Überprüfung bezüglich der Längen der Typen statt!
ROW 100 INT VAR queue ; shift queue down : que := tail . que : ROW 99 INT [queue head] . tail : ROW 99 INT [new queue head] . queue head : queue [1] . new queue head : queue [2] .
Für das Refinement shift queue down wird ein REP MOVSD generiert.
OP := (BYTE VAR b, INT CONST r) : b := BYTE [r] ENDOP := ;
Es wird nur das niederwertige Byte des INTs kopiert.
Achtung:
OP := (INT VAR l, BYTE CONST b) : l := INT [b] ENDOP := ;
Der in diesem Zuweisungsoperator benutzte Forcer »verlängert« das Byte zu einem DWORD, die höherwertigen 3 Byte sind undefiniert!
REF INT VAR bios var := REF INT [0x 404]
Hier wird der Forcer benötigt, um eine Referenz (s.u.) auf einen absoluten Wert zu setzen.
Um die Programmierung effizienter Software zu unterstützen, bietet der L3 ELAN Compiler die INLINE Anweisung für Prozeduren (und Operatoren). Der Code einer als INLINE deklarierten Prozedur wird direkt an der Aufrufstelle eingesetzt. Die INLINE Eigenschaft gilt selbstverständlich nicht nur im definierenden PACKET, sondern auch, wenn die Prozedur aus dem vorinsertierten Standard kommt.
TYPE COMPLEX = STRUCT (REAL real, imag): OP := (COMPLEX VAR dest, COMPLEX CONST source): INLINE ; CONCR (dest) := CONCR (source) END OP := ; REAL PROC real (COMPLEX CONST x): INLINE ; x.real END PROC real ; REAL PROC imag (COMPLEX CONST x): INLINE ; x.imag END PROC imag ;
Zwischen dem Prozedurkopf und dem Schlüsselwort INLINE dürfen nur Blanks und Kommentare stehen.
Die DOS-Emulation und andere Anwendungen haben gezeigt, daß es notwendig ist, in ELAN die Behandlung von Adressen zu ermöglichen. Dazu wird die Typ-Deklaration in ELAN wie folgt geändert:
type declarer :: plain type ; row type ; struct type ; ref symbol token, type declarer. ref symbol :: REF .
Der extended range wird um ein, vom Compiler realisiertes, Ref-Paket erweitert. Es stellt folgende »super«generischen Operationen zur Verfügung:
OP := (REF TYPE VAR l, REF TYPE CONST r)
a) INT VAR x ; REF INT VAR i :: address (x) ; VAL (i) := 0 (* setzt 'x' auf 0 *) b) INT CONST x ; REF INT VAR i :: address (x) ; VAL (i) := 0 (* geht nicht! 'x' ist eine Konstante *) c) REF INT VAR a, b; (* macht der Compiler *) ... a := b d) LET X = REF UBOOT ; X VAR a, b ; ... a := b ;
Als unsicheres Feature wird eine nicht kopierende ref-Proc eingeführt. Sie ist unabdingbar für Systemzwecke.
REF TYPE PROC address (TYPE VAR obj)
Neben den Fehlermeldungen, die sich auf die übersetzten ELAN Programm beziehen, gibt es weitere Fehlermeldungen des ELAN Compilers. Diese beziehen sich auf den Compiler selbst oder auf den vom ELAN Compiler verwalteten Standarddatenraum. Diese Fehlermeldungen erscheinen stets in der Form:
wobei <zahl> folgende Werte annehmen kann:
Bedeutungen und eventuelle Abhilfe
Die Anzahl der Namen aller sichtbaren Pakete ist zu groß. Der Fehler kann auftreten, wenn die Anführungstriche eines TEXT- Denoters fehlen. Andernfalls gibt es keine Abhilfe. Es muß versucht werden, das Programmsystem auf mehrerere Tasks zu verteilen. Häufige Ursache ist ständiges Neuinsertieren eines PACKETs.
Die Anzahl der deklarierten Objekte im gerade übersetzten PACKET ist zu groß. Das PACKET muß in kleinere PACKETs unterteilt werden.
Das PACKET muß in kleinere PACKETs unterteilt werden.
Zu viele Pakete insertiert. Kann insbesondere dann auftreten, wenn in einer Task ein Paket zigfach zwecks Test insertiert wurde. Neue Task beginnen.
Die Tabelle enthält Informationen über die PROCs/OPs, die in der PACKET Schnittstelle stehen und als INLINE deklariert sind. Die INLINE Prozeduren müssen über mehrere PACKETs verteilt werden.
Die benutzerdefinierten lokalen Daten einer Prozedur belegen mehr als 128K. Keine Abhilfe.
Zu viele sichtbare Pakete, Prozeduren und Operatoren ( > 20480 ). Keine Abhilfe.
In dem gerade übersetzten Modul (Prozedur, Operator oder Paketrumpf) werden vom Compiler zu viele Marken benötigt. Marken werden z.B. für die Codegenerierung von Auswahl (IF ...) und Wiederholung (REP ...) gebraucht. Insbesondere bei SELECT-Anweisungen werden casemax - casemin + 2 Marken benötigt, wobei »casemax« der INT-Wert des maximalen, »casemin« der des minimalen CASE-Wertes ist. Dieser Fehler ist somit fast immer auf zu viele und/oder zu weit gespannte SELECT- Anweisungen zurückzuführen. SELECT-Anweisungen über mehrere Prozeduren verteilen oder Spannweiten verringern.
Der insgesamt erzeugte sichtbare Code ist zu umfangreich ( > 5 MB ). Keine Abhilfe.
(Insgesamt mehr als 8192K Paketdaten) Keine Abhilfe möglich.
Es gibt zu viele Vorwärtssprünge, die noch nicht abgesättigt wurden. Es gibt eventuell zu viele geschachtelte IF, SELECT oder REP Anweisungen. Das Programm sollte in mehr Prozeduren gegliedert werden.
Falls eine hier nicht aufgeführte Fehlermeldungsnummer von Compiler ausgegeben wird, liegt ein interner Fehler vor. Setzen Sie sich in diesem Falle bitte mit der ACCOMMODAT GmbH in Verbindung.