ModulaTor logo, 7.8KB

The ModulaTor

Oberon-2 and Modula-2 Technical Publication

Ubaye's First Independent Modula-2 & Oberon-2 Journal! Nr. CS, Jan-1995


Das OpenVMS Alpha Modula-2 und Oberon-2 Compiler Projekt

Copyright (1994) Carlos Schramer, Günter Dotzel (email: [email deleted due to spam]), ModulaWare

Die Entwicklung eines Modula-2 Compilers für den Digital Equipment AlphaAXP-Prozessor unter OpenVMS, sowie die Aktualisierung und Erweiterung der ISO Modula-2 Standardbibliothek wurde im ersten Halbjahr 1994 bei ModulaWare erfolgreich durchgeführt. Der Alpha Modula-2 Compiler wurde im Juli 1994 termingerecht an die Kunden ausgeliefert. Eine besondere Qualitätskontrolle erlaubte die Auslieferung der ersten Version der Software direkt an die Endkunden. Es gab weder sog. "alpha, beta" noch "field test" Versionen. Die gesamte Software ist in Modula-2 geschrieben. Der Bootstrap des Compilers wurde mittels Crosscompilation auf einer VAX unter OpenVMS durchgeführt. Das gesamte Projekt verschlang 27 Mannmonate. Bis Ende Oktober, 1994 wurden nur drei kleinere Fehler gemeldet und korrigiert. Inzwischen wurde auch der Alpha Oberon-2 Compiler, der viele Module des Modula-2 Codegenerators übernimmt, fertiggestellt.

In diesem Bericht soll nun zunächst die Programmiersprache Modula-2 vorgestellt werden. Anschließend folgt eine Übersicht über die technischen Daten, die Befehle und die Datentypen des Alpha-Prozessors von DEC. Der Hauptteil befaßt sich dann mit dem Modula-2 Compiler, genannt MaX. Die meisten Implementierungsdetails gelten auch für den Oberon-2 Compiler. Der letzte Teil des Berichts beschreibt die ISO Modula-2 Standardbibliothek und deren Erweiterungen. Diese Bibliothek kann auch in Oberon-2 verwendet werden.

Die Sprache Modula-2

Modula-2 wurde 1980 von Niklaus Wirth entwickelt, und kann als logischer Nachfolger von Pascal angesehen werden. Die Syntax der Sprache ist außer einigen kleinen Unterschieden im Wesentlichen die gleiche wie bei Pascal.

Module

Die entscheidende Neuerung ist die Verwendung des Modulkonzepts. Ein Modul ist eine logische Einheit von Konstanten, Typen, Variablen und Prozeduren, das für sich allein compiliert wird. Der Programmierer kann dann ein gesamtes Modul oder auch nur einzelne Elemente daraus, in einem anderen Modul importieren und dort verwenden. Damit der Anwender eines Moduls sieht, welche Elemente ihm zur Verfügung stehen und wie diese angesprochen werden, besitzt jedes Modul eine fest definierte Schnittstelle. Hierzu wird der Quellcode in zwei einzelne Teile (zwei einzelne Dateien) aufgeteilt, und zwar in den Definitionsteil und den Implementationsteil. Der Definitionsteil stellt die Schnittstelle des Moduls dar und enthält die Definitionen bzw. Deklarationen derjenigen Konstanten, Typen, Variablen und Prozeduren, die für den Anwender sichtbar sind. Alle anderen Elemente, die nur innerhalb des Moduls benötigt werden, befinden sich im Implementationsteil. Dort werden auch die Prozeduren, die im Definitionsteil deklariert sind, implementiert. Das folgende Beispiel zeigt den Definitions- und Implementationsteil eines einfachen Moduls namens Stack.

    DEFINITION MODULE Stack;

      TYPE
        Stack = RECORD
          top:   INTEGER;
          value: ARRAY [0..999] OF INTEGER;
        END;

      PROCEDURE Clear (VAR s: Stack);
      PROCEDURE Push  (VAR s: Stack; i: INTEGER);
      PROCEDURE Pop   (VAR s: Stack; VAR i: INTEGER);

    END Stack.

    IMPLEMENTATION MODULE Stack;

      PROCEDURE Clear (VAR s: Stack);
      BEGIN
        s.top := 0;
      END Clear;

      PROCEDURE Push (VAR s: Stack; i: INTEGER);
      BEGIN
        IF s.top <= 999 THEN
          s.value[s.top] := i; INC(s.top);
        END;
      END Push;

      PROCEDURE Pop (VAR s: Stack; VAR i: INTEGER);
      BEGIN
        IF s.top > 0 THEN
          DEC(s.top); i := s.value[s.top];
        END;
      END Pop;

    END Stack.

Zur Anwendung des Moduls Stack benötigt der Anwender nur den Definitionsteil (DEFINITION MODULE). Er kann eine Variable vom Typ Stack deklarieren, und damit die Prozeduren Clear, Push und Pop aufrufen. Der Quellcode der einzelnen Prozeduren ist für den Anwender im Allgemeinen jedoch unsichtbar, da er im Implementationsteil (IMPLEMENTATION MODULE) steht. Beispiel:

    MODULE TestStack;

    IMPORT Stack;

      VAR s: Stack.Stack;
          i: INTEGER;

    BEGIN
      Stack.Clear(s);
      Stack.Push(s, 200);
      Stack.Pop(s, i);
    END TestStack.

Das Modul SYSTEM

Die Sprache Modula-2 stellt ein Modul namens SYSTEM zur Verfügung, das zur sogenannten low-level Programmierung dient und entsprechende Konstanten, Datentypen und Prozeduren enthält. Dieses Modul wird genau wie alle anderen Module verwendet und muß vor der Benutzung in einem Programm erst importiert werden. Im Gegensatz zu separaten Modulen ist es jedoch fest im Compiler integriert. Der Inhalt von SYSTEM ist zwar standardisiert, kann aber implementierungsabhängige Erweiterungen enthalten.

Offene Arrays

Eine weitere interessante Neuerung von Modula-2 gegenüber Pascal ist die Möglichkeit, einer Prozedur beim Aufruf Arrays beliebiger Länge zu übergeben. Will man in Pascal beispielsweise eine Prozedur implementieren, die ein Array von Integern ausgibt, muß man sich zunächst einen Typ dieses Arrays mit festen Grenzen definieren, und diesen dann im Prozedurkopf angeben. Beispiel:

    TYPE Array20 = ARRAY [0..19] OF INTEGER;

    PROCEDURE WriteArray (a: Array20);

Dieser Prozedur können dann allerdings nur Arrays vom Typ Array20 übergeben werden. Will man nun auch noch Arrays mit anderen Größen ausgeben, benötigt man für jede Größe einen eigenen Typ und eine eigene Prozedur.

Modula-2 bietet nun die Möglichkeit, eine Prozedur ohne Angabe von Array-Grenzen zu deklarieren:

    PROCEDURE WriteArray (a: ARRAY OF INTEGER);

Einer solchen Prozedur können Integer-Arrays beliebiger Größe übergeben werden.

Beispiel:

    VAR
      a1: ARRAY [2..4] OF INTEGER;
      a2: ARRAY [1..10] OF INTEGER;

    BEGIN
      WriteArray(a1);
      WriteArray(a2);

