- 
32C3 Vorspannmusik
 
- 
Herald: Dann ist es mir jetzt eine ganz
besondere Freude Matthias Koch
 
- 
vorzustellen. Der wird über Compiler
Optimierung für Forth im Microcontroller
 
- 
sprechen. Bitte einen warmen
Applaus für Matthias.
 
- 
Applaus
 
- 
Matthias: Guten Tag, Hallo. Wie gesagt
Compileroptimierung für Forth im
 
- 
Microcontroller und ganz zur Eröffnung
habe ich eine Frage: Wer von euch kennt
 
- 
Forth eigentlich schon? Okay, so etwa die
Hälfte. Das bedeutet aber ich sollte
 
- 
besser noch einmal kurz erklären was
diese Sprache eigentlich ausmacht. Es ist
 
- 
eine Sprache die auf dem Modell eines
Stacks basiert. Vielleicht kennt ihr
 
- 
die umgekehrte polnische Notation wo es so
ist das erst die Parameter kommen und dann
 
- 
die Operatoren. Man legt also Werte auf
den Stack und von oben kommen die
 
- 
Operatoren, nehmen sich ewas und berechnen
ewas und legen es wieder darauf. Es gibt
 
- 
noch einen zweiten Stack, den Return-Stack
worüber die Rücksprungadressen
 
- 
gehandhabt werden. Das bedeutet man kann
nacheinander die verschiedenen Operatoren
 
- 
aufrufen und muss nicht wie bei C den
Stackframe hin und her kopieren. Der
 
- 
Compiler selbst ist sehr simpel aufgebaut.
Es basiert darauf, dass man den Eingabestrom
 
- 
was der Mensch tippt, in Wörter zerhackt,
also ein Space, Tabulator, Zeilenumbruch.
 
- 
Wenn ein Wort gefunden wird, wird es in
der Liste der bekannten Wörter gesucht
 
- 
entweder ausgeführt oder compiliert und
wenn es nicht gefunden werden kann wird es
 
- 
als Zahl interpretiert, auf den Stack
gelegt, oder es wird etwas kompiliert um
 
- 
diese Zahl dann später auf den Stack zu
legen. Das war eigentlich schon die
 
- 
Sprache. Das Besondere daran ist, dass
sie klein genug ist, dass sie in einem
 
- 
Mikrocontroller installiert werden kann.
Was dazu führt das man dann mit einem
 
- 
Terminal mit seinem Chip sprechen kann und
von innen heraus ausprobieren, ob der
 
- 
Hardwarecode funktioniert, weil man dann
nicht viele kleine Testprogramme schreiben
 
- 
muss, sondern ganz einfach von Hand an den
Leitungen wackeln kann und alle
 
- 
Definitionen die man geschrieben hat auch
sofort interaktiv ausprobieren kann. Das
 
- 
führt dann auch dazu, dass die
Definitionen natürlich gleich in der
 
- 
Hardware läuft und auch gleich mit
Echtzeit, so dass man die Fehlersuche
 
- 
stark vereinfachen kann. Das ist so ein
bisschen eine Einführung in Forth.
 
- 
Ich selbst habe diese Sprachen nicht
erfunden, die gibt es schon seit mehr als
 
- 
einem halben Jahrhundert.
 
- 
Aber ich habe Compiler geschrieben
für MSP430, ARM Cortex M0, M3
 
- 
und M4, M7 ist in Planung und es gibt noch
eine Variante, die in Zusammenarbeit mit dem
 
- 
Bowman gemacht habe, die auf einem
FPGA läuft. Aber dazu ein bisschen mehr
 
- 
später. Eigentlich ist das ungewöhnlich
sich selbst vorzustellen aber meine
 
- 
Freunde meinen das sollte man sagen. Ich
bin Diplomphysiker und habe Physik mit
 
- 
Nebenfach Gartenbau studiert, bin gerade in
meiner Doktorarbeit in der Laserspektroskopie
 
- 
habe gemischte Interessen durcheinander, 
besonders Radionavigation
 
- 
und meine Lieblingssprachen 
kann man hier sehen.
 
- 
Der Name mag vielleicht etwas ungewöhnlich
sein, aber die erste unterstützte
 
- 
Plattform war der MSP430 von MSP und
"écris" aus dem französischen kam der
 
- 
Name dann. Überschreibt den MSP430
weil es da innen drin ist und man schreibt
 
- 
direkt hinein. Unterstützt dann alle
MSP430-Launchpads, sehr viele ARM Cortex
 
