LLVM IR AllocaInst/StoreInst/LoadInst



  • Hi,

    ich schreibe seit 2 Wochen an einer eigenen Programmiersprache mit LLVM Backend. Das ganze ist bislang erstaunlich einfach gewesen. Funktionen + Aufrufe mit Argumenten, globale Constanten/lokale Variablen Deklarationen usw. Jetzt wollte ich mit den lokalen Variablen die bislang nur deklariert und mit einem Default Wert initialisiert wurden auch etwas anfangen. Also z.B. eine Wertzuweisung sowie einfache +/- Operationen umsetzen. Hier funktioniert das Ganze aber etwas anders als erwartet und mir fehlt noch die abschließende Idee dies zu implementieren. Ich weis gar nicht genau wie ich es überhaupt erklären soll. Evtl. mal ein IR Code als Beispiel um eine Variable zu deklarieren und einen Wert zuzuweisen und zurückzugeben:

    define i32 @Add(i32 %value1, i32 %value2) {
    entry:
      %result = alloca i32, align 4
      %result_store = store i32 0, i32* %result, align 4
      ret i32 0
    }
    

    Das "alloca" (llvm::AllocaInst) macht die Speicherallokierung auf dem Stack und das "store" (llvm::StoreInst) initialisiert den Speicher der "result" Variablen mit "0". Meine ursprüngliche Idee war nun um den Store der variablen zuordnen zu können diesem den gleichen Namen zu geben wie die Variable selber. Aber das war die erste Überraschung. Unterschiedliche Objekte können nicht mit einem identischen Namen versehen werden. Selbst wenn man das versucht hängt LLVM einen numerischen Zähler hinten an. Z.B. "result1". Gut dann nenne ich den Store halt "result_store" mit "_store" Postfix als Konvention. Aber das war nun die zweite Überraschung. Den Store kann man gar nicht wiederverwenden. Das Grundproblem ist wohl, dass man Registern nur einmalig einen Wert zuweisen kann. D.h. für eine spätere Wertzuweisung muss man einen zweiten Store erzeugen. Damit wurde mir klar, dass das zuordnen von Objekten über deren Namen wohl nicht möglich ist.

    Aber es gibt ja auch noch die llvm::LoadInst Objekte. Damit wird man wohl den richtigen/aktuellen Wert aus dem aktuellen Register lesen können. Es wird aber noch etwas kurioser. Z.B. folgender IR Code für eine Addition:

    %result2 = fadd i32 %value1, %value2
    

    Ich wollte ursprünglich die beiden Funktionsargumente "value1" und "value2" addieren und "result zuweisen. Übergebe ich dem
    llvm::CreateFAdd Aufruf aber den "Variablennamen" "result" erzeugt mir LLVM eine "result2" Variable bzw. Store? ARGGHH. Und spätestens jetzt habe ich einen Knoten im Kopf. Alle möglichen Operationen erzeugen lauter neue Objekte/Registereinträge mit anderen Namen usw. Wie um alles in der Welt soll man da noch etwas einander zuordnen können?

    Vielleicht ist ja jemand hier der sich mit LLVM auskennt und etwas zu der AllocaInst/StoreInst/LoadInst Geschichte sagen kann? Ein LLVM IR Funktions-Beispeil für eine Wertzuweisung + Rückgabe oder ein Funktions-Beispiel für eine Addition der Funktionsargumente in eine lokale Variable + Rückgabe wären z.B. hilfreich. Will nur vermeiden, dass ich am Ende lauter Registerwerte schreibe und lade was am Ende womöglich total ineffizient ist.

    Denn mal angenommen man hat eine Funktion mit vielen Operationen. Ganz am Anfang wird eine Variable erzeugt+initialisert. Und mal angenommen diese Variable söllte im Verlauf dieser Funktion noch zwei weitere Male verwendet nicht aber geändert werden. Muss ich dann für beide Fälle ein "load" machen? Das klingt doch schwachsinnig wenn der Wert zuvor schon einmal geladen wurde :). Aber woher wüsste ich, dass der Wert bei der zweiten Verwendung bereits geladen wurde. Fragen über Fragen :).



  • define i32 @Add(i32 %value1, i32 %value2) {
    entry:
      call void @Print(i32 %value1)
      call void @Print(i32 %value2)
      %result = alloca i32, align 4
      %result_store = store i32 0, i32* %result, align 4
      %result1 = load i32, i32* %result, align 4
      call void @Print(i32 %result1)
      %result_store2 = store i32 42, i32* %result, align 4
      %result3 = load i32, i32* %result, align 4
      call void @Print(i32 %result3)
      %add = add i32 %value1, %value2
      %result_store4 = store i32 %add, i32* %result, align 4
      %result5 = load i32, i32* %result, align 4
      call void @Print(i32 %result5)
      %result6 = load i32, i32* %result, align 4
      ret i32 %result6
    }
    

    Habe es nun so implementiert, dass vor jedem Lesen ein "CreateLoad" ausgeführt wird. Das Programm funktioniert nun deutlich besser. Vorher wurden ja nur die ursprünglichen Initialisierungswerte ausgegeben. Aber ich denke ich habe nun ab und zu ein paar Lese-Operationen zu viel. Z.B. bei

      %result6 = load i32, i32* %result, align 4
    

    Hier hat sich am "%result5" Wert zwei Zeilen drüber ja nichts geändert. Aber gut mal sehen, ob ich dazu noch eine Lösung finde.



  • Wie schaut der generierte Maschinencode aus? Ich denke, das sollte LLVM von sich aus wegoptimieren.


Anmelden zum Antworten