Offene Array-Parameter haben als untere Grenze immer den Wert 0. Ihre obere Grenze wird von der Funktion HIGH(Arrayname) zurückgeliefert. Werden der Prozedur wie im obigen Beispiel Arrays übergeben, deren untere Grenze nicht 0 ist, können die Indexwerte als entsprechend verschoben betrachtet werden. Die einzelnen Elemente a1[2], a1[3] und a1[4] der Variablen a1 werden innerhalb der Prozedur WriteArray also beispielsweise mit a[0], a[1] und a[2] angesprochen.

Prozedurtypen

Die letzte Neuerung, die hier erwähnt werden soll, sind die Prozedurtypen. Sie dienen zur Definition einer bestimmten Art von Prozedur, bezogen auf deren Parameter. Das folgende Beispiel definiert einen Prozedurtyp mit zwei Übergabeparametern (ein Integerwert und ein Zeichen) sowie einem booleschen Rückgabewert.

    TYPE Proc = PROCEDURE (INTEGER, CHAR): BOOLEAN;

Einer Variablen vom Typ PROCEDURE können nun alle Prozeduren zugewiesen werden, die strukturell die gleiche Deklaration aufweisen. Anschließend ist es dann möglich, die Prozedurvariable wie eine gewöhnliche Prozedur aufzurufen.

Beispiel:

    VAR Proc: PROCEDURE (CHAR);

    PROCEDURE Write (c: CHAR);

    ...

    END Write;

    BEGIN
      Proc := Write;
      Proc("C");

Mit Hilfe der Prozedurtypen wird es nun auch möglich, den Ablauf einer Prozedur zu steuern, indem man ihr als Parameter andere Prozeduren übergibt und diese innerhalb der Prozedur aufruft.

Beispiel:

    TYPE RealFunc = PROCEDURE (REAL): REAL;

    VAR s, c: REAL;

    PROCEDURE SIN (x: REAL): REAL;
    ...
    END SIN;

    PROCEDURE COS (x: REAL): REAL;
    ...
    END COS;

    PROCEDURE Integral (a, b: REAL; function: RealFunc): REAL;
    ...
    END Integral;

    BEGIN
      s := Integral(0.0, 3.14, SIN);
      c := Integral(0.5, 3.0, COS);

Internationaler Sprachstandard für ISO Modula-2

Die Modula-2-Compiler MVR (VAX) und MaX (Alpha) von ModulaWare richten sich nach dem internationalen Sprachstandard für ISO Modula-2. Dieser Standard wird von einem internationalen Komitee, bestehend aus mehr als 20 nationalen Arbeitsgruppen festgelegt. Das Komitte kommt einmal im Jahr zusammen, um über Erweiterungen und Veränderungen der Sprache Modula-2 zu diskutieren. Die Vorschläge hierfür werden während des Jahres von nationalen Komitees entworfen. Der Standard schließt nicht nur die Syntax von Modula-2 ein, sondern auch das statische und dynamische Laufzeitverhalten, sowie Bibliotheksmodule. Des weiteren werden zur Implementierung Richtlinien in Form einer eigenen Spezifikationssprache vorgegeben, der Vienna Definition Method - Semantic Language (VDM-SL).

Die Standardisierung ist mit dem Modula-2 DIS 10154, der im Juni 1994 mit 707 Seiten Umfang erschienen ist, abgeschlossen. Das Dokument ist vom Beuth Verlag in Berlin erhältlich. Es liegen bereits Vorschläge für eine objektorientierte Erweiterungen (OOE) als künftige Revision von ISO Modula-2 vor. ModulaWare wird den OOE von ISO Modula-2 nicht folgen, da mit Oberon-2 mittlerweile ein geeigneter OOP-Nachfolger für Modula-2 geschaffen wurde. ModulaWare liefert bereits einen Oberon-2 Compiler für OpenVMS, sowohl für Alpha als auch für VAX.

Der Alpha-Prozessor

Die folgende Kurzbeschreibung der Alpha Architektur wurde aus der Sicht des Compilerentwicklers geschrieben.

Der Alpha-Prozessor von DEC ist ein RISC-Prozessor mit Taktfrequenzen von 150 MHz oder 200 MHz. Es sind mittlerweile sogar Prozessoren mit 275 MHz Takrate lieferbar und Modelle mit über 300 MHz sind geplant. Der Prozessor ist in der Lage, mehrere Befehle pro Takt zu verarbeiten (bei der aktuellen Version sind es zwei). Hierdurch wird eine Vervielfachung der Rechengeschwindigkeit erzielt. Bei einem 200-MHz-Rechner mit zwei Befehlen pro Takt erhält man also eine theoretische Leistung von 400 MIPS. Das Ziel der Firma DEC für die Zukunft ist ein Alpha-Prozessor mit 1 GHz Taktfrequenz und 16 Befehlen pro Takt.

Der Alpha-Prozessor ist eine 64-Bit Rechner mit 128 Bit breitem Datenbus und 64 Bit breiten Registern. Der Prozessor besitzt eine Load/Store-Architektur, das heißt die einzigen Zugriffsmöglichkeiten auf den Hauptspeicher sind das Laden eines Speicherinhaltes in ein Register, bzw. das Speichern eines Registerinhaltes an eine Speicheradresse. Eine direkte Veränderung von Speicherdaten, wie sie in den meisten CISC-Architekturen erlaubt ist, ist nicht möglich. Alle arithmetischen und logischen Operationen können nur mit Registern ausgeführt werden. Hierfür stehen 32 Ganzzahlregister und 32 Gleitkommaregister zur Verfügung.

Um nun beispielsweise den Inhalt einer Speicherzelle zu inkrementieren, benötigt man drei Befehle. Einen Befehl zum Laden des Inhaltes in ein Register, einen zum Inkrementieren des Registers und einen zum Schreiben eines Registerinhaltes an die Speicheradresse. Die meisten CISC-Prozessoren erledigen dies zwar mit einem einzigen Befehl, benötigen für diesen jedoch mehrere Takte. Da der Alpha-Prozessor dagegen in der Lage ist, unter günstigen Umständen pro Takt bis zu zwei Befehle gleichzeitig zu verarbeiten, gleicht dies den Mehraufwand an Befehlen wieder aus.

Befehlstypen

Alle Maschinenbefehle des Alpha-Prozessors sind 32 Bit lang. Je nach Bitbelegung werden sie in unterschiedliche Typen eingeteilt. Im folgenden werden die wichtigsten Befehlstypen zusammen mit ihrer Belegung beschrieben.

Speicherbefehl

Diese Format wird für alle Hauptspeicherzugriffe (Load, Store) verwendet. Die Speicheradresse, deren Inhalt gelesen bzw. geschrieben werden soll, berechnet sich dabei aus dem Inhalt des Ganzzahlregisters Rb plus dem vorzeichenbehafteten Wert Displacement. Ra bezeichnet je nach Befehl entweder das Zielregister (Load) oder das Quellregister (Store). Bei Ganzzahlbefehlen steht Ra für eines der 32 Ganzzahlregister, bei Gleitkommabefehlen für eines der 32 Gleitkommaregister.

Speicher-Funktions-Befehl

Diese Format wird für Spezialbefehle und unbedingte Sprünge verwendet. Bei den Spezialbefehlen hängt die Abarbeitung vom Opcode ab. Bei unbedingten Sprüngen ergibt sich die Adresse a des Sprungziels aus dem Inhalt von Register Rb. Der aktuelle Wert des Befehlszählers PC wird in Register Ra gespeichert. Anschließend wird PC auf a gesetzt. Das Feld Function stellt hierbei je nach Sprungbefehl eine bestimmte Sprungvorhersage dar.

