我在查看 Atmel 网站上的文档时发现了这个示例,其中解释了重新排序的一些问题。
这是示例代码:
#define cli() __asm volatile( "cli" ::: "memory" )
#define sei() __asm volatile( "sei" ::: "memory" )
unsigned int ivar;
void test2( unsigned int val )
{
val = 65535U / val;
cli();
ivar = val;
sei();
}
Run Code Online (Sandbox Code Playgroud)
在此示例中,他们正在实施类似关键区域的机制。cli 指令禁用中断,sei 指令启用中断。通常,我会保存中断状态并恢复到该状态,但我离题了......
他们注意到的问题是,启用优化后,第一行的除法实际上会移到 cli 指令之后。当您尝试尽可能短地进入关键区域时,这可能会导致一些问题。
如果 cli() MACRO 扩展为显式破坏内存的内联汇编,这怎么可能呢?编译器如何自由地在此语句之前或之后移动内容?
另外,我修改了代码,以在每个语句之前以 的形式包含内存屏障__asm volatile("" ::: "memory");,但它似乎没有改变任何内容。
我还从 cli() 和 sei() 宏中删除了内存破坏,并且生成的代码是相同的。
当然,如果我将 test2 函数参数声明为 易失性,则不会进行重新排序,我认为这是因为易失性语句无法相对于其他易失性语句(技术上是内联汇编)进行重新排序。我的假设正确吗?
是否可以针对易失性内联汇编重新排序易失性访问?
是否可以相对于易失性内联汇编重新排序非易失性访问?
奇怪的是,Atmel 声称他们需要内存破坏器只是为了强制执行与 asm 相关的易失性访问的顺序。这对我来说没有任何意义。
如果编译器屏障不是正确的解决方案,那么我该如何防止任何外部代码“泄漏”到关键区域?
如果有人能提供一些线索,我将不胜感激。
谢谢
\n\n如果 cli() MACRO 扩展为显式破坏内存的内联汇编,这怎么可能呢?编译器如何自由地在此语句之前或之后移动内容?
\n
这是由于以下实现细节造成的avr-gcc:编译器的支持库 libgcc 提供了许多以汇编语言编写的函数以提高性能;包括整数除法函数,例如__udivmodhi4. 并非所有这些函数都会破坏avr-gcc ABI 指定的所有被调用者使用的寄存器。特别是,__udivmodhi4不会破坏Z寄存器。
avr-gcc如下利用它:在没有 16 位除法指令(如 AVR)的机器上,GCC 将发出库调用而不是为其内联生成代码。 avr-gcc然而,假装该体系结构确实具有此类除法指令,并将其建模为对处理器寄存器产生影响,就像库调用一样。最后,在所有代码分析和优化之后,avr后端将这条指令打印为[R]CALL __udivmodhi4. 我们称其为透明调用,即编译器分析看不到的调用。
例子
\nint div (int a, int b, volatile const __flash char *z)\n{\n int ab;\n\n (void) *z;\n asm volatile ("" : "+r" (a));\n ab = a / b;\n asm volatile ("" : "+r" (ab));\n (void) *z;\n\n return ab;\n}\nRun Code Online (Sandbox Code Playgroud)\n编译它以avr-gcc -S -Os -mmcu=atmega8 ...获得汇编文件*.s:
div:\n movw r30,r20\n lpm r18,Z\n rcall __divmodhi4\n movw r24,r22\n lpm r18,Z\n ret\nRun Code Online (Sandbox Code Playgroud)\n解释
\n(void) *z从Flash中读取一个字节,为了使用lpm指令,地址必须位于Z由 完成的寄存器中movw r30,r20。读取 via 后lpm,编译器发出rcall __divmodhi4执行有符号 16 位除法的命令。如果这是一个普通(非透明)调用,编译器对被调用者的内部工作一无所知,但由于 avr 后端手动对调用进行建模,编译器知道指令序列不会改变Z,因此可以使用Z通话后再次进行,无需再多费吹灰之力。由于寄存器压力较小,因此可以更好地生成代码,特别z是不需要在除法周围保存/恢复。
它asm只是用于对代码进行排序:它是易失性的,因此不能根据易失性读取重新排序*z。并且asm不能根据除法重新排序,因为asm更改a和ab\xe2\x80\x93 至少是我们假装并通过约束告诉编译器的内容。(这些变量实际上没有改变,但这并不重要。)
\n\n另外,我修改了代码,以在每个语句之前包含内存屏障
\n__asm volatile("" ::: "memory");,它似乎没有改变任何内容。
该除法不会触及内存(这是一个没有内存破坏的透明调用),因此编译器机器可能会根据内存破坏/访问对其进行重新排序。
\n如果您需要特定的顺序,那么您必须引入人工依赖项,如上面的示例所示。
\n为了区分普通调用和透明调用,您可以.s通过-save-temps -dpwhere -dpprints insn 名称将生成的程序集转储到文件中:
void func0 (void);\n\nint func1 (int a, int b)\n{\n return a / b;\n}\n\nvoid func2 (void)\n{\n func0();\n}\nRun Code Online (Sandbox Code Playgroud)\n每个调用既不是call_insn也不call_value_insn是透明调用, *divmodhi4_call在本例中:
func1:\n rcall __divmodhi4 ; 17 [c=0 l=1] *divmodhi4_call\n movw r24,r22 ; 18 [c=4 l=1] *movhi/0\n ret ; 23 [c=0 l=1] return\n\nfunc2:\n rjmp func0 ; 5 [c=0 l=1] call_insn/3\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
668 次 |
| 最近记录: |