- 
Architekturen und mit dem FPGA ein
bisschen mehr. Die klassischen
 
- 
Architekturen, also die auf denen Forth
normalerweise implementiert worden ist
 
- 
– so geschichtlich – waren eine virtuelle
Maschine. Wo eine Liste von Pointern
 
- 
drinen war, wo nacheinander diese Pointer
genommen worden sind und dann entweder wie
 
- 
eine Liste von Pointern zählten oder
aber eben ein Assemblerprimitive.
 
- 
Natürlich ist das sehr schön, wenn man
dann Fehler suchen möchte im Compiler, es
 
- 
wird sehr einfach dadurch und es lassen
sich auch einige ungewöhnliche Sachen
 
- 
dabei implementieren. Aber es frisst
viele viele Taktzyklen. Die ganz alten
 
- 
Systeme hatten sogar die Möglichkeit noch
einmal über eine Tabelle die ganzen
 
- 
verschiedenen Pointer umzuleiten, so dass
man Definitionen die schon liefen
 
- 
nachträglich noch ändern konnte, also
eine Definition ganz tief im System durch
 
- 
etwas neues austauschen. Die wird dann
sofort auch verwendet. Das ging mal.
 
- 
Ausserdem lässt sich Forth sehr leicht
dekompilieren, zumindest bei der
 
- 
klassischen Implementation, so das bei
einer Jupiter Ace. Man braucht ja keine
 
- 
Quelltexte, man hatte den Objektcode, man
konnte ihn desassemblieren zurück in den
 
- 
Quelltext, ändern und neu kompilieren 
fertig. Die Optimierungen, die ich jetzt
 
- 
vorführen werde, zerstören diesen
interessanten Aspekt, weil da Maschinencode
 
- 
heraus kommt und weil da
auch Teile weg optimiert werden.
 
- 
Es gibt also nicht mehr so den eins zu eins
Zusammenhang zwischen dem was man
 
- 
geschrieben hat und dem was hinterher
tatsächlich im Prozessor ausgeführt wird.
 
- 
Anders herum jedoch, hatte Forth
lange auch mit dem Problem zu kämpfen,
 
- 
dass es immer auch ein bisschen langsam galt.
Das habe ich geändert. Und ich möchte
 
- 
gerne heute vorstellen was für
Optimierungen man im Chip selbst
 
- 
durchführen kann, natürlich aus der
Compilertheorie heraus sind das alles alte
 
- 
Hüte. Aber die meisten Compiler brauchen
sehr viel Speicher, laufen auf einem PC wo
 
- 
es ja fast unbegrenzt davon gibt. Ich
möchte aber einmal herausfinden welche
 
- 
Optimierungen man in den Arbeitsspeichern
eines kleinen Microcontrollers
 
- 
implementieren kann. Dazu gehören
Tail-Call, Konstantenfaltung, Inlining,
 
- 
Opcodierung und die Registerallokation. In
welcher Architektur diese jeweils
 
- 
implementiert sind steht da mit bei.
Nun will ich die ganzen einzelnen
 
- 
Optimierungen einmal vorstellen. Tail-Call
ist relativ simpel. Wenn das letzte in
 
- 
einer Routine ein Call ist und danach
direkt ein Return kommt, dann kann man das
 
- 
auch durch einen Sprungbefehl ersetzen.
man braucht dann nicht den Umweg über den
 
- 
Stack zu nehmen. Das ist eigentlich so
weit klar, nichts besonderes.
 
- 
Konstantenfaltung bedeutet: Manchmal
möchte man als Mensch etwas so schreiben,
 
- 
dass man ein paar Konstanten nimmt, die
multipliziert, zusammen verodert; all das
 
- 
immer mit zu kompilieren wäre ja
eigentlich Zeitverschwendung. Es steht ja
 
- 
schon während der Kompilierung fest, was
das Ergebnis dieser Berechnung sein wird,
 
- 
der Compiler kann also durchaus diese
Rechnung schon während dem kompilieren
 
- 
durchführen, nur noch das Ergebnis
einkompilieren. Hier sieht man mal ein
 
- 
kleines Beispiel, also Sechs auf dem Stack
legen, Sieben auf den Stack legen, beide
 
- 
Werte vertauschen, miteinander
multiplizieren und das ganze dann
 
- 
aufsummieren. Eigentlich stehen die ersten
Teile schon fest, das reicht wenn man 42
 