Sprungbefehl

Dieses Befehlsformat dient für bedingte, relative Sprünge. Displacement entspricht der Anzahl an Befehlen, um die gesprungen werden soll. Die Reichweite beträgt hierbei 1 M Instruktionen. Je nach Opcode wird das Register Ra auf eine bestimmte Bedingung hin getestet (gleich 0, größer 0, usw.). Trifft diese Bedingung zu, wird der vorzeichenbehaftete Wert Displacement mit vier multipliziert und zum aktuellen Befehlszähler PC addiert und die Programmabarbeitung an jener Stelle fortgesetzt, andernfalls bleibt der Befehlszähler unverändert.

Ganzzahloperation

Abhängig von Bit 12 des Befehls wird zwischen Registerformat und Direktoperandenformat unterschieden. Die Register Ra, Rb und Rc stehen jeweils für eines der 32 Ganzzahlregister.

Registerformat

Die Ganzzahloperation wird zwischen den Inhalten von Register Ra und Register Rb ausgeführt, und das Ergebnis in Rc gespeichert.

Direktoperandenformat

Die Ganzzahloperation wird zwischen dem Inhalten von Register Ra und dem Direktoperanden ausgeführt, und das Ergebnis in Rc gespeichert.

Gleitkommaoperation

Die Gleitkommaoperation wird zwischen den Inhalten von Register Fa und Register Fb ausgeführt, und das Ergebnis in Fc gespeichert. Die Register Fa, Fb und Fc stehen dabei für eines der 32 Gleitkommaregister.

Ganzzahldatentypen

Der Alpha-Prozessor unterstützt mehrere unterschiedliche Ganzzahldatentypen. Wie das Bitmuster in einem Register oder Speicherplatz vom Prozessor interpretiert wird, hängt dabei vom verwendeten Maschinenbefehl ab. Die folgende Liste stellt die Ganzahltypen zusammen mit ihrer Bitbreite, ihrer Lage im Speicher und im Register sowie ihre Wertebereiche dar. Das niederwertigste Bit wird jeweils mit 0 bezeichnet.


    [V|6|.....|0] a

    a: Byteadresse im Speicher

    V: Vorzeichenbit

    Byte 8 Bit

    Wertebereich: 0 .. 255  bzw. -128 .. 127

Bytes werden nur durch einige Spezialbefehle unterstützt, nicht aber durch Lade-, Speicher- oder Rechenbefehle. Ein Zugriff auf Daten dieser Länge geschieht durch Laden eines ganzen Lang-Wortes (Longword) oder Vierfach-Wortes (Quadword) in ein Register und anschließendem Löschen oder der nicht benötigten Bits oder Vorzeichenreplikation in die höherwertigen Bits.


    [V|14|.............|0] a

    Word 16 Bit

    Wertebereich: 0 .. 65535  bzw. -32768 .. 32767

Words werden nur durch einige Spezialbefehle unterstützt, nicht aber durch Lade-, Speicher- oder Rechenbefehle. Ein Zugriff auf Daten dieser Länge geschieht durch Laden eines ganzen Longwords oder Quadwords in ein Register und anschließendem Löschen der nicht benötigten Bits.


    [V|31|..............................|0] a

    Longword 32 Bit

    Wertebereich: 0 .. 4294967295  bzw.
        -2147483648 .. 2147483647

Hierbei ist zu beachten, daß alle Werte beim Laden in ein Register als vorzeichenbehaftet angesehen werden und eventuell ihr Vorzeichen auf 64 Bit erweitert wird.


    [V|62|...................................................|0] a

    Quadword  64 Bit

    Wertebereich: 0 .. 18446744073709551615  bzw.

        -9223372036854775808 .. 9223372036854775807

Gleitkommadatentypen

Der Alpha-Prozessor unterstützt fünf unterschiedliche Gleitkommadatentypen. Diese werden in VAX- und IEEE-Gleitkommatypen unterschieden. Wie das Bitmuster in einem Gleitkommaregister oder Speicherplatz vom Prozessor interpretiert wird, hängt dabei vom verwendeten Maschinenbefehl ab.

VAX-Gleitkommatypen

Nachfolgend sind alle VAX-Gleitkommatypen zusammen mit ihrer Bitbreite, ihrer Lage im Speicher und im Register sowie ihrer Wertebereiche aufgelistet. Hierbei ist zu beachten, daß aus Gründen der Kompatibilität zu VAX-Rechnern, die Wortgruppen der Zahlen verdreht im Speicher liegen. Beim Laden in ein Register werden die Wortgruppen jedoch in ihre richtige Reihenfolge gebracht.


    [ V|14|...Exp...7|6...Hi...|0] a
    [15|       ...Lo...        |0] a+2

    Exp: Zweierexponent

    Lo:        niederwertigster Teil der Mantisse

    Hi:        höchstwertiger Teil der Mantisse

    F-Floating  32 Bit

    Wertebereich: -1.701411E+38 .. 1.701411E+38

Der Exponent einer F-Floating-Zahl wird beim Laden in ein Register so erweitert, daß eine gültige G-Floating-Zahl mit gleichem Wert entsteht.


    [ V|14|...Exp.....4|3..Hi..|0] a
    [15|       ...MidH...      |0] a+2
    [15|       ...MidL...      |0] a+4
    [15|       ...Lo...        |0] a+6

    MidL: zweitniederwertigster Teil der Mantisse

    MidH: zweithöchstwertiger Teil der Mantisse

    G-Floating  64 Bit

    Wertebereich: -8.988465674311573E+307 .. 8.988465674311573E+307

    [ V|14|...Exp...7|6...Hi...|0] a
    [15|       ...MidH...      |0] a+2
    [15|       ...MidL...      |0] a+4
    [15|       ...Lo...        |0] a+6

    D-Floating  64 Bit

    Wertebereich: -1.7014118346046922E+38 .. 1.7014118346046922E+38

Der Typ D-Floating wird nur aus Kompatibilitätsgründen zu VAX-Rechnern unterstützt, für ihn sind beim Alpha-Chip nur Lade- und Speicherbefehle vorhanden, keine arithmetischen Befehle.

IEEE-Gleitkommatypen

Im folgenden werden alle IEEE-Gleitkommatypen zusammen mit ihrer Bitbreite, ihrer Lage im Speicher und im Register sowie ihrer Wertebereiche aufgelistet.


    [15|       ...Lo...        |0] a
    [ V|14|...Exp...7|6...Hi...|0] a+2

    S-Floating  32 Bit

    Wertebereich: -3.402823E+38 .. 3.402823E+38

Der Exponent einer S-Floating-Zahl wird beim Laden in ein Register so erweitert, daß eine gültige T-Floating-Zahl mit gleichem Wert entsteht.


    [15|       ...Lo...        |0] a
    [15|       ...MidL...      |0] a+2
    [15|       ...MidH...      |0] a+4
    [ V|14|...Exp.....4|3..Hi..|0] a+6

    T-Floating  64 Bit

    Wertebereich: -1.79769313486231E+308 .. 1.79769313486231E+308

Der Modula-2 Compiler MaX

