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中不需要的一些指令。
- 宏指令
- 关于
__attribute__((interrupt))
的一些细节。
GCC的asm输出(https://godbolt.org/z/zzbY5KE3c)使用了__gcc_isr 1
之类的伪指令。
更新的GCC(Godbolt上的9.2)支持-mno-gas-isr-prologues
,以使GCC显示与上述Atmel Studio中的反汇编相匹配的真实的指令。因此,如果有人想玩这个,在https://godbolt.org/z/q6M518qfP中有效果的东西在真实的Atmel Studio中可能会有同样的效果。
1条答案
按热度按时间xghobddn1#
C代码是空的,汇编代码对吗?
是的。这是avr-gcc v7及以下版本的代码。较新版本的编译器可能会生成更高效的代码,请参阅GCC v8发行说明。原因如下:
平均-gcc ABI:R 0和R1建模、使用和优势
当avr-gcc ABI被设计出来的时候,决定将R 0和R1建模为固定寄存器。*“固定寄存器 *”意味着编译器不会在寄存器分配或其他任何方式中使用它们。这些寄存器的唯一使用是在编译的最后阶段,当汇编代码被打印到
*.s
时,其中这些寄存器可以在各自的输出字符串中隐式使用。这基本上与通过内联汇编输出的指令相同,对编译器是不透明的。选择此选项的原因是,通过使用这些额外的寄存器可以提高代码的整体质量,其中R 0用作临时寄存器(即
__tmp_reg__
),而R1(即__zero_reg__
)包含值0。例如,要比较寄存器%0中的16位整数与42,您只需而不需要任何进一步的麻烦,即不需要分配某个临时寄存器、清除它等。
R 0和R1为固定寄存器的缺点
这种方法的缺点是这些寄存器没有生命信息的使用,例如在乘法代码中,如
您必须根据ABI将R1重置为0,因为
MUL
会破坏其内容:第一个
clr r1
是多余的,因为后面的mul
将覆盖它。ABI设计还导致了这些昂贵的ISR序言和结语,因为无法分析R 0、R1是否被使用或更改,SREG也是如此。因此,经典的ISR序言必须
无论ISR的主体是什么,结语都必须还原它们。
avr-gcc v8+解决方案:ISR中的伪指令__gcc_isr
由于问题的复杂性,从提交PR20296到解决它花了12年时间。通过伪指令
__gcc_isr
,大部分分析工作从编译器转移到了汇编器。要了解它是如何工作的,请考虑下面的C代码:以及来自AVR-GCCV 8 + X1 M7 N1 X的汇编代码:
编译器的作用:
__gcc_isr
(在配置过程中确定气体是否接受-mgcc-isr
),如果优化关闭,如果ISR属于no_gccisr
,如果-mgas-isr-prologues
已关闭等,则不生成__gcc_isr
。__gcc_isr
。__gcc_isr
伪指令,而不是实际的ISR序言/结语。汇编程序的作用:
__gcc_isr 1
开始直到最后块0的八个完整ISR代码,并记录R 0、R1的使用情况以及对SREG的影响。[r]call
,假设R 0、R1和SREG的情况最糟。编译器已经处理了尾部调用(通过某个跳转指令进行的调用)。对于上面的示例,最终代码将是:
让汇编程序进行分析的明显优点是,它甚至可以处理对GCC不透明的内联汇编代码。
“内联asm是否重要?":关于内联汇编的一个注记
首先要注意的是,我们可以用当前的方法免费分析内联asm。尽管处理内联asm并不是决定让gas来做这项工作的原因,但这只是一个不错的副作用。DR为什么我们用气体作为工作马。
内联asm必须将所有副作用都显式化,这是正确的,但要有所保留:
在cc 0 →CCmode transition之前,没有条件码寄存器可以重写,所以假设基本上每个insn都会重写cc 0,这种情况在引入CCmode后没有太大变化(实际上变得更糟了):比较insn正在设置CC,但是除了分支或超简单1指令insn之外,几乎每隔一个insn都在重击CC。
原因是很多insn都有非常复杂的insn输出打印机,例如用于特定的算术运算或多字节加载/存储。不可能通过任何合理的工作量来在该级别上建模精确的CC行为,因此只假设CC乱码。这也适用于内联asm:自从CCmode出现以来,avr后端只是将“cc”乱码添加到 * 所有内联汇编 * 中,这样遗留代码就不会中断,请参见avr.cc。
tmp_reg的情况类似:Insn打印机将在当时和何时隐式地使用它,因此编译器无法以任何合理的精度计算出它的使用/乱码状态,即使 * 它 * 是一个普通的、已分配的寄存器,而不是一个固定的寄存器。
zero_reg也是一样的,它也是固定的。一些insn打印机只在特殊情况下使用它,而且也不可能以合理的方式对此建模。正如您已经注意到的,insns(和inline asm)可能会假设zero_reg = 0,这就是为什么ISR使用单个
将工作并神奇地示例化zero_reg。
当然,不可能在内联asm中添加像
"r" (0)
这样的隐式操作数--即使可能,这也会破坏现有代码。而且,对R 0或R1进行重敲仍然是无效的,因为它们是固定的,所以我们不希望依赖于重敲器的存在。从技术上讲,对zero_reg进行重敲,然后将其恢复为0的内联asm不会对它进行重敲。然而,ISR仍然需要知道。