这是由于苹果的基于 LLVM 的 GCC(llvm-gcc
)在转换 OpenMP 区域并处理其中内置函数调用时存在的一个 bug。可以通过检查中间树转储(通过向 gcc
传递 -fdump-tree-all
参数获取)来诊断该问题。如果没有启用 OpenMP,则会生成以下最终代码表示形式(从 test.c.016t.fap
):
main (argc, argv)
{
D.6544 = __builtin_object_size (temp, 0);
D.6545 = __builtin_object_size (temp, 0);
D.6547 = __builtin___memcpy_chk (temp, D.6546, 10, D.6545);
D.6550 = __builtin_ia32_shufpd (v_a, v_a, 1);
}
这是编译器在所有转换之后内部看到代码的类 C 表示形式。这就是它转换为汇编指令的内容。(此处仅显示涉及内置函数的行)
启用 OpenMP 后,并行区域被提取为自己的函数,main.omp_fn.0
:
main.omp_fn.0 (.omp_data_i)
{
void * (*<T4f6>) (void *, const <unnamed type> *, long unsigned int, long unsigned int) __builtin___memcpy_chk.21;
long unsigned int (*<T4f5>) (const <unnamed type> *, int) __builtin_object_size.20;
vector double (*<T6b5>) (vector double, vector double, int) __builtin_ia32_shufpd.23;
long unsigned int (*<T4f5>) (const <unnamed type> *, int) __builtin_object_size.19;
__builtin_object_size.19 = __builtin_object_size;
D.6587 = __builtin_object_size.19 (D.6603, 0);
__builtin_ia32_shufpd.23 = __builtin_ia32_shufpd;
D.6593 = __builtin_ia32_shufpd.23 (v_a, v_a, 1);
__builtin_object_size.20 = __builtin_object_size;
D.6588 = __builtin_object_size.20 (D.6605, 0);
__builtin___memcpy_chk.21 = __builtin___memcpy_chk;
D.6590 = __builtin___memcpy_chk.21 (D.6609, D.6589, 10, D.6588);
}
我只留下了与内置函数有关的代码。显而易见的是(但原因对我来说并不立即显而易见),OpenMP代码转换器确实坚持通过函数指针调用所有内置函数。这些指针赋值:
__builtin_object_size.19 = __builtin_object_size;
__builtin_ia32_shufpd.23 = __builtin_ia32_shufpd;
__builtin_object_size.20 = __builtin_object_size;
__builtin___memcpy_chk.21 = __builtin___memcpy_chk;
生成对不是真正的符号而是由编译器特殊处理的名称的外部引用。链接器然后尝试解析它们,但无法在代码链接到的任何对象文件中找到任何__builtin_*
名称。这也可以通过将-S
传递给gcc
以获取的汇编代码来观察:
LBB2_1:
movapd -48(%rbp), %xmm0
movl $1, %eax
movaps %xmm0, -80(%rbp)
movaps -80(%rbp), %xmm1
movl %eax, %edi
callq ___builtin_ia32_shufpd
movapd %xmm0, -32(%rbp)
这基本上是一个函数调用,它需要3个参数:在%eax
中的一个整数和在%xmm0
和%xmm1
中的两个XMM参数,结果将在%xmm0
中返回(根据SysV AMD64 ABI函数调用约定)。相比之下,没有使用-fopenmp
生成的代码是内部函数展开的指令级扩展:
LBB1_3:
movapd -64(%rbp), %xmm0
shufpd $1, %xmm0, %xmm0
movapd %xmm0, -80(%rbp)
当你传递参数
-D_FORTIFY_SOURCE=0
时,会导致
memcpy
没有被 "fortified" 检查版本所替换,而是使用常规的
memcpy
调用。这将消除对
object_size
和
__memcpy_chk
的引用,但无法删除对内置函数
ia32_shufpd
的调用。
显然,这是一个编译器错误。如果您确实必须使用苹果的GCC编译代码,则一种临时解决方案是将有问题的代码移动到外部函数中,因为该错误显然只影响从
parallel
区域提取的代码。
void func(char *temp, char *argv0)
{
__m128d v_a, v_ar;
memcpy(temp, argv0, 10);
v_ar = _mm_shuffle_pd(v_a, v_a, _MM_SHUFFLE2 (0,1));
}
int main(int argc, char *argv[])
{
char *temp;
#pragma omp parallel
{
func(temp, argv[0]);
}
}
与进入和退出parallel
区域相比,额外的函数调用开销微不足道。您可以在func
内部使用OpenMP编译指示 - 它们将工作,因为parallel
区域具有动态作用域。
也许苹果将来会提供一个修复过的编译器,也可能不会,考虑到他们致力于用Clang替换GCC。
Xcode
附带的llvm-gcc
编译器中的一个错误。它是一个带有GCC前端的LLVM编译器。OpenMP阶段正在生成一些内置函数,而后端无法识别。由于Xcode
正逐步完全替换GCC为clang
,因此该错误可能永远不会得到修复。只需从源代码或其他方法安装真正的GCC并使用它来编译OpenMP代码即可。 - Hristo Ilievgcc
只用于OpenMP代码。此外,我没有直接在我的代码中使用内置函数。我使用memcpy
和_mm_shuffle_pd
。我猜这些应该被clang
支持。无论如何,感谢您的回复。 - angainor_mm_suhffle_pd
被实现为调用__builtin_ia32_shufpd
的内联函数。其他内置函数来自于大多数Unix上启用了FORTIFY_SOURCE
。它将某些不安全的函数替换为更安全的检查变体,这些变体可以是内联实现或由树转换产生。 - Hristo Iliev