Der Modula-2 Compiler MaX läuft auf Alpha-Maschinen unter dem Betriebssystem OpenVMS. Da es sich hierbei um ein 32-Bit-Betriebssystem handelt, wurde auch MaX als 32-Bit-Compiler realisiert, d.h. alle Adressen sowie die standardmäßigen Ganzzahltypen INTEGER und CARDINAL sind nur 32 Bits lang. Um jedoch die Möglichkeiten des 64-Bit-Alpha-Prozessors nutzen zu können, werden durch das Modul SYSTEM zusätzlich auch 64-Bit-Ganzzahlen unterstützt. Prozedur- und Datenadressen sind jedoch schon mit 64 Bits in der Objektdatei aufgelöst, d.h. die 64-Bit-Erweiterung von Open VMS ist bereist vorgesehen.

Aufbau des Compilers

Als Grundlage für den Modula-2-Compiler MaX diente ModulaWare's Modula-2 Compiler MVR für die VAX. Der Quellcode dieses Compilers liegt selbst in der Sprache Modula-2 vor, das heißt, der Compiler kann sich selbst übersetzen. Weiterhin handelt es sich bei dem Compiler um einen sogenannten Multi-Pass-Compiler. Die Quelldatei wird jedoch nur einmal gelesen. Die Verarbeitungsfolge ist 1. Scannner, 2. Parser für Deklarations- und 3. Body-Analyse, sowie 4. Codegenerierung in OpenVMS Objektcode-Dateiformat.

Dementsprechend ist auch der Quellcode des Compilers in mehrere einzelne Module aufgeteilt, die jeweils zu einem bestimmten Schritt gehören. Die Verarbeitungsschritte 1 - 3 sind für syntaktische und semantische Analyse und für die Erzeugung der Symbolfiles aus den Definitionsmodulen zuständig. Dieser Teil des Compilers wird auch als Frontend bezeichnet. Der vierte Schritt dient zur Codeerzeugung, die zugehörigen Module bilden das Backend. Die folgende Liste zeigt die wichtigsten Module und eine kurze Beschreibung dazu: Frontend (Schritt 1, 2, 3):


    MRCBASE            Basis Definitionen

    MRCPUBLIC          Hauptprogramm zur Behandlung der Compileroptionen und zum
                       Aufruf der einzelnen Schritte 1 - 4.

    MRCP1              Scanner zum Einlesen von Sourcecodefiles und Symbolfiles,
                       syntaktische Analyse.

    MRCP2              Überprüfung und Bearbeitung der Konstanten-, Typen-,
                       Variablen- und Prozedurdeklarationen aller auftretenden
                       Symbole, Speicherreservierung.

    MRCSYMFILE         Erzeugung der Symboldatei (enthält Modul-Schnittstellen-
                       Informationen), falls das zu übersetzende Modul ein
                       Definitionsmodul ist.

    MRCP3              Semantische Analyse der restlichen Anweisungen.

Backend (Schritt 4):


    *MRCP4 (MAXP4)             Codeerzeugung aller Anweisungen.

    *MRCMNEMONICS (MAXM)       Definition aller Maschinenbefehle und Disassembler.

    *MRCATTRIBUT (MAXA)        Registerallokation, Sprungverwaltung.

    MRCIO4                     Interpass-Schnittstelle.

    *MRCEXPR4 (MAXEXPR4)       Codeerzeugung für logische und arithmetische
                               Ausdrücke.

    *MRCR4 (MAXR4)             Codeerzeugung für Gleitkommaarithmetik.

    *MRCC4 (MAXC4)             Codeerzeugung für komplexe Arithmetik.

    *MRCCALLSY (MAXCALLSY)     Codeerzeugung für Standardfunktionen/-prozeduren
                               und Prozeduraufrufe.

    *MRCDBSYS (MAXDBSYS)       Datenbankerweiterung für frühere Version.

    *MRCLI4 (MAXLI4)           Interface für VMS-Linker(Objectfile).

    *MRCLS4 (MAXLS4)           Erzeugung eines Maschinencodelistings
                               (Disassembler).

    MRCLISTING                 Erzeugung eines Quelldateilistings, optional mit
                               Maschinencodelisting.

    MRCXREF                    Erzeugung eines Cross-Reference-Listings.

    *MAXS                      Instruction Scheduler.

    *MAXL                      Linker Definitionen.

Die mit einem Stern gekennzeichneten Module sind maschinenabhängig und müssen für MaX neu implementiert werden, sie erhalten die in Klammern stehenden neuen Namen.

Außerdem kommt ein weiteres neues Modul im Backend hinzu, der Instruction Scheduler. Er dient zur Optimierung des erzeugten Maschinencodes und wird nun im folgenden Abschnitt erläutert.

Instruction Scheduler

Wie schon erwähnt, kann der Alpha-Chip unter günstigen Umständen bis zu zwei Maschinenbefehle pro Taktzyklus ausführen. Es werden jedoch nicht alle Befehle mit Ablauf eines Taktes abgeschlossen. Viele Befehle benötigen weitere Takte bis das Ergebnis der Operation im angegebenen Register vorliegt. Diese Takte sind jedoch nicht verloren, da während dieser Zeitdauer andere Befehle, die nicht vom Ergebnis dieser Operation abhängen, ausgeführt werden können. Im umgekehrten Fall kann es, bei Abhängigkeiten zwischen den Befehlen, auch zu unnötigen Wartezyklen kommen. Das folgende Beispiel zeigt nun eine Folge von Maschinenbefehlen und die zugehörigen Taktzyklen:


    Nr.: Befehl:             Taktzyklus T =

                             01234567890123

    (1)  LDL  R24, #10(R14)  I
    (2)  ADDL R24, #1, R24   ...I
    (3)  STL  R24, #10(R14)      I
    (4)  LDL  R25, #18(R14)      .I
    (5)  ADDL R25, #2, R25         ..I
    (6)  STL  R25, #18(R14)        ..I
    (7)  LDL  R26, #20(R14)           I
    (8)  ADDL R26, #3, R26            ...I
    (9)  STL  R26, #20(R14)               I

Die Befehlssequenz dient dazu, die Inhalte dreier Speicheradressen zu verändern. Hierzu wird der Wert jeweils in ein Register geladen (LDL), das Register verändert (ADDL) und anschließend der Registerinhalt an die Speicheradresse zurückgeschrieben (STL). Die Adressen berechnen sich dabei immer aus einem hexadezimalen Offset, der zum Inhalt von Register 14 addiert wird. Die Verarbeitung der Befehlsfolge läuft nun wie folgt ab:

In Takt 0 werden die beiden Befehle (1) und (2) gelesen, und die Bearbeitung von Ladebefehl (1) wird angestoßen. Zur gleichen Zeit könnte auch die Bearbeitung von Befehl (2) angestoßen werden, da dieser jedoch das Register 24 lesen möchte, das von Befehl (1) geschrieben wird, muß er auf die Beendigung von (1) warten. Im Taktzyklus 3 ist der geladene Wert im Register 24 angekommen und der Additionsbefehl (2) kann angestoßen werden. Erst jetzt, in Takt 4, kann der Prozessor die nächsten beiden Befehle (3) und (4) lesen. Die Addition ist zu diesem Zeitpunkt schon beendet, weswegen sofort der Speicherbefehl (3) angestoßen werden kann, um den Inhalt von R24 abzuspeichern. Noch im gleichen Takt könnte die Abarbeitung des Ladebefehls (4) beginnen, es besteht jedoch die architektonische Beschränkung, daß in einem Taktzyklus immer nur ein Speicherzugriffsbefehl verarbeitet werden kann, (4) muß also einen Takt warten, und wird erst in Takt 5 angestoßen. Im nächsten Takt werden die Befehle (5) und (6) gelesen, müssen allerdings warten, bis (4) beendet ist, und der Wert in R25 vorliegt. Dann kann Befehl (5) angestoßen werden. Da das Ergebnis des Additionsbefehls noch im gleichen Takt vorliegt, kann auch noch im gleichen Takt der Speicherbefehl gestartet werden. In Takt 9 werden die nächsten beiden Befehle gelesen, (7) wird sofort angestoßen, (8) muß erst auf das Ergebnis in Register 26 warten und wird in Takt 12 begonnen. Schließlich wird in Takt 13 noch der letzte Befehl gelesen und ausgeführt. Zur Abarbeitung der gesamten Befehlssequenz werden also 14 Taktzyklen (0 - 13) benötigt.

