4.1  Assembler: Grundlagen und Syntax                                   4 -  1
________________________________________________________


4.  Der  68000 Assembler

Die Megamax Modula-Implementation erlaubt Ihnen, innerhalb von Modula-Anwei~
sungen Programmteile in 68000-Assembler einzufgen. Dazu ist im Compiler
ein kompletter symbolischer Ein-Pass-Assembler integriert. Auf alle Datenstruk~
turen und Prozeduren, die in Modula deklariert wurden, kann ber die jeweiligen
Bezeichner zugegriffen werden.

Die folgende Beschreibung des Assemblers setzt voraus, da Sie die 68000
CPU in Assembler programmieren knnen. Dargestellt wird hier vor allem das
Zusammenspiel von Assembler- und Modula-Programmteilen. Dabei werden Sie
auch einiges ber das 'Innenleben' des Modula-Compilers erfahren. Wenn Sie
die Assemblerprogrammierung erst lernen wollen, sollten Sie eines der zahlreichen
Lehrbcher  konsultieren,  z.  B.  Kane/Hawkins/Leventhal:  68000  Assembly
Language Programming, Osborne/McGraw-Hill 1981


4.1  Grundlagen  und  Syntax

Egal, ob Sie komplette Assembler-Prozeduren mit allen Raffinessen program~
mieren wollen, oder nur mal einen Systemaufruf in drei Zeilen  brauchen  -
diesen Abschnitt sollten Sie in jedem Fall lesen. (Danach trennen sich dann die
Wege: Gelegenheits-Assemblerprogrammierer bitte in Abschnitt 4.2, Experten
in 4.3 weiterlesen!)


Assembler-Anweisungen in Modula-Programmen

Der  Megamax-Compiler  erweitert  die  Modula-Syntax  um  die  zustzliche
Anweisung ASSEMBLER, die aus dem SYSTEM-Modul importiert werden mu.
Die Syntax lautet

   Statement = .. | ASSEMBLER  AsmStatement  END | ..

berall, wo eine Modula-Anweisung erwartet wird, kann also ein Block  von
Assembler-Anweisungen eingeschoben werden. Wie ein AsmStatement genau
aussieht, erfahren Sie im folgenden Abschnitt. Vieles sehen Sie sicher schon
dem folgenden Beispiel an, das die bitweise Spiegelung einer CARDINAL-Variablen
('Bit Reversal') demonstriert:
4.1  Assembler: Grundlagen und Syntax                                   4 -  2
________________________________________________________


    FOR k := 0 TO kMax DO
                                                 :
         ASSEMBLER               ; *** kRev  = BitReversal (k)
               MOVE  k, D0        ; hole Schleifenindex
               MOVEQ #15, D1      ; 16 Bit sind umzukehren
         lp    LSR     #1, D0       ; schiebe Bit ins eXtend-Flag
               ROXL   #1, D2       ; .. und von dort ins Zielregister
               DBF     D1, lp        ; das Ganze 16 mal
               MOVE  D2, kRev    ; schreibe Ergebnis in die Variable kRev
         END;
         ...
    END (* FOR k *)


Aufbau von Assembleranweisungen

Der  Aufbau  einer  Assembleranweisung  ist  Ihnen  vermutlich  von  einem  der
konventionellen separaten Assembler bekannt:

   AsmStatement =  Label   Opcode  Operand    ';' Comment .

Zu den einzelnen Komponenten des AsmStatement finden Sie in den folgenden
Abschnitten dieses Kapitels nhere Informationen.

Zu  beachten  ist,  da  die  Assembler-Instruktionen  und  die  Registernamen
immer in Grobuchstaben geschrieben sein mssen! Es ist nicht mglich, nur
die Assembler-Blcke mit der $C-Option zu klammern, um sie klein schreiben
zu knnen. Aufgrund interner  Gegebenheiten  im  Compiler  wrden  Sie  dann
Schwierigkeiten haben, auf Variablen und Funktionen zuzugreifen. Statt dessen
mten Sie dann schon das ganze Modul mit $C- bersetzen.

bliche Assembler erwarten, da Sie ein AsmStatement pro Textzeile in Ihren
Quelltext schreiben. Der integrierte Megamax-Assembler pat sich hier den
Modula-Gepflogenheiten an: Er ist formatfrei; Sie knnen also ein AsmStatement
ber mehrere Zeilen verteilen oder auch mehrere Statements auf einer Zeile
zusammenfassen. Eine Besonderheit ist allerdings zu beachten: Ein ';'  leitet
einen Kommentar ein, der stets bis zum Zeilenende reicht.

Auer der Kommentierung nach ';' ist auch in Assemblerteilen die Verwendung
der Modula-Kommentarklammern (* *) mglich. Sie erlaubt einfaches Ausklam~
mern ganzer Programmteile.
4.1  Assembler: Grundlagen und Syntax                                   4 -  3
________________________________________________________


