LLVM为什么要分配冗余变量?


9

这是一个带有枚举定义和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个值。%1如果直接返回零,那对应什么?


1
您使用了哪些标志来产生此IR?
arrowd

@arrowd,我安装了最新的稳定的LLVM套件并运行clang-9 -S -emit-llvm simple.c
macleginn

1
我认为这与之前的初始化maingodbolt.org/z/kEtS-s)有关。该链接显示了程序集如何映射到源
Pradeep Kumar

2
@PradeepKumar:的确,如果将函数的名称更改为以外的名称main,神秘的多余变量将消失。有趣的是,如果您return完全省略该语句(main在C中合法且等效于return 0;),它也会消失。
Nate Eldredge

1
@macleginn:我不太确定。如果声明mainint main(int argc, char **argv)你看到argcargv复制到堆栈,但神秘的零变量仍然存在,除了给他们。
Nate Eldredge

Answers:


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。在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的体系结构基于相当简单的前端和许多遍。前端必须生成正确的代码,但不一定是好的代码。他们可以做最简单的事情。

在这种情况下,Clang生成了两条指令,结果证明这些指令不用于任何事情。通常这不是问题,因为LLVM的某些部分将摆脱多余的指令。Clang相信这种情况会发生。Clang不需要避免发出无效代码;它的实现可能侧重于正确性,简单性,可测试性等。


1

因为Clang是通过语法分析完成的,但是LLVM甚至还没有开始进行优化。

Clang前端已生成IR(中间表示),而不是机器代码。这些变量是SSA(单一静态分配);它们尚未绑定到寄存器,实际上经过优化后,永远不会因为它们是冗余的。

该代码是源代码的某种文字表示。这就是clang交给LLVM进行优化的原因。基本上,LLVM从此开始并从那里进行优化。实际上,对于版本10和x86_64,llc -O2最终将生成:

main: # @main
  xor eax, eax
  ret

我了解此级别的流程。我想知道为什么生成此IR。
macleginn

您可能将编译器视为一次通过。从Clang前端开始有一系列的通道生成IR。它甚至没有生成此文本IR,而是有人向clang -emit-llvm -S file请求。cppClang实际上生成了IR的二进制可序列化位码版本。LLVM被构造为多次通过,每次通过并优化IR。第一次LLVM通过从Clang获得IR。这需要IR,因为您可以用Fortran FE替换Clang,以便使用相同的优化器+代码生成器支持另一种语言。
Olsonist
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.