Durch eine geschicktere Anordnung der einzelnen Befehle ist es nun möglich, die Ausführungszeit zu verringern. Das folgende Bild zeigt eine verbesserte Ausführungsreihenfolge:


    Nr.: Befehl:             Taktzyklus T =

                             0123456       

    (1)  LDL  R24, #10(R14)  I
    (4)  LDL  R25, #18(R14)  .I
    (7)  LDL  R26, #20(R14)    I
    (2)  ADDL R24, #1, R24     .I
    (3)  STL  R24, #10(R14)      I
    (5)  ADDL R25, #2, R25       I
    (6)  STL  R25, #18(R14)       I
    (8)  ADDL R26, #3, R26        I
    (9)  STL  R26, #20(R14)        I

Hier werden alle Wartezyklen, innerhalb der einzelnen, zusammengehörigen Teilsequenzen (z.B. die Befehle (1), (2) und (3)) dadurch eliminiert, daß Befehle von anderen unabhängigen Teilsequenzen eingeschoben werden. Die logische Abarbeitungsreihenfolge jeder einzelnen Teilsequenz muß dabei natürlich unverändert bleiben, damit keine falschen Ergebnisse entstehen. Die neue Befehlsfolge benötigt nun nur noch 7 Taktzyklen zur Ausführung und ist damit doppelt so schnell wie die ursprüngliche Sequenz.

Dieses Umordnen von Maschinenbefehlen ist nun die Aufgabe des Instruction Schedulers. Hierbei handelt es sich um eine Prozedur, die vom Compiler für jeden BEGIN-END-Block aufgerufen wird, nachdem der zugehörige Maschinencode erzeugt wurde. Der Scheduler trägt alle Maschinenbefehle in eine Liste ein, rechnet dann sämtliche sinnvollen Umordnungen der Sequenz durch, und wählt diejenige mit der kürzesten Ausführungszeit aus. Die genaue Vorgehensweise des Schedulers soll hier nicht weiter beschrieben werden. Dies würde den Rahmen dieses Berichts sprengen. Ein wichtiger Punkt muß jedoch noch erwähnt werden, nämlich die Behandlung von Sprungbefehlen.

Soll der Scheduler eine solche Befehlsfolge optimieren, so muß er darauf achten, daß keine Befehle, die in der ursprünglichen Sequenz vor einem Sprung stehen, in der neuen Version nach diesem Sprung kommen und umgekehrt. Andernfalls könnte das Programm eventuell falsche Ergebnisse liefern. Um nun solche Probleme zu vermeiden, muß die Befehlsfolge in mehrere, von einander unabhängige Teilsequenzen zerlegt werden, sogenannte Basic Blocks. Diese Basic Blocks werden nach folgenden Kriterien bestimmt:

Jeder Basic Block ist eine zusammenhängende Befehlsfolge.

Jedes Sprungziel ist der Beginn eines neuen Basic Blocks.

Jeder Sprungbefehl bildet das Ende eines Basic Blocks.

Das folgende Bild zeigt die Aufteilung der obigen Sequenz in ihre Basic Blocks.

Der Scheduler kann nun der Reihe nach jeden Basic Block einlesen, die günstigste Umstellung berechnen und den modifizierten Basic Block zurückschreiben. Dabei muß beachtet werden, daß ein Sprungbefehl in einem Basic Block immer nur an letzter Stelle stehen kann, und diese Position niemals verändert werden darf.

Diese Berechnungen sind je nach Läge des Basic Blocks sehr zeitintensiv. Aus diesem Grund wird der Instruction Scheduler auch nicht automatisch, sondern nur dann aufgerufen, wenn dies vom Benutzer ausdrücklich durch Angabe einer Compileroption gewünscht wird.

Der Algorithmus zur Berechnung der günstigsten Befehlsanordnung lag ursprünglich als C-Quellcode vor und wurde nach Modula-2 portiert und angepaßt. Hierzu mußte noch ein neuer Teil implementiert werden, der die Maschinenbefehlssequenz in einzelne Basic Blocks aufteilt, und einer, der die Befehle dieses Basic Blocks in ihre Bitgruppen zerlegt, um Opcode, Register, Displacement und die Operation zu bestimmen. Im ursprünglichen C-Quellcode, war die Befehlszerlegung nicht nötig, da mit Strukturen von Bitfeldern gearbeitet wurde. In einer solchen Struktur ist es möglich, bestimmten Bits oder Bitgruppen eines Speicherelements Bezeichner zuzuordnen und diese dann wie ganz gewöhnliche Variablen anzusprechen.

Das folgende Beispiel zeigt die Struktur für Speicherbefehle.

    struct
    { int      memorydisplacement : 16;
      unsigned rb : 5;
      unsigned ra : 5;
      unsigned opcode : 6;
    } mformat;

Hierbei geben die Zahlen, die Anzahl der Bits an, die zu einem Bitfeld gehören. Die einzelnen Bitfelder werden von oben nach unten, beginnend beim niederwertigsten Bit des Speicherplatzes, angeordnet. Das folgende Bild zeigt die resultierende Speicherbelegung.

Wird dieser Variablen nun ein Speicherbefehl zugewiesen, so können dessen Teilfelder auf einfachste Weise abgefragt werden.

Beispiel:

    if (mformat.opcode == STQ) { ... };

Diese Eigenschaft von C hat allerdings einen Nachteil. Der Feldzugriff ist nämlich inhärent ineffizent. Wenn einzelne Felder mehrmals benötigt werden, muß jedesmal die Bitgruppe ausgepackt werden. In der High-Level-Language Modula-2 gibt es keine Bit-Level gepackten Strukturen. Es wurde deshalb folgende Vorgehensweise gewählt. Vor Beginn des Schedulings wird jeder Maschinenbefehl in seine einzelnen Bestandteile zerlegt und in einem tagless varianten Record gespeichert.

Das folgende Beispiel zeigt einen Ausschnitt dieses Records.

    TYPE
      alphainst = RECORD
        CASE: CARDINAL OF
        | 0: common: RECORD
               opcode: INTEGER;
               a: INTEGER;
             END;
        | 1: mformat: RECORD
               opcode: INTEGER;
               ra: INTEGER;
               rb: INTEGER;
               memorydisplacement: INTEGER;
             END;
        | 2: bformat: RECORD
               opcode: INTEGER;
               ra: INTEGER;
               branchdisplacement: INTEGER;
             END;
        | 3: 
             ...
        END;
      END;

Hier ist zu beachten, daß alle Varianten eines Records übereinander liegen und den selben Speicherplatz verwenden. Hat man also in einer Variablen dieses Typs einen aufgeschlüsselten Maschinenbefehl gespeichert, weiß jedoch nicht, um welches Format es sich handelt, so kann man den Opcode aus einer beliebigen Variante herauslesen, da er immer an erster Stelle steht, und somit immer den gleichen Speicherplatz bezeichnet.