Befehlscodes und Adressierungsarten

Der Assembler bersetzt alle Befehlscodes und Adressierungsarten der 68000
CPU. Zur Notation wird die Standard-Syntax von Motorola verwendet, wie sie
in allen uns bekannten 68000-Lehrbchern verwendet wird. Als Assembler-
Knner drfen Sie also ohne Einschrnkungen drauflosprogrammieren.

Einige Besonderheiten sind allerdings zu beachten: Teilweise mu die genaue
Anweisung  geschrieben  werden,  auch  wenn  der  Assembler  dies  eigentlich
selbst korrigieren knnte. Bei Nichtbeachtung meldet der Compiler einen Fehler.
Beispiele:

  CMPI     #x,D0       ; hier kann nicht CMP verwendet werden
  CMPA.L  A0,A1       ; dito
  MOVE.L  A2,A0      ; hier allerdings braucht nicht MOVEA stehen

  TST      0(A0,D0.W) ; Offset (hier: 0) und Gre (.W) sind notwendig

  CLR      (A2)        ; erzeugt indirekte Adr. ohne Offset
  CLR      0(A2)       ; erzeugt indir. Adr. mit Offset
  CLR      offs(A2)    ; ist offs Null, wird indir. Adr. ohne Offset erzeugt

Als Operanden knnen Sie unter anderem Konstanten, Variablen und Prozeduren
verwenden, die Sie in Modula definiert haben. Allerdings mssen Sie etwas
Rcksicht auf den Compiler und die Art, wie er seine Variablen und Prozeduren
handhabt, nehmen. In den folgenden Abschnitten wird genau verraten, wie das
funktioniert; hier schon einmal die wichtige Grundregel:

Auf globale Objekte (Variable und Prozeduren) wird mit absoluter Adressierung
zugegriffen; auf lokale Objekte und Label mit relativer Adressierung (Label und
Prozeduren: PC-relativ; Variable: Adreregister mit Displacement). Trotz der
Verwendung absoluter Adressen bleiben auch Module mit Assemblerteilen stets
verschiebbar: Der Assembler legt automatisch die Informationen an, die zum
Umrechnen der Adressen vor dem Starten des fertigen Moduls bentigt werden.

Bei Konstant-Ausdrcken verhlt es sich exakt wie in CONST-Anweisungen.
Auch  stehen  alle  Modula-Funktionen,  wie  SIZE  oder  CHR,  zur  Verfgung.
Lediglich,  wenn  negative  Werte  angegeben  werden  sollen,  kann  unter
Umstnden der Ausdruck nicht mit dem Minuszeichen beginnen. Statt dessen
mu  dann  eine  Null  vorangestellt  werden,  um  daraus  eine  Subtraktion  zu
machen. Beispiele:

    MULU     #TSIZE (ArrayElement),D0
    MOVE.B  #0-LENGTH (StringConst),D1
4.1  Assembler: Grundlagen und Syntax                                   4 -  4
________________________________________________________


Label (Marken)

Als Label  (Sprungmarke)  knnen  alle  Bezeichner  verwendet  werden,  die  in
Modula als Variablennamen etc. zulssig wren. Reserviert sind allerdings alle
Opcode-Bezeichnungen der 68000 und auch die Modula-Schlsselworte. (Mein
Lieblingsfehler:  LOOP  ist  kein  erlaubtes  Label!)  Natrlich  drfen  Sie  auch
Bezeichner  benutzen,  die  auerhalb  des  Assemblerteils  bereits  anderweitig
(z. B. als globale Variable) definiert sind - auch der Assembler untersttzt das
Modula-Konzept der Lokalitt. Dabei mssen Sie allerdings Rcksicht darauf
nehmen, da auch der Assembler nur einen Durchgang durch den Quelltext
macht: In diesen Fllen mu das Label definiert sein, bevor Sie das erste Mal
darauf Bezug nehmen; sonst nimmt der Assembler an, die bereits bekannte
Bedeutung des Bezeichners sei gemeint. Normalerweise darf ein Label aber
ohne weiteres vor seiner Definition benutzt werden; die Definition mu dann im
gleichen Prozedur- bzw. Modulrumpf nachfolgen.

Labels darf ein Doppelpunkt hinten angestellt werden.

Labels drfen - wie schon erwhnt - nur in PC-relativen Adressierungsarten
benutzt werden. Beispiele fr korrekte Anwendungen:

               LEA     Daten(PC), A0        ; Zugriff auf Tabellen
               LEA     Ziel(PC), A1           ; gleich noch einer
    Schleife:  MOVE   (A0)+, (A1)+          ; diese Marke endet mit ':'
               BNE     Schleife               ; Bcc  und BSR sind mglich
               RTS
    Daten     DC.W   5, 4, 3, 2, 1, 0       ; der ':' mu aber nicht sein
    Ziel       DS      20


Relative Sprnge (Bcc und BSR)

