avr-gcc 中的饱和加法优化

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

我正在为 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 在不使用内联汇编的情况下正确优化它?
是否有正确的方法通过内联汇编来优化它,从而优化常量?

c optimization embedded avr avr-gcc
1个回答
0
投票

您可以使用内置的 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;
}
© www.soinside.com 2019 - 2024. All rights reserved.