Warum weist LLVM eine redundante Variable zu?

9

Hier ist eine einfache C-Datei mit einer Aufzählungsdefinition und einer mainFunktion:

enum days {MON, TUE, WED, THU};

int main() {
    enum days d;
    d = WED;
    return 0;
}

Es wird auf das folgende LLVM-IR übertragen:

define dso_local i32 @main() #0 {
  %1 = alloca i32, align 4
  %2 = alloca i32, align 4
  store i32 0, i32* %1, align 4
  store i32 2, i32* %2, align 4
  ret i32 0
}

%2ist offensichtlich die dVariable, der 2 zugewiesen werden. Was %1entspricht, wenn Null direkt zurückgegeben wird?

Macleginn
quelle
1
Mit welchen Flags haben Sie diese IR erzeugt?
Menschenmenge
@arrowd, ich habe die neueste stabile LLVM-Suite installiert und liefclang-9 -S -emit-llvm simple.c
macleginn
1
Ich denke, es hat etwas mit der vorherigen Initialisierung zu tun main( godbolt.org/z/kEtS-s ). Der Link zeigt, wie die Assembly der Quelle zugeordnet wird
Pradeep Kumar
2
@PradeepKumar: Wenn Sie den Namen der Funktion in etwas anderes als ändern main, verschwindet die mysteriöse zusätzliche Variable. Interessanterweise verschwindet es auch, wenn Sie die returnAussage vollständig weglassen (was mainin C legal ist und gleichbedeutend mit return 0;).
Nate Eldredge
1
@ Macleginn: Ich bin nicht so sicher. Wenn Sie deklarieren, mainwie int main(int argc, char **argv)Sie sehen, argcund argvauf den Stapel kopieren, aber die mysteriöse Nullvariable zusätzlich zu ihnen noch vorhanden ist.
Nate Eldredge

Antworten:

3

Dieses %1Register wurde von clang generiert, um mehrere return-Anweisungen in einer Funktion zu verarbeiten . Stellen Sie sich vor, Sie hätten eine Funktion zum Berechnen der Fakultät einer ganzen Zahl. Anstatt es so zu schreiben

int factorial(int n){
    int result;
    if(n < 2)
      result = 1;
    else{
      result = n * factorial(n-1);
    }
    return result;
}

Sie würden das wahrscheinlich tun

int factorial(int n){
    if(n < 2)
      return 1;
    return n * factorial(n-1);
}

Warum? Weil Clang die resultVariable einfügt , die den Rückgabewert für Sie enthält. Yay. Das ist genau der Zweck davon %1. Suchen Sie im ir nach einer leicht modifizierten Version Ihres Codes.

Geänderter Code,

enum days {MON, TUE, WED, THU};

int main() {
    enum days d;
    d = WED;
    if(d) return 1;
    return 0;
}

IR,

define dso_local i32 @main() #0 !dbg !15 {
    %1 = alloca i32, align 4
    %2 = alloca i32, align 4
    store i32 0, i32* %1, align 4
    store i32 2, i32* %2, align 4, !dbg !22
    %3 = load i32, i32* %2, align 4, !dbg !23
    %4 = icmp ne i32 %3, 0, !dbg !23
    br i1 %4, label %5, label %6, !dbg !25

 5:                                                ; preds = %0
   store i32 1, i32* %1, align 4, !dbg !26
   br label %7, !dbg !26

 6:                                                ; preds = %0
  store i32 0, i32* %1, align 4, !dbg !27
  br label %7, !dbg !27

 7:                                                ; preds = %6, %5
  %8 = load i32, i32* %1, align 4, !dbg !28
  ret i32 %8, !dbg !28
}

Jetzt siehst du, dass %1er sich nützlich macht, oder? Wie die anderen bereits betont haben, wird diese Variable für Funktionen mit nur einer return-Anweisung wahrscheinlich durch einen der optimalen Durchgänge von llvm entfernt.

Droptop
quelle
1

Warum ist das wichtig - was ist das eigentliche Problem?

Ich denke, die tiefere Antwort, die Sie suchen, könnte sein: Die Architektur von LLVM basiert auf ziemlich einfachen Frontends und vielen Durchgängen. Die Frontends müssen korrekten Code generieren, aber es muss kein guter Code sein. Sie können das Einfachste tun, was funktioniert.

In diesem Fall generiert Clang einige Anweisungen, die sich als für nichts verwendbar herausstellen. Dies ist im Allgemeinen kein Problem, da ein Teil von LLVM überflüssige Anweisungen entfernt. Clang vertraut darauf, dass dies geschieht. Clang muss nicht vermeiden, toten Code auszugeben. Die Implementierung kann sich auf Korrektheit, Einfachheit, Testbarkeit usw. konzentrieren.

arnt
quelle
1

Da Clang mit der Syntaxanalyse fertig ist, hat LLVM noch nicht einmal mit der Optimierung begonnen.

Das Clang-Frontend hat IR (Intermediate Representation) und keinen Maschinencode generiert. Diese Variablen sind SSAs (Single Static Assignments). Sie waren noch nicht an Register gebunden und werden es nach der Optimierung niemals sein, weil sie redundant sind.

Dieser Code ist eine etwas wörtliche Darstellung der Quelle. Es ist das, was LLVM zur Optimierung zur Verfügung stellt. Grundsätzlich beginnt LLVM damit und optimiert von dort aus. In der Tat wird llc -O2 für Version 10 und x86_64 schließlich Folgendes generieren:

main: # @main
  xor eax, eax
  ret
Olsonist
quelle
Ich verstehe den Prozess auf dieser Ebene. Ich wollte wissen, warum diese IR zuerst erzeugt wurde.
Macleginn
Sie können sich einen Compiler als einen einzelnen Durchgang vorstellen. Es gibt eine Pipeline von Durchläufen, die mit dem Clang-Frontend beginnen und IR erzeugen. Es wurde nicht einmal diese Text-IR generiert, die stattdessen von jemandem mit clang -emit-llvm -S file.cpp angefordert wurde. Clang hat tatsächlich eine binär serialisierbare Bitcode-Version der IR generiert. LLVM besteht aus mehreren Durchgängen, die jeweils IR aufnehmen und optimieren. Der erste LLVM-Pass nimmt IR von Clang. IR ist erforderlich, da Sie Clang durch Fortran FE ersetzen können, um eine andere Sprache mit demselben Optimierer + Codegenerator zu unterstützen.
Olsonist