Die  68000  kennt  bekanntlich  relative  Sprnge  mit  byte-  oder  wortlanger
Angabe   der   Sprungweite.   Wird   dabei   eine   bereits   bekannte   Marke
angesprungen,  so  verwendet  der  Assembler  automatisch  die  passende
Argumentgre.

Ist  dagegen  die  angesprungene  Marke  noch  undefiniert,  so  versucht  der
Assembler im Normalfall, einen kurzen Sprung zu erzeugen. Stellt sich  bei
Definition  der  Marke  heraus,  da  sie  zu  weit  von  einem  solchen  Sprung
entfernt ist, so ist eine nderung der Sprunglnge nicht mehr mglich - ein
Assemblerfehler wird angezeigt.

Um explizit die Erzeugung einer langen Sprungweite zu fordern, knnen Sie
Bcc.W  schreiben.  Die  explizite  Forderung  eines  kurzen  Sprungs  ist  nicht
erforderlich, aber durch Bcc.S oder Bcc.B mglich.
4.2  Einfache Assembleranwendungen                                     4 -  5
________________________________________________________


4.2  Einfache  Assembleranwendungen

Wenn  Sie  gelegentlich  ein  paar  maschinennahe  Operationen  in  Assembler
codieren mchten, ohne gleich das halbe Innenleben Ihres Atari zu studieren,
sind Sie in diesem Absatz richtig. Die folgenden Informationen gengen zur
Realisierung vieler Anwendungen. Die Darstellung aller Mglichkeiten des Assem~
blers bleibt jedoch dem Abschnitt 4.3 vorbehalten.


Belegung der CPU-Register

Die Register der 68000 CPU drfen Sie in Assemblerteilen nicht alle nach
Belieben verwenden. Der vom Compiler erzeugte Code benutzt die Register als
Zwischenspeicher. Einige Adreregister haben besondere Funktionen und mssen
daher sogar langfristig ihren Inhalt behalten.

       D0
        ..     Zwischenspeicher
       D2

       D3
        ..     reserviert
       D7

       A0     Zwischenspeicher
       A1     Zwischenspeicher
       A2     Zwischenspeicher
       A3     reserviert
       A4     reserviert
       A5     reserviert
       A6     reserviert - Zeiger auf aktuelle lokale Variable
       A7     reserviert - Zeiger auf CPU-Stack

Zwischenspeicher knnen Sie in Assemblerteilen frei verwenden; diese Register
behalten jedoch i.A. in Modula-Programmstcken nicht ihre Werte. Reservierte
Register drfen Sie gar nicht verndern!


Zugriff auf globale Variable

Der Zugriff auf globale Variable ist wirklich einfach: Nur den Namen benutzen
- fertig! Der Assembler ersetzt den Namen durch die absolute Adresse der
Variablen. Variablen sind also immer als  Source-  oder  Destination-Adresse
zulssig, wenn die 68000 absolute Adressierung erlaubt. Beispiele:
4.2  Einfache Assembleranwendungen                                     4 -  6
________________________________________________________


CLR.W        MeinCardinal
CMPI.B        #'x', MyChar
SUBQ.L       #2, LongCount
MOVE.W      j,k
LEA           ArrayVar, A0

Etwas mehr mssen Sie naturgem tun, wenn die Variable nicht 1, 2 oder 4
Bytes lang ist, also keine der 68000-Operandengren hat. Beispiele:

         RecTyp:       RECORD
                           name:     ARRAY  0..9  OF CHAR;
                           left, right: ADDRESS;
                        END;
VAR     MyReal:       LONGREAL;
         Index:        CARDINAL;
         MyString:     ARRAY  0..79  OF CHAR;
         MyRecord:    RecTyp;

BEGIN
  ASSEMBLER
    LEA           MyReal, A0      ; Real-Variable auf den Parm-Stack bringen
    MOVE.L       (A0)+,(A3)+
    MOVE.L       (A0),(A3)+

    LEA           MyString, A0    ; D1 := MyString  Index 
    MOVE.W      Index, D0
    MOVE.B       0(A0,D0.W), D1
  END
END ...

Das letzte Beispiel zeigt, wie ein Stringelement erreicht  werden  kann.  Der
Zugriff auf beliebige Felder wird in Abschnitt 4.3 erlutert.

Namen von RECORD-Feldern interpretiert der Assembler als den Abstand des
Feldes vom RECORD-Anfang. Ein RECORD-Feld knnen Sie also adressieren,
indem Sie die RECORD-Anfangsadresse in ein Register laden und den Feldnamen
als 'Displacement' verwenden. Beachten Sie, da trotzdem vor dem Feldnamen
der Typname des Records stehen mu, damit der Assembler den Feldnamen
berhaupt  erkennt  (es  sei  denn,  der  Assemblerteil  steht  innerhalb  einer
WITH-Anweisung, die das benutzte RECORD erffnet).

    LEA           MyRecord, A0        ; *** teste, ob MyRecord.left = NIL
    TST.L         RecTyp.left (A0)
    BEQ           empty
