寄存器访问抑制其他可能的优化(avr-gcc)

问题描述 投票:0回答:2

以下代码包含一个简单的示例,例如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
完成编译。

c optimization embedded avr avr-gcc
2个回答
1
投票

这似乎是一个库和/或编译器(端口)错误,而且似乎比错过优化更严重。我可以在 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) 

0
投票

在这种情况下,avr-gcc 会抑制什么优化?<1>

要找出答案,请添加

-fdump-tree-all

,因为它可能位于其中一个树通道中。然后检查 
<module>.c.<nnn>t.*
 中的中间转储,例如
module.c.154t.sccp
。这些是包含 tree-ssa 转储的文本文件。通过 SCCP(
稀疏条件常数传播)似乎以不同的方式处理这些情况,但后面的传递也可能会产生影响。

© www.soinside.com 2019 - 2024. All rights reserved.