- 
plus kompilieren würde. Das ist die
Konstantenfaltung. Das ist jetzt ein
 
- 
glasklarer Fall, wo man das direkt sieht,
aber manchmal gibt es auch aus anderen
 
- 
Optimierungen heraus noch die
Möglichkeit, dass Konstantenfaltungen
 
- 
möglich werden kann. Das ist dann nicht
mehr ganz so offensichtlich. Dazu, wie das
 
- 
implementiert wird, möchte ich erst
einmal kurz zeigen wie der klassische
 
- 
Forth Compiler implementiert worden ist.
Es war so, dass der Eingabestrom den der
 
- 
Benutzer eingetippt hat in einzelne
Wörter, in Tokens zerhackt worden ist,
 
- 
dann wurde geprüft ob es in der Liste der
bekannten Definitionen auftaucht, oder
 
- 
eben auch nicht. Ist das der Fall, ist die
Frage ob kompiliert werden soll oder
 
- 
nicht. Es gibt einen Ausführ- und
einen Kompiliermodus, der eine interaktive
 
- 
Sprache. Im Kompiliermodus und was nicht
immer geht das – auch eine Spezialität
 
- 
von Forth. Dann wird ein Aufruf in der
Definition einkompiliert, also ein Call
 
- 
Befehl geschrieben. Immediate bedeutet
übrigens, dass etwas das kompiliert
 
- 
werden soll selbst ausgeführt wird. Das
braucht man für Kontrollstrukturen, die
 
- 
dann noch so Sprünge handhaben müssen
und ähnliches und ansonsten ist man im
 
- 
Ausführmodus, wird die Definition
ausgeführt. Nichts gefunden: Versucht man
 
- 
es als Zahl zu interpretieren und je
nachdem ob kompiliert werden soll oder
 
- 
nicht, wird auf den Stack gelegt oder es
wird etwas kompiliert, was die Zahl dann
 
- 
bei der Ausführung auf den Stack legen
wird. Wenn es auch keine gültige Zahl
 
- 
ist, ist es ein Fehler. Um dort
Konstantenfaltung einzfügen sind keine so
 
- 
großen Änderungen nötig. Wichtig ist
jetzt eigentlich nur, dass man die
 
- 
Konstanten nicht kompiliert, zumindest
nicht gleich, sondern erst einmal sammelt
 
- 
und dann wenn eine Operation kommt die bei
konstanten Eingaben auch konstante
 
- 
Ausgaben produziert, diese dann
auszuführen. Die Änderungen sind so,
 
- 
dass jetzt ganz am Anfang der aktuelle
Stackfüllstand gemerkt werden muss, denn
 
- 
man muss ja auch wissen, wie viele
Konstanten gerade zur Verfügung stehen.
 
- 
Soll es ausgeführt werden, wurde es
gefunden, dann brauchen wir keine
 
- 
Konstantenfaltung machen, dann schmeißen
wir den Pointer wieder weg, alles gut,
 
- 
vergessen wir. Sind wir im Kompiliermodus,
wird geprüft ob diese Definition mit
 
- 
konstanten Eingaben auch eine konstante
Ausgabe produzieren kann und wenn ja, sind
 
- 
genug dafür vorhanden. Eine Invertierung
einer Zahl braucht eine Konstante. Eine
 
- 
Addition braucht zwei Konstanten usw. das
muss geprüft werden. Wenn das gut geht
 
- 
kann sie ausgeführt werden. Sie lässt
dann die Ergebnisse dort liegen und
 
- 
dadurch, dass wir wusten wo der
Stackpointer vorher gewesen ist, wissen
 
- 
wir auch wie viele Konstanten danach noch
auf dem Stack liegen geblieben sind. Es
 
- 
kann also durchaus variabel viele
Ausgabekonstanten produzieren. Ist diese
 
- 
Definition jedoch nicht faltbar, dann
bleibt uns nichts anderes übrig, als
 
- 
alles was dort an Konstanten liegt
einzukompilieren und dann einen
 
- 
klassischen Call Befehl zu machen. Ja,
aber man kann den klassischen Callbefehl
 
- 
auch nochmal ein bisschen abwandeln. Man
kann kucken ob da eine sehr kurze
 
- 
Definition ist und dann Opcodes direkt
einfügen und bei Forth natürlich
 
- 
Imediate überprüfen was bedeutet, dass
diese Definition selber irgendwelche
 
- 
Spezialfälle umsetzen kann. Nicht
gefunden, Zahlen bleiben stets auf dem
 
