ISR需要很长时间,所以我查看了asm,看看它在做什么。
我用gcc -O3 -mmcu=attiny13a
和一些其他选项编译了这个C。
#include <avr/interrupt.h>
ISR(TIM0_COMPA_vect)
{
}
avr-objdump.exe -d test.elf
输出:
00000048 <__vector_6>:
48: 1f 92 push r1
4a: 0f 92 push r0
4c: 0f b6 in r0, 0x3f ; 63
4e: 0f 92 push r0
50: 11 24 eor r1, r1
52: 0f 90 pop r0
54: 0f be out 0x3f, r0 ; 63
56: 0f 90 pop r0
58: 1f 90 pop r1
5a: 18 95 reti
虽然C代码是空的,但汇编程序代码是对的吗?
这些链接解释了一些关于ISR()
的内容,但没有详细说明需要asm的哪些部分,也没有详细说明是否可以让GCC优化掉简单ISR中不需要它们的一些指令。
- https://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html
ISR()
宏 - https://gcc.gnu.org/onlinedocs/gcc/AVR-Function-Attributes.html关于CCD_ 5的一些细节
GCC的asm输出(https://godbolt.org/z/zzbY5KE3c)使用类似CCD_ 6的伪指令。
更新的GCC(Godbolt上的9.2)支持-mno-gas-isr-prologues
,以使GCC显示与上面Atmel Studio中的反汇编相匹配的真实指令。所以,如果有人想玩这个https://godbolt.org/z/q6M518qfP在真正的Atmel工作室中可能会有同样的效果。
尽管C代码为空,但汇编程序代码是否正确?
是。这是avr gcc v7及以下版本的代码。较新版本的编译器可能会生成更高效的代码,请参阅GCC v8发行说明。原因是:
avr-gcc ABI:R0和R1建模、使用和优点
在设计avr-gcc-ABI时,决定将R0和R1建模为固定寄存器";固定寄存器";意味着编译器不会在寄存器分配或以任何方式使用它们。这些寄存器的唯一用途是在编译的最后阶段,当汇编代码打印到*.s
时,这些寄存器可以在各自的输出字符串中隐式使用。这基本上与通过内联汇编输出的指令相同,后者对编译器来说是不透明的。
这一选择背后的原因是,通过手头有这些额外的寄存器,可以提高整体代码质量,其中R0被用作临时寄存器。9和R1 aka。__zero_reg__
包含零值。例如,要将寄存器%0中的16位整数与42进行比较,您可以只使用
cpi %A0, 42
cpc %B0, __zero_reg__
没有任何进一步的麻烦,即不需要分配一些临时寄存器,清除它等
R0和R1是固定寄存器的缺点
这种方法的缺点是这些寄存器没有使用寿命信息,例如在之类的乘法代码中
char mul (char x)
{
return x * x * x * x;
}
您必须根据ABI将R1重置为0,因为MUL
会破坏其内容:
mul:
mul r24,r24
mov r24,r0
clr r1 ; Superfluous
mul r24,r24 ; Overrides r1
mov r24,r0
clr r1 ; Restore __zero_reg__ to 0
ret
第一个clr r1
是多余的,因为后面的mul
将覆盖它
ABI的设计也导致了这些昂贵的ISR pro和Epilogue,因为没有关于R0、R1是否被使用或更改的分析,SREG也是如此。因此,的经典ISR序言
- 保存R0、R1和SREG
- 将R1设置为0(因为它可能会像上面的mul序列一样暂时保持非0值,但ISR代码要求R1=0)
无论ISR的主体是什么,结语都必须还原它们。
avr gcc v8+解决方案:isr中的伪指令__gcc_isr
由于问题的复杂性,从提交PR20296到解决问题花了12年时间。通过伪指令__gcc_isr
将大部分分析从编译器转移到汇编程序。要了解它是如何工作的,请考虑以下C代码:
volatile char c;
__attribute__((__signal__))
void __vector_X (void)
{
++c;
}
以及来自avr gcc v8+-Os -save-temps
:的汇编代码
__vector_X:
__gcc_isr 1
lds r24,c
subi r24,lo8(-1)
sts c,r24
__gcc_isr 2
reti
__gcc_isr 0,r24
编译器的作用:
如果Binutils不支持
__gcc_isr
(在配置气体是否接受-mgcc-isr
时确定),如果优化关闭,如果ISR被归因于no_gccisr
,如果-mgas-isr-prologues
已关闭等,则不生成CCD_16。如果ISR有开放的编码调用或做一些奇怪的事情,如非本地goto(setjmp/longjmp),则不要生成
__gcc_isr
。如果一切顺利,请打印
__gcc_isr
伪指令,而不是实际的ISR序言/尾声。
汇编程序的作用:
它分析了从序言块
__gcc_isr 1
开始到最终块0的occomplete ISR代码,并记录了R0、R1的使用情况以及对SREG的影响。不要分析函数调用背后的代码:如果遇到
[r]call
,假设R0、R1和SREG最坏。编译器已经处理了尾部调用(通过某些跳转指令进行的调用)。根据R0、R1、SREG的使用情况,打印区块1的优化序言和区块2的尾声。用块0指定的寄存器可以用于推送/弹出SREG,因为编译器无论如何都会使用此寄存器。
对于上面的例子,最终代码将是:
<__vector_X>:
8f 93 push r24
8f b7 in r24, 0x3f ; SREG
8f 93 push r24
80 91 60 00 lds r24, 0x0060 ; <c>
8f 5f subi r24, 0xFF
80 93 60 00 sts 0x0060, r24 ; <c>
8f 91 pop r24
8f bf out 0x3f, r24 ; SREG
8f 91 pop r24
18 95 reti
让汇编程序进行分析的明显优点是,它甚至适用于对GCC不透明的内联汇编中的代码。
";内联asm重要吗":关于内联装配的一个注记
首先要注意的是,我们使用当前的方法免费获得对内联asm的分析。内联asm的处理并不是决定让gas来做这项工作的原因,不过这只是一个很好的副作用。所以接下来的基本上是TL;DR为什么我们用汽油当工作马。
内联asm必须明确所有副作用,这是正确的
cc0之前→CC模式转换时,没有条件码寄存器可以对cc0进行缓冲,因此假设基本上每个insn都会对cc0缓冲。随着CC模式的引入,情况并没有发生太大变化(实际上情况变得更糟了):比较insn正在设置CC,但除了分支或超简单的1-指令is之外,几乎所有其他insn都在打击CC。
原因是许多insn都有非常复杂的insn输出打印机,例如用于特定的算术或多字节加载/存储。用任何合理的工作量都不可能在这个级别上对CC的行为进行精确的建模,因此只假设CC会崩溃。这也适用于内联asm:自从CCmode出现以来,avr后端只添加了";cc";clobbers到所有内联程序集,这样遗留代码就不会中断,请参阅avr.cc.
tmp_reg:Insn打印机将隐式使用它,然后和何时使用,因此编译器无法以任何合理的精度计算出它的使用/阻塞状态,即使是,如果它是一个普通的、分配的寄存器而不是固定的寄存器。
zero_reg也是如此,它也是固定的。一些insn打印机只会在特殊情况下使用它,也不可能以合理的方式对此进行建模。正如您已经注意到的,insns(和内联asm)可能假设zero_reg=0,这就是ISR使用单个起作用的原因
asm ("sts 0,__zero_reg__");
将工作并神奇地初始化zero_reg。
当然,在内联asm中添加像"r" (0)
这样的隐式操作数是不可能的——即使可能,这也会破坏现有的代码。撞击R0或R1仍然是无效的,因为它们是固定的,所以我们不想依赖撞击者的存在。从技术上讲,一个内联asm会破坏zero_reg,然后将其恢复为0,但不会破坏它。然而,ISR仍然需要知道。