【问题标题】:clang/LLVM project level optimizationclang/LLVM 项目级优化
【发布时间】:2020-07-19 03:36:37
【问题描述】:

所以我会定期尝试 LLVM,因为我有这个理论,它应该优于 GNU。然后很遗憾没有。

部分理论与其将模块/对象链接在一起并进行优化的能力有关,通常优化是基于每个文件/对象进行的。

我看到了如何为特定的默认目标构建而不是使用通用目标

rm -rf llvm-project
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
git checkout llvmorg-10.0.0
mkdir build
cd build
cmake -DLLVM_ENABLE_PROJECTS='clang;lld' -DCMAKE_CROSSCOMPILING=True -DCMAKE_INSTALL_PREFIX=/opt/llvm/llvm10armv6m -DLLVM_DEFAULT_TARGET_TRIPLE=armv6m-none-eabi -DLLVM_TARGET_ARCH=ARM -DLLVM_TARGETS_TO_BUILD=ARM -G "Unix Makefiles" ../llvm
make -j 8
make -j 4
make
sudo make install

还有测试文件

test.c

unsigned int one ( void )
{
    return(1);
}
unsigned int two ( void );
unsigned int testone ( void )
{
    return(one());
}
unsigned int testtwo ( void )
{
    return(two());
}

两个.c

unsigned int two ( void )
{
    return(2);
}

基本运行

clang -O2 -fomit-frame-pointer -c test.c -o test.o
llvm-objdump -D test.o

00000000 one:
       0: 01 20                         movs    r0, #1
       2: 70 47                         bx  lr

00000004 testone:
       4: 01 20                         movs    r0, #1
       6: 70 47                         bx  lr

00000008 testtwo:
       8: 80 b5                         push    {r7, lr}
       a: ff f7 fe ff                   bl  #-4
       e: 80 bd                         pop {r7, pc}

正如所料,one() 已内联到 testone() 中。

希望也可以内联 testwo()。

clang -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
llc both.bc -o both.s
cat both.s
opt -O2 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s

给予

testone:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  one
    pop {r7, pc}


testtwo:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  two
    pop {r7, pc}

testone:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  one
    pop {r7, pc}

testtwo:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  two
    pop {r7, pc}

那就更糟了。

opt -std-link-opts both.bc -o both.opt.bc

相同,没有更好

现在可以了

clang -O2 -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -O2 -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
opt -O2 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s

testone:
    .fnstart
@ %bb.0:                                @ %entry
    movs    r0, #1
    bx  lr

testtwo:
    .fnstart
@ %bb.0:                                @ %entry
    movs    r0, #2
    bx  lr

人们会认为不优化部分会给整体优化提供更多的肉来咀嚼。是的?尽管这表明并非如此。

clang -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
opt -O3 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s

testone:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  one
    movs    r0, #1
    pop {r7, pc}

testtwo:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  two
    movs    r0, #2
    pop {r7, pc}

-O3 也无济于事,而且这个输出非常糟糕,它调用了函数并内联了它。那里发生了什么?!

llvm-dis both.opt.bc
cat both.opt.ll

; ModuleID = 'both.opt.bc'
source_filename = "llvm-link"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv6m-none-unknown-eabi"

; Function Attrs: noinline nounwind optnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: noinline nounwind optnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
  %call = call i32 @one()
  ret i32 1
}

; Function Attrs: noinline nounwind optnone
define dso_local i32 @testtwo() local_unnamed_addr #0 {
entry:
  %call = call i32 @two()
  ret i32 2
}

; Function Attrs: noinline nounwind optnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
  ret i32 2
}

如何撤销它?

clang -O2 -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -O2 -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
llvm-dis both.bc
cat both.ll
opt -O3 both.bc -o both.opt.bc
llvm-dis both.opt.bc
cat both.opt.ll

给予

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: nounwind
define dso_local i32 @testtwo() local_unnamed_addr #1 {
entry:
  %call = tail call i32 @two() #2
  ret i32 %call
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
  ret i32 2
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testtwo() local_unnamed_addr #0 {
entry:
  ret i32 2
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
  ret i32 2
}

那么,您必须在文件/对象级别的任何地方应用优化以使项目级别得到优化,这是否正确?

然后是尾调用或叶子等优化的问题,如果没有别的testtwo:即使在第一种情况下

clang -O2 -fomit-frame-pointer -c test.c -o test.o

可以简单地分支到 two() 并且不设置堆栈帧而不做任何事情。或者这是拇指的事情? b 无法到达?

one:
       0:   b8 01 00 00 00  movl    $1, %eax
       5:   c3  retq

testone:
      10:   b8 01 00 00 00  movl    $1, %eax
      15:   c3  retq

testtwo:
      20:   e9 00 00 00 00  jmp 0 <testtwo+5>