Das nachfolgende Beispiel zeigt einen Ausschnitt der Prozedur Unpack, die für das Zerlegen eines Maschinenbefehls und Füllen des Records zuständig ist.

    PROCEDURE UnPack (inst: CARDINAL; VAR instruction: alphainst);
    BEGIN
      WITH instruction DO
        CASE OPCODE(inst) OF
        | LDA, LDAH, LDF..STQC, LDQC, LDQU, STQU, SYNC:
          WITH mformat DO
            opcode := OPCODE(inst);
            ra := FRA(inst);
            rb := FRB(inst);
            memorydisplacement := MEMORYDISPFIELD(inst);
          END;
        |
          ...
        END;
      END;
    END UnPack;

Zunächst wird anhand des Opcodes das Format des Befehls inst und damit die zugehörige Variante des Records instruction bestimmt. Anschließend werden dann die einzelnen Felder dieser Variante belegt. Handelt es sich beim Opcode beispielsweise um einen der in der obigen CASE-Anweisung aufgeführten Speicherbefehle (LDA, LDAH, LDF, usw.), so wird die Variante mformat gefüllt. Die einzelnen Felder der Varianten erhalten ihre Werte von speziellen Prozeduren (OPCODE, FRA, FRB, MEMORYDISPFIELD), die die gewünschte Bitgruppe durch mehrmaliges Shiften aus dem 32-Bit-Maschinenbefehl inst extrahieren.

Das folgende Beispiel verdeutlicht dies am Registers Rb eines Speicherbefehls.

Durch diese Zerlegung und Speicherung ist nun, analog zum C-Beispiel, ein einfacher, aber wesentlich effizienterer Zugriff auf die einzelnen Teilfelder eines Maschinenbefehls möglich.

    VAR inst: alphainst;
    ...
    IF inst.mformat.opcode = LDQ THEN
      ...
    END;

Nach Beendigung des Schedulings müssen wieder alle Befehle aus ihren einzelnen Feldern mit Hilfe von Shift- und Or-Anweisungen zusammengesetzt werden. Dies geschieht in ähnlicher Weise wie die Zerlegung und soll nicht weiter erläutert werden.

Gleitkommazahlen VAX und IEEE Format

Modula-2 unterstützt zwei Gleitkommatypen, REAL (einfache Genauigkeit) und LONGREAL (doppelte Genauigkeit). In der VAX-Version MVR des Modula-2-Compilers von ModulaWare wurden diese beiden Typen mit Hilfe der Rechnerdatentypen F-Floating und D-Floating implementiert. Um nun mit früheren Programmen bzw. alten Dateien, in denen Gleitkommazahlen gespeichert sind, kompatibel zu bleiben, werden auch in der Alpha-Version MaX standardmäßig diese beiden Rechnerdatentypen verwendet. Da bei den Alpha-Prozessoren jedoch der Datentyp D-Floating nur durch Lade-, Speicher- und Konvertierungsbefehle unterstützt wird, nicht aber durch Rechenbefehle, müssen diese mit Hilfe des Typs G-Floating simuliert werden. Hierzu werden die D-Floating-Werte aus dem Speicher mit D-Floating-Befehlen in Gleitkommaregister geladen und in G-Floating-Werte umgewandelt. Mit diesen Werten wird dann eine G-Floating-Rechenoperation ausgeführt. Das Rechenergebnis wird anschließend wieder in einen D-Floating-Wert konvertiert und mit einem D-Floating-Befehl gespeichert.

Bei dieser Simulation tritt jedoch ein Problem auf. Der Wertebereich des Typs G-Floating (ungefähr -8E+307 bis 8E+307) ist zwar größer als der von D-Floating (ungefähr -1E+38 bis 1E+38), die Genauigkeit seiner Mantisse ist jedoch geringer. Bei D-Floating wird die Mantisse mit 55 + 1 Bits dargestellt (ca. 16 gültige Dezimalstellen), bei G-Floating hingegen nur mit 52 + 1 Bits (ca. 15 gültige Dezimalstellen). Bei einer Konvertierung von D- nach G-Floating wird deshalb die Mantisse um 3 Bits verkürzt und gerundet. Bei Operationen mit der größtmöglichen bzw. kleinstmöglichen D-Floating-Zahl MAX(LONGREAL) bzw. MIN(LONGREAL) kann dies dann zu unerwarteten Überlauffehlern führen.

Beispiel:

    r := MAX(LONGREAL);

    r := ABS(r); (* Überlauf *)

Der Überlauf bei der Zuweisung des Betrages von r kommt wie folgt zustande:

Der D-Floating-Wert von r wird in ein Register geladen und anschließend nach G-Floating konvertiert, hierbei werden die hinteren drei Bits der Mantisse abgeschnitten und binär auf den nächst höheren Wert aufgerundet, da sie alle drei 1 sind. Da bei MAX(LONGREAL) alle Bits der Mantisse 1 sind, kann ihr Wert nicht weiter vergrößert werden, und die Aufrundung erfolgt durch Nullsetzen der Mantisse und Erhöhen des binären Exponenten. Für einen D-Floating-Wert ist dieser Exponent jedoch zu groß, und nachdem das Vorzeichenbit gelöscht wurde, kommt es bei der Zurückkonvertierung zu einem Überlauf. Dieses Problem muß jedoch zu Gunsten der Kompatibilität mit alten VAX-Programmen und Dateien in Kauf genommen werden.

Verzichtet der Anwender auf diese Kompatibilität, so kann er dies dem Compiler durch den Aufrufparameter /IEEE mitteilen. Die Datentypen REAL und LONGREAL werden dann mit den Alpha-Typen S-Floating und T-Floating implementiert. Diese werden vollständig unterstützt, und es kommt zu keinen unerwarteten Überlauffehlern.

Erzeugung des Modula-2-Compilers MaX

Da die syntaktische und die semantische Analyse von Modula-2 maschinenunabhängig ist, und da aus Kompatibilitätsgründen sowohl für die Alpha-Symbolfiles als auch für die VAX-Symbolfiles das gleiche Format verwendet wird, bleiben die Module des Front-End bei der Alpha-Version die gleichen wie bei der VAX-Version. Lediglich die Module zur Codeerzeugung müssen neu implementiert werden.

Die vorhandenen Module und die speziellen Alpha-Module liegen in Modula-2-Quellcode vor, und werden mit dem vorhandenen VAX-Modula-2-Compiler übersetzt. Hierdurch entsteht ein Crosscompiler, der auf einer VAX-Station abläuft, jedoch Objektdateien mit Alpha-Code erzeugt. Diese werden auf der Alpha gelinkt. Jetzt können Compilerfehler mit Hilfe von Testprogrammen ausfindig gemacht und anschließend verbessert werden, solange bis korrekter Code erzeugt wird. Anschließend werden alle Module des Compilers mit dem Crosscompiler übersetzt, und es entsteht eine Version des Modula-2-Compilers, die nicht nur Alpha-Code erzeugt, sondern auch auf einer Alpha-Maschine ablauffähig ist. Als zusätzlicher Test werden nun mit diesem neuen Compiler noch einmal alle Module übersetzt, und erst wenn der Maschinencode des so erzeugten Alpha-Compiler identisch ist mit dem, der ihn übersetzt hat, kann MaX als zuverlässig angesehen werden.

