Kein GAS

Tags: @KeinGAS @Deutsch

Klein angefangen

Seit ich begriffen habe was ein Computer ist, möchte alles von Oben bis Unten verstehen. Inzwischen verstehe ich alles was praktisch nötig war um Software zu schreiben und um jeden Bug der mir begegnet zu beheben. Das schliesst viel vom POSIX Standard und den Linux-Kernel ein. Ich konnte mir immer einreden, dass der ganze Wahnsinn der uns durch gcc oder llvm entgegen kommt ignorieren kann, da die Entwickler ja wissen was sie tun und was das beste ist.

Der Wahnsinn

Oh, lag ich falsch. Llvm ist der komplette Irrsinn, eine objekt-orientierte Kompilerkette? Wirklich? Wann haben wir endgültig gelernt, dass OO für solche daten-orientierte Aufgaben schlecht ist? Es war 2002 am GDC wo Scott Bilas ECS vorstellte. Aber tatsächlich hat es Ivan Sutherland 1963 1 schon ein mal herausgefunden. Pech, llvm wurde um 2000 herum in Angriff genommen. Trotzdem widerspricht es allen Prinzipen die damals schon Jahrzehnte lang klar waren. Seither versucht man vergeblich die Architektur von llvm zu reparieren, denn nutzt man neue daten-orientierte Teile von llvm verliert man all die so verführerischen Features. Also bleibt uns das Schlamassel für immer erhalten.

Der andere Wahnsinn

Wie sieht es bei gcc aus? Etwas besser, der Datenfluss ist wenigstens daten-orientiert gehalten. Dafür hat eine schlechte Sicht auf undefiniertes Verhalten (UB) allgemein grosse Probleme verursacht. In einer bestimmten Implementation eines Kompilers auf einer bestimmten Architektur, gibt es kein undefiniertes Verhalten. Was man auch tut, das Programm verhält sich irgendwie, es hört nicht plötzlich auf zu existieren. UB ist ein rein theoretisches Konzept. Es wurde eingeführt um den C-Kompiler portierbar zu machen. So dass man Operationen die eben nicht auf jeder Plattform gleich sind, entsprechend der Plattform implementieren kann. Aus der Sicht eines C-Programmierers ist undefiniertes Verhalten fast nicht zu vermieden. Entfernt man alles UB von C erhält man etwas, dass fast nicht zu benutzen ist, denn UB lauert überall. Darüber hinaus ist es eben nicht UB wenn man sich nur an einen Kompiler auf einer Plattform hält, was die meisten Entwickler und Organisationen tun. Wie soll ich also überhaupt herausfinden, dass eine Operation UB ist? Natürlich ist bekanntes UB im Standard definiert und es gibt Listen und viel folkore zu UB. Da aber da alles im Fluss ist, entscheided am Ende der Maschinencode der für meine Plattform generiert wird, wenn ich mein Programm auf eine neue Plattform portiere. Das war früher die Realität; es gab einen definierten Moment wo mein Programm plötzlich ohne eine Änderung Fehlverhalten zeigen kann. Dann haben die Kompilerentwickler entschieden, dass sie jedes UB nutzen dürfen um Maschinencode zu optimieren. Aus gross C wurde klein c. Welches einem durch ein Update des C-Kompilers jederzeit um die Ohren fliegen kann. Natürlich, anstatt sich besser zu verhalten, hat man den Wahnsinn auf die Spitze getrieben: Man hat so künstlich eine unglaubliche Zahl von Problemen geschaffen, nur um diese dann mit komplexen Algorithmen zu lösen. Das Resultat ist der Moloch den unsere Heute Computerinfrastruktur bildet. 2

Der traurige Wahnsinn

Es ist mir klar, dass dieses destruktive Verhalten in der ganzen Anthroposphere betrieben wird und sogar als vorbildlich angesehen wird. Das ist schliesslich Fortschritt - ist es? - und Fortschritt ist immer gut - ist es? Wir expandieren und optimieren, bis wir kollabieren. Besonders schade ist dies bei unserem Planeten. Trotzdem möchte ich persönlich Alternativen erforschen. Es ist auch klar, dass es wichtigeres gäbe als Kompiler, der Planet hat gute Alternativen dringend nötig. Aber ich bin halt Ingeniör und mich interessiert so langweiliges Zeug.

Mein Wahnsinn

Dieses Projekt geht eigentlich schon seit 20 Jahren, aber jetzt habe ich Zeit mich wieder darum zu kümmern. Ich möchte eine Kompilerkette,