- 
Stack liegen, damit sie halt später in
die Konstantenfaltung rein kommen können.
 
- 
Wichtig dabei ist zu wissen, dass dabei,
während die Zahlen gesammelt werden, ja
 
- 
schon ein Merker in den Stack gesetzt
wurde um den Füllstand zu bestimmen. Ist
 
- 
es nicht als Zahl zu interpretieren, ist
es ein Fehler. Das ist im Prinzip der
 
- 
Kerngedanke um Konstantenfaltung in Forth
zu implementieren. Das hier ist
 
- 
grundsätzlich auf jeder Architektur
möglich wo Forth läuft und ist auch
 
- 
relativ unabhängig davon wie das Forth
innen drin implementiert hat. Ich habe
 
- 
schon gesehen, dass jemand Matthias Troote
(?) von der MForth für AVR, angefangen hat
 
- 
das auch einzubauen und das noch
zusammen recognizern. Also es geht recht
 
- 
gut, es ist auch Standardkonform. Die
nächste Sache: Inlining. Klar macht der
 
- 
C-Kompiler auch. Kurze Definitionen die
nur ein paar Opcodes haben, können mit
 
- 
einigen Vorsichtsmaßregeln auch direkt
eingefügt werden, denn wozu sollte man
 
- 
einen Call wohin tun, wenn die Opcodes
kürzer sind als der Call selbst. Und hier
 
- 
das Beispiel von Plus. Man ruft nicht
die primitive vom Plus auf wenn man den
 
- 
Plus-Opcode direkt einfügen kann. Das
leuchtet eigentlich auch ein.
 
- 
Opcodierungen - ich nenne es mal so, ich
weiß nicht wie es sonst genannt werden
 
- 
soll – ist, wenn ein Opcode eine Konstante
direkt in sich aufnehmen kann. Dann ist es
 
- 
doch sinnvoller die Konstante direkt mit
zu opcodieren, als sie über den Stack zu
 
- 
legen und dann darüber zu verwenden. Man
spart halt ein paar Takte und ein bisschen
 
- 
Platz. Das hängt davon ab was für einen
Prozessor man hat. Beim MSP430 geht das
 
- 
immer wunderbar, bei einem Cortex
manchmal, der hat nur einige Opcodes die
 
- 
Konstanten können und wenn man einen
Stackprozessor hat, geht das gar nicht.
 
- 
Und der Regsiterallokator schließlich,
ist die Überlegung, dass man
 
- 
Zwischenergebnisse, die bei Forth
traditionell auf dem Stack liegen würden,
 
- 
versucht auf Register abzubilden. Denn
klar in der Stacksprache ist das ganz
 
- 
etwas anderes als einen Prozessor der
hauptsächlich mit Registern arbeitet.
 
- 
Beim ARM Cortex ist das ganz besonders
schlimm, denn er kann nicht direkt in den
 
- 
Speicher zugreifen um da irgend etwas zu
berechnen, sondern er muss auf jeden Fall
 
- 
immer aus dem Speicher in Register holen,
im Register etwas machen und in den
 
- 
Speicher zurück schreiben. Das ist
ziemlich aufwendig. Wenn man das abkürzen
 
- 
kann, die Zwischenergebnisse gleich im
Register hält, kann man viel kürzere
 
- 
Befehlssequenzen nutzen, die direkt
zwischen den Registern arbeiten.
 
- 
Wichtig dabei ist noch, dass das ganze
transpartent wird für den Programmierer,
 
- 
wenn man also etwas macht, wo die logische
Struktur des Stacks sichtbar wird oder
 
- 
sichtbar werden könnte, muss der Compiler
auf jeden Fall zurück fallen und alles in
 
- 
den richtigen Stack rein schreiben, so
dass man dann auch direkt im Stack
 
- 
Manipulation machen kann, wenn das denn
mal notwendig ist und das ist bei Forth
 
- 
ziemlich häufig, weil Forth Programmierer
gerne alle möglichen Tricks anwenden.
 
- 
Das wesentliche für den Registerallokator
ist, zu wissen wo welches Element gerade
 
- 
ist, man muss also während der
Kompilierung ein Stackmodell mit laufen
 
- 
lassen, worin vermerkt ist, wo diese Stack-
elemente eigentlich gerade sind. Sind sie
 
- 
noch auf dem Stack selbst, also im
Arbeitsspeicher? Sind sie gerade in einem
 
