Was genau macht der PHI-Befehl und wie wird er in LLVM verwendet?

85

LLVM hat Phi- Anweisungen mit ziemlich seltsamen Erklärungen:

Der Befehl 'phi' wird verwendet, um den φ-Knoten in dem SSA-Graphen zu implementieren, der die Funktion darstellt.

In der Regel wird es zum Implementieren der Verzweigung verwendet. Wenn ich es richtig verstanden habe, ist es erforderlich, eine Abhängigkeitsanalyse zu ermöglichen, und in einigen Fällen kann dies dazu beitragen, unnötiges Laden zu vermeiden. Es ist jedoch immer noch schwer zu verstehen, was es genau tut.

Das Kaleidoskop- Beispiel erklärt es ziemlich gut für den ifFall. Es ist jedoch nicht so klar, wie logische Operationen wie &&und implementiert werden sollen ||. Wenn ich Folgendes in den Online-llvm- Compiler eingebe :

void main1(bool r, bool y) {
    bool l = y || r;
}

Die letzten paar Zeilen verwirren mich völlig:

; <label>:10                                      ; preds = %7, %0
%11 = phi i1 [ true, %0 ], [ %9, %7 ]
%12 = zext i1 %11 to i8

Es sieht so aus, als würde der Phi-Knoten ein Ergebnis erzeugen, das verwendet werden kann. Und ich hatte den Eindruck, dass der Phi-Knoten nur definiert, von welchen Pfaden Werte kommen.

Könnte jemand erklären, was ein Phi-Knoten ist und wie man ihn implementiert ||?

vwvw
quelle
1
Der phiKnoten ist eine Lösung für das Problem bei Compilern, die IR in die Form "Statische Einzelzuweisung" umzuwandeln. Um die Lösung besser zu verstehen, würde ich vorschlagen, das Problem besser zu verstehen. Also werde ich dir eins sagen " Warum ist phiKnoten ".
Vraj Pandya

Antworten:

75

Ein Phi-Knoten ist eine Anweisung, mit der ein Wert abhängig vom Vorgänger des aktuellen Blocks ausgewählt wird (Sehen Sie hier , um die vollständige Hierarchie anzuzeigen - er wird auch als Wert verwendet, der eine der Klassen ist, von denen er erbt).

Phi-Knoten sind aufgrund der Struktur des SSA-Stils (statische Einzelzuweisung) des LLVM-Codes erforderlich - beispielsweise der folgenden C ++ - Funktion

void m(bool r, bool y){
    bool l = y || r ;
}

wird in die folgende IR übersetzt: (erstellt durch clang -c -emit-llvm file.c -o out.bc- und dann angezeigt durch llvm-dis)

define void @_Z1mbb(i1 zeroext %r, i1 zeroext %y) nounwind {
entry:
  %r.addr = alloca i8, align 1
  %y.addr = alloca i8, align 1
  %l = alloca i8, align 1
  %frombool = zext i1 %r to i8
  store i8 %frombool, i8* %r.addr, align 1
  %frombool1 = zext i1 %y to i8
  store i8 %frombool1, i8* %y.addr, align 1
  %0 = load i8* %y.addr, align 1
  %tobool = trunc i8 %0 to i1
  br i1 %tobool, label %lor.end, label %lor.rhs

lor.rhs:                                          ; preds = %entry
  %1 = load i8* %r.addr, align 1
  %tobool2 = trunc i8 %1 to i1
  br label %lor.end

lor.end:                                          ; preds = %lor.rhs, %entry
  %2 = phi i1 [ true, %entry ], [ %tobool2, %lor.rhs ]
  %frombool3 = zext i1 %2 to i8
  store i8 %frombool3, i8* %l, align 1
  ret void
}

Was passiert hier? Im Gegensatz zum C ++ - Code, bei dem die Variable bool lentweder 0 oder 1 sein kann, muss sie im LLVM-IR einmal definiert werden . Also prüfen wir, ob %tobooles wahr ist und springen dann zu lor.endoder lor.rhs.

In haben lor.endwir endlich den Wert des || Operator. Wenn wir vom Eingangsblock angekommen sind, dann ist es einfach wahr. Ansonsten ist es gleich dem Wert von %tobool2- und genau das erhalten wir aus der folgenden IR-Zeile:

%2 = phi i1 [ true, %entry ], [ %tobool2, %lor.rhs ]
Guy Adini
quelle
5
TL; DR φ Knoten ist ein ternärer Ausdruck. Man könnte argumentieren, dass es die Bedingung nicht enthält, aber beim Konvertieren in den endgültigen Code kann man nicht anders bestimmen, welches der Argumente aktiv ist, also muss φ auch die Bedingung haben.
Hi-Angel
31

Sie müssen Phi überhaupt nicht verwenden. Erstellen Sie einfach eine Reihe von temporären Variablen. LLVM-Optimierungsdurchläufe kümmern sich um die Optimierung temporärer Variablen und verwenden dafür automatisch den Phi-Knoten.

Zum Beispiel, wenn Sie dies tun möchten:

x = 4;
if (something) x = x + 2;
print(x);

Sie können dafür den Phi-Knoten verwenden (im Pseudocode):

  1. weise 4 zu x1 zu
  2. wenn (! etwas) zu 4 verzweigen
  3. Berechnen Sie x2 aus x1, indem Sie 2 addieren
  4. weise x3 phi von x1 und x2 zu
  5. Rufen Sie print mit x3 auf

Sie können jedoch auf Phi-Knoten (im Pseudocode) verzichten:

  1. Ordnen Sie eine lokale Variable auf dem Stapel mit dem Namen x zu
  2. Laden in Temp x1 Wert 4
  3. Speichern Sie x1 bis x
  4. wenn (! etwas) zu 8 verzweigen
  5. lade x auf temp x2
  6. addiere x2 mit 4 zu temp x3
  7. Speichern Sie x3 bis x
  8. lade x auf temp x4
  9. Rufen Sie print mit x4 auf

Durch Ausführen von Optimierungsdurchläufen mit llvm wird dieser zweite Code auf den ersten Code optimiert.

Mārtiņš Možeiko
quelle
4
Nach dem, was ich gelesen habe, klingt es so, als ob hier einige Einschränkungen zu beachten sind. mem2reg ist der betreffende Optimierungsdurchlauf und weist einige Einschränkungen auf, auf die im Kaleidoskop-Beispiel hingewiesen wird . Es hört sich so an, als wäre dies jedoch die bevorzugte Methode, um das Problem zu lösen, und wird von Clang verwendet.
Matthew Sanders