Pourquoi LLVM alloue une variable redondante?

9

Voici un simple fichier C avec une définition d'énumération et une mainfonction:

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

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

Il transpile vers le LLVM IR suivant:

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
}

%2est évidemment la dvariable, qui se voit attribuer 2. À quoi %1correspond si zéro est retourné directement?

macleginn
la source
1
Quels drapeaux avez-vous utilisés pour produire cet IR?
arrowd
@arrowd, j'ai installé la dernière suite LLVM stable et clang-9 -S -emit-llvm simple.c
j'ai
1
Je pense que cela a quelque chose à voir avec l'initialisation avant main( godbolt.org/z/kEtS-s ). Le lien montre comment l'assembly est mappé à la source
Pradeep Kumar
2
@PradeepKumar: En effet, si vous changez le nom de la fonction en autre chose que main, la mystérieuse variable supplémentaire disparaît. Fait intéressant, il disparaît également si vous omettez returncomplètement la déclaration (ce qui est légal pour mainen C et équivalent à return 0;).
Nate Eldredge
1
@macleginn: Je n'en suis pas si sûr. Si vous déclarez maincomme int main(int argc, char **argv)vous le voyez argcet argvcopié sur la pile, mais la mystérieuse variable zéro est toujours là en plus d'eux.
Nate Eldredge

Réponses:

3

Ce %1registre a été généré par clang pour gérer plusieurs instructions de retour dans une fonction . Imaginez que vous disposiez d'une fonction pour calculer la factorielle d'un entier. Au lieu de l'écrire comme ça

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

Vous feriez probablement ça

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

Pourquoi? Parce que Clang insérera cette resultvariable qui contient la valeur de retour pour vous. Yay. C'est le but exact de cela %1. Regardez l'ir pour une version légèrement modifiée de votre code.

Code modifié,

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
}

Maintenant vous voyez que se %1rendre utile hein? Comme les autres l'ont souligné, pour les fonctions avec une seule instruction de retour, cette variable sera probablement supprimée par l'une des passes optimales de llvm.

droptop
la source
1

Pourquoi est-ce important - quel est le problème réel?

Je pense que la réponse la plus profonde que vous cherchez pourrait être: l'architecture de LLVM est basée sur des interfaces assez simples et de nombreuses passes. Les frontends doivent générer du code correct, mais il ne doit pas nécessairement être un bon code. Ils peuvent faire la chose la plus simple qui fonctionne.

Dans ce cas, Clang génère quelques instructions qui s'avèrent ne pas être utilisées pour quoi que ce soit. Ce n'est généralement pas un problème, car une partie de LLVM supprimera les instructions superflues. Clang espère que cela se produira. Clang n'a pas besoin d'éviter d'émettre du code mort; sa mise en œuvre peut se concentrer sur l'exactitude, la simplicité, la testabilité, etc.

arnt
la source
1

Parce que Clang a terminé l'analyse syntaxique, mais LLVM n'a même pas commencé avec l'optimisation.

Le frontal Clang a généré des IR (représentation intermédiaire) et non du code machine. Ces variables sont les SSA (Single Static Assignments); ils ne sont pas encore liés aux registres et en fait après l'optimisation, ils ne le seront jamais car ils sont redondants.

Ce code est une représentation quelque peu littérale de la source. C'est ce qui frappe LLVM pour l'optimisation. Fondamentalement, LLVM commence par cela et optimise à partir de là. En effet, pour les versions 10 et x86_64, llc -O2 finira par générer:

main: # @main
  xor eax, eax
  ret
Olsoniste
la source
Je comprends le processus à ce niveau. Je voulais savoir pourquoi cet IR a été généré pour commencer.
macleginn
Vous pensez peut-être à un compilateur comme à un seul passage. Il existe un pipeline de passes commençant par le frontal Clang qui génère des IR. Il n'a même pas généré cet IR textuel que quelqu'un a demandé avec clang -emit-llvm -S file.cpp Clang a en fait généré une version binaire sérialisable de code binaire de l'IR. Le LLVM est structuré en plusieurs passes, chacune prenant et optimisant l'IR. La première passe LLVM prend IR de Clang. Il faut IR car vous pouvez remplacer Clang par le Fortran FE afin de prendre en charge une autre langue avec le même optimiseur + générateur de code.
Olsonist