Dlaczego LLVM przydziela zmienną redundantną?

9

Oto prosty plik C z definicją enum i mainfunkcją:

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

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

Przenosi się na następujący LLVM IR:

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
}

%2jest ewidentnie dzmienną, która otrzymuje 2 przypisane do niej. Co %1odpowiada, jeśli zero jest zwracane bezpośrednio?

macleginn
źródło
1
Jakich flag użyłeś do wyprodukowania tego IR?
arrowd
@arrowd, zainstalowałem najnowszy stabilny pakiet LLVM i uruchomiłemclang-9 -S -emit-llvm simple.c
macleginn
1
Myślę, że ma to coś wspólnego z wcześniejszą inicjalizacją main( godbolt.org/z/kEtS-s ). Link pokazuje, w jaki sposób zespół jest mapowany na źródło
Pradeep Kumar
2
@PradeepKumar: Rzeczywiście, jeśli zmienisz nazwę funkcji na inną niż main, tajemnicza dodatkowa zmienna zniknie. Co ciekawe, znika również, jeśli returncałkowicie pominiesz instrukcję (co jest legalne mainw C i równoważne z return 0;).
Nate Eldredge
1
@macleginn: Nie jestem tego taki pewien. Jeśli deklarujesz, mainjak int main(int argc, char **argv)widzisz argci argvkopiujesz na stos, ale tajemnicza zmienna zerowa nadal tam jest.
Nate Eldredge

Odpowiedzi:

3

Ten %1rejestr został wygenerowany przez clang do obsługi wielu instrukcji return w funkcji . Wyobraź sobie, że masz funkcję obliczania silni całkowitej. Zamiast pisać w ten sposób

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

Prawdopodobnie byś to zrobił

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

Dlaczego? Ponieważ Clang wstawi resultzmienną, która przechowuje dla ciebie wartość zwracaną. Tak To jest właśnie tego cel %1. Spójrz na IR, aby zobaczyć nieco zmodyfikowaną wersję twojego kodu.

Zmodyfikowany kod,

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
}

Teraz widzisz, że %1robi się przydatny, co? Jak zauważyli inni, dla funkcji z tylko jedną instrukcją return ta zmienna prawdopodobnie zostanie usunięta przez jedno z przejść optymalnych lvvm.

droptop
źródło
1

Dlaczego to ma znaczenie - jaki jest faktyczny problem?

Myślę, że głębsza odpowiedź może być następująca: architektura LLVM opiera się na dość prostych interfejsach i wielu przejściach. Interfejsy muszą generować poprawny kod, ale nie musi to być dobry kod. Mogą zrobić najprostszą rzecz, która działa.

W tym przypadku Clang generuje kilka instrukcji, które okazują się nie być wykorzystywane do niczego. Zasadniczo nie stanowi to problemu, ponieważ część LLVM pozbywa się zbędnych instrukcji. Clang ufa, że ​​tak się stanie. Clang nie musi unikać emitowania martwego kodu; jego wdrożenie może koncentrować się na poprawności, prostocie, testowalności itp.

arnt
źródło
1

Ponieważ Clang jest wykonywany z analizą składni, ale LLVM nie zaczął nawet od optymalizacji.

Interfejs Clanga wygenerował IR (reprezentacja pośrednia), a nie kod maszynowy. Te zmienne są SSA (pojedyncze przypisania statyczne); nie były jeszcze związane z rejestrami, a właściwie po optymalizacji, nigdy nie będą, ponieważ są zbędne.

Ten kod jest dosłownie reprezentacją źródła. To właśnie przylega do LLVM w celu optymalizacji. Zasadniczo LLVM zaczyna od tego i optymalizuje od tego momentu. Rzeczywiście, dla wersji 10 i x86_64, llc -O2 ostatecznie wygeneruje:

main: # @main
  xor eax, eax
  ret
Olsonista
źródło
Rozumiem proces na tym poziomie. Chciałem wiedzieć, dlaczego ten IR został wygenerowany na początek.
macleginn
Być może myślisz o kompilatorze jako pojedynczym przejściu. Istnieje szereg przejść zaczynających się od frontonu Clanga, który generuje IR. Nie wygenerował nawet tego IR tekstowego, który zamiast tego ktoś zażądał z plikiem clang -emit-llvm -S.cpp Clang faktycznie wygenerował wersję IR binarnego, możliwego do serializacji kodu bitowego. LLVM ma strukturę wielu przejść, z których każda pobiera i optymalizuje podczerwień. Pierwsze przejście LLVM zabiera IR z Clang. Wymaga IR, ponieważ możesz zamienić Clanga na Fortran FE w celu obsługi innego języka z tym samym optymalizatorem + generatorem kodu.
Olsonistka