4.2  Einfache Assembleranwendungen                                     4 -  7
________________________________________________________


Zugriff auf lokale Variable

Lokale  Variable  haben  keinen  festen  Speicherbereich,  sondern  werden  bei
jedem Aufruf der zugehrigen Prozedur neu angelegt (wichtig fr Rekursion!).
Auf den  Beginn  des  Variablenbereichs,  der  zur  gerade  laufenden  Prozedur
gehrt, zeigt dann das Adreregister A6. Alle lokalen Variablen mssen relativ
zu diesem Register adressiert werden.

Der Assembler untersttzt diese Adressierung, indem er (wie bei Recordfeldern)
lokale Variablennamen in ein Displacement umsetzt. Sie geben hinter diesem
Displacement das Basisregister an (immer A6). Beispiele:

PROCEDURE asmDemo;
  VAR  j, k, MeinCardinal: CARDINAL;
        MyChar: CHAR;
        LongCount: LONGCARD;
        ArrayVar: ARRAY  0..9  OF INTEGER;
  BEGIN
     ASSEMBLER
         CLR.W        MeinCardinal(A6)
         CMPI.B        #'x', MyChar(A6)
         SUBQ.L       #2, LongCount(A6)
         MOVE.W      j(A6), k(A6)
         LEA           ArrayVar(A6), A0
         ...

Um lokale ARRAYs oder RECORDs zu adressieren, laden Sie deren Anfangs~
adresse in ein Register (letztes Beispiel) und verfahren dann genauso weiter,
wie oben fr globale Variable beschrieben.

Schlielich  gibt  es  auch  die  (etwas  kompliziertere)  Mglichkeit,  in  lokalen
Prozeduren Variablen der bergeordneten ueren Prozedur zu  adressieren.
Wenn Sie auch solche Zugriffe in Assemblerroutinen bentigen sollten, sehen
Sie sich bitte den Abschnitt 'lokale Variablen' im folgenden Teil 4.3 an.
4.3  Assembler fr Experten                                             4 -  8
________________________________________________________


4.3  Assembler  fr  Experten

In  diesem  Abschnitt  erfahren  Sie,  wie  Sie  an  beliebige  Modula-Daten  und
-Prozeduren von Assembler aus herankommen. Dazu mssen  wir  allerdings
gelegentlich etwas 'ans Eingemachte' gehen und Details der Laufzeitorganisation
von Modula-Programmen diskutieren. Darauf waren Sie sowieso neugierig? Um
so besser...


Pseudo-Opcodes

Der Assembler untersttzt folgende Pseudo-Opcodes, die vor allem zum Anlegen
von Tabellen dienen:

Define Constant
     legt Konstanten als Byte, Wort oder Langwort im Code ab.
    DC.B     $1001, $1002, 'a'            ; legt LowBytes ab ($01,$02,$61)
    DC.W     $A, $B, 12, 13
    DC.L      WriteLn, WriteString        ; legt Prozeduradressen ab
    DC.D     1.5, -0.3333333             ; legt Longreal-Konstanten ab

Define Storage
    reserviert Platz im Code. Meist ist die Verwendung von Modula-Variablen
    einer DS-Anweisung vorzuziehen!
    DS       100                          ; 100 Byte freihalten

ASCII Constant
    legt Zeichenfolge im Code ab (ohne Endmarke)
    ASC      "Hallo"

ASCII with Zero
    legt Zeichenfolge mit Endmarke 0.B ab (Stringformat).
    ACZ      "Hier folgt eine Null ->"    ; so sehen auch Modula-Strings aus

ASCII with Length (Pascal-like String)
    legt Zeichenfolge mit fhrendem Lngenbyte ab.
    STR      "Pascal"                  ; genauso wie: DC.B 6,'P','a','s','c','a','l'

Synchronisieren
    erzeugt ein Null-Byte, falls der PC ungerade ist. Anzuwenden, wenn z.B.
    nach Byte-Tabellen Befehlscodes folgen.
    SYNC                                  ; hat keine Argumente
4.3  Assembler fr Experten                                             4 -  9
________________________________________________________


Wenn Sie unter den Pseudo-Opcodes Anweisungen zur Definition von Labels
auf bestimmte Adressen vermissen, liegt das nicht daran, da wir die verges~
sen htten - die Deklaration von Konstanten in Modula (CONST-Deklaration)
sowie die Deklaration von Variablen auf festen Adressen  (VAR  a   $1234 )
bietet ja genau diese Funktionen.


Belegung der CPU-Register

