我正在为 ATtiny13 编程,我必须做很多饱和添加。
尝试优化它们,似乎 avr-gcc 根本不知道如何优化任何东西。所有这些都在 AVR gcc 14.1.0 和
-O3
中进行了尝试。这是我到目前为止尝试过的:
uint8_t saturating_add_1(uint8_t a, uint8_t b) {
uint8_t temp = a + b;
if(temp < a)
return 0xFF;
return temp;
}
这成功地在 x86 上进行了优化,但是 avr-gcc 给了我们这个:
saturating_add_1:
.L__stack_usage = 0
mov r25,r24
add r24,r22
cp r24,r25
brlo .L1
ret
.L1:
ldi r24,lo8(-1)
ret
不是很好,也不是很糟糕,它按照我们的指示做了。 让我们尝试另一个已知可以在其他架构上正确优化的版本:
uint8_t saturating_add_2(uint8_t a, uint8_t b) {
if(b > 255 - a)
return 255;
else return a + b;
}
不,那更糟:
saturating_add_2:
.L__stack_usage = 0
ldi r18,lo8(-1)
ldi r19,0
sub r18,r24
sbc r19,__zero_reg__
cp r18,r22
cpc r19,__zero_reg__
brlt .L1
add r24,r22
ret
.L1:
ldi r24,lo8(-1)
ret
好吧,我想我们正在尝试内置编译器。
uint8_t saturating_add_builtin(uint8_t a, uint8_t b) {
if(__builtin_add_overflow(a, b, &a))
return 255;
else return a;
}
saturating_add_builtin:
.L__stack_usage = 0
add r22,r24
cp r22,r24
brlo .L1
mov r24,r22
ret
.L1:
ldi r24,lo8(-1)
ret
它生成的程序集与我们第一次尝试的程序集大致相同。我希望它不会比较,而是使用
brcs
或 brcc
指令(如果进位设置/清除则分支)。
也许我们可以强迫它?
uint8_t saturating_add_reg(uint8_t a, uint8_t b) {
uint8_t temp = a + b;
if(SREG & 1)
return 255;
return temp;
}
saturating_add_reg:
.L__stack_usage = 0
add r24,r22
in __tmp_reg__,__SREG__
sbrs __tmp_reg__,0
ret
ldi r24,lo8(-1)
ret
}
这稍微好一些,从 7 条指令减少到 6 条。但是 avr-gcc 又让我困惑了 - 为什么它使用
sbrs
来跳过 ret
,而不是使用 sbrc
来跳过 ldi
?我是不是错过了什么?
无论如何,我也尝试用内联汇编来修复它,但它有点笨拙:
uint8_t saturating_add_asm_1(uint8_t a, uint8_t b) {
asm (
"add %[a], %[b]\n\t"
"brcc no_overflow_%=\n\t"
"ldi %[a], 255\n\t"
"no_overflow_%=:"
: [a] "+r" (a)
: [b] "r" (b)
: "cc"
);
return a;
}
这工作正常,但编译器无法优化常量(使用
subi
),毕竟我在这方面花费了时间,这在情感层面上造成了伤害。我的另一个尝试是:
uint8_t saturating_add_asm_2(uint8_t a, uint8_t b) {
uint8_t temp = a + b;
asm (
"brcc no_overflow_%=\n\t"
"ldi %[temp], 255\n\t"
"no_overflow_%=:"
: [temp] "+r" (temp)
:
:
);
return temp;
}
但这似乎可能会因为编译器代码重新排序而中断?但我们不能制作
asm
块 volatile
,因为这会禁用更多优化。
因此,我的问题是:
有没有人能够让 avr-gcc 在不使用内联汇编的情况下正确优化它?
是否有正确的方法通过内联汇编来优化它,从而优化常量?
您可以使用内置的 gcc
__builtin_constant_p
对于常量情况回退到非 asm 实现:
(神箭)
uint8_t saturating_add_non_asm(uint8_t a, uint8_t b) {
uint8_t temp = a + b;
if(temp < a)
return 0xFF;
return temp;
}
uint8_t saturating_add(uint8_t a, uint8_t b) {
if (__builtin_constant_p(a) && __builtin_constant_p(b)) {
return saturating_add_non_asm(a, b);
}
asm (
"add %[a], %[b]\n\t"
"brcc no_overflow_%=\n\t"
"ldi %[a], 255\n\t"
"no_overflow_%=:"
: [a] "+r" (a)
: [b] "r" (b)
: "cc"
);
return a;
}