我正在为 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 的行为类似。
为什么会这样?我希望编译器可以假设指向
uint32_t
的指针正确对齐,并发出相同的代码。
C 标准是否出于其他原因禁止后一个函数的优化,或者这是 clang 和 gcc 的缺点,如果是,为什么他们能够优化第一个函数?
是否有其他方法来编写函数以更可靠地实现良好的代码生成,例如手动检查对齐情况,或者此时编写我自己的程序集会更好吗?
如果需要,您可以使用手动对齐。
在函数中使用
__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);
}