【问题标题】:VLD2 structure load of a stricter alignment type更严格对齐类型的 VLD2 结构载荷
【发布时间】:2021-05-22 21:22:57
【问题描述】:

字节指针能否安全地传递给vld2q_u16? 我最关心的是静态分析器投诉。

uint16x8x2_t load_interleaved_shorts (const uint8_t* const ptr) {
    uint16_t* p16 = (uint16_t*)ptr; // possible undefined behavior ?
    return vld2q_u16(p16);
}

在我的例子中: 指针始终与 16 字节边界对齐。 编译器不知道指针的对齐方式。 代码必须是可移植的,并且严格遵循 C90 标准。

假设: 将 vld2q_u16 替换为 vld1q_u8 / vuzpq_u8 会损害性能。 编译器将标量模式优化为vld2q_u16 的概率很小。

编辑:通过强制转换为 void 指针来抑制一些警告。 vld2q_u16((const uint16_t*)(const void*)src)

【问题讨论】:

    标签: c simd intrinsics memory-alignment neon


    【解决方案1】:

    代码必须是可移植的,并且严格遵循 C90 标准。

    ...加上 ARM NEON 内在函数的存在所暗示的一切! (尽管这可能对静态分析器没有帮助)。 (相关:Is `reinterpret_cast`ing between hardware SIMD vector pointer and the corresponding type an undefined behavior? 讨论了 x86,但您的情况有点不同;您的指针是对齐的)。


    在 C 中,在指针类型之间进行转换是安全的(无需取消引用),只要您从不创建与其类型对齐不足的指针。您不需要编译时可见的对齐保证,您只需要永远不要实际创建没有 alignof(uint16_t) 对齐的 uint16_t*

    (这使得静态分析器即使不是这种情况也不太可能抱怨,除非它可以看到类似 (uint16_t*)(1 + (char*)&something_aligned) 的内容,您可以在其中获取对齐的地址并将其偏移奇数,这是可以保证的产生一个未对齐的地址。)

    实际上,针对字节可寻址机器的编译器或多或少地定义了行为,即使是创建未对齐的指针也是如此。 (例如,未对齐加载的英特尔内在函数依赖于创建未对齐的__m128i*。)只要您不取消引用它们,即使在允许未对齐加载的目标上,这在实践中也是不安全的;请参阅 my answer on this Q&A for an example 和涵盖其他示例的博客链接。

    所以你 100% 没问题:你的代码永远不会创建未对齐的 uint16_t*,也不会直接取消引用它。

    如果 ARM 具有未对齐的加载内在函数,甚至可以安全地形成未对齐的 uint16_t* 并将其传递给函数;内部 API 的存在/设计意味着以这种方式使用它是安全的。


    其他未定义但您没有做的事情:

    • 从技术上讲,UB 形成一个不指向对象内部或不指向过去的指针,但实际上主流实现也允许这样做。

    • 取消引用不指向uint16_t 对象的uint16_t* 是严格别名UB。但是任何取消引用都只发生在固有的“函数”内部,所以你不必担心严格的别名规则。 (这可能会将指针转换为某些特殊类型并取消引用,或者可能会将指针传递给内置的__builtin_arm_whatever() 编译器。)

    我假设 ARM 加载/存储内部函数的定义类似于 memcpy,能够读取/写入任何对象的字节。 例如您可以在 intdoublechar 的数组上使用 vld2q_u16。 Intel 内部函数以这种方式定义的(例如 GCC/clang 使用 __attribute__((may_alias))。)如果不是,它就不安全了。

    顺便说一句,char*-can-alias-anything 规则只适用于一种方式。是的,将char* 指向uint16_t 是安全的,但是如果您有一个char buf[100] 的实际数组,那么这些对象肯定是char 对象,并且通过@ 访问它们是UB 987654343@。但是,如果您只有char*,并且只使用了除char* 之外的另一种指针类型,那么您可以将内存视为具有其他类型,并且每个char* 访问别名。

    【讨论】:

    • gcc 转换为 __builtin_neon_hi * 但我找不到该类型的信息。
    • @aqrit:GCC 整数类型大小代码是“si” = 单整数(即int),“hi” = 半整数,“di” = 双整数等。所以我假设它是半整数 (int16_t) 元素的 NEON 向量的内置名称。 IDK 在哪里可以找到有关__builtin_neon_* 的更多一般详细信息,但通过 google 搜索发现 GCC 源代码显示这些名称用户可见。 github.com/gcc-mirror/gcc/blob/….
    • 您可以构建测试用例以查看它们是否尊重别名,例如存储一个uint16,向量加载,存储另一个uint16,再次向量加载。如果优化没有意识到向量负载对 uint16_t 赋值的别名,它可以将第一个作为“死存储”消除。
    • 失败了,刚刚得到error: cast from 'const BYTE *' (aka 'const unsigned char *') to 'const __m128i *' increases required alignment from 1 to 16 [-Werror,-Wcast-align]
    • @aqrit:啊,是的,静态分析有时会警告可能是法律代码的一部分,但也可能是问题的迹象。听起来你应该只使用-Wno-cast-align,或者通过编译指示为某些功能启用它。不要为了让过度谨慎的编译器警告满意而使您的代码变得更糟。 (或者您可以在指针类型上使用__attribute__((aligned(16)))?但这很容易让您陷入混乱,每个 aligned_char 元素的大小为16,包括填充,而不是仅仅承诺编译器一个特定的指针已对齐。)
    猜你喜欢
    • 2014-01-24
    • 2013-03-27
    • 1970-01-01
    • 2019-03-28
    • 2021-12-03
    • 2021-09-21
    • 2013-10-16
    • 2023-03-04
    • 1970-01-01
    相关资源
    最近更新 更多