web-dev-qa-db-fra.com

Que fait exactement l’instruction PHI et comment l’utiliser dans LLVM?

LLVM a phi instruction avec une explication assez étrange:

L'instruction 'phi' est utilisée pour implémenter le nœud dans le graphe SSA représentant la fonction.

Généralement, il est utilisé pour implémenter la création de branches. Si j'ai bien compris, il est nécessaire de rendre l'analyse de dépendance possible et, dans certains cas, d'éviter les charges inutiles. Cependant, il est encore difficile de comprendre ce qu’il fait exactement.

Kaléidoscope exemple l'explique assez bien pour if cas. Cependant, la façon de mettre en œuvre des opérations logiques telles que && et || n'est pas claire. Si je tape ce qui suit à llvm en ligne compilateur:

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

Les dernières lignes me confondent complètement:

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

On dirait que le noeud phi produit un résultat utilisable. Et j’ai eu l’impression que le nœud phi ne fait que définir les chemins à partir desquels les valeurs proviennent.

Quelqu'un pourrait-il expliquer ce qu'est un nœud Phi et comment implémenter || avec celui-ci?

68
user730816

Un nœud phi est une instruction utilisée pour sélectionner une valeur en fonction du prédécesseur du bloc actuel (regardez ici pour voir la hiérarchie complète - elle sert également de valeur, qui est l'une des classes dont il hérite) .

Les nœuds Phi sont nécessaires en raison de la structure du style SSA (assignation unique statique) du code LLVM - par exemple, la fonction C++ suivante

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

est traduit dans l'IR suivant: (créé via clang -c -emit-llvm file.c -o out.bc - puis visualisé via 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
}

Alors, que se passe-t-il ici? Contrairement au code C++, où la variable bool l peut être 0 ou 1, dans le LLVM IR, elle doit être définie once. Nous vérifions donc si %tobool est vrai, puis passons à lor.end ou lor.rhs.

Dans lor.end nous avons enfin la valeur de || opérateur. Si nous arrivons du bloc d’entrée, c’est tout simplement vrai. Sinon, il est égal à la valeur de %tobool2 - et c'est exactement ce que nous obtenons de la ligne IR suivante:

%2 = phi i1 [ true, %entry ], [ %tobool2, %lor.rhs ]
64
Guy Adini

Vous n'avez pas du tout besoin d'utiliser phi. Créez simplement un groupe de variables temporaires. Les passes d’optimisation LLVM s’occuperont de l’optimisation des variables temporaires et utiliseront automatiquement le noeud phi.

Par exemple, si vous voulez faire ceci:

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

Vous pouvez utiliser le noeud phi pour cela (en pseudocode):

  1. assigner 4 à x1 
  2. si (! quelque chose) branche à 4
  3. calculer x2 à partir de x1 en ajoutant 2
  4. assigner x3 phi de x1 et x2
  5. appelez print avec x3

Mais vous pouvez vous passer du noeud phi (en pseudocode):

  1. allouer une variable locale sur une pile appelée x
  2. charger dans temp x1 valeur 4
  3. stocker x1 à x
  4. si (! quelque chose) branche à 8
  5. charger x à temp x2
  6. ajouter x2 avec 4 à temp x3
  7. stocker x3 à x
  8. charger x à temp x4
  9. appelez print avec x4

En exécutant des passes d'optimisation avec llvm, ce second code sera optimisé pour le premier code.

21
Mārtiņš Možeiko