LLVM neden gereksiz bir değişken ayırıyor?


9

İşte bir enum tanımı ve mainişlevi olan basit bir C dosyası :

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

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

Aşağıdaki LLVM IR'ye aktarılır:

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
}

%2" d2" nin atanmış olduğu değişken. %1Sıfır doğrudan döndürülürse neye karşılık gelir?


1
Bu IR'yi üretmek için hangi bayrakları kullandınız?
arrowd

@arrowd, en son kararlı LLVM paketini clang-9 -S -emit-llvm simple.c
yükledim

1
Daha önce başlatma ile ilgili bir şey olduğunu düşünüyorum main( godbolt.org/z/kEtS-s ). Bağlantı, montajın kaynağa nasıl eşlendiğini gösterir
Pradeep Kumar

2
@PradeepKumar: Aslında, işlevin adını başka bir şeyle değiştirirseniz main, gizemli ekstra değişken kaybolur. İlginç bir şekilde, returnifadeyi tamamen atlarsanız da kaybolur ( mainC için yasal ve eşdeğerdir return 0;).
Nate Eldredge

1
@macleginn: Pek emin değilim. Eğer bildirirseniz mainolarak int main(int argc, char **argv)görmek argcve argvyığını üzerine kopyalanan ancak gizemli sıfır değişken onlara ek olarak hala var.
Nate Eldredge

Yanıtlar:


3

Bu %1kayıt clang tarafından bir işlevdeki birden çok dönüş ifadesini işlemek üzere oluşturulmuştur . Bir tamsayının faktöriyelini hesaplamak için bir fonksiyonunuz olduğunu düşünün. Böyle yazmak yerine

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

Muhtemelen bunu yapardın

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

Neden? Çünkü Clang resultsizin için dönüş değerini tutan değişkeni ekleyecektir . Yaşasın. Bunun tam amacı bu %1. Kodunuzun biraz değiştirilmiş bir sürümü için ir'ye bakın.

Değiştirilmiş kod,

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

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

İR,

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
}

Şimdi %1kendini faydalı kılıyor görüyor musun ? Diğerlerinin işaret ettiği gibi, sadece bir dönüş ifadesine sahip işlevler için, bu değişken muhtemelen llvm'in optim pass'larından biri tarafından kaldırılır.


1

Bu neden önemli - asıl sorun nedir?

Bence aradığınız daha derin cevap şu olabilir: LLVM'nin mimarisi oldukça basit ön uçlara ve birçok geçişe dayanıyor. Ön uçların doğru kodu üretmesi gerekir, ancak iyi bir kod olması gerekmez. Çalışan en basit şeyi yapabilirler.

Bu durumda, Clang hiçbir şey için kullanılmadığı ortaya çıkan birkaç talimat üretir. Bu genellikle bir sorun değildir, çünkü LLVM'nin bir kısmı gereksiz talimatlardan kurtulacaktır. Clang bunun olmasına güvenir. Clang'ın ölü kod yaymaktan kaçınmasına gerek yoktur; uygulanması doğruluk, basitlik, test edilebilirlik vb.


1

Clang sözdizimi analizi ile yapıldığından, ancak LLVM optimizasyonla bile başlamadı.

Clang ön ucu, makine kodu değil IR (Ara Temsil) üretti. Bu değişkenler SSAlardır (Tek Statik Atamalar); henüz kayıtlara bağlı değiller ve aslında optimizasyondan sonra asla gereksiz oldukları için olmayacaklar.

Bu kod, kaynağın biraz gerçek bir temsilidir. Optimizasyon için clang LLVM'ye verir. Temel olarak, LLVM bununla başlar ve oradan optimizasyon yapar. Gerçekten de, sürüm 10 ve x86_64 için llc -O2 eninde sonunda üretecektir:

main: # @main
  xor eax, eax
  ret

Süreci bu düzeyde anlıyorum. Bu IR'nin neden başlamak için üretildiğini bilmek istedim.
macleginn

Bir derleyiciyi tek bir geçiş olarak düşünüyor olabilirsiniz. IR üreten Clang ön ucu ile başlayan bir geçiş boru hattı vardır. Bunun yerine clang -emit-llvm -S file.cpp ile talep edilen bu metinsel IR'yi bile oluşturmadı Clang aslında IR'nin ikili bir seri hale getirilebilir bit kodu sürümünü üretti. LLVM, her biri IR'yi alan ve optimize eden çoklu geçişler olarak yapılandırılmıştır. İlk LLVM geçişi IR'yi Clang'dan alır. IR alır, çünkü aynı optimize edici + kod üreteciyle başka bir dili desteklemek için Clang'ı Fortran FE ile değiştirebilirsiniz.
Olsonist
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.