为什么 LLVM 会分配一个冗余变量?
Why does LLVM allocate a redundant variable?
这是一个带有枚举定义和 main
函数的简单 C 文件:
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。直接返回0的话%1
对应什么?
为什么这很重要——实际问题是什么?
我认为您正在寻找的更深层答案可能是:LLVM 的体系结构基于相当简单的前端和许多通道。前端必须生成正确的代码,但不一定是好的代码。他们可以做最简单的事情。
在这种情况下,Clang 会生成一些指令,但这些指令最终并没有被用于任何用途。这通常不是问题,因为 LLVM 的某些部分会删除多余的指令。 Clang 相信这会发生。 Clang 不需要避免发出死代码;它的实现可能侧重于正确性、简单性、可测试性等。
因为 Clang 已经完成了语法分析,而 LLVM 甚至还没有开始优化。
Clang 前端生成了 IR(中间表示)而不是机器代码。这些变量是 SSA(单一静态分配);它们还没有绑定到寄存器,实际上在优化之后,永远不会,因为它们是多余的。
该代码是源代码的某种文字表示。这就是 clang 交给 LLVM 进行优化的原因。基本上,LLVM 从那里开始并从那里优化。实际上,对于版本 10 和 x86_64,llc -O2 最终会生成:
main: # @main
xor eax, eax
ret
这个 %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
变量,它为您保存 return 值。耶。这就是 %1
变量的原因。在 ir 中查看您的代码的略微修改版本。
修改后的代码,
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
}
现在您看到 %1
让自己变得有用了吧?大多数具有单个 return 语句的函数将通过 llvm 的传递之一剥离此变量。
这是一个带有枚举定义和 main
函数的简单 C 文件:
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。直接返回0的话%1
对应什么?
为什么这很重要——实际问题是什么?
我认为您正在寻找的更深层答案可能是:LLVM 的体系结构基于相当简单的前端和许多通道。前端必须生成正确的代码,但不一定是好的代码。他们可以做最简单的事情。
在这种情况下,Clang 会生成一些指令,但这些指令最终并没有被用于任何用途。这通常不是问题,因为 LLVM 的某些部分会删除多余的指令。 Clang 相信这会发生。 Clang 不需要避免发出死代码;它的实现可能侧重于正确性、简单性、可测试性等。
因为 Clang 已经完成了语法分析,而 LLVM 甚至还没有开始优化。
Clang 前端生成了 IR(中间表示)而不是机器代码。这些变量是 SSA(单一静态分配);它们还没有绑定到寄存器,实际上在优化之后,永远不会,因为它们是多余的。
该代码是源代码的某种文字表示。这就是 clang 交给 LLVM 进行优化的原因。基本上,LLVM 从那里开始并从那里优化。实际上,对于版本 10 和 x86_64,llc -O2 最终会生成:
main: # @main
xor eax, eax
ret
这个 %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
变量,它为您保存 return 值。耶。这就是 %1
变量的原因。在 ir 中查看您的代码的略微修改版本。
修改后的代码,
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
}
现在您看到 %1
让自己变得有用了吧?大多数具有单个 return 语句的函数将通过 llvm 的传递之一剥离此变量。