为什么 GCC 对这些 C 函数的优化不同?

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

我正在为 ARM Cortex-M 处理器编写嵌入式代码。我想要一些通用函数将内存复制到连续映射到内存的寄存器。

由于 C 标准禁止对任意类型使用别名,所以我想我只需按字节复制缓冲区,并让优化器确定参数是否正确对齐(或者它们是否需要存储在内存中),希望能得到结果不错的代码生成。

这是功能:

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

typedef volatile uint32_t reg_t;

static inline void copyToReg(reg_t* dest, const void* src, size_t len, bool read_modify_write)
{
    const unsigned char* const src_bytes = (const unsigned char*) src; // when the function is inlined and src is properly aligned the
                                                     // byte wise copying could be optimized away?
    for (size_t i = 0; i < len / 4; i++) {
        uint32_t value = 0;

        value |= src_bytes[i * 4 + 0];
        value |= src_bytes[i * 4 + 1] << 8;
        value |= src_bytes[i * 4 + 2] << 16;
        value |= src_bytes[i * 4 + 3] << 24;
        //value = ((const uint32_t*) src)[i];

        dest[i] = value;                             // volatile write
    }

    size_t remainder = len % 4;

    if (remainder != 0) {
        uint32_t finalValue;

        if (read_modify_write)
            finalValue = dest[len / 4];              // volatile read

        finalValue &= 0xFFFFFFFFU << (remainder * 8);

        for (size_t j = 0; j < remainder; j++)
            finalValue |= src_bytes[len - remainder + j] << (j * 8);

        dest[len / 4] = finalValue;                  // volatile write
    }
}

但是我在测试代码生成时得到的结果好坏参半。 以下两个测试功能:

extern const uint32_t buffer[4];
reg_t x[8];

void test1() {
    copyToReg(x, buffer, 8, false);
}

void test2(const uint32_t* buf) {
    copyToReg(x, buf, 8, false);
}

gcc 13.3.0 的编译方式不同,带有 -O3 (godbolt):

test1:
  ldr r2, .L3
  ldr r3, .L3+4
  ldm r2, {r1, r2}
  str r1, [r3]
  str r2, [r3, #4]
  bx lr
.L3:
  .word buffer
  .word .LANCHOR0
test2:
  str lr, [sp, #-4]!
  ldrb r2, [r0] @ zero_extendqisi2
  ldrb lr, [r0, #1] @ zero_extendqisi2
  ldrb ip, [r0, #2] @ zero_extendqisi2
  orr r2, r2, lr, lsl #8
  orr r2, r2, ip, lsl #16
  ldrb ip, [r0, #3] @ zero_extendqisi2
  ldrb r3, [r0, #4] @ zero_extendqisi2
  orr r2, r2, ip, lsl #24
  ldrb ip, [r0, #5] @ zero_extendqisi2
  ldr r1, .L7
  orr r3, r3, ip, lsl #8
  ldrb ip, [r0, #6] @ zero_extendqisi2
  str r2, [r1]
  ldrb r2, [r0, #7] @ zero_extendqisi2
  orr r3, r3, ip, lsl #16
  orr r3, r3, r2, lsl #24
  str r3, [r1, #4]
  ldr lr, [sp], #4
  bx lr
.L7:
  .word .LANCHOR0
x:
  .space 32

Clang 的行为类似。

  1. 为什么会这样?我希望编译器可以假设指向

    uint32_t
    的指针正确对齐,并发出相同的代码。

  2. C 标准是否出于其他原因禁止后一个函数的优化,或者这是 clang 和 gcc 的缺点,如果是,为什么他们能够优化第一个函数?

  3. 是否有其他方法来编写函数以更可靠地实现良好的代码生成,例如手动检查对齐情况,或者此时编写我自己的程序集会更好吗?

c gcc optimization embedded
1个回答
0
投票

如果需要,您可以使用手动对齐。

在函数中使用

__builtin_assume_aligned()
声明参数对齐。并使用
__attribute__((aligned()))
确保发送到函数的指针对齐。

所以你的代码看起来像这样:

static inline void copyToReg(reg_t* dest, const void* src, size_t len, bool read_modify_write)
{
   dest = __builtin_assume_aligned(dest, 8);
   src = __builtin_assume_aligned(src, 8);
   .... no changes to remainder of the function
}


int main() {
   reg_t dst[4] __attribute__((aligned(8)));
   reg_t src[4] __attribute__((aligned(8)));
   test2(dst, src, sizeof(dst), false);
}
© www.soinside.com 2019 - 2024. All rights reserved.