c - 为什么带有 -O3 的 gcc 会不必要地清除本地 ARM NEON 数组?

标签 c gcc arm64 neon compiler-bug

考虑以下代码( Compiler Explorer link ),在 gcc 和 clang 下编译,使用 -O3优化:

#include <arm_neon.h>

void bug(int8_t *out, const int8_t *in) {
    for (int i = 0; i < 2; i++) {
        int8x16x4_t x;

        x.val[0] = vld1q_s8(&in[16 * i]);
        x.val[1] = x.val[2] = x.val[3] = vshrq_n_s8(x.val[0], 7);

        vst4q_s8(&out[64 * i], x);
    }
}
注意 :这是一个问题的最低可重现版本,在我实际的、更复杂的代码的许多不同功能中出现,其中充满了执行与上面完全不同的操作的算术/逻辑/置换指令。请不要批评和/或建议执行上述代码的不同方式,除非它对下面讨论的代码生成问题产生影响。
clang 生成理智的代码:
bug(signed char*, signed char const*):                            // @bug(signed char*, signed char const*)
        ldr     q0, [x1]
        sshr    v1.16b, v0.16b, #7
        mov     v2.16b, v1.16b
        mov     v3.16b, v1.16b
        st4     { v0.16b, v1.16b, v2.16b, v3.16b }, [x0], #64
        ldr     q0, [x1, #16]
        sshr    v1.16b, v0.16b, #7
        mov     v2.16b, v1.16b
        mov     v3.16b, v1.16b
        st4     { v0.16b, v1.16b, v2.16b, v3.16b }, [x0]
        ret
至于gcc,它插入了很多不必要的操作,显然将最终输入到st4的寄存器清零。操作说明:
bug(signed char*, signed char const*):
        sub     sp, sp, #128
        # mov     x9, 0
        # mov     x8, 0
        # mov     x7, 0
        # mov     x6, 0
        # mov     x5, 0
        # mov     x4, 0
        # mov     x3, 0
        # stp     x9, x8, [sp]
        # mov     x2, 0
        # stp     x7, x6, [sp, 16]
        # stp     x5, x4, [sp, 32]
        # str     x3, [sp, 48]
        ldr     q0, [x1]
        # stp     x2, x9, [sp, 56]
        # stp     x8, x7, [sp, 72]
        sshr    v4.16b, v0.16b, 7
        # str     q0, [sp]
        # ld1     {v0.16b - v3.16b}, [sp]
        # stp     x6, x5, [sp, 88]
        mov     v1.16b, v4.16b
        # stp     x4, x3, [sp, 104]
        mov     v2.16b, v4.16b
        # str     x2, [sp, 120]
        mov     v3.16b, v4.16b
        st4     {v0.16b - v3.16b}, [x0], 64
        ### ldr     q4, [x1, 16]
        ### add     x1, sp, 64
        ### str     q4, [sp, 64]
        sshr    v4.16b, v4.16b, 7
        ### ld1     {v0.16b - v3.16b}, [x1]
        mov     v1.16b, v4.16b
        mov     v2.16b, v4.16b
        mov     v3.16b, v4.16b
        st4     {v0.16b - v3.16b}, [x0]
        add     sp, sp, 128
        ret
我手动添加前缀 #所有可以安全取出的指令,而不影响函数的结果。
另外,以###为前缀的指令执行一次不必要的内存和返回(无论如何,mov 之后的 ### ld1 ... 指令覆盖了由该 ld1 指令加载的 4 个寄存器中的 3 个),并且可以被直接加载到 v0.16b 的单个加载替换-- 和 sshr然后块中间的指令将使用 v0.16b作为其源寄存器。
据我所知,x ,作为局部变量,可以被单元化使用;即使不是,所有寄存器都已正确初始化,因此将它们清零只是为了立即用值覆盖它们是没有意义的。
我倾向于认为这是一个 gcc 错误,但在报告之前,我很好奇我是否遗漏了什么。也许有一个编译标志,一个 __attribute__或者我可以做的其他事情gcc生成健全的代码。
因此,我的问题是:我可以做些什么来生成健全的代码,或者这是我需要向 gcc 报告的错误吗?

最佳答案

在当前开发版本的 gcc 上生成代码似乎有了很大改进,至少在这种情况下是这样。
安装后 gcc-snapshot 包(日期为 20210918),gcc 生成以下代码:

bug:
        ldr     q5, [x1]
        sshr    v4.16b, v5.16b, 7
        mov     v0.16b, v5.16b
        mov     v1.16b, v4.16b
        mov     v2.16b, v4.16b
        mov     v3.16b, v4.16b
        st4     {v0.16b - v3.16b}, [x0], 64
        ldr     q4, [x1, 16]
        mov     v0.16b, v4.16b
        sshr    v4.16b, v4.16b, 7
        mov     v1.16b, v4.16b
        mov     v2.16b, v4.16b
        mov     v3.16b, v4.16b
        st4     {v0.16b - v3.16b}, [x0]
        ret
还不理想——至少两个 mov通过更改 ldr 的目标寄存器,可以在每次迭代中删除指令和 sshr ,但比以前好多了。

关于c - 为什么带有 -O3 的 gcc 会不必要地清除本地 ARM NEON 数组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69488537/

相关文章:

c - MISRA 违规 "441 - Float cast to non-float "

c - gcc中汇编语句的问题

objective-c - Xcode自定义 header 模板: ___DATE___ format

gcc - 关于 ARM 内联装配的不同 clobber 描述的混淆

ios - 创建静态库时未添加 armv7

ios - GoogleMobileAds 的 undefined symbol 问题

c - 如何在 MinGW 上安装 C 库?

c - 作为函数打开文件

c - 将文件指针发送到另一个函数(C 编程)

使用 MinGW32 GCC 编译。只告诉链接库一次?