Ich werde so einiges ausprobieren und oft scheitern, aber ich werde viel lernen und auf dem Weg einige Patches zu Projekten beitragen. Mein Repo das kper heisst - Eskapade -> caper -> kper - hat inzwischen vier Zweige mit unterschiedlichsten Ideen. Am weitesten bin ich mit einer VM die leicht auf ein neues System zu portieren ist, bei der die ganze Komplexität oberhalb der VM im Bytecode gehalten ist. So dass portieren der VM reicht um das komplette System auf einer anderen Plattform zu erhalten. Das ist nichts Neues, Smalltalk hat dies schon seit Jahrzehnten gemacht. Oder das Plan 9 Betriebssystem Inferno ist ein Beispiel dafür. Ich habe lediglich die Schnittstelle zwischen VM und Laufzeit nach meinen Vorstellungen entworfen. So ziemlich jedes Hobbyprojekt wie meines macht etwas in der Art.

Oh nein, noch ein Wahnsinn

Als ich mir aber Gedanken darüber machte, wie eine Umsetzung der VM in Assembler aussehen würde, habe ich gemerkt, dass unser Problem viel tiefer geht. Dank der Evolution von x86 über mehr als 40 Jahre von 16- bis zu 64- oder tatsächlich 512-Bit, sind auch unsere Assembler Moloche und ich habe das Problem mit meiner VM gar nicht umgangen. Scheitern gehört eben dazu. Seit Jahren interessieren mich einfache Kompiler. Eine interessant Variante ist mir kürzlich aufgefallen. Qbe und cproc. 3 Im Moment sieht die cproc Kette so aus:

C -> GNU-CPP -> cproc -> qbe -> gnu assembler (GAS) -> gnu linker -> elf64

Ich konnte schon recht viel Code mit dieser Kette kompilieren:

C -> tinycc-cpp -> cproc -> qbe -> GAS -> tinycc-ld -> elf

Jedoch ist in GAS etwas drin das sich libbfd nennt (Binary File Descriptor), eine Meisterleitung und ein Moloch den ich niemals verstehen werde. libbdf kann den Maschinencode aller grossen und vielen kleiner Architekturen, dazu kommen eine Vielzahl von ABIs und genau so viele Datenformate. Mein Ziel ist also:

C -> tinycc-cpp -> cproc -> qbe -> KEINGAS -> tinycc-ld -> elf

Vermutlich wird auch tinycc einige Problem verursachen, die ich entweder reparieren muss oder tinycc ersetzen.

Was ist nun dieses KEINGAS? Ich habe keine Ahnung:

Zurück an die Wurzeln

Jedoch habe ich meine Permacomputing Infra auf alten x86 Maschinen aufgebaut, da man diese günstig erhält und man lange mit Ersatzteilen rechnen kann. Deshalb möchte ich zunächst einen handgestrickten x86 Assembler bauen, besser gesagt, dabei scheitern. Ich glaube man kann wenn man sich voll auf Einfachheit konzentriert etwas bauen, das läuft - es wird wahrscheinlich in Wahnsinn ausarten, aber so lange ich mich nicht damit beschäftigt habe, weiss ich nicht was vor mir liegt.

Da ich aber zu jeder Zeit die Kompilerkette auch nutzen möchte liegen zunächst Probleme in qbe und cproc vor mir. Was sehr willkommen ist, da ich mich in diese Projekt einarbeiten muss.

int main(void) {
    char test[5];
    *test =  *";";
    return 0;
}

cproc kann im Moment dieses *";" nicht parsen und lua-5.5 hat solchen Code. Lua-5.4 kann ich mit cproc kompilieren, aber mir ist lua-5.5 wegen Gründen ans Herz gewachsen und *";" zu parsen sollte doch zu schaffen sein? Wer weiss. Ich lerne nun schon seit vielen Stunden eine cproc Funktion nach der anderen kennen, weil sich das Problem immer weiter ausdehnt. Wäre es so einfach hätte es der Entwickler von cproc wohl schon umgesetzt.

Der absolute Wahnsinn

Viele Entwicklungen hätten nie stattgefunden, wären die Menschen vernünftig gewesen und hätten den Wahnsinn sein lassen. So hat der Wahnsinn nicht nur schlechtes. Ich hoffe ich werde nächstens davon berichten wie ich *";" geparsed habe.


  1. https://www.cl.cam.ac.uk/techreports/UCAM-CL-TR-574.html der Downloadlink ist ganz klein unten.↩︎

  2. https://c9x.me/compile/bib/ubc.pdf↩︎

  3. Dank dem Autor von cproc habe ich nun auch eine statische gcc-Kompilerkette die sich selbst kompilieren kann: https://github.com/oasislinux/oasis in Kombination mit https://github.com/richfelker/musl-cross-make↩︎