Die Register der 68000 CPU drfen Sie in Assemblerteilen nicht alle nach
Belieben verwenden. Der vom Compiler erzeugte Code benutzt die Register als
Zwischenspeicher. Einige Adreregister haben besondere Funktionen und mssen
daher sogar langfristig ihren Inhalt behalten.  Diese  speziellen  Adreregister
sind teilweise auch fr den Assemblerprogrammierer von Nutzen; ihre Funktion
wird hier genauer erlutert.

       D0
        ..     Zwischenspeicher
       D2

       D3
        ..     reserviert - fr Register- und FOR-Variable
       D7

       A0     Zwischenspeicher
       A1     Zwischenspeicher
       A2     Zwischenspeicher
       A3     reserviert - Zeiger auf Parameter-Stack
       A4     reserviert - fr Register-Variable und WITH
       A5     reserviert
       A6     reserviert - Zeiger auf aktuelle lokale Variable
       A7     reserviert - Zeiger auf CPU-Stack

Zwischenspeicher  knnen  Sie  in  Assemblerteilen  verwenden;  diese  Register
behalten jedoch im allgemeinen in Modula-Programmstcken nicht ihre Werte.

Der Zeiger auf den CPU-Stack (A7) ist Ihnen als Assembler-Programmierer
bereits bekannt. Wie von der Architektur der 68000 vorgegeben, benutzt der
Compiler diesen Stack zum Ablegen von Rcksprungadressen  bei  Unterpro~
grammadressen; auerdem werden die lokalen Variablen fr aufgerufene Proze~
duren auf dem CPU-Stack angelegt. (Die 68000 untersttzt dies durch das
Opcode-Paar  LINK/UNLK).  Auch  in  einigen  anderen  Situationen  (FOR-  und
WITH-Anweisungen) wird Platz auf  dem  CPU-Stack  bentigt.  Dieser  Stack
'wchst' bekanntlich von oben nach unten; beim Aufstapeln neuer Daten wird
A7 erniedrigt (dekrementiert).
4.3  Assembler fr Experten                                             4 - 10
________________________________________________________


Der Zeiger auf den Parameter-Stack (A3) verwaltet einen  weiteren  Stack.
Dieser dient speziell zur bergabe von Parametern (und evtl. Ergebnissen) von
Prozeduren.  Auch  Zwischenergebnisse  bei  der  Auswertung  von  Ausdrcken
werden dort abgelegt. Der Parameter-Stack wchst von unten nach oben, und
zwar direkt dem CPU-Stack entgegen: Im Arbeitsspeicher eines aufgerufenen
Programms steht A7 zunchst am oberen, A3 am unteren Rand. Dadurch wird
der Arbeitsspeicher je nach Bedarf von beiden Stacks beansprucht.

Das Register zur Verwaltung der lokalen Variablen (A5) funktioniert viel einfacher,
als sein Name befrchten lt. Um Rekursion zu ermglichen, mu bei jedem
Aufruf einer Prozedur neuer Platz fr die lokalen Variablen organisiert werden.
(Vielleicht ruft sich die Prozedur ja gerade rekursiv selbst auf; dann werden
die vorher benutzten Variablen nach der Rckkehr wieder gebraucht!)

Dazu bietet die 68000-CPU die LINK-Instruktion an: LINK A5, #Platz rettet
A5 auf dem CPU-Stack, kopiert den Stackpointer nach A5 und subtrahiert
<Platz> vom Stackpointer. Presto - schon zeigt der Stackpointer auf <Platz>
Bytes freien RAM, den wir fr die Variablen benutzen wollen. Am Ende der
Prozedur gengt ein UNLK, um A5 und den Stackpointer wieder in den Zustand
vor dem letzten LINK zu versetzen. - Das Ganze funktioniert natrlich auch
mit anderen Registern als A5; der Compiler hat sich aber auf dieses Register
festgelegt und hofft sehr, da Sie es in Ihren Assemblerteilen immer schn
heil lassen.

Bleibt noch der Zeiger auf die aktuellen lokalen Variablen (A6) zu erwhnen.
Wie Sie im vorigen Absatz gelesen haben, zeigt  nach  der  LINK-Instruktion
zunchst A7 auf den gerade reservierten Platz. Da sich A7 aber (etwa  in
FOR-Schleifen) verschieben wird, bentigen wir einen anderen, stabilen Zeiger
auf die Variablen. Der Compiler reserviert hierfr A6 und erzeugt an jedem
Prozeduranfang Code, der A6 auf die Variablen zeigen lt.


Zugriff auf Modula-Konstanten

In Modula definierte Konstanten knnen Sie ohne Einschrnkungen als Operan~
den verwenden. Sowohl die Benutzung als 'Immediate'-Daten (hinter '#')  als
auch als globale Adressen oder relative Displacements ist mglich. Beispiele:

MOVE.L       #Konstante, D0
LEA           AdressKonst, A0
MOVE.L       D0, AdressKonst
LEA           Offset(A0), A2
4.3  Assembler fr Experten                                             4 -  11
________________________________________________________


Zugriff auf globale Modula-Variable, speziell ARRAYs

