gcc 加载GNU C泛型向量的正确方法是什么?

vaj7vani  于 2023-10-19  发布在  其他
关注(0)|答案(1)|浏览(113)

GCC/Clang提供的向量扩展是一种方便的方法,可以在多种架构(如webassembly,arm 64,x64)上启用SIMD向量化。

using v8x16u = uint8_t __attribute__((vector_size(16)));
v8x16u add(v8x16u a, v8x16u b) { return a + b; }
        add     v0.16b, v1.16b, v0.16b
        ret

它很容易做一些SIMD编程(即使在某些情况下性能不太高)。
我无法从任何文档中找到的是 * 从内存中初始化这些向量的首选/规范方法是什么 *。

v8x16u load(uint8_t const *p) {
   v8x16u a;
   for (int i = 0; i < 16; i++) a[i] = p[i];
}

在clang x64和clang armv 8上确实可以正常工作,但在GCC 7.3上有点不太理想

ldr q0, [x0]    // clang armv8
   movdqu  xmm0, XMMWORD PTR [rdi]  // gcc x64 7.3
   movups  xmm0, xmmword ptr [rdi]  // clang 15.0.0 x64

   sub     sp, sp, #16 // GCC arm64 7.3
   ldr     q0, [x0]
   add     sp, sp, 16

在webassembly中,结果是灾难(循环)。对于webassembly和clang来说,展开加载是有效的,但对于GCC来说则不然,因为GCC会加载16个单独的字节

v8x16u load(uint8_t const *p) {
   v8x16u a{p[0],p[1],p[2],p[3],
            p[4],p[5],p[6],p[7],
            p[8],p[9],p[10],p[11],
            p[12],p[13],p[14],p[15]
   };
   return a;
}
        local.get       0
        v128.load       0:p2align=0
        end_function

最后,类型双关确实可以编译,但它不会引入UB吗?而且clang似乎在实现中可能有一个bug(因为在这种情况下,使用vs typedef应该同样可以使用AFAIK,保留属性)

v8x16u load_fail(uint8_t const *p) {
    using Vec = uint8_t __attribute__((vector_size(16), aligned(1)));
    return *reinterpret_cast<const Vec*>(p);
}
        movaps  xmm0, xmmword ptr [rdi]

v8x16u load_okish(uint8_t const *p) {
    typedef uint8_t Vec __attribute__((vector_size(16)))  __attribute__((aligned(1)));
    return *reinterpret_cast<const Vec*>(p);
        movups  xmm0, xmmword ptr [rdi]
}
pkmbmrz7

pkmbmrz71#

使用适当的aligned属性将指针强制转换为向量类型,并解引用它。当指向内存的类型(它的动态类型,可能与声明的类型不同-在放置new之后,或者如果它首先在堆上分配)与vector的底层标量类型兼容时,它不会引入UB。GCC允许标量和向量类型之间的别名,但还没有文档说明。对于Clang/LLVM,它似乎是相同的(允许在没有文档的情况下使用别名)。对于这两个编译器,自动向量化在内部引入对标量数组的向量类型访问的方式自然会使保证福尔斯失效。
由于评论中提到的Clang错误,需要使用typedef而不是using引入具有减少对齐的变体。
您可以额外引入具有may_alias属性的向量变量,用于访问未知/任意类型的内存(当标量实现将使用memcpy或基于字符的访问时),或者使用char的向量来执行内存访问,然后将向量位转换为计算所需的类型。
使用memcpy是有风险的,因为系统头通常会使用__builtin_object_size的内联变体来覆盖它,以进行加固(所谓的FORTIFY_SOURCE功能),这会干扰优化,特别是内联的速度/大小估计。显式地使用__builtin_memcpy可以避免这种情况,但在这一点上,使用自定义向量类型似乎更干净。

相关问题