ISO Modula-2 Bibliothek

Der ISO-Standard schreibt eine große Anzahl von Bibliotheksmodulen vor, die im Wesentlichen Prozeduren für Ein-/Ausgabe- und Stringkonvertierungszwecke enthalten, sowie mathematische Funktionen. Die Schnittstellen (DEFINITION MODULE) dieser Module werden bis hin zu den Kommentaren genau vorgegeben, für die Implementierung (IMPLEMENTATION MODULE) ist in allen Fällen die Semantik in Form der Spezifikationssprache VDM-SL angegeben. Das folgende Beispiel zeigt den Definitionsteil eines typischen Bibliotheksmodules.

DEFINITION MODULE WholeStr;

  (* Whole-number/string conversions *)

IMPORT
  ConvTypes;

TYPE
  ConvResults = ConvTypes.ConvResults; (* strAllRight, strOutOfRange, strWrongFormat, strEmpty *)

(* the string form of a signed whole number is
     ["+" | "-"], decimal digit, {decimal digit}
*)

PROCEDURE StrToInt (str: ARRAY OF CHAR; VAR int: INTEGER; VAR res: ConvResults);
  (* Ignores any leading spaces in str. If the subsequent characters in str are in the
     format of a signed whole number, assigns a corresponding value to int. Assigns a
     value indicating the format of str to res.
  *)

PROCEDURE IntToStr (int: INTEGER; VAR str: ARRAY OF CHAR);
  (* Converts the value of int to string form and copies the possibly truncated result to str. *)

(* the string form of an unsigned whole number is
     decimal digit, {decimal digit}
*)

PROCEDURE StrToCard (str: ARRAY OF CHAR; VAR card: CARDINAL; VAR res: ConvResults);
  (* Ignores any leading spaces in str. If the subsequent characters in str are in the
     format of an unsigned whole number, assigns a corresponding value to card.
     Assigns a value indicating the format of str to res.
  *)

PROCEDURE CardToStr (card: CARDINAL; VAR str: ARRAY OF CHAR);
  (* Converts the value of card to string form and copies the possibly truncated result to str. *)

END WholeStr.

Standardmodule

Im folgenden werden alle Standardmodule zusammen mit einer kurzen Beschreibung aufgelistet.


ConvTypes       Gemeinsame Typen der String-Konvertierungsmodule.
CharClass       Klassifizierung von Zeichen.
WholeConv       Low-level Ganzzahl/String-Konvertierung.
RealConv        Low-level REAL/String-Konvertierung.
LongConv        Low-level LONGREAL/String-Konvertierung.
WholeStr        Ganzzahl/String-Konvertierung.
RealStr         REAL/String-Konvertierung.
LongStr         LONGREAL/String-Konvertierung.

ChanConsts Gemeinsame Konstanten und Typen für das Öffnen von IO-Kanälen IOConsts Gemeinsame Konstanten für Zugriffe auf IO-Kanäle. IOResult Ergebnis eines Lesezugriffs auf einen IO-Kanal. IOChan Basisfunktionen für den Zugriff auf IO-Kanäle. IOLink Verwaltung von IO-Kanälen. StreamFile Verwaltung von IO-Kanälen zu sequentiellen Datenströmen. SeqFile Verwaltung von IO-Kanälen zu sequentiellen Dateien. RndFile Verwaltung von IO-Kanälen zu Dateien mit wahlfreiem Zugriff. TermFile Verwaltung von IO-Kanälen zum Terminal. StdChans Zugriff auf Standardkanäle.
RawIO Ein-/Ausgabe von Daten ohne Interpretation und Konvertierung. TextIO Ein-/Ausgabe von Zeichen und Strings. WholeIO Ein-/Ausgabe von Ganzzahlwerten in Textform. RealIO Ein-/Ausgabe von REAL-Zahlen in Textform. LongIO Ein-/Ausgabe von LONGREAL-Zahlen in Textform. SRawIO Wie RawIO, jedoch auf Standardkanal. STextIO Wie TextIO, jedoch auf Standardkanal. SWholeIO Wie WholeIO, jedoch auf Standardkanal. SRealIO Wie RealIO, jedoch auf Standardkanal. SLongIO Wie LongIO, jedoch auf Standardkanal.
RealMath Mathematische Funktionen für REAL-Zahlen. LongMath Mathematische Funktionen für LONGREAL-Zahlen. ComplexMath Mathematische Funktionen für komplexe REAL-Zahlen. LongComplexMath Mathematische Funktionen für komplexe LONGREAL-Zahlen.
LowReal Zugriff auf die interne Darstellung von REAL-Zahlen. LowLong Zugriff auf die interne Darstellung von LONGREAL-Zahlen.
Strings Prozeduren zur Stringmanipulation. Processes Verwaltung von zeitlich parallel ablaufenden Prozessen. Semaphores Semaphoreverwaltung für Processes. Storage Prozeduren zur dynamischen Speicherverwaltung. ProgramArgs Zugriffsprozeduren für Programmaufrufparameter.

Da sich die ISO-Bibliothek während des langwierigen Standardisierungsprozesses immer wieder ändert, d.h. Module, Prozeduren, Typen, Konstanten umbenannt, bzw. herausgenommen oder hinzugefügt werden, ergibt sich für die Compilerhersteller ein beträchtlicher Wartungsaufwand. Aus diesem Grund, und um unnötige Codeduplizierung zu vermeiden, war das Ziel bei der Entwicklung von MaX, soweit wie möglich, die gleichen Quellcodes für die Bibliothek zu verwenden, wie bei der VAX-Version MVR.

Erweiterungsmodule

Alle Standardmodule, die mit Gleitkommazahlen zu tun haben, sind aus Kompatibilitätsgründen zum VAX-Compiler MVR mit den Rechnerdatentypen F- und D-Floating implementiert. Außerdem handelt es sich bei dem Betriebssystem OpenVMS, wie auch schon beim Vorgänger VMS, um ein 32-Bit-System, weswegen die beiden Ganzzahltypen INTEGER und CARDINAL auch nur den 32 Bit langen Rechnertyp Longword verwenden. MaX stellt jedoch im Modul SYSTEM für alle Datentypen des Alpha-Prozessors einen Modula-2-Typ zur Verfügung, und so mußten von einigen Standardbibliotheksmodulen zusätzliche Versionen implementiert werden, um diese Typen zu unterstützen.

Gleitkommamodule

Die folgende Liste zeigt alle Erweiterungsmodule zur Konvertierung und Ein-/Ausgabe von Zahlen der Rechnerdatentypen G-, S- und T-Floating. Der Name eines jeden Moduls ist mit dem Namen des Standardmoduls identisch, von dem es abgeleitet wurde. Zur Unterscheidung ist lediglich ein Großbuchstabe G, S oder T eingefügt. Dieser bezeichnet den jeweils unterstützten Datentyp. Die Namen der Typen, Konstanten, Variablen und Prozeduren, die diese Module zur Verfügung stellen, wurden nicht verändert.


RealSConv                 LongGConv                   LongTConv
RealSStr                  LongGStr                    LongTStr
RealSIO                   LongGIO                     LongTIO
SRealSIO                  SLongGIO                    SLongTIO
RealSMath                 LongGMath                   LongTMath
ComplexSMath              LongComplexGMath            LongComplexTMath
LowRealS                  LowLongG                    LowLongT

