自己编写的简单 memset 不适用于 ARMv7 上的 -03 eabi gcc

Self written simple memset not working with -03 eabi gcc on ARMv7

我用 c 编写了一个非常简单的 memset,它在 -O2 之前工作正常,但在 -O3 下就不行了...

内存集:

void * memset(void * blk, int c, size_t n)
{
    unsigned char * dst = blk;

    while (n-- > 0)
        *dst++ = (unsigned char)c;

    return blk;
}

...使用 -O2 时编译为该程序集:

20000430 <memset>:
20000430:       e3520000        cmp     r2, #0                  @ compare param 'n' with zero
20000434:       012fff1e        bxeq    lr                      @ if equal return to caller
20000438:       e6ef1071        uxtb    r1, r1                  @ else zero extend (extract byte from) param 'c'
2000043c:       e0802002        add     r2, r0, r2              @ add pointer 'blk' to 'n'
20000440:       e1a03000        mov     r3, r0                  @ move pointer 'blk' to r3
20000444:       e4c31001        strb    r1, [r3], #1            @ store value of 'c' to address of r3, increment r3 for next pass
20000448:       e1530002        cmp     r3, r2                  @ compare current store address to calculated max address
2000044c:       1afffffc        bne     20000444 <memset+0x14>  @ if not equal store next byte
20000450:       e12fff1e        bx      lr                      @ else back to caller

这对我来说很有意义。我注释了这里发生的事情。

当我用-O3 编译它时,程序崩溃了。我的 memset 反复调用自己,直到它吃掉整个堆栈:

200005e4 <memset>:
200005e4:       e3520000        cmp     r2, #0                  @ compare param 'n' with zero
200005e8:       e92d4010        push    {r4, lr}                @ ? (1)
200005ec:       e1a04000        mov     r4, r0                  @ move pointer 'blk' to r4 (temp to hold return value)
200005f0:       0a000001        beq     200005fc <memset+0x18>  @ if equal (first line compare) jump to epilogue
200005f4:       e6ef1071        uxtb    r1, r1                  @ zero extend (extract byte from) param 'c'
200005f8:       ebfffff9        bl      200005e4 <memset>       @ call myself ? (2)
200005fc:       e1a00004        mov     r0, r4                  @ epilogue start. move return value to r0
20000600:       e8bd8010        pop     {r4, pc}                @ restore r4 and back to caller

我不知道这个优化版本在没有任何 strb 或类似的情况下应该如何工作。我是否尝试将内存设置为“0”或其他设置并不重要,因此该函数不仅会在 .bss(零初始化)变量上调用。

(1) 这是个问题。当函数由于 'n' 为零而未提前退出时,(2) 会调用此推送,而没有匹配的弹出消息会无休止地重复。我用 uart 打印验证了这一点。 r2 也从未被触及,那么为什么与零的比较应该变为真?

请帮助我了解这里发生的事情。编译器是否假设了我可能无法满足的先决条件?

背景:我在我的裸机项目中使用需要 memset 的外部代码,所以我推出了自己的代码。它只在启动时使用一次,对性能无关紧要。

/编辑:使用这些选项调用编译器:

arm-none-eabi-gcc -O3 -Wall -Wextra -fPIC -nostdlib -nostartfiles -marm -fstrict-volatile-bitfields -march=armv7-a -mcpu=cortex-a9 -mfloat-abi=hard -mfpu=neon-vfpv3

你的第一个问题(1)。这是根据调用约定,如果您要进行嵌套函数调用,则需要保留 link 寄存器,并且需要 64 位对齐。该代码使用 r4 以便保存额外的寄存器。那里没有魔法。

你的第二个问题 (2) 它没有调用你的 memset 它正在优化你的代码,因为它认为它是一个低效的 memset。 Fuz 已经回答了你的问题。

重命名函数

00000000 <xmemset>:
   0:   e3520000    cmp r2, #0
   4:   e92d4010    push    {r4, lr}
   8:   e1a04000    mov r4, r0
   c:   0a000001    beq 18 <xmemset+0x18>
  10:   e6ef1071    uxtb    r1, r1
  14:   ebfffffe    bl  0 <memset>
  18:   e1a00004    mov r0, r4
  1c:   e8bd8010    pop {r4, pc}

你可以看到这个。

如果您按照 Fuz 的建议使用 -ffreestanding,那么您会看到这个或类似的东西

