clang-4.0 初始化全局变量时产生冗余方法
clang-4.0 generates redundant methods when initializing global variables
我最近通过观察 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 生成的函数实际上只调用另一个函数(而不做任何其他事情)?甚至两个都没有参数?
免责声明:这是我对逻辑的解释,我不是 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()
中一个接一个地调用它们。这是有道理的,也是明智的,因为事情是以这种方式整齐地组织的,否则可能会在 larger/more 复杂的文件中变成乱码。
请注意,这与您的示例中应用的逻辑完全相同。
否则将暗示该类型的算法:"if there is a single non-trivial global initialization, then put the code directly in the translation unit's global constructor"。
注意以下几点:
- 当前逻辑已经正确处理了这种情况。
- 在优化代码中,最终结果将完全相同。
那么这个逻辑对我们有什么帮助呢?
- 更多分支需要测试。
- 更多机会意外插入错误。
- 更多代码需要维护在 long 运行.
- 在非优化构建中某些翻译单元的全局初始化中删除了单个函数调用。
保持现状是正确的决定。
我最近通过观察 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 生成的函数实际上只调用另一个函数(而不做任何其他事情)?甚至两个都没有参数?
免责声明:这是我对逻辑的解释,我不是 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()
中一个接一个地调用它们。这是有道理的,也是明智的,因为事情是以这种方式整齐地组织的,否则可能会在 larger/more 复杂的文件中变成乱码。
请注意,这与您的示例中应用的逻辑完全相同。
否则将暗示该类型的算法:"if there is a single non-trivial global initialization, then put the code directly in the translation unit's global constructor"。
注意以下几点:
- 当前逻辑已经正确处理了这种情况。
- 在优化代码中,最终结果将完全相同。
那么这个逻辑对我们有什么帮助呢?
- 更多分支需要测试。
- 更多机会意外插入错误。
- 更多代码需要维护在 long 运行.
- 在非优化构建中某些翻译单元的全局初始化中删除了单个函数调用。
保持现状是正确的决定。