ARM Cortex M3 - GNU C工具链导致更高优化级别的总线故障[duplicate]

gcmastyq  于 2022-12-03  发布在  其他
关注(0)|答案(2)|浏览(229)

此问题在此处已有答案

Getting GCC to compile without inserting call to memcpy(5个答案)
21天前关闭。
我尝试在ARM Cortex M3处理器上执行一个GNU C项目。该项目在-Og优化级别上运行良好,但当我尝试将优化级别增加到-O2、-O3时,我遇到了总线故障。
GNU工具链是“arm-none-eabi V10.3.1”
尝试阅读BFSR寄存器,结果显示是PRECISERR & STKERR。错误发生在自实现的memset函数中,由于项目不需要标准CLib,因此已解决该问题。

void* memset(void s, int c, size_t len){
 unsigned char *dst; 
 dst = (unsigned char) s;
 while (len > 0) {
     *dst = (unsigned char) c;
      dst++; 
      len--;
 } 
return s; }

此外,在检查了此函数的汇编后,注意到这对于-Og选项(工作)和崩溃的-O2/3/s选项来说是完全不同的。
我在这里复制了两个选项的装配屏幕截图。
第一次
相信是该函数的返回导致了STKERR,而且我看到了BL指令(在-O2/O3/O 4选项中),这可能是根本原因,因为它将下一个指令地址推入链接寄存器,随后的弹出PC可能会失败?
但是我能够通过修改代码并使变量成为volatile来解决这个问题。下面是新的实现。

void* memset(void *s, int c, size_t len) {
     unsigned char * volatile dst;
     volatile size_t count = 0;
     dst = (unsigned char * volatile) s;

    while (count <  len) {
        dst[count] = (unsigned char) c;
        count++;
    }
    return s;
}

请想知道这是否是GNU工具链中的一个bug?
有问题的memset函数的汇编如下(-O2/-O3/-Os):-

.section    .text.memset,"ax",%progbits
    .align  1
    .p2align 2,,3
    .global memset
    .syntax unified
    .thumb
    .thumb_func
    .type   memset, %function
memset:
    .cfi_startproc
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    push    {r4, lr}
    mov r4, r0
    cbz r2, .L34
    uxtb    r1, r1
    bl  memset
    mov r0, r4
    pop {r4, pc}
    .cfi_endproc

使用-Og选项编译的memset函数的程序集(有效)

.section    .text.memset,"ax",%progbits
    .align  1
    .global memset
    .syntax unified
    .thumb
    .thumb_func
    .type   memset, %function
memset:
    .cfi_startproc
    @ args = 0, pretend = 0, frame = 0
    @ frame_needed = 0, uses_anonymous_args = 0
    @ link register save eliminated.
    mov r3, r0
.L20:
    strb    r1, [r3], #1
    subs    r2, r2, #1
    cmp r2, #0
    bne .L20
    bx  lr
    .cfi_endproc
.LFE81:
    .size   memset, .-memset
n3schb8v

n3schb8v1#

即使使用-ffreestanding-nostdlib和类似的选项,愚者仍然会发出对某些“库函数”的调用,当它发现它可以将代码优化为对这些函数之一的调用时。memset就是其中之一。因此愚者将memset函数优化为对memset的调用,这当然会导致无限递归和崩溃。
这在www.example.com上有记录https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Standards.html#Standards:
愚者使用的大多数编译器支持例程都存在于libgcc中,但也有一些例外。愚者要求独立环境提供memcpy、memmove、memset和memcmp。最后,如果使用了__builtin_trap,并且目标没有实现陷阱模式,则愚者发出一个调用以中止。
我们希望您用汇编语言实现这些函数,而且您可能希望这样做,以便可以使用手动优化的版本。正如您所注意到的,您可以使用volatile等技巧来抑制优化,但这将导致memset的最幼稚和最不优化的版本,您可能对性能不太满意。
Getting GCC to compile without inserting call to memcpy还建议使用-fno-tree-loop-distribute-patterns。与-O3结合使用,它使用16字节SIMD存储,而不是单字节strb,因此这是一个相当大的改进。https://godbolt.org/z/K8645on1e。但是,未来的一些编译器版本可能会打破这一点。

62lalag4

62lalag42#

所以我们基本上在昨天的评论中回答了这个问题。然后Nate回答了如何解决这个问题。那就是不要使用-O3,使用-O2(至少在我使用的gnu上)。一般来说不要使用-O3。

typedef __SIZE_TYPE__ size_t;
void* memset(void *s, int c, size_t len)
{
 unsigned char *dst; 
 dst = (unsigned char *)s;
 while (len > 0) {
     *dst = (unsigned char) c;
      dst++; 
      len--;
 } 
 return s; 
} 


00000000 <memset>:
   0:   b510        push    {r4, lr}
   2:   4604        mov r4, r0
   4:   b112        cbz r2, c <memset+0xc>
   6:   b2c9        uxtb    r1, r1
   8:   f7ff fffe   bl  0 <memset>
   c:   4620        mov r0, r4
   e:   bd10        pop {r4, pc}


typedef __SIZE_TYPE__ size_t;
void* fun(void *s, int c, size_t len)
{
 unsigned char *dst; 
 dst = (unsigned char *)s;
 while (len > 0) {
     *dst = (unsigned char) c;
      dst++; 
      len--;
 } 
 return s; 
} 

00000000 <fun>:
   0:   b510        push    {r4, lr}
   2:   4604        mov r4, r0
   4:   b112        cbz r2, c <fun+0xc>
   6:   b2c9        uxtb    r1, r1
   8:   f7ff fffe   bl  0 <memset>
   c:   4620        mov r0, r4
   e:   bd10        pop {r4, pc}

相关问题