Das Wichtigste ber globale Variablen haben wir schon in Kapitel 4.2 erlutert
(Abschnitt 'Zugriff auf globale Modula-Variable'); diese Grundlagen wollen wir
hier  nicht  wiederholen.  Schuldig  geblieben  sind  wir  aber  die  Beschreibung
allgemeiner ARRAY-Zugriffe.

Die Anfangsadresse des ARRAYs wird in ein Adreregister geladen. Ist die
untere Grenze des Indexbereiches nicht Null, so mssen Sie vom Indexwert
zunchst diese Untergrenze subtrahieren - das erste Element des Feldes steht
also immer direkt am Beginn des Feldplatzes. Der so korrigierte Index wird
dann mit der Byte-Gre der Feldelemente multipliziert. Bei der Bestimmung
der Elementgren hilft Anhang A.3. Beispiel:

LEA           MyArray, A0         ; MyArray  Index  auf den CPU-Stack bringen
MOVE.W      Index, D0
SUB.W        #LowBound, D0       ; Untergrenze vom Index abziehen
LSL.W        #3, D0               ; Index mit Elementgre (2^3) multiplizieren
MOVE.L       0(A0,D0.W), -(A7)  ; erst die vorderen 4 Bytes auf den Stack...
MOVE.L       4(A0,D0.W), -(A7)  ; ... dann noch den Rest

Ist das Produkt aus Elementlnge und Index-Untergrenze kleiner als 128, dann
ist eine vereinfachte Version des Feldzugriffs mglich: Statt die Untergrenze
vom  Index  zu  subtrahieren,  knnen  Sie  auch  das  Displacement  beim
eigentlichen  Zugriff  korrigieren.  Beispiel  fr  den  hufigen  Fall,  da  die
Untergrenze 1 ist (MyArray: ARRAY  1..n  OF CARDINAL):

LEA           MyArray, A0          ; *** Ziel := MyArray Index 
MOVE.W      Index, D0
ADD.W        D0, D0               ; Elementlnge ist 2 Byte
MOVE.W      -2(A0,D0.W), Ziel   ; Zugriff mit Korrektur der Untergrenze


Zugriff auf lokale Variable

Auch zu diesem Thema finden Sie die grundlegenden Informationen in Kapitel
4.2. Im Folgenden werden Sie zustzlich erfahren, wie in lokalen Prozeduren
auf die Variablen bergeordneter Prozeduren zugegriffen wird.

Auch diese Variablen sind noch nicht global, werden also zur Laufzeit dynamisch
angelegt. Um sie wiederfinden zu knnen, baut der Compiler zur Laufzeit eine
Zeigerkette auf: In lokalen Prozeduren zeigt Adreregister A6 gar nicht direkt
auf die aktuellen Variablen der laufenden Prozedur (da haben wir oben  ein
bichen vereinfacht). Unter der Adresse (A6) finden Sie zunchst einen Zeiger
auf die Variablen der bergeordneten Prozedur; erst dahinter (Adresse 4(A6))
beginnen die eigenen Variablen. Bei der Adressierung ber den Variablennamen
wird dieser Offset natrlich automatisch bercksichtigt.
4.3  Assembler fr Experten                                             4 - 12
________________________________________________________


Damit ist Ihnen sicher schon klar, wie Sie an die ueren Daten herankommen.
Das folgenden Beispiel beseitigt hoffentlich die letzten Zweifel:

PROCEDURE auen;
    VAR  aVar: CARDINAL;

    PROCEDURE innen;
       VAR iVar: CARDINAL;
       BEGIN
          ASSEMBLER
             MOVE.L  (A6), A0        ; A0 zeigt auf Variablen von 'auen'
             ADDQ    #1, aVar(A0)    ; jetzt ist aVar erreichbar
             MOVE    aVar(A0), iVar(A6)
          END
       END innen;
    ..
    END auen;

Angenommen, in diesem Beispiel ist auch 'auen' lokal zu einer dritten Prozedur
'ganzAuen'  deklariert,  und  an  deren  Variablen  wollen  Sie  nun  von  'innen'
herankommen - dann brauchen Sie die Zeigerkette natrlich nur eine Stufe
weiter zu verfolgen, denn auch 'auen' hat vor seinen Variablen einen Zeiger
auf die bergeordneten Daten. Also:

     MOVE.L  (A6), A0    ; A0 zeigt auf Variablen von 'auen'
     MOVE.L  (A0), A0    ; A0 zeigt auf Variablen von 'ganzAuen'

Da wir vorhaben, einige Optimierungen an der Code-Erzeugung des Compilers
vorzunehmen,  weisen  wir  vorsorglich  darauf  hin,  da  Zugriffe  auf  lokale
Variablen innerhalb von WITH-Anweisungen nicht durchgefhrt werden sollen.
Statt dessen  ist  besser  eine  lokale  Prozedur  anzulegen,  die  innerhalb  der
WITH-Anweisung aufgerufen werden kann und  in  der  dann  die  Assembler-
Zugriffe, wie oben beschrieben, auf die lokalen Variablen erfolgen knnen.
4.3  Assembler fr Experten                                             4 - 13
________________________________________________________


