PHI talimatı tam olarak ne yapar ve LLVM'de nasıl kullanılır?


91

LLVM, oldukça garip bir açıklamayla phi talimatına sahiptir:

'Phi' talimatı, işlevi temsil eden SSA grafiğindeki φ düğümünü uygulamak için kullanılır.

Tipik olarak, dallanma uygulamak için kullanılır. Doğru anladıysam, bağımlılık analizini mümkün kılmak gerekir ve bazı durumlarda gereksiz yüklemelerden kaçınmaya yardımcı olabilir. Ancak yine de tam olarak ne yaptığını anlamak zor.

Kaleidoscope örneği , durum için oldukça güzel açıklıyor if. Ancak, &&ve gibi mantıksal işlemlerin nasıl uygulanacağı o kadar net değil ||. Ben aşağıdakileri yazarsanız çevrimiçi LLVM derleyici:

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

Son birkaç satır tamamen kafamı karıştırıyor:

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

Görünüşe göre phi düğümü kullanılabilecek bir sonuç veriyor. Ve phi düğümünün sadece hangi yollardan gelen değerleri tanımladığını düşünüyordum.

Birisi Phi düğümünün ne olduğunu ve ||onunla nasıl uygulanacağını açıklayabilir mi?


1
phiDüğüm "Statik bir atama" forma IR dönüştürmek Derleyiciler sorunun bir çözümdür. Çözümü daha iyi anlamak için sorunu daha iyi anlamanızı öneririm. Bu yüzden size " Neden phidüğümdür " diyeceğim .
Vraj Pandya

Yanıtlar:


77

Bir phi düğümü, mevcut bloğun öncülüne bağlı olarak bir değer seçmek için kullanılan bir talimattır ( Tam hiyerarşiyi görmek için buraya bakın - aynı zamanda miras aldığı sınıflardan biri olan bir değer olarak da kullanılır).

LLVM kodunun SSA (statik tek atama) stilinin yapısı nedeniyle Phi düğümleri gereklidir - örneğin, aşağıdaki C ++ işlevi

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

aşağıdaki IR'ye çevrilir: (aracılığıyla oluşturulur clang -c -emit-llvm file.c -o out.bc- ve sonra görüntülenir 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
}

Peki burada ne oluyor? Değişkenin bool l0 veya 1 olabileceği C ++ kodunun aksine, LLVM IR'de bir kez tanımlanması gerekir . Bu yüzden doğru olup olmadığını kontrol ediyoruz %toboolve sonra lor.endveya seçeneğine atlıyoruz lor.rhs.

Gelen lor.endbiz nihayet değerini var || Şebeke. Giriş bloğundan geldiysek - o zaman bu doğru. Aksi takdirde, değerine eşittir %tobool2- ve bu tam olarak aşağıdaki IR satırından elde ettiğimiz şey:

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

6
TL; DR φ düğümü, üçlü bir ifadedir. Biri koşulu içermediğini iddia edebilir, ancak gerçekten, son koda dönüştürdükten sonra, hangisinin canlı olduğunu başka türlü belirleyemezsiniz, bu nedenle φ koşulu da olmalıdır.
Hi-Angel

31

Phi kullanmanıza hiç gerek yok. Sadece bir sürü geçici değişken oluşturun. LLVM optimizasyon geçişleri, geçici değişkenlerin optimize edilmesiyle ilgilenecek ve bunun için otomatik olarak phi düğümünü kullanacaktır.

Örneğin, bunu yapmak istiyorsanız:

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

Bunun için phi düğümünü kullanabilirsiniz (sözde kodda):

  1. 4'ü x1'e atayın
  2. eğer (! bir şey) 4'e dal
  3. 2 ekleyerek x1'den x2'yi hesapla
  4. x1 ve x2'den x3 phi atayın
  5. x3 ile yazdırma çağrısı

Ancak phi düğümü olmadan da yapabilirsiniz (sözde kodda):

  1. yerel değişkeni x adlı yığın üzerinde tahsis et
  2. temp x1 değeri 4'e yükle
  3. x1'den x'e depolayın
  4. eğer (! bir şey) 8'e dal
  5. x'i sıcaklık x2'ye yükle
  6. temp x3'e 4 ile x2 ekleyin
  7. x3'ten x'e depolayın
  8. x'i sıcaklık x4'e yükle
  9. x4 ile yazdırma çağrısı

Llvm ile optimizasyon geçişlerinin çalıştırılmasıyla, bu ikinci kod birinci koda göre optimize edilecektir.


4
Okuduklarıma göre, burada akılda tutulması gereken birkaç kısıtlama var gibi görünüyor. mem2reg , söz konusu optimizasyon geçişidir ve Kaleidoscope örneğinde belirtilen birkaç sınırlaması vardır . Görünüşe göre bu, sorunu halletmenin tercih edilen yolu ve Clang tarafından kullanılıyor.
Matthew Sanders
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.