clang-4.0在初始化全局变量时生成冗余方法

时间:2017-09-03 17:27:45

标签: c++ clang llvm

这些天我通过观察clang如何处理复杂情况来学习LLVM。我写了(顶级,不是函数):

int qaq = 666;
int tat = 233;

auto hh = qaq + tat;

我使用命令:

clang-4.0 003.cpp -emit-llvm -S -std=c++11

clang生成这样的代码:

@qaq = global i32 666, align 4
@tat = global i32 233, align 4
@hh = global i32 0, align 4
@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 65535, void ()* @_GLOBAL__sub_I_003.cpp, i8* null }]

; Function Attrs: noinline uwtable
define internal void @__cxx_global_var_init() #0 section ".text.startup" {
  %1 = load i32, i32* @qaq, align 4
  %2 = load i32, i32* @tat, align 4
  %3 = add nsw i32 %1, %2
  store i32 %3, i32* @hh, align 4
  ret void
}

; Function Attrs: noinline uwtable
define internal void @_GLOBAL__sub_I_003.cpp() #0 section ".text.startup" {
  call void @__cxx_global_var_init()
  ret void
}

我对_GLOBAL__sub_I_003.cpp感到困惑:为什么clang会生成一个实际上只调用另一个函数的函数(而不是做其他任何事情)?甚至他们两个都没有参数?

1 个答案:

答案 0 :(得分:3)

免责声明:这是我对逻辑的解释,我不是LLVM团队的成员。

为了理解这背后的原因,您必须了解软件工程中的一个基本概念:复杂性会产生错误,并使测试更加困难。

但首先,让我们让你的榜样更有趣:

int qaq = 666;
int tat = 233;

auto hh = qaq + tat;
auto ii = qaq - tat;

导致:

; Function Attrs: noinline uwtable
define internal void @__cxx_global_var_init() #0 section ".text.startup" !dbg !16 {
  %1 = load i32, i32* @qaq, align 4, !dbg !19
  %2 = load i32, i32* @tat, align 4, !dbg !20
  %3 = add nsw i32 %1, %2, !dbg !21
  store i32 %3, i32* @hh, align 4, !dbg !21
  ret void, !dbg !20
}

; Function Attrs: noinline uwtable
define internal void @__cxx_global_var_init.1() #0 section ".text.startup" !dbg !22 {
  %1 = load i32, i32* @qaq, align 4, !dbg !23
  %2 = load i32, i32* @tat, align 4, !dbg !24
  %3 = sub nsw i32 %1, %2, !dbg !25
  store i32 %3, i32* @ii, align 4, !dbg !25
  ret void, !dbg !24
}

; Function Attrs: noinline uwtable
define internal void @_GLOBAL__sub_I_example.cpp() #0 section ".text.startup" !dbg !26 {
  call void @__cxx_global_var_init(), !dbg !28
  call void @__cxx_global_var_init.1(), !dbg !29
  ret void
}

因此我们看到CLANG为每个非平凡的初始化发出一个函数,并在_GLOBAL__sub_I_example.cpp()中一个接一个地调用它们。这是有道理的,也是明智的,因为事情以这种方式整齐地组织起来,否则可能会变成更大/更复杂的文件中的乱码。

请注意,这与您的示例中应用的逻辑完全相同。

否则将意味着类型的算法:"如果存在单个非平凡的全局初始化,则将代码直接放入翻译单元的全局构造函数"。

请注意以下事项:

  • 当前逻辑已正确处理该案例。
  • 在优化代码中,最终结果将完全相同。

那么这个逻辑会给我们带来什么呢?

  • 更多分支进行测试。
  • 意外插入错误的机会更多。
  • 长期维护更多代码。
  • 在非优化版本中的某些翻译单元的全局初始化中删除单个函数调用。

保持现状,这是正确的决定。