在 gnu 中,链接器会修补蹦床的任何分支到达或模式问题

arm-none-eabi-gcc -c -O2 -mcpu=cortex-m0 test.c -o test.o
arm-none-eabi-objdump -D test.o

00000000 <one>:
   0:   2001        movs    r0, #1
   2:   4770        bx  lr

00000004 <testone>:
   4:   2001        movs    r0, #1
   6:   4770        bx  lr

00000008 <testtwo>:
   8:   b510        push    {r4, lr}
   a:   f7ff fffe   bl  0 <two>
   e:   bd10        pop {r4, pc}

好吧,我已经纠正了......

clang --version
clang version 10.0.0 (https://github.com/llvm/llvm-project.git d32170dbd5b0d54436537b6b75beaf44324e0c28)
Target: armv6m-none-unknown-eabi
Thread model: posix
InstalledDir: /opt/llvm/llvm10armv6m/bin

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

我想问题是,如果一个人想使用 llvm-link 和 opt 进行项目级优化,是对所需的每个单独项目进行优化,还是我缺少命令行选项。对进入源代码本身的编译器特定属性不感兴趣,希望代码既不感染 gcc 也不感染 llvm 细节。

在 gcc 5.xx 之后,代码变得更加臃肿,希望 llvm 有机会,但每当我尝试这个(在项目上不仅仅是 10 行代码)gcc 最终会执行更少的指令,和/或更少的内存访问等等。对于像上面这样的简单演示功能,除了一些例外,它们会产生相同/等效的输出。

为了从 clang/llvm 中获得更多信息,我是否缺少其他工具或命令行选项?

是不是这个例子太微不足道了,工具无法发挥作用?

根据答案编辑

clang -c start.s -o start.o
clang -O2 -flto=thin -fomit-frame-pointer -c test.c
clang -O2 -flto=thin -fomit-frame-pointer -c two.c
ld.lld start.o test.o two.o -o test.elf
llvm-objdump -D test.elf

000110fc testtwo:
   110fc: 02 20                         movs    r0, #2
   110fe: 70 47                         bx  lr

00011100 two:
   11100: 02 20                         movs    r0, #2
   11102: 70 47                         bx  lr

所以摆脱 -emit-llvm 并使用 lto 基本上可以得到所需的结果。

看bc反汇编

clang -O2 -flto=thin -fomit-frame-pointer -c test.c
llvm-dis test.o
cat test.o.ll

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: nounwind
define dso_local i32 @testtwo() local_unnamed_addr #1 {
entry:
  %call = tail call i32 @two() #3
  ret i32 %call
}

启用/添加尾调用。我真的不喜欢使用编译器/shell 作为链接器(对于具有自己的引导程序和链接器脚本的嵌入式项目),llvm-ldd 的用法不容易弄清楚或基本上无法弄清楚,但 ld.lld 也支持tlo 的东西,所以解决了。

【问题讨论】:

  • 这似乎并不是 LLVM 开发人员经常谈论或关注的事情。你这里的东西很小,从某种意义上说优化是没有意义的——无论有没有这种变化,程序都会小而快。您假设 LLVM 在有帮助的情况下不会内联,因为您看到它在这种情况下不会内联,也就是说……您可能是对的,但我不会打赌。尝试一些非常大的东西,选择一个可能有帮助或有害的优化,并评估 LLVM 选择哪些站点接受优化。
  • 如果它不能消除这里的调用,那么我想知道它会如何处理更复杂的事情。但它确实消除了对某些命令行选项组合的调用
  • 有一个类可以内联,并且源代码包含一个注释说“内联的调用是有利可图的决定在别处实现。”该类在许多地方使用。我没有费心去看盈利能力评估逻辑。您的调用似乎如此无利可图——一个最低成本的调用,只执行一次,内联不可能实现其他优化。

标签: optimization llvm compiler-optimization llvm-clang


【解决方案1】:

实际上答案很简单:永远不要使用 llc / opt / llvm-link 来执行“最终用户”项目级别的优化。这些是具有不同默认值、阈值等的开发人员端工具。基本上,它们只是各种 LLVM 工具箱的简单命令行前端。

为了执行正确的链接时间优化,您需要使用专门用于此类任务的管道。基本上,使用“clang -flto”编译所有内容,然后通过“clang -flto”再次链接所有内容都可以。使用像 lld 这样的 LTO 感知链接器也是先决条件。

还可以在此处找到有关 ThinLTO 的更多信息:https://clang.llvm.org/docs/ThinLTO.htmlhttp://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html

【讨论】:

  • 感谢您根据我对 LTO 的发现编辑答案,基本上 clang -flto 加上 lto 感知链接器有效。
猜你喜欢
  • 1970-01-01
  • 2021-06-17
  • 2013-03-10
  • 2017-03-05
  • 2014-02-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多