Die Implementierung der meisten dieser Module war bis auf einige Kleinigkeiten relativ unproblematisch. Da die Ausgangsmodule sehr flexibel gehalten sind, mußten häufig nur einige Konstanten, Datentypen und Namen von importierten Betriebssystemfunktionen verändert werden. Größere Probleme ergaben sich bei den Modulen LowRealS und LowLongT. In ihnen wird hauptsächlich mit der internen Bitdarstellung der Gleitkommazahlen gearbeitet, und die unterscheidet sich bei S- und F-Floating bzw. T- und D-Floating erheblich, vor allem bei der Interpretation des Exponenten. Die beiden Module mußten im Prinzip völlig neu implementiert werden.

Ganzzahlmodule

Folgende Liste zeigt alle Erweiterungsmodule zur Konvertierung und Ein-/Ausgabe von 64-Bit-Ganzzahldatentypen.


WholeLConv                WholeLStr
WholeLIO                  SWholeLIO                   WholeLDiv

Es werden sowohl vorzeichenbehaftete Zahlen (SIGNED_64) als auch vorzeichenlose Zahlen (UNSIGNED_64) unterstützt. Die Module basieren auf den Standardmodulen zu den Ganzzahltypen INTEGER und CARDINAL und besitzen auch die gleichen Modulnamen. Zur Unterscheidung wurde lediglich der Großbuchstabe L für LONG eingefügt. Die Implementierung der Erweiterungsmodule war, wie bei den Modulen für Gleitkommazahlen, wegen der flexiblen Programmierung der Ausgangsmodule relativ einfach. Lediglich die benötigten Divisions- und Modulo-Operationen brachten ein Problem mit sich. Da der Alpha-Prozessor keine Divisions- bzw. Modulo-Befehle für Ganzahltypen besitzt, muß der Compiler für solche Operationen eine Laufzeitroutine aufrufen. Da es sich jedoch um einen 32-Bit-Compiler handelt, sind bisher noch keine Laufzeitroutinen für 64-Bit-Ganzzahlen vorhanden. Aus diesem Grund wurde das Modul WholeLDiv implementiert, in dem nun Prozeduren zur Division und Modulo-Rechnung mit 64-Bit-Ganzzahlen vorhanden sind.

Verwendung von MaX

Der Aufruf von MaX unter OpenVMS sieht wie folgt aus:


    $ MAX {/qualifier} sourcefile {/qualifier}

Handelt es sich bei dem Textfile sourcefile um ein MODULE bzw. ein IMPLEMENTATION MODULE, so wird ein standardmäßige OpenVMS-Objektdatei erzeugt, das mit Hilfe des OpenVMS-Linkers zu einem lauffähigen Programm gebunden werden kann. Enthält sourcefile ein DEFINITION MODULE, so wird anstelle der Objektdatei eine Symboldatei erzeugt. Eine solche Symboldatei enthält in codierter Form alle Elemente des Moduls, die von anderen Modulen verwendet werden können. Jedesmal, wenn in einem Modul ein anderes Modul importiert wird, lädt der Compiler die zugehörige Symboldatei, um prüfen zu können, ob die verwendeten Elemente tatsächlich im importierten Modul enthalten sind.

{/qualifier} steht für eine beliebige Anzahl von Compileroptionen, mit denen das Verhalten von MaX gesteuert werden kann. Die folgende Liste zeigt alle möglichen Optionen und eine kurze Beschreibung dazu.

/CHECK (D) Laufzeitüberprüfung von Bereichsüberschreitungen. /COMPLEX (D) Erlaubt die Verwendung von komplexen Zahlentypen. /CROSS_REFERENCE Erzeugt ein Cross-Reference-Listing. /DEBUG Erzeugt Informationen für den OpenVMS-Debugger. /FOREIGN_CODE Erzeugt Code, um die Prozeduren aus anderen Sprachen heraus aufrufen zu können. /IEEE Verwendung der IEEE-Typen anstelle der VAX-Typen für REAL und LONGREAL. /InstructionMode Aktivierung spezieller Alpha-Befehlsoptionen. /ISO [=Nummer] Wählt den zu verwendenden ISO Modula-2 Kompatibilitätsgrad aus. Voreinstellung: Nummer = 2. /LIST [=File] Erzeugt eine Listingdatei evtl. mit Fehlerangaben. File gibt Gerät, Verzeichnis und Dateiname an. Voreinstellung ist Gerät, Verzeichnis und Name der Quelldatei mit Dateityp .mod_lis. /LOG Ausgabe von Informationen über den Compiler und den Compiliervorgang. /MACHINE_CODE Ausgabe des erzeugten Maschinencodes in der Listingdatei. /NAME_SEPERATOR="x" Trennzeichen zwischen Modul- und Prozedurnamen in der Objektdatei. Voreinstellung: "_" /OBJECT [=File] (D) Erzeugt eine Objektdatei. File gibt Gerät, Verzeichnis und Dateiname an. Voreinstellung ist Gerät, Verzeichnis und Name der Quelldatei mit Dateityp .obj. /OMIT_MODULE_NAME Weglassen des Modulnamens und Trennzeichens bei Prozeduren in der Objektdatei. /Optimize=(Options) Verwendung einer oder mehrerer der folgenden Optimierungsmethoden: schedule, alignCheck, rotatePreserved, rotateScratch, stackCheck, eliminateUnreachableCode (Voreinstellung: rotateScratch, stackCheck) /Packed_Records (D) Gepacktes Ablegen der Record-Felder im Speicher. /QUERY MaX fragt explizit nach den zu verwendenden Symboldateien. /SYMFILE [=File] (D) Erzeugt eine Symboldatei. File gibt Gerät Verzeichnis und Dateiname an. Voreinstellung ist Gerät, Verzeichnis und Name der Quelldatei mit Dateityp .sym.

Die mit (D) gekennzeichneten Optionen sind voreingestellt. Zu allen Optionen existiert eine negative Form, die mit /NO... beginnt, z.B. /NOQUERY. Bei den Optionen, die nicht mit (D) gekennzeichnet sind, ist die zugehörige negative Form voreingestellt. Die im Compileraufruf angegebenen Optionen überschreiben die Voreinstellungen für diesen Aufruf.

Beispiel:

    $ MAX test /OBJECT /LIST /MACHINE_CODE /NOOPTIMIZE

Die Textdatei test.mod wird compiliert (ohne Codeoptimierung) und es wird eine Objektdatei namens test.obj sowei eine Listingdatei namens test.mod_lis erzeugt. Dieses Listing enthält außerdem den erzeugten Maschinencode.

Literatur

Weitere technische Einzelheiten zur Compiler Implementierung wurden veröffentlicht in

Günter Dotzel: OpenVMS AlphaAXP Modula-2 and Oberon-2 Compiler Project in dem Buch von

Schulthess, Peter (Ed.): JMLC'94 Conference Proceedings, Sep-1994, ISBN 3-89559-220-x (Seite 481-504).

Dieser Artikel wurde zuerst in DECKBLATT 12/94, AWi-Verlag, D-85630 Grasbrunn veröffentlicht.


[ Home | Site_index | Legal | OpenVMS_compiler | Alpha_Oberon_System | ModulaTor | Bibliography | Oberon[-2]_links | Modula-2_links | General interesting book recommendations ]

Books Music Video Enter keywords...


Amazon.com logo
Webdesign by www.otolo.com/webworx, 21-Nov-1998. © (1998) ModulaWare.com