Чому LLVM виділяє надлишкову змінну?


9

Ось простий файл C із визначенням enum та mainфункцією:

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

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

Він транслюється в наступний ІК LLVM:

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очевидно, це dзмінна, яка отримує 2 присвоєні їй. Що %1відповідає, якщо нуль повертається безпосередньо?


1
Які прапори ви використовували для виготовлення цього ІЧ?
стрілок

@arrowd, я встановив останній стабільний пакет LLVM і побігclang-9 -S -emit-llvm simple.c
macleginn

1
Я думаю, що це має щось спільне з ініціалізацією раніше main( godbolt.org/z/kEtS-s ). Посилання показує, як збірка відображена до джерела
Pradeep Kumar

2
@PradeepKumar: Дійсно, якщо змінити назву функції на щось інше, ніж mainзагадкова додаткова змінна зникає. Цікаво, що він також зникає, якщо ви returnповністю опустите заяву (що є законним mainв C та еквівалентно return 0;).
Нейт Елдредж

1
@macleginn: Я не так впевнений. Якщо ви декларуєте, mainяк int main(int argc, char **argv)бачите, argcі argvскопіювали на стек, але загадкова нульова змінна все ще є на додаток до них.
Нейт Елдредж

Відповіді:


3

Цей %1регістр був створений клангом для обробки декількох операторів повернення у функції . Уявіть, у вас була функція обчислити ціле число. Замість того, щоб писати так

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

Ви, мабуть, зробите це

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

Чому? Тому що Clang вставить ту resultзмінну, яка містить значення для вас. Так. Точно це і є %1. Подивіться на ir для трохи зміненої версії вашого коду.

Змінений код,

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

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

ІЧ,

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
}

Тепер ви бачите, що %1робить себе корисним так? Як зазначали інші, для функцій, що мають лише один оператор return, ця змінна, ймовірно, буде позбавлена ​​одним з оптимальних проходів llvm.


1

Чому це має значення - яка реальна проблема?

Я думаю, що більш глибокою відповіддю, яку ви шукаєте, може бути: архітектура LLVM базується на досить простих фронтах і безлічі проходів. Фронти повинні генерувати правильний код, але це не повинен бути хорошим кодом. Вони можуть зробити найпростіше, що працює.

У цьому випадку Кланг генерує пару інструкцій, які, як виявляється, не використовуються ні для чого. Це взагалі не проблема, оскільки частина LLVM позбудеться зайвих інструкцій. Кланг довіряє, що це станеться. Кланг не потрібно уникати видалення мертвого коду; її реалізація може бути зосереджена на правильності, простоті, передбачуваності тощо.


1

Оскільки Кланг робиться з синтаксичним аналізом, але LLVM навіть не почався з оптимізації.

Передній кінець Clang створив ІЧ (проміжне представництво), а не машинний код. Ці змінні - SSA (Single Static Assignings); вони ще не прив’язані до реєстрів, а насправді після оптимізації вони ніколи не будуть, оскільки вони є зайвими.

Цей код є дещо буквальним поданням джерела. Це те, що Кланг передає LLVM для оптимізації. В основному, LLVM починається з цього і оптимізується звідти. Дійсно, для версій 10 та x86_64, llc -O2 з часом генерує:

main: # @main
  xor eax, eax
  ret

Я розумію процес на цьому рівні. Мені хотілося знати, для чого цей ІЧ створений для початку.
macleginn

Ви можете думати про компілятор як про один пропуск. Існує трубопровід проходів, починаючи з переднього кінця Clang, який генерує ІЧ. Він навіть не генерував цей текстовий ІР, який натомість хтось запитував за допомогою clang -emit-llvm -S file.cpp Clang фактично генерував двійкову серіалізаційну версію бітового коду ІК. LLVM структурований у вигляді декількох проходів, кожен з яких приймає та оптимізує ІЧ. Перший пропуск LLVM приймає ІК від Clang. Знадобиться ІР, оскільки ви можете замінити Clang на Fortran FE, щоб підтримати іншу мову з тим же оптимізатором + генератором коду.
Олсоніст
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.