RISC-V中一个优化导致的问题案例

描述

本文转自公众号,欢迎关注

优化导致的问题案例 (qq.com)

  • 一.过程
  • 二.思考

一.过程

关键代码如下

通过串口驱动接口,注册串口接收回调函数,uart_rx_callback

该回调函数中如果收到串口数据,长度非0,则更新全局变量uart_rx_len

主循环中再检查全局变量uart_rx_len,如果大于0说明收到了串口数据,将收到的数据再发送出去,实现简单的串口回环测试。

static int uart_rx_len = 0;
void uart_rx_callback(const void *buffer, uint32_t length)
{
    if(length >0)
    {
        uart_rx_len = length;
    }
}
int main(void)
{
    ......
    debug_uart_init(IOT_UART_PORT_1);
    uint8_t buffer[64];
    iot_uart_register_rx_callback(IOT_UART_PORT_1,buffer,sizeof(buffer),uart_rx_callback);
    while(1)
    {
        if(uart_rx_len > 0)
        {
            iot_uart_write_buffer(IOT_UART_PORT_1,buffer,uart_rx_len);
            uart_rx_len=0;
        }
    }
}

现象是并没有实现上述回环测试的功能。

于是进行调试,先确认是否进入了接收处理,

b uart_rx_callback

发现可以进入该回调函数,说明收到了数据。

step单步运行到执行完uart_rx_len = length;,再查看该变量的值

(gdb) p uart_rx_len
$2 = 1

也确实收到了一个字节。

然后继续往下看,看如下条件是否进入

if(uart_rx_len   > 0)
        {
            iot_uart_write_buffer(IOT_UART_PORT_1,buffer,uart_rx_len);
            uart_rx_len=0;
        }

b main.c:146iot_uart_write_buffer(IOT_UART_PORT_1,buffer,uart_rx_len所在行146行,打断点。

发现进不了该断点。

这里就比较奇怪了,前面uart_rx_len确实是1,了但是这里条件却进不去,其他地方也没有写uart_rx_len的地方。

那么只有继续看该处代码对应的汇编代码

先在uart_rx_callback前打断点,串口接收一个字节触发该回调执行。再在 if(uart_rx_len > 0)所在的行144行前打断点,b main.c:144c运行到该处。

此时看到uart_ex_len的值是1正确的。

(gdb) p uart_rx_len
$2 = 1

layout split打开汇编和C对照窗口。

查看变量uart_rx_len的地址,为0x20300c8

(gdb) p &uart_rx_len
$4 = (int *) 0x20300c8 < uart_rx_len >
(gdb) info reg s1
s1             0x2030000        33751040

S1寄存器的值设置为0x2030000, lui s1,0x2030 lui的u表示up(高20位),加载0x2030到S1的高20位。

lw a2,200(S1)即将S1对应的地址偏移200(0xC8)地址处的值加载到a2寄存器。

正好是获取0x2030C8(uart_rx_len)的值到A2,

然后再执行

blez a2,0x20001ba进行判断uart_rx_len和0的值比较,判断是否往下执行还是在此死循环。

串口

初看没问题,一细看就有端倪了。

假设一开始uart_rx_len=0,

那么后面始终执行的是一条语句,blez a2,0x20001ba,a2寄存器的值不再更新了,这就有问题了,内存中0x2030C8(uart_rx_len)的值变了,但是寄存器a2的值不再变化。

这就是编译器自作主张优化,生成的代码没有继续去从内存0x2030C8(uart_rx_len)处更新值到a2寄存器了。理论上是需要再次执行上述lw a2,200(S1)指令的。这就编译器优化导致的问题。

我们修改

static int uart_rx_len = 0;

改为

`static volatile int uart_rx_len = 0;``

再来看汇编代码。

可以看到如果a5小于0,会跳转到addi s1,s2,200处执行再继续lw a5,0(S1)处加载uart_rx_len的值到a5,会不断从内存处更新值到寄存器。

串口

这就是volatile的作用,加了volatile后编译器始终,会从内存中更新值到寄存器,而不会自作主张使用寄存器中缓存的值。

默认SCons/riscv_tools.py中是Os优化,

CCFLAGS = common_flags + [
        "-Os",
    ]

不加volatile且优化改为

-O3,-O2,-O1,-O0分别看一下。

可以看到-O1优化编译就进行了优化,后面-O2,-O3就不用看了。

-O0

串口

-O1

串口

二.思考

这里函数uart_rx_callback写了变量uart_rx_len

void uart_rx_callback(const void *buffer, uint32_t length)
{
    if(length >0)
    {
        uart_rx_len = length;
    }
}

且uart_rx_callback函数也作为回调函数使用了,理论上编译器应该指导uart_rx_len会被改写,不应该作此优化。

手动调用以下uart_rx_callback

uart_rx_callback(0, 0);
    debug_uart_init(IOT_UART_PORT_1);

还是一样的优化了

串口

看来编译器还是聪明过头了。

这里主要是

while(1)后面第一条语句就是判断uart_rx_len,如果之前还有其他语句,则编译器可能不会优化了。

while(1)

    {

        if(uart_rx_len > 0)

        {

            iot_uart_write_buffer(IOT_UART_PORT_1,buffer,uart_rx_len);

            uart_rx_len=0;

        }

    }

串口
  
审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分