 Ja, herzlich willkommen zum Vortrag, was jede Programmiererin oder jeder Programmierer über die Nutzung eines Inline-Assemblers wissen sollte. Speziell geht es hier im Vortrag von Christoph Marlon um den GCC Inline-Assembler, wie der funktioniert, wie man ihn benutzen sollte und wann man ihn doch besser nicht nutzen sollte. Denn ich immer bringt es wirklich etwas mit Assembler zu versuchen, seinen Code kurzzeitig zu verbessern. Christoph hat hier in Karlsruhe Informatik studiert und forscht nun an der Universität des Saarlands in Saarbrücken im Compiler Design Lab zu Compilerbau. Er hat zuletzt publiziert im Bereich zu effizienter Registerzuweisung und statischen Codeanalysen. Also heißen wir Christoph herzlich willkommen und freuen uns auf seinen Vortrag. Hallo, danke für die prägnante Einführung. Ich bin nicht mehr dort, inzwischen bin ich einfach in Lohn und Brot bei einer Firma. Aber ja, das tut erst keinen Abbruch. Ja, was jeder Programmierer über Inline-Assembler wissen sollte, das ist jetzt natürlich ein bisschen dick aufgetragen. Ich habe einfach mal so einen klassischen Titel gewählt, entlehnt an dem schönen Aufsatz What Every Computer Scientist Should Know About Floiding Point, in dem es darum geht, ja, viele Fehlannahmen, was jetzt wie man zur Gleitkommarrechnung funktioniert, damit man aufzuräuben, das man ordentlich zu erklären. Vielleicht nicht ganz so in der Größenordnung, aber so in der Richtung möchte ich es ein bisschen mit GCC Inline-Assembler tun. Ja, vielleicht mal kurz zu meinem Hintergrund, wie ich jetzt draufgekommen bin. Ich wollte einfach mal einen Vortrag hier halten, habe ich irgendwie letzte Woche und haben mal beschlossen oder vorletzte Woche. Und da ich immer noch so ein bisschen hobbymäßig an einem Compiler-Stricke und ich da auch tatsächlich dem GCC Inline-Assembler beigebracht habe, also wie er das verarbeiten kann. Ich dachte, okay, dass er es im Internet so drüber liest über, wie man das benutzt, das besteht viel aus Kargokult-Musizismus und leider die meisten Tutorials, die da irgendwas drüber schreiben, schaffen es nicht mal korrekt über das erste Beispiel raus. Ich hoffe, ich kriege das ein bisschen besser hin. Ja, erstmal ganz allgemein Inline-Assembler. Inline-Assembler ist also eine Möglichkeit, irgendwie Assembler-Kotstücke in meine Hochsprache einzubetten. Meistens ist das C, aber es gibt auch einige andere Hochsprachen, die der Anbindung bieten, Pascal, D auch, diverse andere. Entsprechend ist natürlich nichts standardisiert eigentlich. Jeder kocht das ein eigenes Hüppchen. Die bekanntesten habe ich jetzt hier vielleicht mal so aufgezählt. Das ist einmal vom Visual Studio Inline-Assembler-Syntax, die sieht grob so aus, also mit gesprungenem Klammern nach einem Asen. Da muss man zu sagen, es sind ziemlich fragile Geschichte, das Ding ist auch eher langsam, dass immer über den Speicher geht und das vermutlich weils fragiles haben es auch im Prinzip inzwischen abgeschafft. Ja, es gibt es genau für 32 Bittigs 86, für keine andere Plattformen, die Visual Studio unterstützt, also insbesondere nicht so wie heute wichtiger, wie am D64 oder ARM, gibt es das einfach nicht. Vielleicht noch ein bisschen bekannt ist der OpenWattcom, der unterstützt ein bisschen mehr. Da kann man schon ganz nette Dinge mit tun. Ich kann so ganze Funktionen in den Assembler schreiben und somit so eigene Aufrufkonventionen dafür wünschen. Das funktioniert über ein hübsches Pragma. Ja, aber wahrscheinlich, ich vermute mal hier kaum einer wird hier den OpenWattcom benutzt haben. Es gibt auch noch zig andere, meist von irgendwelchen proprietären Compilern. Ich möchte mich heute hier um den wahrscheinlich populärsten kümmern. Das ist der GCC und all die anderen, die zu ihm irgendwie kompatibel sind. Was ist denn so das Hübsche an ihm? Der ist unglaublich flexibel, was dieser Inland Assembler kann. Ist auch ziemlich mächtig. Man kann da ziemlich ins Detail gehen und wie gesagt wird von diversen unterstützt. Also nicht nur von GCC selbst, sondern auch Klang-LVM unterstützen den, der Intel-C-Compiler und auch tatsächlich der Forschungskompiler an dem ich gearbeitet habe und auch ein bisschen daran arbeite. Das ist der heiße C-Parsalip-Firm. Das ist vermutlich der beste C-Compiler, von dem man noch nie gehört hat. Inland Assembler von GCC. Ich werde an diversen Stellen zumindest immer mal kurz was zu der Doku sagen oder sie zumindest irgendwie ein bisschen referenzieren. Wer sie nachschauen will, die findet sich unter der URL für den, für das letzte Release zum GCC. Das ist im Aktuell bei 9.1. Was gibt es da generell, du erstmal drüber zu sagen, der ist so ein Fenster, ein so richtig in die Code-Erzeugung von diesem Compiler. Also Compiler, da steckt man ja irgendwie C-Code rein, am Ende fällt so Assembler raus und irgendwie muss da ein Haufen Logik dazwischen passieren, damit das so am Ende passiert, was rauskommt und eben dieser Inlandersambler, der sieht fast so ein bisschen aus, wie tatsächlich die Code-Erzeugung von diesem GCC selbst funktioniert, was ihn damit sehr, sehr mächtig macht, allerdings auch so ein bisschen empfindlich. Also spricht nicht, dass er irgendwie von der, vom GCC selbst fehleranfällig ist, aber man benutzt ihn leicht falsch, weil es eben sehr subtil ist, was man da tun muss. Oder zumindest wenn man es richtig tun will. Weitere Manco an der ganzen Geschichte ist die Dokumentation. Sie ist GCC typisch eher schwierig zu verwenden. Sie ist leider nicht vollständig. Einiges, man ist nur so implizit angedeutet. Diverses versteht man eigentlich nur, wenn man irgendwie mindestens sich mit Übersetzerbau beschäftigt oder am besten direkt an dem GCC schon mal irgendwas gemacht hat. Zum Beispiel sind Teile von der Benutzer-Doku für den Inlandersambler eiskalt einfach aus GCCint. Das ist die Entwickler-Doku kopiert und entsprechend liest es sich dann auch. Deswegen orientiert man sich wahrscheinlich heutzutage eher so aus Informationen, die man sonst so im Internet findet, irgendwelche Anleitung oder Tutorials. Und da muss ich leider sagen, im Prinzip ist da alle machen mehr oder minder grobe Fehler. Wie gesagt, ich versuche das hier zumindest heute zu vermeiden. Mal schauen, ob es gelingt. Es gibt so einige Aspekte, auf die ich jetzt im Folgen immer wieder eingehen werde, die quasi immer über uns schweben. Und da, wie gesagt, der GCC Inlandersambler so ein Fenster in die Coderzeugung ist, sind das natürlich auch Themen, die aus den Übersetzerbau kommen. Alles hat ein Ende, nur der Übersetzer hat drei. Wir haben vor an das Frontend, also da stecke ich mein C-Konto rein. Sprich, da muss ich auch irgendwie mein Inlandersambler hinschreiben. Das hat eine gewisse Sonntag. Das hat eine gewisse Semantik, also eine bestimmte Bedeutung. Das schauen wir uns an. Am anderen Ende fällt der Assembler raus. Da müssen wir schauen, welchen Einfluss ich habe auf meinen Stück Inlandersambler, dass das da auch hoffentlich richtig rauskommt. Ja, und zwischendrin ist das Mittelend. Keiner hat behauptet, dass Übersetzerbau kreativ sind. Da passieren eben viele Transformationen, also was man landläufig Optimierung nennt. Übersetzerbau versuchen, das Wort ein bisschen zu vermeiden, weil es wird halt nicht immer optimal. Aber irgendwann passieren da sehr viele Dinge drum herum. Und dann muss man eben dafür sorgen, dass der Kompiler auch genug weiß von meinem Stück Inlandersambler, dass da auch das Richtige passiert. Also sprich, dass es nachher eigentlich um die Ohren fliegt. Ja, bevor wir dann in den Inlandersambler einsteigen, vielleicht ganz kurz ein bisschen Assemblersyntags. Also es wird kein Vortrag, wie schreibe ich, x86-Assembler, sondern mir geht es wirklich um die Nutzung von dem Inlandersambler. Das ist eigentlich weitgehend interessanterweise für Assembler. Natürlich plattformunabhängig, weil diese Schnittstelle sieht eigentlich immer gleich aus für den GCC. Aber ich brauche noch ein paar Beispiele. Und da verwende ich das, was wahrscheinlich die meisten Leute irgendwie vor sich stehen haben oder unter ihrem Schreibtisch, nämlich x86-Assembler. In GCC typischen AT&T-Syntags. Der Syntags arbeitet sehr viel mit Sigils, also so einem, irgendein Sonderzeichen, das vorher manchen Wörtern steht. Wenn ich einen Register benennen will, dann mache ich das mit einem Prozentzeichen vorne dran. Wenn ich so eine Konstante haben will, dann ist so ein Dollar davor. Auch wenn ich es von der Fahrt und Variabelnnahme habe, eigentlich ist es nur eine Marke eigentlich. Das ist einfach die Adresse, die von diesem Ding. Speicher kann ich auch zugreifen. Das hat dann kein Sonderzeichen vorne dran. Dafür kann ich da mehr oder minder wirre Dinge tun. Die Marke, ein bisschen Versatz drauf gerechnet noch, um da von der Stelle zu laden oder zu speichern, je nach dem, was der Befehl tut, natürlich. Oder irgendwie das hier heißt einfach, nimm den Inhalt von dem Register, addiere noch vier drauf, kann man sich jetzt irgendwie überlegen, warum auch immer jemand Klammern gemacht hat für die Adition und lade dann von der Stelle oder ein bisschen Speicher auf diese Stelle. Auch noch mit Versatze hin. Operantenreinfolge ist auch ein bisschen ungewöhnlich. Die ist links die Quelle rechts das Ziel, also sprich das hier kopiert die Konstante 42 in das Register eRx rein. Das ist ein bisschen Plattformabhängigeschicht. Es gibt auch andere, die die Reihenfolge haben, zum Beispiel auch Spark. Die meisten haben es in die Gegenrichtung, also Arm, MIPS, RISC-5 und so weiter. Die haben alle das Ziel links. Muss man sich einfach dran gewöhnen. Ja und das Letzte ist vielleicht noch dieses komische L hinter diesem Moff Befehlen, der eigentlich ja Copy heißen sollte, aber egal. Das L heißt auf was für eine Operationsgröße wir arbeiten. In dem Fall ist das 32-Bit. Für das L-Sufix, das nur am Rande. Das stört uns eigentlich jetzt nicht so sehr. Dazu ein kleines Beispiel. Ich habe mir kleine Funktionen geschrieben. Noch immer noch keinen Ensembler, aber danach geht es los. Die einfach, die hat zwei Parameter, Brown B, und da sage ich einfach, ich möchte von, ich tue sowas wie B ein Array und zeige auf das 1. Element eines Arrays und dann nimm ich das bitteschön das Element 1 dahinter, lade davon Adire 2 drauf, schifte das nach links und schifte dann A nach links und gibt das Ganze zurück. Nur damit man so ein bisschen sieht, was da so passiert. Viel Sinn hat die Funktion nicht. Das ist generell in meinen Beispielen. Die sind drauf getrimmt, hoffentlich überschaubart zu sein, denn tiefere Sinn gibt es keinen. Ja, wie funktioniert das Ganze? Es gibt einfach Plattformaufrufkonventionen, die für diese Plattform auf der wir gerade sind am D64, folgendermaßen, der erste Parameter liegt im Register EDi, darauf muss der Aufruf von meiner Funktion sorgen. Der zweite Parameter liegt im Register RSI. Also, der zweite Parameter ist mein Zeiger, von dem muss ich erst mal laden, also brauche ich so einen Mof-Befil, und zwar bitteschön mit Versatz 4. Ist ein Int, der ist von meiner Plattform 4 weit groß, also muss ich, wenn ich einen Element weiter G, 4 weit drauf machen. Und das Ganze hätte ich dann bitteschön gerne im Register ECX, damit habe ich jetzt mal den Wert b von 1 da drin. Dann lade ich auch noch den anderen Operranden, der ist eigentlich schon im Register, aber den kopierte erst mal ein zweites Register rüber, wenn wir gleich sehen, warum. Jetzt addiere ich noch die 2 drauf. ECX ist gerade b von 1 drin, also addiere ich noch die 2 drauf. Und jetzt kommt eigentlich ein Schiff. Ich schiebe hier meinen Wert in Rx, um CL nach links, das ist auch so eine kleine x86 Besonderheit. Die Register haben unterschiedliche Namen, je nachdem, wie viele Bits sich daraus haben will. Wenn ich 8-Bit haben will, dann heißt das sein, dass das C-Register CL, wenn ich 32-Bit haben will, heißt ECX. Ist nicht so wichtig für uns, nur damit es einem nicht verwirrt, kommt noch ein paar mal vor. Ja, jetzt haben wir das ganze Ergebnis in ERX drin und das geben wir jetzt zurück. Es ist einfach durch einen Rücksprungbefehl und der Wert, das Aufrufkonvention, der Wert hat einfach in ERX zu sein. Das erklärt auch, warum wir mal vorhin erst mal den Parameter da umkopieren, weil er muss nachher einfach da drin sein. Gut, so viel zur Assembler-Syntax und mal als ein für ein Beispiel. Jetzt schauen wir uns doch mal an, wieso Inland-Assembler funktioniert. GCC-Kinder von 2 Spielarten, die erste heißt Basic Assembler, das sind übrigens die versprochenen Referenzen auf die Doku als Untertitel. Den kann ich an zwei Stellen verwenden. Ich kann diesen Inland-Assembler außerhalb von Funktionen hinschreiben, was ein bisschen seltsam wirkt, weil es eigentlich kein Inland-Assembler ist, aber es ist immerhin Inline in meinem C-Code oder in meiner C-Datei. Da kann ich dann zum Beispiel so eine ganze Funktion in Assembler hinschreiben, so mit allem. Name der Funktion, was sie so tut, Rücksprung, ja. Nicht sehr spannend, interessiert uns nicht so sehr. Das zweite Möglichkeit ist, ich kann das Inhalt von der Funktion hinschreiben. Und da ist ganz wichtig zu betrachten, also die Syntax hierfür ist eben Schlüsselwort Assem. Das kann Funktionsaufruf, sondern das ist einfach nur eine Anweisung, wie so ein IF oder irgendwas anderes auch, mit dem der Compiler eben etwas tut. Und in dem Falle ist das, was der Compiler tun soll, da Cod der darin steht in den, in seinen Assembler-Ausgabe, die er nachher als Ergebnis produziert, da reinzukopieren. Ja. Und was da drin steht, ist ihm egal. Ja, also da guckt er einfach nicht drauf. Er kopiert einfach eiskalt, was da steht ins Ergebnis. Und das war's. Das ist eben auch genau das, was nachher auch noch wichtig wird, wie man da auch ein bisschen was erzählen kann. Ja, was ist das Problem mit diesen Basic-Assen? Naja, die erste Annahme, die der Compiler an dieser Stelle macht, steht einfach so hart in der Doku ist, ja, du veränderst keine Register, was schon die Eingaben, was schon die Nützlichkeit extrem einschränkt, weil das meist was Irgendwas Sinnvolles tut, tut mal irgendwelche Werte in die Register rein, rechnet ein bisschen drauf rum. Eher schlecht. Was man eigentlich machen darf, sind irgendwie global zugreifbare Variabeln lesen oder schreiben. Und ja, das war's dann auch schon. Und das Problem dabei ist, ja, das sind so ungefähr die einzigen beiden Beispiele, die mir eingefallen sind, tue nichts, no operation, nob, oder mach einen Debug Breakpunkt, das ist auf x86-Esembler, ist das einfach ein Interab 3. Ja, damit kann man leider nicht so viel anfangen, deswegen hat der GCC noch so den großen Bruder davon, dass der Extended Asen nicht erschreckt sein, das ist so das Ganze in seiner vollen syntaktischen Pracht, e.g. klar man heißen, all diese Elemente sind optional. Ja, wer's wem's vor Assembler grauscht und irgendwie der auch gotuschrecklich findet, der findet da oben wahrscheinlich sein Meister. Wir schauen uns so die meisten Sachen davon an, ich werde nicht ganz alles schaffen, aber zumindest die ganzen Sachen, wie ich ihm sagen kann, wie ich mit meiner Umgebung spreche. Und jetzt kann ich ihm nämlich sagen hier, ja, was ist meine Schnittstelle? Was tut mein Stück Assembler? Zumindest in Kommunikation mit dem ganzen umliegenden Kot. Ja, wer Inlandersamt da schon mal gesehen hat, der wird sich wahrscheinlich auch eher seine Brüder hier vielleicht gesehen haben, syntaktisch, die tun exakt dasselbe, das sind einfach nur andere Schlüsselwörter für genau die gleiche Bedeutung. Der Unterschied ist nur, dass man GCC auf einen sehr stricken Modus schalten kann, dann würde er das Wort Assem nicht erkennen, aber die anderen würde trotzdem immer noch als Schlüsselwörter erkennen. Aber wie vorhin auch schon gesagt, das, was eigentlich hier die eigentlich Assembler Befehle sind, muss natürlich so ein Zeichenketten literal sein, sonst kann das nicht zur Übersetzungszeit ins Ergebnis rein kopieren. Das guckt da immer noch nicht an. Alles, was ihr beachtet ist, was wir hier tun. Und da schauen wir uns doch jetzt mal an, was man da so tun kann. Das erste ist ein so ausgabroberanten definieren, also sprich, ich kann dem Übersetzer sagen, was mein Stück Assembler schreiben wird. Dazu möchte ich hier eine kleine Funktion implementieren. Sie ist random und sie gibt einen fair gewürfelten Wert zurück, der also sehr zufällig ist und den Wert 4 hat. Also schiebe hier den Wert 4 in, ja in Was eigentlich. Was muss ich jetzt spezifizieren? Das hier ist der Ausgabroberant, das ist jetzt ein Ausgabroberant, aber steht das zwei Teilen. Das erste ist ein Constraint, heißt das in GCC Sprache und da sage ich, ja, das ist tatsächlich ein Ausgabroberant. Und bitte such dir irgendein Register aus, dass dir gefällt. Da muss der rein. Es muss irgendein Internetscher Register sein und binde das an die Variable REST. Also da wird nachher in diesem Register, wird mein Ergebnis drin sein und auf Hochsprachenebene greife ich das dann eben als Variable REST zu. So, jetzt hat er irgendein Register gewählt bei der Coderzeugung. Jetzt muss ich ja irgendwie an dieses Register rankommen. Das funktioniert so ähnlich wie bei Print F mit so Platzhaltern. In dem Falle heißt der Platzhalter fängt wie bei Print F mit Prozent an, null für das der erste Operant, der da ist. Die Operanten werden einfach von vorn nach hinten einfach durchnummeriert. Genau und damit haben wir jetzt auch schon unser erstes kleines Beispiel gestrickt. Wenn man das jetzt so durch den GCC jagt oder wie gesagt, ICC lang ist völlig egal oder wer es irgendwie ganz faul haben möchte, kann es auch so in den Compile Explorer Godbolt Org stecken. Da kann man es auch so hübsch ausprobieren. Dann fällt einfach sowas raus. Wie man vorhin gehabt haben, wieder eine Funktion. Also das hier ist quasi alles aus der Hochsprache rausgefallen, die erste und letzte Zeile. Und das hier ist wirklich so, was ich da hingeschrieben habe als Stück Innenersambler. Nur eben, dieser eine Platzhalter ist ersetzt durch das Register, das er gewählt hat und der GCC ist natürlich ein bisschen nicht völlig doof, sondern hat gleich geschickterweise das Register gewählt, wo nachher der Ergebnissewert für eine Funktion drinstehen muss. Und das ist eben wie vorhin gesagt ERX. Ja, hier ist mal jetzt mal ein Gegenbeispiel, wie man es nicht macht, deswegen hier mit einem schönen großen If Bad außen rum. Ich könnte sagen, ich weiß ja, das Ergebnis muss ein Register ERX sein. Ja, dann kann ich es da doch einfach von Hand da so reintun, schreibe ich einfach so ein Basic Assum hin und dann ist es gut. Das war eins, zwei. Schreibe ich so ein Basic Assum hin und dann ist es einfach gut. Scheint auch in erster Blick mal zu funktionieren. Also ich habe mal dieses schöne Stück Code, also unsere Random Funktion von eben, nur eben jetzt halt mit Basic Assum hingeschrieben, wo ich einfach sage, ich schreibe das Rückkabel riechs da rein und dann ruf ich die Funktion auf und steckst ja einfach bei uns weiteres Argument, was die Rückkabel wehrt, in die nächste Funktion rein, nur als kleines Beispiel, damit mal irgendwas geschieht. Ja, das scheint erstmal so gut zu funktionieren. In beiden Fällen, egal ob ich die schlechte Variante, die ich jetzt gerade beschrieben habe, oder die gute, das ist genau dieselbe wie von der Folie vorher, verwende, wenn ich so übersetze mit ohne Inlining, also sprich schön brav die Funktion aufrufen, dann scheint das zu funktionieren. Das fällt mich zweimal exakt dieses Stück Code raus. Was ist jetzt das Problem? Naja, ich sage halt mal diesen Schalter nicht, ich lass den Compile jetzt halt mal so inline und was passiert dann? Naja, unsere Funktion Random sieht immer noch genauso aus, aber jetzt hat da statt den Aufruf zu machen, sagt er jetzt einfach hier, ja, den Inhalt setze ich doch einfach ein. Ja, und wenn ich das aber übersetze, wird auch der, geht es, ziehe mir sagen, ah, Vorsicht, deine Funktion, die kehrt zurück, aber sie hat gar kein Rücker bewährt. Und genauso verhält das ich dann auch, sagt so, okay, das Argument, das ich jetzt in die Funktion reinstecken will, keine Ahnung, was es ist, ich nehme einfach mal eine Null, du hast mir nichts gesagt, was das Argument ist. Deswegen, dass das, also diese Zeile hier, dieses Xor von Register mit sich selbst liefert immer den Wert Null. Ja, und wie sieht's im richtigen Fall aus? Also sprich, unsere Variante, wenn wir nicht bad sind, dann sieht das so aus. Er sagt einfach hier, das ist mein Stück Inline-Assembler, also diese Zeile hier und jetzt hat er geschickt, gewählt, gleich das richtige Register, ich tue es ins EDi rein, weil da muss das erste Argument für ein Funktionsaufruf rein. Also sprich, so macht man es nicht, weil ich dem Compiler für nicht gesagt habe, was dieses Ding tut. Und entsprechend verhält das ich auch. Und im anderen Fall auch hier, dadurch, dass ich es ordentlich gemacht habe, kann das auch gleich geschickt machen, statt halt nochmal zum Beispiel umkopieren zu müssen, wie er es im Aufruffall tun muss. So, für mal zu Ausgabeuranten gibt es natürlich auch das Gegenstück, Eingabe, funktioniert ähnlich, das sind die nächste Liste von Operanten, also diese Listen sind immer durch einen Doppelpunkt getrennt, meine Listen haben unter allem nur ein Element, damit irgendwie alles auf die Folien passt, daher nicht so spannend. Ich sage wieder, ich möchte jetzt was in mein Inline-Assembler reinstecken, wieder in einen beliebigen Register sucht ihr eins raus und zwar, ich hätte gerne den Wert, der in Variabel innen drin steht, da reingesteckt. Ja, und dann sagt der Compiler, ja, mach ich dir. Hier ist übrigens ein Ergebnis, in dem Falle sagt er, ich tue übrigens Eingabe und Ausgabe in selbe Register, weil du hast mich nicht verboten zu tun, kann ich tun, mach ich einfach. Ist es zufälliger Ergebnis, er hätte hier auch an dieser Stelle sagen können, okay, die 42 stecken ein beliebiges anderes Register und das werden dich als Eingabe-Register für das Stück Inline-Assembler hat aber in dem Falle nicht getan. Ja, werden wir noch sehen, was damit auch Probleme geben kann, wenn man das nicht sauber spezifiziert. Ja, auf jeden Fall die Anahme, die jetzt der Compiler auch wieder trifft für diese Eingaben ist, ist eine Reineingabe. Ich werde auf keinen Fall jetzt sagen, irgendwie schreibe nach %1 rein, sondern ich darf nur davon lesen. Ja, das ist auch wieder, weil ich nichts anderes spezifiziert habe, ich habe gesagt, ich lese davon und sonst nix. Ja, wir können meinen Sprechen jetzt auch ein Beispiel bauen, in dem es kaputt geht. Lassen wir jetzt aber mal. Ja, jetzt habe ich aber so mein Stück, mein Assemblerbefehl, der kann vielleicht aber mehr als nur irgendwie von Registern lesen. Tatsächlich, der MOF-Befehl von x86 kann den ganzen Haufen mehr, kann, wie wir vorhin auch gesehen haben, einfach so eine Konstante irgendwo rein kopieren. Und das wird so jetzt schön, wenn ich dem Compiler sagen kann. Übrigens, du hast hier doch so ein paar Mehrfreiheitsgrade, du kannst netten ein Register nehmen, du kannst auch eine Konstante nehmen und genau das geht. Jetzt habe ich den Eingabe-Apparanten verglichen zuvorher, eins aufgebohrt, also sage ich nicht nur, du darfst ein Register nehmen, du kannst alternativ auch so ein Immediate nehmen, also ein beliebiger so direkt Wert. Und dann schaut sich der Compiler so die Umgebung an und sieht so, oh, ich kenne den Wert der Variable in, die ist ja konstant an der Stelle, das ist so eine 42. Dadurch die billigste Methode, die ich so machen kann, ist einfach zu sagen, ja, hier bitteschön, hast du die 42 als operant. Also er sucht sich einfach aus, was ihm da am besten gefällt und entsprechend der Platzhalter wird ersetzt. Ähnliches kann ich auch Speicher machen, ich habe jetzt hier mal zum Spaß auch gesagt, die Ausgabe darf auch gerne im Speicher sein, das wäre auch möglich, aber hier entscheidet es sich nicht anders, weil sagt, ich brauche es eh in dem Register, ist billiger, mache ich doch das. Ja, da kommen jetzt ein bisschen auch die Gegend rein, wo die Dokumentation so ein bisschen fragwürdig wird, weil die ist jetzt teilweise einfach aus den Interner kopiert. Das ist wirklich so, wie dieses Ding ganz tief drin funktioniert und da sind auch so Sachen erwähnt, die leider im Inlandeseimler nicht sinnvoll nutzbar sind, also entweder es tut einfach gar nicht oder man kann es irgendwie nicht sinnvoll weiter verwenden. Ja, ich kann da letztendlich nicht alles aufführen, weil die Liste von Pro Plattform und allem ist ziemlich lang. Also hier muss man so ein bisschen mit der Dokum vorsichtig sein, vielleicht kann man auch die GCC-Leute ein bisschen mit Bucks quälen. Muss sagen, in den letzten Jahren hat sich da einiges getan, also früher war die Dokum wesentlich lückenhafter, inzwischen hat sich da vieles getan, man kann sie wesentlich besser ausschlachten. Ja, die Platzhalter, die wir bisher gesehen haben, das ist nochmal einfach das Beispiel von gerade eben, die haben so ein Problem, nämlich die sind so einfach von vorne durchnummeriert, angenommen, ich bohr jetzt meinen Inlandeseimler an der Stelle so ein bisschen auf und jetzt gibt es noch einen zweiten Ausgaboperanten. Ja, da muss ich unbedingt dran denken, dass ich aus dieser Prozent 1, die ja meinen Eingaboperanten referenzieren soll, eins drauf addiere. Ja, natürlich, natürlich schlecht, wenn man das vergisst, aber es gibt zum Glück ein bisschen syntaktischen Zucker hier, ich kann operanten auch einfach Namen geben, das ist nochmal ein bisschen mehr Syntax, so eine eckigen Klammer davor, kann ich so einen beliebigen Namen vergeben, der muss mit nix anderem irgendwas zu tun haben. Also hier hinten habe ich mal den gleichen Namen genommen, hier vorne habe ich mal einen anderen Namen genommen und die kann ich dann einfach statt der nummerierten Platzhalter verwenden. Also ich kann die nummerierten immer noch verwenden, aber ich kann insbesondere jetzt auch die Namen verwenden. Ja, Bedeutung ist exakt dieselbe wie ich zuvor, entsprechend fällt auch exakt dasselbe Ergebnis raus, macht die ganze Sache einfach nur ein bisschen wartungsfreundlicher. Man hat quasi schon mal so ein gratis Kommentar, weil ich habe jetzt so ein Ding, so einen vernünftigen Namen gegeben möglicherweise und wie gesagt bei Änderung ist es wesentlich robuster. Ja, bisher habe ich immer nur so einzeln Befehler reingesteckt, was mache ich denn mit mehreren Befehlen? Ja, an der Stelle möchte ich jetzt einfach mal hier wie gesagt keinen tieferen Sinn in diesen Beispielen zwei Ausgabeuberranten haben, das soll jeweils so eine Konstante reingesteckt werden. Also ich möchte irgendwie wissen effektiv was 6 mal 9 ist. Und also spezifiziere ich zwei Ausgabeuberranten und entsprechend brauche ich zwei Befehle und die muss ich jetzt irgendwie trennen und das Plattform unabhängigste was GCC da anbietet zum trennen ist einfach so ein New Line reinzubauen. Ja, ich habe jetzt rein aus ästhetischen Gründen das Ganze in 2-Springs aufgespalten, dass sie auch wirklich so hübsch untereinander stehen, macht sich leichter lesbar, verhält sich wie ganz normales Springs in C auch immer noch, also sprich das ist in Wirklichkeit, ist es einfach nur ein einziges Zeichenketten, literal an der Stelle, ist einfach nur damit man es besser lesen kann. Ja, es gibt auch andere Zeichen erlaubt, aber das Abhängig von Assembler zum Beispiel diverse Assembler erlauben sind die Kohle und als Befehls dran. Bei anderen ist es das Kommentarzeichen eher schlecht. Ja, wie sieht das Ganze dann aus? Das sieht so aus, dass es kein Tippfehler von mir, das ist tatsächlich was der GCC so ausgibt, weil ich habe ihm nicht gesagt, rücke danach ein, entsprechend ist die nächste Zeile halt vorne, deswegen sieht man aus ästhetischen Gründen oft nicht back-sh-n an der Zeilenende, wer es zum Beispiel mal gesehen hat, sondern back-sh-n, back-sh-t, also noch ein Tappzeichen davor, damit es einfach eingerückt ist. Ja, ganz wichtig, das Ganze, wenn ich so ein Päckchen von Assembler-Befehlen brauche und die sollen auch beieinander bleiben, ist es in dem Beispiel natürlich nicht gegeben, die könnte ich auch problemlos in zwei aufspalten, geht nichts kaputt. Natürlich geht er mit seinem eigenen Ausgabe aber dann, aber wenn ich so ein Päckchen von Assembler-Befehlen haben will, dann muss es in ein Inline-Assemb-Statement rein. Es gibt keine Garantie vom Kompiler, dass die sonst irgendwie in der richtigen Reihenfolge bleiben, direkt beieinander bleiben, irgendwas. Ja, das ist auch wieder so ein Ding, was einfach von der Spezifikationsseite her sagt. Ich gebe dir nur eine Garantie, dass alles zusammenbleibt, dann, wenn es genau eins ist. Ja, als nächstes haben wir den Aspekt, wenn mehrere Befehle drin sind, dann kann es jetzt dazu kommen, wenn wir vorhin gesehen haben, dass einen Ausgaben die gleiche Register bekommen und das kann kaputt gehen. Ein kleines Beispiel, hier, jetzt statt die neuen, hier drin zu haben, das ist wie eben, habe ich jetzt gesagt, das ist ein Eingabe operant, auch wieder als Register. Was ist jetzt das Problem? Schauen wir uns einfach mal an, wir tun mal so, als würde man das hier übersetzen, aber ohne dieses Upsche Unzeichen, dass es genau geht, also sprich, das sind dieselben zwei Move Befehle und wir sagen den Kompiler, hier, sucht ihr beliebige Ausgaberegister, sucht ihr beliebiges Eingaberegister und was passiert jetzt? Er wählt zufälligerweise, weil wir ihm nichts anderes gesagt haben, für den Ausgabe operant B, das Register ERX und für den Eingabe operanten wählt er auch das Register ERX, aber wir schreiben erst auf den Ausgabe operanten drauf und danach lesen wir von ihm, es ist jetzt eher schlecht, weil ich überschreibe quasi erst mal was da, was ich danach erst noch lesen will und das muss man ihm irgendwie sagen, dass das keine gute Idee ist und das Ganze nennt sich in GCC Sprache Early Clubber, also das hier wird früh irgendwie hau ich da drauf auf einen operanten und sprich zwar auf den Ausgabe operanten, also ich schreibe auf den Ausgabe operanten, bevor ich fertig bin mit Eingaben lesen so rum, genau, dann muss ich ihm das sagen, dass dieses Ding quasi früh geschrieben wird in meiner Befehlssequenz. Hatten wir vor nicht nötig, weil wir hatten immer nur einen Befehl drin, da kann sowas eher nicht passieren, aber bei mehreren Befehlen kann das vorkommen, deswegen sage ich mit hier zusätzlich bitte dieses Ding keinesfalls in ein Register rein tun, das mit irgendeinem, das die für eine Eingabe verwenden ist und dann sieht das Ganze so aus, effektiv, er schnappt sich einfach ein, jetzt zwei verschiedene Register, also unsere, die sechs schreiben jetzt in Register edx und die andere Eingabe, die einzige Eingabe, die tun wir eben jetzt in Register edx, jetzt kommt sich keiner mehr in die Quere und das Ganze funktioniert dann auch, ja, das ist so eine Subtilität am Rande, Speicher operanten zählen, auch wenn sie Ausgaben sind als Eingaben, weil wie sieht so ein Speicher operant aus, na ja meistens wenn da net eine globale Variable ist, muss da irgendwie eine Adresse rein, in Form von einem Register steht eine Adresse drin, also spricht es heimlicherweise ein Eingabe operant, das ist so ein Ding, das steht in der Roku drin, so beiläufig in irgendeinem Satz, kann einem auch böse explodieren, wenn man so eine Subtilität nicht beachtet, ja, es gibt nicht nur Early Clover, es gibt auch noch sonstige Clover, das ist die dritte Liste, also wir haben bisher die Liste Ausgaben, Liste Eingaben und jetzt kann ich noch so andere Dinge sagen, nicht einfach so verwende, das sieht folgendermaßen aus, da kann ich jetzt interessanterweise direkt Registernamen hinschreiben oder andere magische Dinge und es bedeutet einfach, dieses Register benutze ich einfach innerhalb meines Inlinersamblers, also sprich, ich mache damit irgendwas, benutze es weder als Eingabe noch als Ausgabe, erwarte auch nicht, dass nachher noch derselbe Wert drin steht wie vorher, das ist einfach mal für dieses Stückchen Assembler ist es quasi für den Compiler einfach irgendwie Tabu, in dem Falle für mein Beispiel habe ich das folgendermaßen gemacht, ich schreibe einfach sinnloserweise hier in meinem Stück Inlinersambler in das Register EX, man beachte jetzt Doppelprozent Zeichen wie bei Print F, Doppelprozent um 1% zu bekommen und nicht eben einen Operanten zu ersetzen und entsprechend hält sich wie man hier bei den restlichen Befehlen, die so aus dem aus dem Inlinersambler rausfallen, sieht man wird eben kein, wird in das Register EX nicht verwendet. Das andere magische Ding hinten, CC, das sagt den Compiler an der Stelle übrigens, ich schreibe auch auf das Flexregister, also das Flexregister ist ein internes Register vom Prozessor, das so sagt ah, der letzte Vergleich, der hat kleiner, größer, gleich, Ergebnis war negativ solche Dinge, sie stehen da drin und auf x86 greift eben mal mein Adbefehl auf dieses Register zu, indem man sagt hier übrigens über das Ergebnis es ist null, es ist nicht null, es ist negativ solche Dinge und damit der Compiler weiß, übrigens das Flexregister ist eventuell hier auch kaputt gegangen, muss ich Ihnen das auch noch sagen, wenn man jetzt eins mal tatsächlich in so der freien Wildplan schaut, die meisten Leute beschreiben das, finden sie x86 in einem Assembler schreiben nicht, weil es gibt so eine stillschweigende Konvention an der Stelle, ja für x86 nimmt der, nimmt der GCC einfach an, dass du die, dass du das CC-Klober einfach immerhin geschrieben hättest, ist auch einfach nicht dokumentiert an der Stelle, muss man wissen, der Sauberkeit halber sollte man es einfach immer wenden, für andere Plattformen nimmt der GCC mich nicht an, ja es gibt noch eine andere Sorte, magisches Wort für Klobers und das ist Memory, das ist insbesondere auch ein Ding, das gerne falsch gemacht wird, daher hier wieder ein negativ Beispiel, wir schauen uns erstmal die Variante Bad an, also wir haben hier eine Variable, die heißt Resta, ist in 23 drin erstmal und da sage ich jetzt Folgendes in meinem in meinem Inline-Assembler, also wir schauen nur die Zeile hier an, schreibe bitte eine 42 hierhin, das ist hierhin, das ist die Adresse von Ress, stecke ich hier in mein Inline-Assembler rein und da ich ja jetzt nicht fdes Register schreiben will, sondern dahin, wo dieses Register zeigt, klammern drum, so ist die Sündtags einfach von x86 an der Stelle und dann schauen wir doch einfach mal an, was passiert, wie man das besetzen, also am Ende geben wir einfach erst zurück, was tut der Kompiler an der Stelle, also wir haben hier anfang unserer Funktion, das hier berechnet jetzt einfach die Adresse, wo diese Variable ist, müssen wir jetzt nicht weiter auseinander nehmen, im Register Rax, das ist der 64 Mitnahme vom Register A, da steckt jetzt einfach die Adresse drin, ja und dann schreiben wir dahin, wunderbar, die 42 ist jetzt da drin, das ist genau unser Befehl, ja und danach schreibe eine 23 in eax und kehr zurück, ja, also die Frage war, wenn ich einen Speichen Operanten verwende, was der Kompiler als Größe annimmt für diesen Operanten, das ist eine sehr wichtige Frage, das wird nämlich auch gerne subtil falsch gemacht, das ist also der dritte Fall, der hier der richtige Fall ist, er nimmt genau das an, was hier steht, also exakt diese Variable res und sonst nichts, also wahrscheinlich zieht die Sache ab, wenn es ein Array wäre, ja und dann schreibe ich jetzt so a von 5 hin, was sagt er, das glaubt der Kompiler, was ich da jetzt überschreibe und es ist genau die Stelle a von 5, nicht a von 4, nicht a von 6, sondern a von 5, das wird auch gerne falsch gemacht, ja, kommen wir aber gleich noch drauf, ja, für die erste Variante, die also falsch ist, hat er jetzt nämlich der Kompiler Folgendes gemacht, er hat jetzt einfach diese Initialisierung von der Variable hinten dran geschrieben quasi, warum, wie kommt da auf die Sache, na wir haben ihm nie gesagt, dass wir auf die Variable res schreiben in einem Assembler, wir haben nur gesagt, ich stecke die Adresse von res rein, da steht nirgendwo, ich schreibe da drauf und genau so fällt es sich auch, in dem Fall hat er einfach beschlossen, ja, kann ich auch den Wert hinten dran reinschreiben, ins Rückabregister reinschreiben, der muss ja immer noch 23 sein, hat ja keiner was zwischendrin geändert und das war es, ja, wie kann ich ihm trotzdem sagen, dass ich da reinschreibe, die andere Möglichkeit wäre, zu sagen hier, zweite Variante, Memory Clobber, Memory Clobber heißt jetzt, dieses Stück in einem Assembler wird auf beliebige Speicherstellen, beliebige Variabeln schreiben können oder auch von ihnen lesen und dann verhält er sich der Kompiler auch entsprechend anders, er schreibt erst mal eine 23 rein in unsere Speicherstelle, wo die Variable res abgebildet ist drauf, weil, könnt ihr davon gelesen werden, weiß er nicht, er weiß nur, wird gelesen oder geschrieben oder alles Mögliche, er berechnet immer noch die Adresse, die steckt da jetzt da rein und das Stück in einem Assembler schreibt die 42 draus und danach liest er von der schelben Speicherstelle, ja gut, könnte es sich geändert haben, die Variable weiß er nicht, also muss er davon laden, ja, also das wäre so die sichere Methode ist zu tun, das würde jetzt auch funktionieren, wenn ich irgendwie beliebige Stellen eines Arrays überschreiben will, dann funktioniert das auf die Weise, was wäre hier die gute Methode ist zu tun, wenn es unbedingt in einem Speicher sein muss, also in dem Beispiel muss es eigentlich nicht ein Speicher sein, aber es gibt mal hier nur das Prinzip, wenn es im Speicher sein muss, dann habe ich eben hier die Variante zu sagen, ja gut, es ist ein Speicheroperand, der muss einfach so sein und dann sieht der Kot gleich anders aus, hier habe ich gesagt, ich schreibe nur drauf, entsprechend sieht man hier auch, ich schreibe schon, der Kompiler hat schon diese Zuweisung von 23 weggeworfen, weil er weiß, das Ding wird überschrieben, liest keiner vorher von, also haben wir eben diesen sinnlosen Befehl weggemacht, dann schreiben wir eben die 42 in den Speicher, danach lädt der Kompiler von, jetzt generiert den Kot und kehrt zurück, alles funktioniert, ja also es ist besser, wenn man es so direkt spezifiziert, weil dann weiß der einfach der Kompiler genauer, was passiert und auch insbesondere jetzt angenommen, es geht noch tausend andere Variabeln, die natürlich irgendwo rum geistern, in dem Falle mit dem Memory Clover muss er annehmen, dass die auch beliebig geändert worden sind, macht eventuell halt auch nicht den generierten Kot besser, ja, das Ganze kann ich noch kombinieren, einen Ausgabeuberanten, einem, statt einen Gleichheitszeichen ist ein Plus, das sagt einfach, der Operant wird sowohl gelesen als auch geschrieben, gehört auch die Liste der Ausgabeuberanten, ist einfach nur eine gewisse Abkürzung an der Stelle, ja, ansonsten passiert hier nichts spannendes, es ist ähnliches Beispiel vorher, das gibt es noch ein, eine, ja, nur mal größer, ich kann mir so Paarungen wünschen, das sieht folgendermaßen aus, ich habe einen Ausgabeuberanten und dem sage, und ich möchte auch einen Eingabeuberanten haben, der soll unbedingt das gleiche Register, kann notwendig sein für die Befehle, die man zu verwendet, zum Beispiel 8 auf x86 liess und schreibt vom selben Register, kann man sich nicht gegen wehren, so sieht die Maschine einfach aus, also wenn ich eine Eingabe haben will, die anders heißt, als die Ausgabe, dann kann ich es mit so eine Operantenpaarung machen, also ich habe einen Ausgaberegister hier spezifiziert und als Eingabe sage ich Operand Null, ich möchte, also ich geht nicht sagen, such dir ein Lieblingsregister aus, sondern ich sage, welches Register auch immer du für Operand Null genommen hast, das nimm auch für diesen Eingabeoperanten, genau. Das muss übrigens ein Register sein, ansonsten passiert Folgendes, also wenn das hier ein M wäre für Speicheroperant, dann bekomme ich solche netten Sachen gesagt wie Ah, MemoryInput1 ist not directly addressable oder solche schrecklichen Dinge, wenn unbedingt ein Speicheroperant ein Ausgabe sein soll, dann unbedingt plus verwenden funktioniert nicht zuverlässig oder auch gar nicht mit diesen MatchingConstraints wie die in GCC Sprech heißen. Ja, bisher haben wir immer nur so Constraints gesehen, die für quasi alle Architekturen funktionieren, R ist was auch immer für diese Architekturen ein Register ist und man Integer reinsteckt. Es gibt auch welche die Maschinen spezifisch sind, zum Beispiel X86 wäre es der Buchstabe 10, wenn ich ein Schiff haben will, sagt die, wenn ich so in X86 die Filzabelle gucke, der Betrag um dem ich schifte, der muss im Register C sein. Kann nämlich nicht gegen wehren und deswegen gibt es da auch einen speziellen Maschinen-Constraint dafür um zu sagen, das tut mir diesen Wert bitte in Register C, also mein Parameter amount kommt in Register C. Jetzt, wenn wir den generierten Ersempler angucken, man beachte, ich habe hier von handen Register hingeschrieben, sieht nämlich so aus, er kopiert uns einen zweiten Parameter der bekanntlich in EZ ist nach ECX, dessen 32-Bitname, aber eigentlich muss ich hier den 8-Bitname hinschreiben, sagt auch wieder die Befehlsliste einfach. Ja, funktioniert an der Stelle, weil ich habe gesagt, tu es in Register C, also kann ich auf Register C zugreifen, ist aber ein bisschen unschön. Und dafür gibt es jetzt auch wieder so ein bisschen Abhilfe, ich kann jetzt bei den Ausgaben, da wieder Modifikatoren dran schreiben, so ähnlich über Print F, kann ich ja sagen, mach mir für eine Nullen, mach mir ein Bitte in Führen des Plus für positive Werte und so ähnlich. Funktioniert es hier eben auch für den Innern-Assembler, da kann ich hier zum Beispiel ran schreiben, es ist genau das gleiche Beispiel wie eben, nur schreibe ich jetzt nicht mehr hier hart, das Register C hin, sondern ich sage, bitte den Operanten, aber nehme den 8-Bitname, also den Beidnamen für dieses Register, also entsprechend gibt er dann hier, statt diesem Platzhalter, CL aus. Ja, das ist wieder ein Fall von Doku, die Doku dazu ist hochoffiziell, Print the QI, Modname of the Register. Ja, also jetzt muss man sich mit Gezzehnten einen auskennen, um zu wissen, was ein QI-Mod ist. Wenn nicht, hat man einfach an der Stelle Pech gehabt. Es steht für Quarter Intature, also ein Viertel eines Ints. Ja, es gibt, könnte man etwas freundlicher ausdrücken, ja. Ja, es gibt noch andere Dinge, also ich habe hier zum Beispiel auch mal gesagt, das suffix für den Befehlen möchte ich auch quasi so generisch haben, kann, gibt es auch einfach ein Modifikator dafür. Wer alle nicht dokumentierten Dinge sehen will, mich eigentlich ist es leider nur für X86 dokumentiert und ansonsten für nix. Und selbst für X86 nur teilweise, der muss leider in den GCC Sourcecode gucken, an den Stellen findet man es, also pro Backend, also Dollar-Arch ist für die Architektur, gibt es da so eine Funktion für, ja. Ziemlich gut ist er. Also zumindest so den, ja, also wie viel, wie schlimm ist in der, der Support für diesen undokumentierten Sachen, von anderen Compilern, also hier, interessant ist natürlich Klang und ICC. Also ICC ist wirklich ziemlich gnadenlos GCC-Kompatibel. Der macht's, also die meisten Sachen denke ich, macht damit, also ich hab jetzt nicht so viel Erfahrung mit dem, aber die Dinge, die man so in der freien Wildbahn antrifft, die kann er. Für Klang-LVM, die versuchen auch da sehr kompatibel zu sein. Insbesondere klang hat ja auch so den Anspruch, irgendein so GCC-Drop-In-Replacement zu sein. Also alles, was mich so in der G-Lip-C findet, an Inline-Assembler funktioniert. Und das sagt schon ziemlich viel, weil die G-Lip-C tut ziemlich schmutzige Dinge. Vorhin haben wir gesehen, ganz am Anfang mal, man kann auch vor dem Asen, zwischen dem Asen und dem, was in den Klammern ist, da noch einige lustige Wörter hinschreiben. Auf eines möchte ich eingehen, das ist Waller-Teil, das ist nämlich auch ein sehr missverstandenes Wort. Ich habe hier mal ein kleines Beispiel gebastelt. Möchte ich ganz kurz erklären. Read-Time-Stand-Counter ist einfach ein X86-Befil, der gibt einem eine Zahl, die einfach ständig steigt. Ja, es ist einfach ein interner Zähler, im Prozessor, der zählt einfach hoch. Und damit können ich jetzt zum Beispiel so Zeit messen. Ich möchte gucken, wie lange braucht meine Funktion Do-Work? Also rufe ich zuerst mal meine Funktion Get-Time-Auf, in der mein Inline-Assembler drin ist, um den Zeit-Stampel auszulesen. Dann rufe ich mein Do-Work-Funktion auf, dann rufe ich nochmal meine Get-Time-Funktion auf und ziehe die beiden Zeiten voneinander ab und hoffentlich kommt denn da so eine Zeitdifferenz raus, mit der ich irgendwas anfangen kann. Also sie ist nicht in Sekunden, sondern in Prozessarticks, aber das soll uns hier nicht stören. Ja, wenn ich das hinschreibe ohne das Worterteil, kommt das hier raus. Nämlich rufe die Funktion Do-Work auf und gib eine Null zurück. Warum? Ganz einfach. Ohne das Worterteil bedeutet das Asen für den GCC, das hier ist eine, im mathematischen Sinne eine Funktion. Also sprich, selbe Eingaben, dann kommen die selben Ausgaben raus. In dem Fall hat das Ding gar keine Eingaben. Also das heißt, es muss quasi immer dasselbe Ergebnis liefern und genauso verhält das ich dann auch. Also zum Beispiel inline die Funktion Get-Time. Dann steht hier quasi zweimal dieses Asen. Dann stellt der GCC fest, hm, du hast jetzt zweimal dasselbe Asen stehen, da muss er dasselbe rauskommen. Ich wirf die zweite Instanz weg und benutze einfach das Ergebnis zweimal. Und dann sieht er da, oh, du ziehst zweimal was voneinander, was von sich selbst ab, also x minus x. Ja, da kommt Null raus und übrigens das Asmding, dessen den Wert benutzt du jetzt auch nicht mehr, weil ich weiß, das kommt immer Null raus, dann werfe ich es auch noch weg. Das ist genau das, was ich ihm gesagt habe, entsprechend passiert das hier. Mit Volatile sage ich ihm hingegen, dieses Ding hat eine Nebenwirkung. Jedes Mal, wenn es aufgerufen wird, ist das wichtig. Dann muss es genau an der Stelle aufgerufen werden, mit gewissen Einschränken für genau die Stelle. Aber wenn ich es fünfmal aufrufe, dann, wenn ich so ein Quellcode durchgehe, dann muss nachher auch fünf Aufrufe irgendwie für diesen Internet Assembler geben. Und mit dem Volatile funktioniert es eben, also er ruft zweimal, für zweimal diesen Befehl aus und dann bekomme ich eben dann eine Differenz. Dieses Volatile hat im Prinzip die gleiche Bedeutung, wie das Volatile in C, ja? Wird dann das Volatile asm reichen, um den Kryptokier aus dem Memory zu Nullen, oder brauche ich dann noch irgendwelche Blackmagic, dass das wirklich zu 100 Prozent durchgeht? Ja, das ist eine spannende Frage. Also wenn ich könnte ja sagen, okay, ich habe ein, ich mache einen Output-Operanten-Memory und da ist mein Kryptokier, da schreibe ich drauf. Wenn der GCC merkt, oh, dieser Output-Operant, niemand liest mehr danach davon, dann kann er immer noch diesen Asen wegschmeißen, weil er sagt, oh, du schreibst das zwar drauf, aber niemand liest mehr danach. Und dann ist es schade. Wenn ich jetzt Volatile inschreibe, dann muss er dieses Stück Asen auch wirklich tatsächlich produzieren. Ja, das sollte an der Stelle dann funktionieren. Wobei ich an der Stelle eher zu den, auf die OpenBSD-Leute verweise, die haben einfach so eine Memset-Safe-Funktion, die hinreichend zugepflastert ist mit Dingen, um garantiert ausgeführt zu werden und Dinge zu Nullen, ohne dass irgendjemand Fragen stellt. Also in der Falle würde ich sagen, nicht in einer Sender finden, sondern lieber jemanden fragen, der sich damit auskennt. Ja, noch eine kurze Ergänzung dazu hier. Man kann sich da nicht drauf verlassen mit dem Krypto-Ki, weil man nicht weiß, ob der Compiler vorher mehrere Kopien davon im Speicher angelegt hat. Ja, das stimmt. Das ist eine andere Problematik. Der Compiler könnte natürlich sagen, okay, ich brauche natürlich die Daten irgendwo und er kann beliebige Transformationen anstellen, die natürlich zu irgendwelchen Kopien führen kann. Aber das hat es nicht speziell mit Ihnen in einer Sammlert zu tun. Ja, genau. Was tut das Volatile noch so? Es verhindert, dass eben Volatile Asens umgeordnet werden. Es verhindert, dass es mit anderen Volateil zugriffen, also wirklich so Zeiger auf Volateile Dinge oder Volatile Variabeln, dass es das umgeordnet wird, aber nur auf Quelltextebene quasi und entsprechend auf Ersemblerebene. Was die Maschine intern immer noch tut, ist eine andere Geschichte. Also zum Beispiel, es ist keine Speicherbarriere für Multithreading oder sowas, da brauche ich andere Dinge. Da empfiehlt sich auch wieder benutzt irgendwelche Synchronisationsprimitive, die euch hier in den Bibliotheken anbieten, also P-Threads oder C11-Threads, solche Dinge. Und benutzt nicht, versucht die da nicht irgendwie Inlandersambler zu irgendwas zum Missbrauchen, wofür es nicht gedacht ist. Ja, das wäre es mit den Dingen, die ich so thematisch schaffe. Es gibt eigentlich noch viele, viele Dinge mehr. Ich möchte noch ein, zwei kleine Fallbeispiele bringen, so in den letzten Minuten so als Schmankerl. Das hier ist ein echtes Stück Inlandersambler aus IOquake 3. Jetzt wissen wir, war es mal irgendwie bis vor acht Jahren. Und man sieht, es ist ein Riesenhaufen geschlachte. Also ich wollte nicht im Einzelte darauf eingehen, aber effektiv möchte dieses Ding einfach nur ein Stück gechitteten Kot aufrufen. Also die Spiellogik in Quake 3 ist folgendermaßen, die ist in so einem Bytecode. Der Bytecode wird zumindest in manche Plattformen gechittet und dann aufgerufen. Und es passiert hier mit einem Call über einen Funktionszeiger. Ja, und weil sie irgendwie die die ABI versaut haben, also die Aufrufkommission, muss man per Inlandersambler tun, weil es halt eben nicht ein normaler Funktionserfrucht tut. Und dann hat sich dann dieses Monstrum drum drum gebildet um irgendwie diese drei Eingaben da reinzukriegen und auch wieder Ergebnisse rauszubekommen. Und das geht alles über den Speicher. Hier sieht man alle Operandens in den Speicher. Das wird von Handregister durch die Gegend kopiert. Das werden auch von Hand, Push A, das ist der Befehl für sichere Mal alle Register bitte weg auf den Stack. Weil sie gemerkt haben, dass es mit normalen Speichoperanten auch alles kaputt geht, haben sie noch Static-Variabeln benutzen müssen, um die Parameter rein zu machen, weil hier endere ich den Stack-Pointer. Jetzt ist es schlecht, wenn ich auf Stack-relative Variabeln zugreife. Und vor vielen, vielen Jahren habe ich ihnen einfach einen Patch geschickt und danach sah es so aus. Es ist jetzt genau dieser Befehl im Innerassembler noch drin, macht diesen Aufruf und alles außenrum sagt einfach, bitte tu mir die Dinge an die richtige Stelle. Also das ist ein Fall von man kann es sauberer tun. Also es ist jetzt nicht, man kann jetzt aber streiten, ob das jetzt hier hübsch ist, aber es ist zumindest deutlich sauberer. Es funktioniert zuverlässig und lustige Weise danach konnten wir es auch mit unserem Forschungskompiler dann KREG 3 bauen. Und es ist gelaufen. Wie es uns ganz um uns entstanden im Prinzip, wenn man ein paar Zahlen drüber guckt, da steht einfach der Code für den Visual Studio. Er sieht genau so aus und weil dieser Innerassembler wesentlich primitiver ist und eben solche Nettigkeiten nicht erlaubt, hat man fast keine anderen Chancen, als das zu tun und das hat dann einfach jemand 1 zu 1 quasi so mit Gewalt in GCC Innerassembler umgesetzt. Also hier wäre auch eher vielleicht die letzte Anmerkung dazu, warum, wenn ihr eh schon Code schittet, dann macht doch einfach so eine Aufrufkonvention die normale Aufrufkonvention ist, dann hätte es nämlich ein normaler Funktionszeuge Aufruf getan und gar kein Innerassembler. So mein letztes Beispiel, ich habe zwei Stück hier, neben dem Quake ist noch aus STL, also das Simple Direct Media Layer. Da habe ich mal eben einen Tag damit verbracht, einen Crash zu debaggen und es sah irgendwie alles sehr komisch aus. Also irgendwie waren Grafiken verschoben und danach ist es instantan gesägt folgt. Und ich möchte jetzt nicht auf die genauen Details eingehen, weil dann durch allein nochmal eine Viertelstunde um das zu erklären, was da abgeht. Aber im Prinzip, das hier ist ein Stückchen aus einer Eigenimplementierung von MemMove und sie haben einfach so die einen der impliziten Annahmen, die der GCC so sagt, gebrochen, sprich dieser Befehl hat gefehlt und ja, nach Einfügen hat es dann funktioniert. Das andere Problem ist eigentlich, warum zum Geier hat jemand versucht selbst MemMove zu implementieren. Diese Implementierung ist, wer sich ein bisschen mit X86 auskennt, auch noch langsam eigentlich in den meisten Fällen, ruft doch einfach MemMove auf. Ihr habt eine Zehplothek aus dem Grund. Ja. Ja, und damit möchte ich zum Abschluss kommen. So als Faustregeln verwendet bitte in dein Assemble nur echt, wenn es sein muss. So, wenn er hardware-spezifisch sein muss. Wenn er ein Betriebssystem implementiert und was ich an PageTables rumformen müsste oder einen spezifischen Registern von von dem Prozessor oder sowas, dann kommt man nicht drum rum. Dann ist es auch ganz elegant besser, als irgendwie ganze Dateien in den Assembler zu schreiben. So 2, 3 Inland Assemblerbefehl sind hübsch, zumindest hübscher als riesige Assemblerwüsten. Wenn man es schon benutzen muss, so kleine mögliche halten. Ich verweise da noch mal auf dieses Beispiel. Ja, und das Problem ist, das Ding ist sehr mächtig. Es macht einen diverse Annahmen. Ich hoffe, ich habe hier einige, die wichtigen näher gebracht. Wenn er es bewendet, spezifiert es jetzt auch richtig. Nimmt euch die Zeit. Guckt, dass er wirklich das wirklich spezifiziert, was euer Assembler tut, weil der Compiler guckt diesen String da nicht an. Er kopiert ihn da rein, ersetzt die Platzhalter und das war es. Nur was hinten dran steht, ein Ausgaben. Das guckt er sich an. Ja, und vielleicht noch so ganz generell, wenn es so am Mof oder so am Anfang am Ende ist. Ja, meine Beispiele sind dessen schuldig, aber die sind einfach nur deswegen, damit es kurze Beispiele sind. Dann kann man besser einfach so die richtige Angabe machen von wegen. Tu mir das bitte ins richtige Register rein oder gibts mir im Speicher oder wie auch immer. Ja, das war es. Mir hat in diese Zeit Lade nicht reingepasst. Es gibt noch viele, viele andere Dinge, die wir jetzt erst erzählen können. Aber ich hoffe, ich habe einen zumindest mal einige Grundlagen geschaffen, wie man, wenn man es wenn man es enttun muss in dein Assembler für den GCC und alle, die zu Inkompatibel sind, richtig verwendet. Dankeschön. Ja, vielen Dank. Vielen Dank an Christoph für den tollen Vortrag. Ich sehe schon, es gibt erste Fragen. Wir haben noch kurz Zeit dafür. Du hattest kurz gezeigt, dass man quasi angeben kann, dass Register gekloppert werden und dann kann man die benutzen. So weiß ich lokal. Kann man auch sagen, gib mir irgendein Register, dass ich nur Benutzer ist. Temporiere war. Ja, richtig. Stimmt den Aspekt, habe ich vergessen zu erwähnen. Er steht auf meinem Fohlename. Ich habe es nicht gesagt. Ja, genau. Also wenn ich hier ein Klopper sage, muss ich da einen fixen Namen hinschreiben. Ja, und dann muss ich den auch, dann kann ich ihn auch verwenden. Aber wenn ich sage, es mir eigentlich egal welches, gibt mir irgendeines, dann habe ich es hingeschrieben. Ja, genau, da steht es. Stattdessen sollte man sich einfach so eine Variable beschaffen, in die man nichts reintut, sie als Ausgabeoberanten setzen und dann kann man sie ja nicht auch verwenden. Und eventuell muss sie Eureklauber sein, damit man früh auf sie schreiben darf. Aber im Prinzip ist es eleganter, weil dann kann sich der Compiler das Register raussuchen. Das sieht man jetzt an meinem Beispiel hier. Weil ich ERX benutzt habe, muss das eigentliche Ergebnis in den ECX stecken. Und danach, nach dem Innenersambler, also das MOF gehört nicht mehr zu meinem Innenersambler, sondern es hat der Compiler jetzt erzeugen müssen, weil er durfte ERX nicht verwenden. Um ein Rückgabewert ins richtige Register zu tun, musste er es jetzt aus ECX dann nochmal nach ERX kopieren. Hätte ich hier statt dem Klopper einen Ausgabeoberanten hinzugemacht, hätte er es klüger machen können und wahrscheinlich halt nicht ERX verwendet, sondern was anderes. Hier dann ERX verwendet als Ergebnisregister und dann keine Kopie im Ende machen. Also das wäre definitiv der bessere Zug. Aber ich brauche dafür immer eine lokale Variable. Aber ich brauche dafür immer eine lokale Variable. Leider ja. Es ist nicht erlaubt, operanten quasi nur diesen Constraints dringend zu schreiben, ohne einen Ausdruck hinten dran. Das ist leider syntaktisch nicht erlaubt. Wäre aber hübsch, ja. So gibt es noch eine weitere Frage. Wie seid ihr denn bei euch im Forschungsumfeld dazu gekommen, Quake als Benchmark zu benutzen für euren Compiler? Ja gut. Wir wussten halt auch mal irgendwie mal größere Sachen da testen. Also wir haben natürlich 1.000 kleine Beispielprogrammchen gehabt. Auch größere Dinge, als Speckbenchmark ist vielleicht so ein Begriff. Aber man braucht auch mal so große Dinge, so irgendwie Quak3 hat irgendwie so 150.000 Zeilen Kot oder so irgendwas. Also mal so ein Batzenkot, wo viel passiert. Ja, und dann bis es dann richtig läuft, das muss man auch dann testen. Ja, wenn keine weiteren Fragen mehr sind, dann danken wir Christoph noch mal ganz herzlich für den fantastischen Vortrag. Und ja, danke, dass du uns gezeigt hast, wann man und wie man den Inline-Assemble benutzen sollte und wann man es lieber lassen sollte. Danke.