Почему LLVM выделяет избыточную переменную?

9

Вот простой C-файл с определением перечисления и mainфункцией:

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

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

Он переносится в следующий 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
}

%2это, очевидно, dпеременная, которой присвоено 2. Что %1соответствует, если ноль возвращается напрямую?

macleginn
источник
1
Какие флаги вы использовали для производства этого IR?
arrowd
@arrowd, я установил последний стабильный пакет LLVM и запустилclang-9 -S -emit-llvm simple.c
macleginn
1
Я думаю, что это как-то связано с инициализацией раньше main( godbolt.org/z/kEtS-s ). Ссылка показывает, как сборка отображается на источник
Pradeep Kumar
2
@PradeepKumar: Действительно, если вы измените имя функции на нечто иное main, таинственная дополнительная переменная исчезнет. Интересно, что он также исчезает, если вы полностью опускаете returnутверждение (что допустимо для mainC и эквивалентно return 0;).
Нейт Элдридж
1
@macleginn: я не уверен. Если вы объявляете, mainкак int main(int argc, char **argv)видите, argcи argvкопируете в стек, но загадочная нулевая переменная все еще существует в дополнение к ним.
Нейт Элдридж

Ответы:

3

Этот %1регистр был создан clang для обработки нескольких операторов return в функции . Представьте, что у вас есть функция для вычисления факториала целого числа. Вместо того, чтобы писать это так

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. Посмотрите на ИК для немного измененной версии вашего кода.

Модифицированный код,

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.

droptop
источник
1

Почему это важно - какова реальная проблема?

Я думаю, что более глубокий ответ, который вы ищете, может быть таким: архитектура LLVM основана на довольно простых интерфейсах и множестве проходов. Интерфейсы должны генерировать правильный код, но это не обязательно должен быть хороший код. Они могут сделать самое простое, что работает.

В этом случае Clang генерирует пару инструкций, которые, как оказалось, ни для чего не используются. Как правило, это не проблема, потому что некоторая часть LLVM избавится от лишних инструкций. Clang верит, что это произойдет. Clang не нужно избегать испускания мертвого кода; его реализация может быть направлена ​​на корректность, простоту, тестируемость и т. д.

Арнт
источник
1

Потому что Clang сделан с синтаксическим анализом, но LLVM даже не начал с оптимизации.

Внешний интерфейс Clang сгенерировал IR (промежуточное представление), а не машинный код. Эти переменные - SSA (одиночные статические назначения); они еще не были связаны с регистрами и фактически после оптимизации никогда не будут, потому что они избыточны.

Этот код является несколько буквальным представлением источника. Это то, что раздается LLVM для оптимизации. По сути, LLVM начинается с этого и оптимизируется оттуда. Действительно, для версии 10 и x86_64 llc -O2 в итоге сгенерирует:

main: # @main
  xor eax, eax
  ret
Olsonist
источник
Я понимаю процесс на этом уровне. Я хотел знать, почему этот IR был создан с самого начала.
Маклингин
Вы можете думать о компиляторе как об одном проходе. Существует конвейер проходов, начинающийся с внешнего интерфейса Clang, который генерирует IR. Он даже не генерировал этот текстовый IR, который вместо этого кто-то запрашивал с помощью clang -emit-llvm -S file.cpp Clang фактически генерировал двоичную сериализуемую версию IR с битовым кодом. LLVM структурирован как несколько проходов, каждый из которых принимает и оптимизирует ИК. Первый проход LLVM получает IR от Clang. Это требует IR, потому что вы можете заменить Clang на Fortran FE для поддержки другого языка с тем же оптимизатором + генератором кода.
Олсонист