Fehlerprfungen vom Assembler

Der Assembler fhrt ein paar berwachungen durch, auf die wir Sie vielleicht
hinweisen sollten:

Fhren Sie einen Datenzugriff auf eine Modula-Variable  durch  (z.B.  MOVE,
ADD, nicht jedoch LEA), prft der Compiler, je nachdem, ob dies sinnvoll sein
kann. So macht es keinen Sinn, MOVE var,D0 zu programmieren, wenn die
Variable  lokal  ist.  Ebenso  unsinnig  wre  der  Zugriff  mit  einem  Adre-
Displacement  bei  einer  globalen  Variablen.  In  solchen  Fllen  meldet  der
Assembler den Fehler Logisch falsche Adressierung. Die selbe Fehlermeldung
knnen Sie auch bei anderen, sinnlosen Verwendungen von Modula-Bezeichnern
erwarten. Allerdings meckert er nicht, wenn Sie beim Zugriff auf eine lokale
Variable das falsche Adreregister benutzen (also nicht A6), Sie knnten A6 ja
in das verwendete Register kopiert haben.


Aufruf von globalen Modula-Prozeduren

Um eine global unter Modula deklarierte Prozedur aufzurufen, gengt ein JSR
ProzedurName.  Der  Compiler  erzeugt  daraus  einen  Aufruf  mit  absoluter
Adressierung (Sie erinnern sich an die Grundregel aus Abschnitt 4.1?). Das
funktioniert ohne weiteres auch fr importierte Prozeduren, z.B. JSR WriteLn.

Wenn eine Prozedur Parameter erwartet, liegt es beim Aufruf aus Assembler~
programmen in Ihrer Verantwortung, diese richtig bereitzustellen. Das geht so:

* Vor dem Aufruf einer Prozedur werden alle Parameter in der Reihenfolge
ihrer Deklaration im Prozedurkopf auf den Parameter-Stack gebracht.

* Bei Wert-Parametern ('call by value') wird eine Kopie des Wertes bergeben;
bei  VAR-Parametern  ('call  by  reference')  bergeben  Sie  die  Adresse  der
Variablen.

* Wichtig bei Wert-bergaben: Der Parameter-Stackpointer mu nach jedem
Parameter  synchronisiert  werden!  Ist  ein  Parameter  eine  ungerade  Anzahl
Bytes lang, anschlieend A3 um 1 Byte erhhen!

*  An  Open  ARRAY-Parameter  wird  (bei  Wert-  und  bei  VAR-Parametern)
immer die Adresse und anschlieend der HIGH-Wert (als Wort) bergeben. Bei
bergabe an Open ARRAY Wert-Parameter mu eine Kopie des Parameters
erzeugt und bergeben werden, damit die aufgerufene Prozedur das Original
nicht verndern kann.
4.3  Assembler fr Experten                                             4 - 14
________________________________________________________


* Die aufgerufene Prozedur sorgt fr das Abrumen der Parameter vom A3-Stack.
Funktions-Prozeduren legen vor der Rckkehr statt dessen ihren Ergebniswert
dort ab.

Noch nicht alles klar? Dann helfen sicher einige Beispiele:

MODULE AsmBsp;

FROM SYSTEM IMPORT ASSEMBLER;

VAR MeinCardinal : CARDINAL;  (* 16 Bit: 1 Word *)
     MeinString : ARRAY  0..39  OF CHAR;
     MeinReal, DeinReal : LONGREAL;

BEGIN
  ASSEMBLER
     ; WriteCard (c: LONGCARD; space: CARDINAL)
    MOVEQ.L     #0, D0                      ; Long-Wert in D0 lschen
    MOVE.W      MeinCardinal, D0             ;Wert von MeinCardinal
    MOVE.L       D0, (A3)+                   ; und auf den Stack damit
    MOVE.W      #8, (A3)+                    ;Feldbreite fr Ausgabe
    JSR           WriteCard                   ;Ausgeben

     ; ReadCard (VAR c: CARDINAL)
    MOVE.L       #MeinCardinal, (A3)+        ; Adresse von MeinCardinal
    JSR           ReadCard                    ;Einlesen

     ; ReadString (VAR s: ARRAY OF CHAR)
    LEA           MeinString, A0              ;ein  ARRAY  0..39  OF CHAR
    MOVE.L       A0, (A3)+                    ;Adresse des Strings
    MOVE.W      #SIZE(MeinString)-1,(A3)+   ;HIGH-Wert
    JSR           ReadString

     ; sin (x: REAL): REAL
    LEA           MeinReal, A0                ; Realzahl auf den Stack
    MOVE.L       (A0)+, (A3)+                ; ..
    MOVE.L       (A0), (A3)+                  ; ..
    JSR           sin                           ; sin (MeinReal) berechnen
    LEA           DeinReal, A0                 ; Real-Ergebnis vom Stack abholen
    MOVE.L       -(A3), 4(A0)                 ; ..
    MOVE.L       -(A3), (A0)                  ; ..