- 
Register drin? Wenn ja, in welchem? Oder,
wenn neue Zwischenergebnisse auftreten:
 
- 
Haben wir noch genug Register? Denn wenn
mehr Zwischenergebnisse da sind als
 
- 
Register zur Verfügung stehen, dann
müssen die Register wieder in den
 
- 
Arbeitsspeicher auf den Stack geschrieben
werden und das ist es was innen drin das
 
- 
besondere ausmacht. Man kann es sehr klein
implementieren, aber man muss daran
 
- 
denken, dass das sehr seltsam ist,
dass das im Microcontroller läuft,
 
- 
normalerweise gibt es bei Register-
allokatoren viele Algorithmen drum herum,
 
- 
die überlegen, wie man das
möglichst gut über möglichst weite
 
- 
Strecken im Programm machen kann. Ich habe
es sehr einfach gemacht. An den Stellen wo
 
- 
Kontrollstrukturen verzweigen hört man
einfach auf. Man schreibt dann alles in
 
- 
den Stack und fertig. Ist eine sehr simple
Implementation und globale Optimierung
 
- 
habe ich gar nicht drin. Aber es ist ein
Anfang. Das sind jetzt all die
 
- 
Optimierungen die angesprochen werden
sollen. Nun will ich ein paar Beispiele
 
- 
dafür zeigen. Erst einmal muss ich aber
noch sagen: Mercrisp-Ice ist nicht allein
 
- 
meine Arbeit sondern basiert auf vielen
vielen anderen schönen Sachen die auch
 
- 
vorgestellt worden sind. James Bowman hat
den J1 Prozessor entwickelt. Clifford
 
- 
Wolf, Cotton Seed und Mathias Lasser haben
die erste freie Toolchain für FPGAs
 
- 
entwickelt und darauf basiert das alles.
Da drin habe ich die Konstantenfaltung,
 
- 
automatisches Inline kurzer Definitionen 
und Tail-Call-Optimierung. Hier ist jetzt
 
- 
mal ein kleines Beispiel. Das ist noch
aus der LEDcom Implementation, wie man
 
- 
über eine Leuchtdiode kommunizieren kann.
Für die die jetzt nicht bei der Assembly
 
- 
gesehen haben, es ist so, dass man eine
Leuchtdiode nicht nur zum Leuchten sondern
 
- 
auch als Fotodiode nutzen kann und wenn
man das schnell hintereinander abwechselt
 
- 
leuchtet und kucken wie hell es ist, hat
man eine serielle Schnittstelle über eine
 
- 
Leuchtdiode. Was natürlich auch dazu
führt, wenn man den Compiler auch noch im
 
- 
Chip hat, dann kann man über die Power On
Lampe seiner Kaffeemaschine neue
 
- 
Brühprogramme einspeichern und
Fehlermeldungen auslesen. Aber das ist
 
- 
jetzt noch etwas anderes,
das nur so nebenbei.
 
- 
Gelächter
Kucken wir uns das jetzt einmal genauer an.
 
- 
Als erstes werden Konstanten definiert
für Anode und Kathode, wo die gerade
 
- 
angeschlossen sind und dann eine
Definition, – "shine" soll sie heißen –
 
- 
wo die Anode und die Kathode beide als
Ausgang gesetzt werden und die Anode
 
- 
"high". Wenn man sich das jetzt einmal
disassembliert ansieht ist da schon
 
- 
einiges passiert. Als erstes "Anode,
Kathode or". Ist zu einer einzigen Konstante
 
- 
Hex F zusammen gefasst worden. Das
war die Konstantenfaltung. Dann als
 
- 
nächstes, ganz unten, das letzte wäre
ein Call um etwas zu speichern im io-Teil.
 
- 
Dort wird jetzt ein Jump und kein Call
eingefügt, das war die
 
- 
Tail-Call-Optimierung. Ist das
soweit noch ganz klar?
 
- 
Hier kann man noch einmal das Inlining sehen,
 
- 
denn an der Stelle hier,
Kathode AND, das "AND" wurde auch direkt
 
- 
eingefügt als Alu-Opcode und wurde nicht
als Call eingefügt und dann darum herum
 
- 
natürlich wieder die üblichen
Verdächtigen. Unten passiert Tail-Call
 
- 
und für die Konstantenfaltung habe ich
nochmal ein kleines Beispiel und zwar das
 
- 
was ich ganz am Anfang hatte, wie das
aussieht. Ganz einfach: Es wird
 
- 
ausgerechnet vom Compiler schon während
des kompilierens, die Konstante wird
 
