以下代码包含一个简单的示例,例如avr128da32 MCU。 人们可以通过旧的宏(如
VPORTA_DIR
)或通过结构映射(如VPORTA.DIR
)访问SFR。
#include <avr/io.h>
#include <stdint.h>
static uint16_t g;
int main() {
for(uint8_t i = 0; i < 20; i++) {
++g;
// VPORTA_DIR; // <1> suppresses optimization
VPORTA.DIR; // <2> OK
}
}
对于情况
<2>
,负载/存储到 g
的负载/存储按预期在环路外提升/下沉。
但是对于情况
<1>
这不会发生 - 但代码应该是相同的。
在
<1>
情况下,avr-gcc 会抑制哪些优化?
组装
<1>
:
main:
ldi r24,lo8(20) ; ivtmp_4,
.L2:
lds r18,g ; g, g
lds r19,g+1 ; g, g
subi r18,-1 ; tmp48,
sbci r19,-1 ; ,
sts g,r18 ; g, tmp48
sts g+1,r19 ; g, tmp48
in r25,0 ; vol.1_7, MEM[(volatile uint8_t *)0B]
subi r24,lo8(-(-1)) ; ivtmp_4,
cpse r24,__zero_reg__ ; ivtmp_4,
rjmp .L2 ;
ldi r24,0 ;
ldi r25,0 ;
ret
组装
<2>
:
main:
lds r24,g ; g_lsm.6, g
lds r25,g+1 ; g_lsm.6, g
ldi r18,lo8(20) ; ivtmp_2,
.L2:
in r19,0 ; vol.1_7, MEM[(struct VPORT_t *)0B].DIR
subi r18,lo8(-(-1)) ; ivtmp_2,
cpse r18,__zero_reg__ ; ivtmp_2,
rjmp .L2 ;
adiw r24,20 ; tmp49,
sts g,r24 ; g, tmp49
sts g+1,r25 ; g, tmp49
ldi r24,0 ;
ldi r25,0 ;
ret
使用avr-gcc 12.2和
-Os
完成编译。
这似乎是一个库和/或编译器(端口)错误,而且似乎比错过优化更严重。我可以在 avr-gcc 12.2(以及更旧的版本)中重现它,如下所示:
gcc -std=c11 -pedantic -Wall -Wextra -Os -mmcu=avr51 -fno-strict-aliasing
#include <stdint.h>
typedef struct VPORT_struct
{
volatile uint8_t DIR; /* Data Direction */
volatile uint8_t OUT; /* Output Value */
volatile uint8_t IN; /* Input Value */
volatile uint8_t INTFLAGS; /* Interrupt Flags */
} VPORT_t;
#define VPORTA (*(VPORT_t*) 0x0000)
static uint16_t g;
int main() {
for(uint8_t i = 0; i < 20; i++) {
++g;
VPORTA.DIR;
}
}
输出(完全是香蕉):
__zero_reg__ = 1
main:
ldi r24,lo8(20)
.L2:
lds r18,g
lds r19,g+1
subi r18,-1
sbci r19,-1
sts g+1,r19
sts g,r18
lds r25,0
subi r24,lo8(-(-1))
cpse r24,__zero_reg__
rjmp .L2
ldi r24,0
ldi r25,0
ret
这里的
#define VPORTA (*(VPORT_t*) 0x0000)
(根据OP在AVR库中完成)是一个脏的强制转换,这似乎会使编译器出错。不知何故,似乎认为 VPORT
可能是 g
的别名,这只是无稽之谈。 uint16_t
与 VPORT_t
不兼容。严格别名规则的任何例外情况均不适用。
奇怪的是,它在启用严格别名时有效(这应该是默认设置
-fstrict-aliasing
),但当我禁用它时则不起作用-fno-strict-aliasing
。然而这里不存在严格别名的情况。通过严格的别名,生成预期的代码,大致如下:
.L2:
lds r19,0
subi r18,lo8(-(-1))
cpse r18,__zero_reg__
rjmp .L2
0x0000 恰好是空指针常量似乎并不重要,无论使用什么地址,我都会得到相同的错误机器代码。如果我这样做,我会收到一个无意义的警告:
警告:数组下标 0 超出了 'VPORT_t[0]' {aka 'struct VPORT_struct[]'} [-Warray-bounds] | 的数组边界#定义 VPORTA ((VPORT_t) 0x0001)
见鬼?这一切似乎完全被打破了,我无法解释为什么。显然,不要使用这样的脏类型转换,但如果这是 AVR 库提供的内容,那么该库就很糟糕。
此外,编译器应该能够从代码中删除对
g
的所有访问,因为它未在任何包含副作用的表达式中使用。
明显的解决方法是删除结构:
#define VPORTA_DIR (*(volatile uint8_t*)0x0000u)
要找出答案,请添加在这种情况下,avr-gcc 会抑制什么优化?<1>
-fdump-tree-all
,因为它可能位于其中一个树通道中。然后检查
<module>.c.<nnn>t.*
中的中间转储,例如
module.c.154t.sccp
。这些是包含 tree-ssa 转储的文本文件。通过 SCCP(稀疏条件常数传播)似乎以不同的方式处理这些情况,但后面的传递也可能会产生影响。