4.3  Assembler fr Experten                                             4 - 15
________________________________________________________


Aufruf von lokalen Modula-Prozeduren

Keine Sorge - alles, was Sie eben ber Parameterbergaben gelernt haben, gilt
fr lokale Prozeduren ganz genau wie fr globale. Einen Unterschied gibt es
nur beim Aufruf selbst: Lokale Prozeduren werden PC-relativ adressiert, also
durch BSR ProzedurName.

Vor dem Aufruf mssen Sie allerdings noch eine weitere Information bereitstel~
len: einen Zeiger auf den nchsthheren erreichbaren Variablenbereich, der im
Datenregister D2 zu bergeben ist. Welcher Bereich sichtbar ist, hngt von
der Deklarations-Hierarchie von Aufrufer und aufgerufener Prozedur ab. Das
folgende  Beispiel  illustriert  die  beiden  gngigen  Anordnungen  und  zeigt  die
Bereitstellung des Zeigers:

PROCEDURE auen;

   PROCEDURE innen1;
   END innen1;

   PROCEDURE innen2;

       PROCEDURE ganzInnen;
       END ganzInnen;

       BEGIN (* innen2 *)
          ..
          MOVE.L   A6, D2    ; Aufrufer ruft zu ihm lokale Prozedur:
          BSR       ganzInnen  ; Variablen des Aufrufers sichtbar
          ..
          MOVE.L   (A6), D2   ; Aufrufer ruft Prozedur auf gleicher Ebene:
          BSR       innen1      ; die Variablen, die der Aufrufer sieht,
          ..
                                 ;  sieht auch der Gerufene
          MOVE.L   (A6), D2   ;  rekursive Aufrufe rufen ebenfalls
          BSR      innen2      ;  eine Prozedur auf gleicher Ebene
           ..
      END innen2;

   BEGIN
      ..
   END auen;


Nochmal   in   Kurzfassung:   Parameter   bereitstellen   (wie   unter   "globale
Prozeduren" beschrieben); je nach Aufruf-Hierarchie passenden Zeiger nach
D2 holen; Aufruf mit BSR.
4.3  Assembler fr Experten                                             4 - 16
________________________________________________________


bernahme von Parametern (Link-Option)

Normalerweise sorgt der Compiler dafr, da zu Beginn einer Prozedur die
Parameter in lokale Variablen bernommen werden. In Assembleranweisungen
knnen Sie - wie auch unter Modula - die Parameter genau wie lokale Variablen
ber ihre Namen adressieren.

Wenn Sie einen kompletten Prozedurrumpf in Assembler implementieren wollen,
kann das Umkopieren vom Parameter-Stack in lokale Variablen (und von dort
spter in CPU-Register) ineffizient sein. Oft ist es wnschenswert, die Parame~
ter direkt vom Stack in CPU-Register zu bernehmen. In diesem Fall knnen
Sie das Umkopieren  durch  den  Compiler  unterdrcken,  indem  Sie  vor  den
Prozedurrumpf die Compileroption (*$ L- *) setzen (s. 3.3). Dann gilt:

* Die Parameter stehen so auf dem Stack (A3), wie oben im Abschnitt 'Aufruf
globaler Modula-Prozeduren' beschrieben wird. Die aufgerufene Prozedur (also
Ihr Assemblerprogramm)  ist  fr  das  Abrumen  der  Parameter  vom  Stack
verantwortlich.

* Funktions-Prozeduren mssen vor der Rckkehr den Ergebnis-Wert auf den
Parameterstack bringen.

* Prozeduren, die unter (*$ L- *) bersetzt werden,  drfen  keine  lokalen
Variablen haben! Falls Sie auer den CPU-Registern zustzlichen Speicherplatz
bentigen, sollten Sie ihn auf dem CPU-Stack (A7) oder mittels DS-Anweisungen
selbst anlegen.

Als Beispiel hier noch einmal der 'Bit Reversal'-Algorithmus aus 4.1, diesmal als
komplette Funktion, wie sie in einem FFT-Modul (Fast Fourier Transformation)
Verwendung finden knnte:

PROCEDURE BitRev (c: CARDINAL): CARDINAL;
    (*$ L- *)
    BEGIN
         ASSEMBLER
               MOVE   -(A3),D0    ; hole Parameter
               MOVEQ #15, D1      ; 16 Bit sind umzukehren
         lp    LSR     #1, D0       ; schiebe Bit ins eXtend-Flag
               ROXL   #1, D2       ; .. und von dort ins Zielregister
               DBF     D1, lp        ; das Ganze 16 mal
               MOVE  D2,(A3)+    ; schreibe Ergebnis auf den Stack
         END;
    END BitRev;
    (*$ L= *)
