标量是否可以安全地对GCC/Clang的向量类型进行别名访问?
我一直在使用 Apple SIMD,它似乎是用GCC/Clang的 向量扩展 实现的。尽管C 标准因为 严格别名规则 禁止大多数指针类型的别名化,我还是在讨论一个非标准扩展,因此C 标准可能不适用。
我注意到针对 simd_packed_* 的Apple头文件提到了以下内容:
These types should be used to access buffers that may not be sufficiently
aligned to allow them to be accessed using the "normal" simd vector types.
As an example of this usage, suppose that you want to load a vector of
four floats from an array of floats. The type simd_float4 has sixteen byte
alignment, whereas an array of floats has only four byte alignment.
Thus, naively casting a pointer into the array to (simd_float4 *) would
invoke undefined behavior, and likely produce an alignment fault at
runtime. Instead, use the corresponding packed type to load from the array:
simd_float4 vector = *(simd_packed_float4 *)&array[i];
// do something with vector ...
这段描述若成立,意味着至少可以安全地把一个(对齐充分的)标量浮点缓冲区使用向量类型进行别名化。
然而,反过来是否也成立?
假设我有:
simd_float4 vec; // __attribute__((__ext_vector_type__(4))) float
float *scalar_ptr = (float *)&vec; // Is this safe?
printf("%f\n", *scalar_ptr);
我可以安全地把 vec 的分量作为标量进行别名化吗,而不使用诸如 vec.x 或 vec[0] 这样的向量分量访问器?
我之所以这样问,是因为直接取向量分量的地址(如 &vec.x)会导致编译时错误,但将向量指针强制转换为标量指针似乎是一个变通办法。
GCC或 Clang对向量与标量之间的别名化安全性是否有任何说明?
解决方案
与向量元素相同类型时,我想这是对的,但我还没看到任何实际的GCC或 Clang文档明确表示这是对的(既包括把向量指针指向标量数组,亦或反向)。我认为至少GCC会这样处理,也大概Clang也会把向量浮点类型视为由实际的 float 对象组成,因为它们是用一个以 float 为基类型的typedef定义的。如果是这样,这也解释了为什么把一个 float* 指向一个 simd_float4 是安全的。
使用不同元素类型时,例如把 int32_t 置入一个由 long long 组成的向量中(如 __m256i),显然不行: GCC AVX __m256i cast to int array leads to wrong values 是一个实际破坏的例子。
在我偶尔尝试时(并非把数据存到临时数组,或手动使用打乱来为对低位元素的自由强制转换做准备),我从未见过同类型指针造成的破坏现象。并且这也符合编译器开发者对向量的理解,并据此编写编译器。否则你将始终需要 memcpy 或一个加载内在来从数组加载GNU C原生向量。 (或者通过 [] 运算符遍历向量的标量元素,但我认为这并非预期用法。它们似乎文档不足,尽管如此。)
将向量对象强制转换为同等大小但元素类型不同的向量只是重新解释位模式。我认为这就是例如将 int32_t 的位模式加载到一个含有 float 的向量中,或从一个 char[] 缓冲区加载的预期方式。
如果你在 typedef float aliasing_v4f __attribute__((vector_size (16),may_alias)); 上(如英特尔的 __m128)你可以把它指向任何东西(包括 int32_t 或 int64_t 数组),但你仍然不能把其他标量指针指向它,你需要一个 aliasing_i32 typedef来获取一个作为 int 的位模式。指针类型需要具备 may_alias,指向的类型是否具备并不重要1。
你也可以定义对齐要求更低的向量,因此对这样的向量指针进行解引用时会生成未对齐加载指令。比如 typedef float v4f_u __attribute__((vector_size(16),aligned(1),may_alias));。你可以把它指向任何对齐的对象,以加载16字节的数据。你所引用的Apple头文件把这称为 simd_packed_float4,其中“packed”表示“未对齐”,就像结构体上的 __attribute__((packed)) 可能导致成员未对齐一样。我不确定他们使用的是 aligned(1) 还是 aligned(4),但注释说它比默认的 16 的对齐性要低。(Clang允许向量类型之间进行隐式转换;GCC可能需要一个 (simd_float4)*deref 强制转换来避免警告。)
我对Clang的 __ext_vector_type__(4) 了解不多;我不认为它和GCC的 __vector_size__(16) 在语义上有本质不同,唯一的区别可能是按元素而不是按字节来计数。
Footnote 1:在匿名动态分配的内存中,只有通过指针进行访问时,其中一个可以是非别名类型,只要其他都是通过typedef的 may_alias 类型或属于 char* 的类型。就抽象机器而言,那里对象的类型是去掉 may_alias 的那个类型,其他的则是在别名指向它。
相关: