This page (revision-21) was last changed on 03-Feb-2023 15:21 by Florian Dingler 

This page was created on 20-Feb-2010 19:38 by Carsten Strotmann

Only authorized users are allowed to rename pages.

Only authorized users are allowed to delete pages.

Page revision history

Version Date Modified Size Author Changes ... Change note
21 03-Feb-2023 15:21 20 KB Florian Dingler to previous

Page References

Incoming links Outgoing links

Version management

Difference between version and

At line 1 changed one line
General Information \\
!General Information
At line 6 removed 2 lines
[actionschneller.png]
At line 9 changed one line
Welcher Atari-Fan kennt ihn nicht, den superflinken ACTION!-Compiler von Optimized Systems Software (OSS)? Ohne Übertreibung ist das die beste und schnellst Programmiersprache für alle 8-Bit Computer von Atari, wenn nicht die schnellste für alle 6502-Computer überhaupt. Und die darf natürlich im Rahmen der Assemblerecke nicht vergessen werden.
!! Peter's Assemblerecke
!!!Action! noch schneller
Welcher Atari-Fan kennt ihn nicht, den superflinken ACTION!-Compiler von Optimized Systems Software (OSS)? Ohne Übertreibung ist das die beste und schnellste Programmiersprache für alle 8-Bit Computer von Atari, wenn nicht die schnellste für alle 6502-Computer überhaupt. Und die darf natürlich im Rahmen der Assemblerecke nicht vergessen werden.
At line 11 changed one line
Den Lesern der Assemblerecke ist ACTION! schon längst kein Unbekannter mehr, denn in der CK 10/85 haben wir bereits ein Musikprogramm vorgestellt, das vollkommen in ACTION! geschrieben war. Diesmal wird noch etwas tiefer gebohrt und gezeigt, wie man ACTION!-Programme noch kürzer und noch schneller machen kann. Außerdem gibt’s als besonderes Bondbon eine Runtime-Package, mit der ACTION!-Programme auch ohne Steckmodul laufen.
Den Lesern der Assemblerecke ist ACTION! schon längst kein Unbekannter mehr, denn in der [CK 10/85|Musik in ACTION] haben wir bereits ein Musikprogramm vorgestellt, das vollkommen in ACTION! geschrieben war. Diesmal wird noch etwas tiefer gebohrt und gezeigt, wie man ACTION!-Programme noch kürzer und noch schneller machen kann. Außerdem gibt's als besonderes Bonbon eine Runtime-Package, mit der ACTION!-Programme auch ohne Steckmodul laufen.
At line 15 changed one line
In vielen ACTION!-Listings, die in amerikanischen Zeitschriften zu finden waren, wurden eifrig PEEK- und POKE-Befehle eingesetzt. Das zeigt, dass die Autoren noch nicht erkannt haben, welche eleganten und leistungsfähigen Konstruktionen ACTION! anbietet und damit PEEK und POKE vollkommen überflüssig werden lässt. Nehmen wir als Beispiel nut die Abfrage eines Joysticks über die Speicherzelle 632 (STICK0). Ein Test auf die Mittelstellung könnte lauten:
In vielen ACTION!-Listings, die in amerikanischen Zeitschriften zu finden waren, wurden eifrig PEEK- und POKE-Befehle eingesetzt. Das zeigt, dass die Autoren noch nicht erkannt haben, welche eleganten und leistungsfähigen Konstruktionen ACTION! anbietet und damit PEEK und POKE vollkommen überflüssig werden lässt. Nehmen wir als Beispiel nur die Abfrage eines Joysticks über die Speicherzelle 632 ([STICK0]). Ein Test auf die Mittelstellung könnte lauten:
At line 24 changed one line
Diese Variable ist nicht nur um einiges kürzer, sondern im Ablauf mindesten 10 mal schneller! Um das zu verstehen, muss man sich ansehen, wie der ACTION!-Compiler intern arbeitet. PEEK() ist innerhalb von ACTION! als Funktion vordefiniert und wird daher als Aufruf eines Unterprogramms per JSR übersetzt. Zuvor muss das Argument von PEEK noch in die Register des 6502-Prozessors geladen werden, so dass sich etwa folgendes Maschinenprogramm für den Zugriff auf die Speicherzelle 632 ergibt:
Diese Variable ist nicht nur um einiges kürzer, sondern im Ablauf mindesten 10 mal schneller! Um das zu verstehen, muss man sich ansehen, wie der ACTION!-Compiler intern arbeitet. PEEK() ist innerhalb von ACTION! als Funktion vordefiniert und wird daher als Aufruf eines Unterprogramms per JSR übersetzt. Zuvor muss das Argument von PEEK noch in die Register des 6502-Prozessors geladen werden, so dass sich etwa folgendes Maschinenprogramm für den Zugriff auf die Speicherzelle 632 ergibt:
At line 38 changed one line
Auf der anderen Seite ist klar, dass mit diesen adressierten Variablen nicht jeder PEEK- oder POKE-Befehl ersetzbar ist. Einfachstes Beispiel wäre das Ändern eines der fünf Farbregister in Abhängigkeit von einer Variablen. Mit POKE(708+I,0) würde das mit I ausgewählte Farbregister mit der Farbe schwarz vorbelegt. Hier bieten sich die ungeheuer universalen Arrays des ACTION!-Compilers an. Wie bei einfachen Variablen können auch Felder auf feste Adressen gelegt werden und somit ist es möglich, eine Gruppe von Betriebssystem- oder Hardware-Registern als Array aufzufassen. Mit BYTE ARRAY FARBE = 708 definiert man die Schattenregister der Farbspeicher als ein Feld FARBE, und die obige POKE-Anweisung verkürzt sich zu FARBE(I)=0.
Auf der anderen Seite ist klar, dass mit diesen adressierten Variablen nicht jeder PEEK- oder POKE-Befehl ersetzbar ist. Einfachstes Beispiel wäre das Ändern eines der fünf Farbregister in Abhängigkeit von einer Variablen. Mit POKE(708+I,0) würde das mit I ausgewählte Farbregister mit der Farbe schwarz vorbelegt. Hier bieten sich die ungeheuer universalen Arrays des ACTION!-Compilers an. Wie bei einfachen Variablen können auch Felder auf feste Adressen gelegt werden und somit ist es möglich, eine Gruppe von Betriebssystem- oder Hardware-Registern als Array aufzufassen. Mit
{{{BYTE ARRAY FARBE=708}}} definiert man die Schattenregister der Farbspeicher als ein Feld FARBE, und die obige POKE-Anweisung verkürzt sich zu
At line 40 changed one line
Aber damit haben wir den Compiler noch lange nicht ausgeschöpft. Mit einem kleinen Trick wird der erzeugte Code viel kürzer und schneller: BYTE ARRAY FARBE (0) =708. Diese Definition sagt dem Compiler, dass er es mit einem kurzen Feld zu tun hat, das schnell adressiert werden kann. Im Objektprogramm kann der Compiler dann die X-Indizierung ausnutzen und die Anweisung FARBE(INDEX)=0 so übersetzten:
{{{FARBE(I)=0}}}
At line 43 added 10 lines
Aber damit haben wir den Compiler noch lange nicht ausgeschöpft. Mit einem kleinen Trick wird der erzeugte Code viel kürzer und schneller:
{{{BYTE ARRAY FARBE(0)=708}}}
Diese Definition sagt dem Compiler, dass er es mit einem kurzen Feld zu tun hat, das schnell adressiert werden kann. Im Objektprogramm kann der Compiler dann die X-Indizierung ausnutzen und die Anweisung
{{{FARBE(INDEX)=0}}}
so übersetzen:
At line 46 changed one line
Wieder ist das Ergebnis selbst in Assembler kaum schneller zu programmieren. Lässt man jedoch den Zusatz „(0)“ weg, wir der Compiler annehmen, dass es sich auch um ein längeres Feld handeln könnte und muss daher zur indirekten Adressierung greifen. Dazu wird bei der Definition ein Zeiger auf das Array angelegt, der bei der Verwendung erst in die Zero-Page verlagert werden muss. Nur dort kann der 6502 indirekte Adressierungsarten ausführen. Das Ergebnis sähe dann so aus:
Wieder ist das Ergebnis selbst in Assembler kaum schneller zu programmieren. Lässt man jedoch den Zusatz "(0)" weg, wird der Compiler annehmen, dass es sich auch um ein längeres Feld handeln könnte und muss daher zur indirekten Adressierung greifen. Dazu wird bei der Definition ein Zeiger auf das Array angelegt, der bei der Verwendung erst in die Zero-Page verlagert werden muss. Nur dort kann der 6502 indirekte Adressierungsarten ausführen. Das Ergebnis sähe dann so aus:
At line 61 changed one line
Sie sehen schon, das ist ungleich aufwendiger und daher auch viel langsamer (aber dennoch schneller als der ursprüngliche POKE-Befehl). Kurze Byte Arrays sollten Sie daher immer mit „(0)“ definieren. Die lange „Zeiger“-Definition braucht man nur bei umfangreichen Feldern (ab 256 Bytes) oder falls das Feld während des Programmlaufes auf eine andere Adresse gelegt werden soll (auch das kann ACTION!).
Sie sehen schon, das ist ungleich aufwendiger und daher auch viel langsamer (aber dennoch schneller als der ursprüngliche POKE-Befehl). Kurze Byte Arrays sollten Sie daher immer mit "(0)" definieren. Die lange "Zeiger"-Definition braucht man nur bei umfangreichen Feldern (ab 256 Bytes) oder falls das Feld während des Programmlaufes auf eine andere Adresse gelegt werden soll (auch das kann ACTION!).
At line 63 changed one line
Vielleicht noch eine Anmerkung für all diejenigen Leser, denen Felder ohne Längenangabe noch „spanisch“ vorkommen. Der ACTION!-Befehl ARRAY wirkt wie die DIM-Anweisung in BASIC, nur DIM ist wesentlich weniger flexibel, da es dem Programmierer alle Arbeit abnimmt. Bei ARRAY kann man selbst bestimmen, wo das Feld im Speicher liegen soll, aber man ist auch für alle Konsequenzen selbst verantwortlich. So finden keinerlei Überprüfungen der Index-Grenzen statt, und wenn man sein Array so legt, dass lebenswichtige Zellen des Computers überschrieben werden, so hilft eben nur noch der Reset-Knopf. Damit erklären sich auch so anscheinend sinnlose Definitionen, wie es ein Feld mit der Länge Null oder ohne Längenangabe ist. Dies ist nur als ein Zeichen für den Compiler zu verstehen, der Programmierer muss schließlich selbst über die tatsächliche Länge wachen.
Vielleicht noch eine Anmerkung für all diejenigen Leser, denen Felder ohne Längenangabe noch "spanisch" vorkommen. Der ACTION!-Befehl ARRAY wirkt wie die DIM-Anweisung in BASIC, nur DIM ist wesentlich weniger flexibel, da es dem Programmierer alle Arbeit abnimmt. Bei ARRAY kann man selbst bestimmen, wo das Feld im Speicher liegen soll, aber man ist auch für alle Konsequenzen selbst verantwortlich. So finden keinerlei Überprüfungen der Index-Grenzen statt, und wenn man sein Array so legt, dass lebenswichtige Zellen des Computers überschrieben werden, so hilft eben nur noch der Reset-Knopf. Damit erklären sich auch so anscheinend sinnlose Definitionen, wie es ein Feld mit der Länge Null oder ohne Längenangabe ist. Dies ist nur als ein Zeichen für den Compiler zu verstehen, der Programmierer muss schließlich selbst über die tatsächliche Länge wachen.
At line 67 changed one line
Letzte und universelle Möglichkeit zum Ersetzten von PEEK und POKE bietet der Pointer. Derartige Zeiger-Variablen enthalten eine Adresse einer anderen Variablen oder einer beliebigen Speicherzelle. Basic-Kenner werden jetzt einwerfen, dass daran nichts Besonderes ist, schließlich kann jede normale Variable auch eine Adresse enthalten. Wozu also Pointer? Diese Spezial-Variablen haben Fähigkeiten, die über das normale Maß hinausgehen. So kann durch das Anfügen des Pointer-Symboles „^“ der Inhalt der Variablen erreicht werden, auf die der Pointer zeigt. Sehen Sie sich das gleich an einem Beispiel an:
Letzte und universelle Möglichkeit zum Ersetzen von PEEK und POKE bietet der Pointer. Derartige Zeiger-Variablen enthalten eine Adresse einer anderen Variablen oder einer beliebigen Speicherzelle. Basic-Kenner werden jetzt einwerfen, dass daran nichts Besonderes ist, schließlich kann jede normale Variable auch eine Adresse enthalten. Wozu also Pointer? Diese Spezial-Variablen haben Fähigkeiten, die über das normale Maß hinausgehen. So kann durch das Anfügen des Pointer-Symboles "^" der Inhalt der Variablen erreicht werden, auf die der Pointer zeigt. Sehen Sie sich das gleich an einem Beispiel an:
At line 72 changed 2 lines
ZEIGER^ ==+1
ZEIGER ==+2}}}
ZEIGER ==+1
ZEIGER^ ==+2}}}
At line 75 changed one line
Die erst Zeile definiert einen Pointer des Namens ZEIGER, der auf das erste Farbregister gerichtet ist. Der Variablen F wird der Inhalt von Speicherzelle 708 zugeordnet, das entspricht dem Befehl F=PEEK(708). Nun kommt der Trick: Der Pointer wird um eins erhöht, so dass er nun auf die Zelle 709 deutet. Die dritte Zeile erhöht schließlich den Inhalt von 709 um zwei, in Basic müsste man hier mit POKE 709,PEEK(709)+1 viel umständlicher vorgehen.
Die erst Zeile definiert einen Pointer des Namens ZEIGER, der auf das erste Farbregister gerichtet ist. Der Variablen F wird der Inhalt von Speicherzelle 708 zugeordnet, das entspricht dem Befehl F=PEEK(708). Nun kommt der Trick: Der Pointer wird um eins erhöht, so dass er nun auf die Zelle 709 deutet. Die dritte Zeile erhöht schließlich den Inhalt von 709 um zwei, in Basic müsste man hier mit POKE 709,PEEK(709)+2 viel umständlicher vorgehen.
At line 88 added 4 lines
Anmerkungen:
1. Code für das Zeigerbeispiel korrigiert. Das Pointer-Symbol "^" muss in Zeile 4, nicht in Zeile 3.
2. Im Gegensatz zur normalen Initialisierung einer Variable mit eckigen Klammern (z.B. {{{BYTE A=[5]; A enthält dann "5"}}}) werden Zeiger nur mit Gleichheitszeichen initialisiert (z.B. {{{BYTE POINTER P=710 ; P ist dann gleich 710}}}, zeigt also auf Speicherzelle 710)
At line 79 changed one line
Erneut haben wir den ACTION!-Compiler nur mit Halbgas gefahren. Jedem Assemblerprogrammierer wird bei der Besprechung der Pointer aufgefallen sein, dass es sich hierbei um eine reinrassige indirekte Adressierung handelt. und so etwas kann der 6502 nun mal eben nur in der Zero-Page. Wenn daher ein Pointer benutzt wird, muss der Compiler dafür sorgen, dass die Adresse in ein Arbeitsregister der Zero-Page geschrieben wird, und dann erst kann der Zugriff erfolgen. Warum also nicht gleich alle Pointer in die Zero-Page legen? Und es funktioniert! Der ACTION!-Compiler erkennt, wenn ein Pointer gleich dort ist, wo er sein sollte und arbeitet dann auch wesentlich schneller. Nimmt man an, dass ZEIGER in der Zero-Page liegt so wird ZEIGER=255 folgendermaßen übersetzt:
Erneut haben wir den ACTION!-Compiler nur mit Halbgas gefahren. Jedem Assemblerprogrammierer wird bei der Besprechung der Pointer aufgefallen sein, dass es sich hierbei um eine reinrassige indirekte Adressierung handelt, und so etwas kann der 6502 nun mal eben nur in der Zero-Page. Wenn daher ein Pointer benutzt wird, muss der Compiler dafür sorgen, dass die Adresse in ein Arbeitsregister der Zero-Page geschrieben wird, und dann erst kann der Zugriff erfolgen. Warum also nicht gleich alle Pointer in die Zero-Page legen? Und es funktioniert! Der ACTION!-Compiler erkennt, wenn ein Pointer gleich dort ist, wo er sein sollte und arbeitet dann auch wesentlich schneller. Nimmt man an, dass ZEIGER in der Zero-Page liegt so wird ZEIGER=255 folgendermaßen übersetzt:
At line 130 changed one line
Wer das ACTION!-Handbuch (Seite 144) genau gelesen hat, weiß, dass die Adresslage des erzeugten Objektprogramms mit den Speicherzellen $0E, $0F und $491, $492 gesteuert werden kann. Durch geschickte DEFINEs kann man sich quasi zusätzliche Befehle zum Verändern der Adressen schaffen. Die neue Anweisung ZEROPAG verlagert alle nachfolgenden Definitionen in die Zero-Page. Da es dort recht eng zugeht, sollten Sie nur wichtige und häufig benutzte Variablen dahin legen. Sie dürfen auch niemals vergessen, die Adresslage nach den Definitionen mit RESTORE wieder in einen „normalen“ Bereich zurückzulegen. In Listing 1 finden Sie auch gleich ein Beispiel für die Definition eines Pointers in der Seite Null.
Wer das ACTION!-Handbuch (Seite 144) genau gelesen hat, weiß, dass die Adresslage des erzeugten Objektprogramms mit den Speicherzellen $0E, $0F und $491, $492 gesteuert werden kann. Durch geschickte DEFINEs kann man sich quasi zusätzliche Befehle zum Verändern der Adressen schaffen. Die neue Anweisung ZEROPAG verlagert alle nachfolgenden Definitionen in die Zero-Page. Da es dort recht eng zugeht, sollten Sie nur wichtige und häufig benutzte Variablen dahin legen. Sie dürfen auch niemals vergessen, die Adresslage nach den Definitionen mit RESTORE wieder in einen "normalen" Bereich zurückzulegen. In Listing 1 finden Sie auch gleich ein Beispiel für die Definition eines Pointers in der Seite Null.
At line 151 changed one line
Besser geht es natürlich mit eine Runtime-Package (RTP).Von OSS wurde zwar eine RTP angekündigt, die aber bei uns nie auf dem Markt erschien. Gerüchten nach zu urteilen, hat es Probleme zwischen OSS uns Action Computer Services (dem Hersteller von ACTION!) gegeben. Damit bleibt dem geplagten Benutzer nur eine Lösung: Eine eigene Runtime-Package muss her. Und hier ist sie. Sie brauchen nur Listing 2 und 3 abzutippen und unter den angegebenen Filenamen abzuspeichern. Später schreiben Sie in Ihr Programm
Besser geht es natürlich mit eine Runtime-Package (RTP).Von OSS wurde zwar eine RTP angekündigt, die aber bei uns nie auf dem Markt erschienen. Gerüchten nach zu urteilen, hat es Probleme zwischen OSS uns Action Computer Services (dem Hersteller von ACTION!) gegeben. Damit bleibt dem geplagten Benutzer nur eine Lösung: Eine eigene Runtime-Package muss her. Und hier ist sie. Sie brauchen nur Listing 2 und 3 abzutippen und unter den angegebenen Filenamen abzuspeichern. Später schreiben Sie in Ihr Programm
At line 153 changed 2 lines
INCLUDE „RUN1.ACT“
INCLUDE „RUN2.ACT“
INCLUDE "RUN1.ACT"
INCLUDE "RUN2.ACT"
At line 166 changed one line
Ich hoffe, dass die Assembler-Fans nicht verärgert sind, weil es diesmal eine reine ACTION!-Ecke geworden ist. Ich glaube aber, dass sich inzwischen auch viele ML-Freaks mit Sprachen wie ACTION! beschäftigen und die Assemblerecke auch ein Forum für assemblernahe Sprachen sein sollte. Zum Trost: in der nächsten Ausgabe gibt’s wieder reichlich Futter für den Assembler.
Ich hoffe, dass die Assembler-Fans nicht verärgert sind, weil es diesmal eine reine ACTION!-Ecke geworden ist. Ich glaube aber, dass sich inzwischen auch viele ML-Freaks mit Sprachen wie ACTION! beschäftigen und die Assemblerecke auch ein Forum für assemblernahe Sprachen sein sollte. Zum Trost: in der nächsten Ausgabe gibt's wieder reichlich Futter für den Assembler.