Ganz kurzes Accessory - die Zweite

ALS ASSEMBLER-PROGRAMMIERER FÜHLTE ICH MICH NATÜRLICH HERAUSGEFORDERT, EIN 'BIS AUFS LETZTE BYTE OPTIMIERTE ACCESSORY' [JÜRGEN STESSUN: EIN GANZ KURZES ACCESSORY, ST COMPUTER 101 89] NOCH KÜRZER ZU SCHREIBEN. DABEI SOLLTEN KEINE UNDURCHSICHTIGEN TRICKS ZUR ANWENDUNG KOMMEN, SONDERN EFFEKTIVER ASSEMBLER-CODE, DER SICHDURCH REDUZIERUNG AUF DAS ALLERNÖTIGSTE AUSZEICHNET.

Mit den resultierenden 264 Bytes Länge ist noch längst nicht das Minimum erreicht; in Zeile 33 des Listings ist erläutert, wie man weitere 2 Bytes einsparen kann; in Zeile 35 ist ein langer Sprung (bsr), der wahrscheinlich durch Umstrukturierung optimiert werden kann. Ich hab's stehen gelassen (was einmal steht und gut läuft...).

Da unser MINI_ACC verschiedene AES-Funktionen aufruft und wir keine Bibliothek hinzulinken wollen, müssen die AES-Parameter von Hand gesetzt werden. Das Control-Array muß stets mit 5 Werten belegt werden: die Funktionsnummer nach control [0] sowie die Anzahl der Parameter in den übrigen Feldern nach control [1] bis [4]. Hinzu kommen, je nach Funktion, noch weitere Parameter, die in intin und addrin übergeben werden. In meiner MINI_ACC-Version verwende ich den movep-Befehl zur Übergabe der Parameter in control [1]bis [4]. Dadurch verkürzt sich die Handarbeitauf move.l #$01010 100,d1.

Noch ein Wort zum movep-Befehl: Dieser Befehl transportiert Bytes mit einer Schrittweite von 2 Bytes. Anders gesagt: Je ein Byte wird wortweise (an einer geraden oder an einer ungeraden Adresse beginnend) in den Speicher geschrieben oder gelesen. Diese Befehlsverwendung ist für die GEM-Parameterübergabe eindeutig die kürzeste und außerdem sehr übersichtlich. Leider kamen frühere Versionen des GFA-Assemblers mit dem movep-Befehl nicht zurecht, doch seit der Version 1.3 ist dieser Fehler behoben.

Zur Speicheradressierung wird Register A5 mit dem Label contrl geladen und im weiteren Verlauf nicht geändert, sonst müßte es gerettet oder neu geladen werden. Alle benötigten Adressen werden dann durch symbolische Offsets angesprochen. In den Zeilen 18-25 werden die Konstanten dazu vereinbart, die Werte entstehen durch Addition der benötigten Distanz zum vorhergehenden. Verständlicher wird die Berechnung der Konstanten, wenn man sie mit den Labels im BSS-Segment vergleicht. Dem aufmerksamen Leser wird nun auffallen, daß das Label evnt- msg im Text-Segment gar nicht angesprochen wird. Doch dies geschieht in Zeile 44 mit lea.l evnt(a5),a0. In der Tat kann bei der Programmierung mit symbolischen Displacements auf das Setzen von Marken im BSS-Segment verzichtet werden. Man muß nur die Konstanten korrekt berechnen und genügend Speicherplatz anlegen. Es muß auch nicht jedes Displacement als Konstante definiert sein, z.B. wird intin [1] mit move.w #1 ,int+2(a5) erreicht. Im vorliegenden Quelltext wird in Zeile 38 die ID-Nummer der Applikation mittels move.w apid(a5),int(a5) von global[2] nach intin [0] übertragen.

Für die Überlegungen der Optimierung ist interessant, daß eine Routine im Programm häufig aufgerufen wird (aes), in der die sich wiederholende Arbeit erledigt wird. Daher sind die spezifischen Parameter der Funktionsaufrufe sehr schnell in Datenregister geladen (Zeile 31 und 32). Die Programm-ID (Identifikationsnummer) und die Nummer des Accessory-Eintrag müssen nicht zwischengespeichert werden. Die ID ist ohnehin im Global-Feld eingetragen (macht GEM bei der Anmeldung). Korrekterweise müßte man auf Fehlermeldungen, besonders bei menu_register entsprechend reagieren, indem man das Accessory 'auf Eis' legt, also statt evnt_mesag dann event_-timer mit einer langen Zeitdauer aufrufen. Es kann ja durchaus vorkommen, daß sich vorher schon sechs andere Accessories angemeldet haben.

Die Warteschleife des Accessories beginnt automatisch mit dem AES-Aufruf evnt_mesag und wird nur dann verfassen, wenn das Ereignis AC_OPEN (Zeile 46) gemeldet wird. GEM sorgt schon dafür, daß nur die Meldungen, die unser Programm betreffen, im Message-Puffer landen. Dann geht's sofort weiter mit der Ermittlung des freien Speicherplatzes; dieser wird auf eher konventionelle Weise nach dezimal und dann nach ASCII gewandelt. Leider ist mir auch keine universelle Routine eingefallen, die von der Länge her mithalten könnte. Diese Wandlungsroutine ist auf 7 dezimale Ausgabestellen festgelegt, wobei allerdings führende Nullen unterdrückt werden. Sie ist aber ganz ordentlich optimiert: D4 wird mit moveq.l #0,d4 initialisiert und dient dann als Flag für führende Spaces. Der Zähler D l, der ja die Dezimalstelle repräsentiert, wird einfach (und schnell) zu D4 dazugeodert. Ab dem ersten Ergebniswert werden dann dezimale Ziffern ausgeben.

Etwas unästhetisch, aber unvermeidbar (hat jemand eine elegante Lösung?), ist die Langwortdivision in Zeile 69-71. Beim ersten Durchgang entsteht ein Überlauf, da der Quotient größer als die zugelassene Wortlänge ist (der Startwert für 7 Stellen ist 1.000.000). Bvc heißt: ist das Überlaufflag nicht gesetzt, (war also die Division fehlerfrei), dann mache weiter mit der Schleife dbra... Sonst ersetze das Fehlergebnis durch den zweiten Wert 100.000. Im vorliegenden Beispiel entstehtkein Divisionsrest, sonst müßte man das Ergebnis auf den Quotienten mit andi.l #$FFFF,d2 oder auch mit ext.l d2 bereinigen.

Die restlichen Zeilen sind im Quelltext erläutert, das minimal angelegte BSS-Segment reicht natürlich nur für die vorliegenden Funktionsaufrufe; wenn es nicht auf die absolute Minimierung ankommt wie hier, sollten die Felder immer reichlich bemessen sein.


Harald Günterberg
Aus: ST-Computer 02 / 1991, Seite 90

Links

Copyright-Bestimmungen: siehe Über diese Seite