- 
geschrieben. Der Stack-Prozessor kann
keine Konstante in Opcodes mit einbauen,
 
- 
also gibt es da keine weitere Optimierung
mehr. Dann kommt plus. Plus ist drin im
 
- 
Prozessor und der J1 hat noch die
Möglichkeit auch gleich den Rücksprung
 
- 
mit im Opcode zu haben. Fertig. Tail-Call
im Prinzip auch erledigt. So. Zum J1
 
- 
Prozessor kann man viel erzählen, ich
will nur kurz sagen, er ist sehr klein,
 
- 
sehr verständlich - das sind 200 Zeilen
Verilog, es lohnt sich wirklich sich das
 
- 
mal anzukucken. Schaut mal rein, wenn
ihr euch dafür interessiert. MSP430, das
 
- 
ist ein Prozessor, der sehr viele
verschiedene Adressierungsarten
 
- 
unterstützt und auch eigentlich recht gut
zu Forth passt. Mit Tail-Call gab es so
 
- 
ein paar Probleme, weil es einige Tricks
gibt, die mit den Rücksprungadressen
 
- 
etwas machen und dann knackst es. Also
habe ich keinen Tail-Call drin. Aber
 
- 
Konstantenfaltung, Inlining und
Opcodierung sind drin. Hier noch ein paar
 
- 
Beispiele. Hier kann man wieder sehen, es
werden Konstanten definiert und ganz am
 
- 
Ende sollen dann wieder Leuchtdioden
angesteuert werden, soll der Taster
 
- 
vorbereitet werden, hat man nur
Initialisierung für so ein Launchpad.
 
- 
Das sieht kompiliert so aus. Es tritt mehreres
in Aktion. Die Konstanten kommen wieder
 
- 
über die Konstantenfaltung und diese
Befehle werden über Inlining eingebaut
 
- 
und dadurch, dass sie direkt Parameter in
den Opcode übernehmen können, kriegen
 
- 
sie auch die Opcodierungen, so, dass das
was hinterher heraus kommt eigentlich das
 
- 
gleiche ist was ich auch in Assembler
schreiben würde. Man sieht es auch immer,
 
- 
die Zahlen immer nur ein Opcode, das
war es. Das letzte ist übrigens der
 
- 
Rücksprung, der sieht immer bisschen
komisch aus, aber das funktioniert.
 
- 
Mecrisp-Stellaris ist eine direkte
Portierung von Mecrisp auf einen ARM
 
- 
Cortex M4 gewesen. Stellaris-Launchpad war
die erste Plattform die unterstützt war.
 
- 
Der Name klingt gut, habe ich so gelassen.
Eigentlich identisch mit dem MSP430, wenn
 
- 
es um Optimierung geht. Aber ich habe
jetzt gerade noch (?) müssen noch /
 
- 
werden noch fertig geworden, einen
Registerallokator rein bekommen, den
 
- 
möchte ich noch kurz zeigen. Hier sieht
man ein Beispiel was schon ein bisschen
 
- 
schwieriger ist. Das oben ist der gray
Code, der gray Code ist so eine Sache,
 
- 
wenn man darin zählt, ändert sich immer
nur eine Bitstelle. Na gut, aber darum
 
- 
soll es jetzt nicht gehen, sondern darum,
dass man hier sieht, das keine
 
- 
Stackbewegungen mehr da sind. Das oberste
Stackelement ist im ARM in Register 6 enthalten
 
- 
und die Zwischenergebnisse, also duplicate
legt das oberste Element nochmal auf den
 
- 
Stack, dann kommt die Eins; man sieht
schon, der Schiebebefehl hat als
 
- 
Zielregister ein anderes Register, also
ein reines Zwischenergebnisregister und
 
- 
exklusiv-oder nimmt es von da und tut es
wieder auf das oberste Stackelement,
 
- 
so dass man gar kein Stackbewegung mehr
braucht und das Quadrat genauso. Das ist
 
- 
eben die Sache, dass man versucht
Zwischenergebnisse in Registern zu halten,
 
- 
soweit möglich. Das hier ist ein klein
bisschen aufwendigeres Beispiel. Hier ist
 
- 
es so, das Variablen geholt werden sollen,
zwei Stück, addiert und wieder zurück
 
- 
geschrieben. Im ARM Cortex kann man
übrigens einen Offset an jeden Ladebefehl
 
- 
daran fügen. Am Anfang wird also die
Adresse der Variablen geladen, dann wird
 
