-
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!