SDw*_*rfs 10 optimization avr llvm clang llvm-clang
我目前正在尝试avr-llvm(支持AVR作为目标的llvm).我的主要目标是使用它希望更好的优化器(与gcc相比)来实现更小的二进制文件.如果你对AVR了解一点,你知道你只有很少的记忆.
我目前使用的是一个ATTiny45,4KB闪存和256字节(只是字节而不是KB!)的SRAM.
我正在尝试编译一个简单的C程序(见下文),以检查生成的汇编代码以及机器代码大小的开发方式.我使用"clang -Oz -S test.c"来生成组件输出并优化它以实现最小尺寸.我的问题是不必要的保存寄存器值,知道这种方法永远不会返回.
我怎么能告诉llvm它可以破坏任何寄存器,如果需要而不保存/恢复它的内容?任何想法如何更优化它(例如更高效的堆栈设置)?
这是我的测试程序.如上所述,它是使用"clang -Oz -S test.c"编译的.
#include <stdint.h>
void __attribute__ ((noreturn)) main() {
volatile uint8_t res = 1;
while (1) {}
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,它只有一个类型为uint8_t的"volatile"变量(如果我没有将它设置为volatile,那么所有内容都会被优化掉).此变量设置为1.最后有一个无限循环.现在让我们看一下程序集输出:
.file "test.c"
.text
.globl main
.align 2
.type main,@function
main:
push r28
push r29
in r28, 61
in r29, 62
sbiw r29:r28, 1
in r0, 63
cli
out 62, r29
out 63, r0
out 61, r28
ldi r24, 1
std Y+1, r24
.BB0_1:
rjmp .BB0_1
.tmp0:
.size main, .tmp0-main
Run Code Online (Sandbox Code Playgroud)
是啊!这是一个简单程序的很多机器代码.我刚刚测试了一些变化,并查看了AVR的参考手册......所以我可以解释会发生什么.我们来看看每个部分.
这是"牛肉",它正在做我们的c程序.它加载r24的值为"1",它存储在Y + 1的存储器中(堆栈指针+ 1).我们当然有无限循环:
ldi r24, 1
std Y+1, r24
.BB0_1:
rjmp .BB0_1
Run Code Online (Sandbox Code Playgroud)
注意:需要无限循环.否则将__attribute__ ((noreturn))被忽略,并且稍后将恢复堆栈指针+已保存的寄存器.
就在此之前,设置了"Y"中的指针:
in r28, 61
in r29, 62
sbiw r29:r28, 1
in r0, 63
cli
out 62, r29
out 63, r0
out 61, r28
Run Code Online (Sandbox Code Playgroud)
这里发生的是:
堆栈寄存器的这种设置似乎效率低下.要增加堆栈指针,我只需要"将r0"推入堆栈.然后我可以将SPH/SPL的值加载到r29:r28中.但是,这可能需要对源代码中的llvm优化器进行一些更改.如果必须为局部变量保留超过3个字节的堆栈,则上述代码才有意义(即使优化-O3,对于-Oz,最多6个字节也是有意义的).怎么样......我想我们需要触摸llvm的来源; 所以这超出了范围.
更有趣的是这部分:
push r28
push r29
Run Code Online (Sandbox Code Playgroud)
由于main()不打算返回,这没有意义.这只会浪费RAM和闪存以获取愚蠢的指令(请记住:在某些设备中我们只有64,128或256字节的SRAM可用).
我进一步研究了这个:如果我们让main返回(例如没有无限循环)堆栈指针被恢复,我们在末尾有一个"ret"指令,寄存器r28和r29通过"pop r29,pop"从栈中恢复28" .但编译器应该知道,如果函数"main"的范围永远不会被遗留,那么所有寄存器都可以被破坏,而不会将它们存储到堆栈中.
当我们谈论2字节RAM时,这个问题似乎有点"愚蠢".但是想想如果程序开始使用其余寄存器会发生什么.
所有这些都改变了我对当前"编译器"的看法.我想今天通过汇编程序进行优化的余地不大.但似乎有......
所以,问题仍然是......
您是否知道如何改善这种情况(提交错误报告/功能请求除外)?
我的意思是:是否只有一些我可能忽略的编译器开关......?
使用__attribute__ ((OS_main))avr-gcc的作品.
输出如下:
.file "test.c"
__SREG__ = 0x3f
__SP_H__ = 0x3e
__SP_L__ = 0x3d
__CCP__ = 0x34
__tmp_reg__ = 0
__zero_reg__ = 1
.global __do_copy_data
.global __do_clear_bss
.text
.global main
.type main, @function
main:
push __tmp_reg__
in r28,__SP_L__
in r29,__SP_H__
/* prologue: function */
/* frame size = 1 */
ldi r24,lo8(1)
std Y+1,r24
.L2:
rjmp .L2
.size main, .-main
Run Code Online (Sandbox Code Playgroud)
这是(我认为)最佳尺寸(6个指令或12个字节)以及此示例程序的速度.llvm有任何等效属性吗?(clang版本'3.2(主干160228)(基于LLVM 3.2svn)'既不知道OS_task也不了解OS_main的任何信息).
Anton 在他的评论中提到了所提问题的答案:问题不在于 LLVM,而在于你的 AVR 目标。例如,以下是通过 Clang 和 LLVM 针对其他目标运行的等效程序:
% cat test.c
__attribute__((noreturn)) int main() {
volatile unsigned char res = 1;
while (1) {}
}
% ./bin/clang -c -o - -S -Oz test.c # I'm on an x86-64 machine
<snip>
main: # @main
.cfi_startproc
# BB#0: # %entry
movb $1, -1(%rsp)
.LBB0_1: # %while.body
# =>This Inner Loop Header: Depth=1
jmp .LBB0_1
.Ltmp0:
.size main, .Ltmp0-main
.cfi_endproc
% ./bin/clang -c -o - --target=armv6-unknown-linux-gnueabi -S -Oz test.c
<snip>
main:
sub sp, sp, #4
mov r0, #1
strb r0, [sp, #3]
.LBB0_1:
b .LBB0_1
.Ltmp0:
.size main, .Ltmp0-main
% ./bin/clang -c -o - --target=powerpc64-unknown-linux-gnu -S -Oz test.c
<snip>
main:
.align 3
.quad .L.main
.quad .TOC.@tocbase
.quad 0
.text
.L.main:
li 3, 1
stb 3, -9(1)
.LBB0_1:
b .LBB0_1
.long 0
.quad 0
.Ltmp0:
.size main, .Ltmp0-.L.main
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,对于所有这三个目标,生成的唯一代码是保留堆栈空间(如有必要,它不在 x86-64 上)并在堆栈上设置值。我认为这是最小的。
也就是说,如果您确实发现 LLVM 优化器存在问题,获得帮助的最佳方法是向开发邮件列表发送电子邮件,或者如果您有特定的输入 IR 序列应该产生更小的输出 IR,则提交错误。
最后,回答您的问题评论中提出的问题:实际上,LLVM 的优化器在某些领域比 GCC 更强大。然而,也有一些领域它的威力明显减弱。=] 对您关心的代码进行基准测试。
| 归档时间: |
|
| 查看次数: |
2673 次 |
| 最近记录: |