Почему LLVM выделяет избыточную переменную?
Вот простой файл 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
соответствуют, если ноль возвращается напрямую?
3 ответа
Эта %1
Регистр был сгенерирован clang для обработки нескольких операторов возврата в функции. Представьте, что у вас есть функция для вычисления факториала целого числа. Вместо того, чтобы писать так
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
делая себя полезным, да? Как указывали другие, для функций с одним оператором возврата эта переменная, вероятно, будет удалена одним из проходов llvm optim.
Поскольку Clang выполняется с помощью синтаксического анализа, а LLVM даже не приступил к оптимизации.
Внешний интерфейс Clang сгенерировал IR (промежуточное представление), а не машинный код. Эти переменные - SSA (одиночные статические присвоения); они еще не были привязаны к регистрам и фактически после оптимизации никогда не будут привязаны к регистрам, потому что они избыточны.
Этот код является в некоторой степени буквальным представлением источника. Это то, что лязгает руками LLVM для оптимизации. По сути, LLVM начинает с этого и оптимизирует оттуда. Действительно, для версии 10 и x86_64 llc -O2 в конечном итоге сгенерирует:
main: # @main
xor eax, eax
ret
Почему это важно - в чем собственно проблема?
Думаю, более глубокий ответ, который вы ищете, может быть следующим: архитектура LLVM основана на довольно простых интерфейсах и множестве проходов. Интерфейсы должны генерировать правильный код, но это не обязательно должен быть хороший код. Они могут делать самое простое, что работает.
В этом случае Clang генерирует пару инструкций, которые ни для чего не используются. Обычно это не проблема, потому что некоторая часть LLVM избавляется от лишних инструкций. Clang надеется, что это произойдет. Clang не нужно избегать выдачи мертвого кода; его реализация может быть ориентирована на правильность, простоту, тестируемость и т. д.