00000000 <xmemset>:
   0:   e3520000    cmp r2, #0
   4:   012fff1e    bxeq    lr
   8:   e92d41f0    push    {r4, r5, r6, r7, r8, lr}
   c:   e2426001    sub r6, r2, #1
  10:   e3560002    cmp r6, #2
  14:   e6efe071    uxtb    lr, r1
  18:   9a00002a    bls c8 <xmemset+0xc8>
  1c:   e3a0c000    mov r12, #0
  20:   e3520023    cmp r2, #35 ; 0x23
  24:   e7c7c01e    bfi r12, lr, #0, #8
  28:   e1a04122    lsr r4, r2, #2
  2c:   e7cfc41e    bfi r12, lr, #8, #8
  30:   e7d7c81e    bfi r12, lr, #16, #8
  34:   e7dfcc1e    bfi r12, lr, #24, #8
  38:   9a000024    bls d0 <xmemset+0xd0>
  3c:   e2445009    sub r5, r4, #9
  40:   e1a03000    mov r3, r0
  44:   e3c55007    bic r5, r5, #7
  48:   e3a07000    mov r7, #0
  4c:   e2851008    add r1, r5, #8
  50:   e1570005    cmp r7, r5
  54:   f5d3f0a0    pld [r3, #160]  ; 0xa0
  58:   e1a08007    mov r8, r7
  5c:   e583c000    str r12, [r3]
  60:   e583c004    str r12, [r3, #4]
  64:   e2877008    add r7, r7, #8
  68:   e583c008    str r12, [r3, #8]
  6c:   e2833020    add r3, r3, #32
  70:   e503c014    str r12, [r3, #-20] ; 0xffffffec
  74:   e503c010    str r12, [r3, #-16]
  78:   e503c00c    str r12, [r3, #-12]
  7c:   e503c008    str r12, [r3, #-8]
  80:   e503c004    str r12, [r3, #-4]
  84:   1afffff1    bne 50 <xmemset+0x50>
  88:   e2811001    add r1, r1, #1
  8c:   e483c004    str r12, [r3], #4
  90:   e1540001    cmp r4, r1
  94:   8afffffb    bhi 88 <xmemset+0x88>
  98:   e3c23003    bic r3, r2, #3
  9c:   e1520003    cmp r2, r3
  a0:   e0466003    sub r6, r6, r3
  a4:   e0803003    add r3, r0, r3
  a8:   08bd81f0    popeq   {r4, r5, r6, r7, r8, pc}
  ac:   e3560000    cmp r6, #0
  b0:   e5c3e000    strb    lr, [r3]
  b4:   08bd81f0    popeq   {r4, r5, r6, r7, r8, pc}
  b8:   e3560001    cmp r6, #1
  bc:   e5c3e001    strb    lr, [r3, #1]
  c0:   15c3e002    strbne  lr, [r3, #2]
  c4:   e8bd81f0    pop {r4, r5, r6, r7, r8, pc}
  c8:   e1a03000    mov r3, r0
  cc:   eafffff6    b   ac <xmemset+0xac>
  d0:   e1a03000    mov r3, r0
  d4:   e3a01000    mov r1, #0
  d8:   eaffffea    b   88 <xmemset+0x88>

这看起来像是简单的内联内存集,它不知道你的代码(更快的那个)。

因此,如果您希望它使用您的代码,请坚持使用 -O2。你的效率很低,所以不确定为什么你需要比以前更进一步。

20000444:       e4c31001        strb    r1, [r3], #1            @ store value of 'c' to address of r3, increment r3 for next pass
20000448:       e1530002        cmp     r3, r2                  @ compare current store address to calculated max address
2000044c:       1afffffc        bne     20000444 <memset+0x14>  @ if not equal store next byte

如果不将您的代码替换为其他代码,它不会比这更好。

Fuz 已经回答了问题:

Compile with -fno-builtin-memset. The compiler recognises that the function implements memset and thus replaces it with a call to memset. You should in general compile with -ffreestanding when writing bare-metal code. I believe this fixes this sort of problem, too

它正在用 memset 替换您的代码,如果您不希望它这样做,请使用 -ffreestanding。

如果您想知道为什么 -fno-builtin-memset 不起作用,这是 gcc 人员的问题,请提交工单,让我们知道他们说了什么(或者只是看看编译器源代码)。