- 
die erste Variable geholt, dann die
zweite, beide werden addiert und zurück
 
- 
geschrieben. Wieder keine
Stackbewegungen nötig.
 
- 
Wer jetzt ein bisschen neugierig geworden
ist und sofort loslegen möchte:
 
- 
Alle Launchpads von Texas Instruments
 
- 
werden unterstützt, die ARM Cortex, viele
davon, von STM, Texas Instruments,
 
- 
Infineon neuerdings und Freescale und wer
andere Systeme benutzt, kann natürlich
 
- 
auch gerne Forth ausprobieren. Es gibt
Gforth für den PC, AmForth für die Atmel
 
- 
AVR-Reihe, für PIC gibt es FlashForth und
für den Z80 und einige andere CamelForth.
 
- 
Ganz ganz viele verschiedene. Es kommt
nämlich daher, das dadurch, dass Forth
 
- 
recht klein ist und recht leicht
implementiert werden kann, dass viele
 
- 
Leute zum kennen lernen der Sprache
einfach selber ihren eigenen Compiler
 
- 
implementieren. Das macht Spaß und ich
denke – auch wenn man mir jetzt dafür den
 
- 
Kopf abreißen wird, in einigen Kreisen –
man möge es tun. Denn dabei lernt man
 
- 
sehr viel über das Innere kennen. Andere
sagen natürlich man soll sich erst einmal
 
- 
mit der Philosophie der Sprache
auseinander setzen. Sei es drum, beide
 
- 
Seiten haben ihre guten Argumente. Ich
muss sage, ich habe direkt mit dem
 
- 
Schreiben meines ersten Compilers begonnen
und stehe nun ja. Ich habe noch einige
 
- 
Beispiele mitgebracht und ich habe
noch ein bisschen Zeit über. Das hier
 
- 
generiert Zufallszahlen mit dem Rauschen
des AD-Wandler der einen Temperatursensor
 
- 
trägt. Das ist einfach eine kleine
Schleife, die 16 mal durchläuft und es
 
- 
wird jeweils aus dem Analogkanal zehn im
MSP430 ein Wert gelesen, das unterste Bit
 
- 
wird maskiert, dann hinzu getan zu dem was
man schon hat und das nächste Bit. Das
 
- 
ist das wie es kompiliert worden ist. Als
erstes werden die Schleifenregister frei
 
- 
gemacht, dann wird eine Null auf den Stack
gelegt, wenn man es sich hier nochmal
 
- 
ankuckt, eine Null ganz am Anfang wurde da
schon hingelegt, also der Wert wo dann
 
- 
hinterher die Bits aus dem AD-Wandler rein
kommen. Dann, der Shiftbefehl wurde per
 
- 
Inlining eingefügt, dann kommt die
Konstante Zehn auf den Stack. Leider gibt
 
- 
es nur einen Push-Befehl im MSP430, also
hier die Kombination aus Stackpointer
 
- 
erniedrigen, etwas darauf legen, dann wird
analog klassisch ausgeführt mit einem
 
- 
Callbefehl und anschließend wieder
Inlining und Opcodierungen. Das Maskieren
 
- 
des unteren Bits ist nur noch ein Opcode,
Xor genauso und kann direkt eingefügt
 
- 
werden. Dann wird der Schleifenzähler
erhöht, verglichen und die Schleife
 
- 
springt zurück. Wenn die Schleife fertig
ist, Register zurück holen, Rücksprung.
 
- 
Hier hat man mal die ganzen Optimierungen
alle in einem gesehen, wie das in einem
 
- 
echten Beispiel aussieht, weil das davor
ja doch so ein bisschen gestellt gewesen ist
 
- 
um es schön zu zeigen. Das hier ist
ein etwas größeres Beispiel auf
 
- 
dem ARM Cortex. Die Bitexponentialfunktion
ist so etwas wie eine Exponentialfunktion,
 
- 
die aber auf Integer funktioniert. Und
hier kann man auch nochmal verschiedene
 
- 
Sachen sehen wie das im ARM Cortex
aussieht und was passiert wenn
 
- 
Kontrollsturkturen dazwischen kommen. Ganz
am Anfang wird verglichen ob der Wert eine
 
- 
bestimmte Größe erreicht hat. Dann,
dieses "push lr" kommt daher, dass im ARM
 
- 
Cortex so ein Link-Register existiert,
der dafür da ist, dass
 
