我正在查看Atmel网站上的文档,我遇到了example,他们解释了重新排序的一些问题。
以下是示例代码:
#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();
}
在这个例子中,他们正在实施一个类似于关键区域的机制。 cli指令禁用中断,sei指令启用它们。通常情况下,我会保存中断状态并恢复到该状态,但我离题了......
他们注意到的问题是,在启用优化的情况下,第一行的除法实际上会在cli指令之后移动到。当您尝试尽可能在最短的时间内进入关键区域时,这可能会导致一些问题。
如果cli()MACRO扩展为内联asm并明确地破坏了内存,那怎么可能呢?编译器如何在此语句之前或之后自由移动?
此外,我修改了代码以在__asm volatile("" ::: "memory");
形式的每个语句之前包含内存屏障,并且它似乎没有任何改变。
我还从cli()和sei()MACRO中删除了内存clobber,生成的代码完全相同。
当然,如果我将test2函数参数声明为volatile,则不会重新排序,我认为这是因为不能对其他volatile语句(技术上是内联函数)重新排序volatile语句。我的假设是否正确?
可以针对易失性内联asm重新排序易失性访问吗?
非易失性访问是否可以针对易失性内联asm进行重新排序?
有些奇怪的是,Atmel声称他们需要内存崩溃来强制执行与asm相关的易失性访问的排序。这对我没有任何意义。
如果编译器障碍不适合这个,那么我怎么能防止任何外部代码“泄漏”到关键区域?
如果有人能说清楚,我会很感激。
由于
答案 0 :(得分:0)
如果cli()MACRO扩展为显式破坏内存的嵌入式asm,怎么可能呢?编译器如何自由地在此语句之前或之后移动内容?
这是由于avr-gcc
的实现细节所致:编译器的支持库libgcc提供了许多以汇编形式编写的函数,以提高性能。包括用于整数除法的函数,例如__udivmodhi4
。并非所有这些功能都会破坏所有callee-used registers as specified by the avr-gcc ABI。特别是,__udivmodhi4
不会掩盖Z
寄存器。
avr-gcc
的用法如下:在没有16位除法指令的机器(如AVR)上,GCC会发出库调用而不是为它内联生成代码。 avr-gcc
却假装该体系结构确实有这种划分指令,并将其建模为像库调用一样对处理器寄存器产生影响。最后,在所有代码分析和优化之后,avr后端将此指令打印为[R]CALL __udivmodhi4
。我们称其为 透明调用 ,即编译器分析看不到的调用。
示例
int div (int a, int b, volatile const __flash char *z)
{
int ab;
(void) *z;
asm volatile ("" : "+r" (a));
ab = a / b;
asm volatile ("" : "+r" (ab));
(void) *z;
return ab;
}
使用avr-gcc -S -Os -mmcu=atmega8 ...
进行编译以获取程序集文件*.s
:
div:
movw r30,r20
lpm r18,Z
rcall __divmodhi4
movw r24,r22
lpm r18,Z
ret
说明
(void) *z
从闪存读取一个字节,并且为了使用lpm
指令,该地址必须位于Z
完成的movw r30,r20
寄存器中。通过lpm
读取后,编译器发出rcall __divmodhi4
以执行带符号的16位除法。如果这是一个普通的(非透明的)调用,则编译器将不了解被调用方的内部工作,但是由于avr后端通过手工对调用进行建模,因此编译器知道指令序列不会改变{ {1}} ,因此可以在通话后再次使用Z
,而无需费心。由于较少的寄存器压力,因此可以更好地生成代码,尤其是Z
不需要在分区周围保存/恢复。
z
仅用于订购代码:它是易失性的,因此不能针对易失性读取asm
重新排序。并且*z
不得针对除法进行重新排序,因为asm
会更改asm
和a
–至少这就是我们要假装并通过约束告诉编译器。 (这些变量实际上并未更改,但这在这里无关紧要。)
我还修改了代码,以
ab
的形式在每个语句之前添加了内存屏障,并且似乎没有任何改变。
该部分不涉及内存(这是没有内存障碍的透明调用),因此编译器机制可能会针对内存障碍/访问对其进行重新排序。
如果需要特定的命令,则必须像上面的示例中那样引入人为的依赖关系。
为了区分普通调用和透明调用,您可以通过__asm volatile("" ::: "memory");
将生成的程序集转储到.s
文件中,其中-save-temps -dp
打印insn名称:
-dp
每个既不是void func0 (void);
int func1 (int a, int b)
{
return a / b;
}
void func2 (void)
{
func0();
}
也不是call_insn
的呼叫都是透明呼叫,在这种情况下是call_value_insn
:
*divmodhi4_call