- 
Unterprogrammeinsprünge die keine weitere
Ebene haben direkt im Register bleiben und
 
- 
nicht auf den Return-Stack gelegt werden.
Wenn aber Kontrollstrukturen kommen und
 
- 
noch nicht klar ist ob in einem der zwei
Zweige vielleicht doch noch ein
 
- 
Unterpgrogramm aufgerufen werden muss,
muss er jetzt gesichert werden. Dann zeigt
 
- 
der Sprung der zum "if" gehört, eine Zahl
wird herunter geworfen und eine Neue rein
 
- 
gelegt, was aber im Endeffekt nur ein
Ladebefehl ist, weil ja der Register Top
 
- 
of Stack ohnehin schon die ganze Zeit
bereit gelegen hat. Im else Zweig ist ein
 
- 
bisschen mehr zu tun. Das duplicate
brauchen wir nicht. Ein Registerallokator
 
- 
dahinter. Dann der Vergleich, wieder das
"if", hier bei "1 rshift" kann man wieder
 
- 
sehen, dass das alles in einen Opcode
zusammen gefügt worden ist, das ist
 
- 
wieder Kombinationen aus Konstantenfaltung
und Inlining. Dann, der else-Zweig, so,
 
- 
hier ist ein bisschen mehr zu tun. Man
kann jetzt auch sehen, dass mehrere
 
- 
Zwischenergebnisse im Register auftreten.
Also r3 und r2, beide mit dabei, die Werte
 
- 
werden jetzt nicht mehr auf den Stack
gelegt, sondern zwischen den Registern hin
 
- 
und her geschoben. Vielleicht wird das
jetzt ein bisschen unübersichtlich, aber
 
- 
ich denke, wenn man das direkt vergleicht,
ich habe immer dort wo Assembler steht
 
- 
auch den Forth Quelltext daneben
und das sind die Opcodes die jeweils
 
- 
für die Zeile generiert werden.
Was man hier noch sehen kann,
 
- 
dass man im ARM Cortex leider nicht
eine Konstante in den einzelnen Befehl mit
 
- 
einfügen kann. Deswegen wird es über ein
anderes Register geladen. Aber, andere
 
- 
Sachen – wie der Shiftbefehl – können
Konstanten direkt übernehmen. Das ist
 
- 
hier passiert und ganz am Ende muss
aufgeräumt werden. Bislang war das kein
 
- 
Problem, weil das oberste Element immer in
r6 geblieben ist. Jetzt aber wurden durch
 
- 
die Zwischenergebnisse und das hin und her
jonglieren, der Fall erreicht, dass das
 
- 
oberste Element auf dem Stack eben nicht
mehr in dem Register ist der normalerweise
 
- 
das oberste Element trägt. Deswegen der
vorletzte Befehl, der move-Befehl dient
 
- 
zum aufräumen. Der hat kein Äquivalent
in Forth, aber er dient dazu, das
 
- 
Stackmodell was gerade in einem Zustand
ist wie es sonst nicht sein sollte wieder
 
- 
auf den kanonischen Stack zurück zu
führen, damit an der Schnittstelle alles
 
- 
sauber übergeben werden kann. Wohl
gemerkt, globale Optimierungen gibt es
 
- 
noch nicht, wird es wohl auch erst einmal
nicht geben, aber man kann schon einmal
 
- 
sehen, dass man die gesamte Sache ohne
Stackbewegung geschafft hat, mal davon
 
- 
abgesehen, das der Returnstack einmal die
Adresse zum Rücksprung aufgenommen hat,
 
- 
was ich aber nicht vermeiden konnte, weil
dieser Compiler nicht voraus schauen kann.
 
- 
Er kann immer nur sehen, wo er gerade ist
und versuchen dafür zu generieren. Um das
 
- 
weglassen zu können, müsste man dann
vorausschauen können um zu sehen was
 
- 
in den Kontrollstrukturen
vielleicht doch noch passiert.
 
- 
Damit bin ich am Ende
angelangt, alle Beispiele gezeigt. Kann
 
- 
ich nur noch wünschen: Alles Gute zum
neuen Jahr! Wenn dann noch Fragen sind,
 
- 
kommt gleich nochmal zu mir, schreibt mir,
ich freue mich über viele E-Mails.
 
- 
Vielen Dank.
 
- 
Applaus
 
- 
Herald: Okay, alles klar,
vielen Dank Matthias.
 
- 
Abspannmusik
 
- 
subtitles created by c3subtitles.de
Join, and help us!