在Mac OS X Lion上使用OpenMP进行编译失败(memcpy和SSE内置函数)

8

我遇到了以下问题。下面的代码片段在我尝试的任何Xcode版本(4.4、4.5)中都无法与Mac OS X联接。

#include <stdlib.h>
#include <string.h>
#include <emmintrin.h>

int main(int argc, char *argv[])
{
  char *temp;
#pragma omp parallel
  {
    __m128d v_a, v_ar;
    memcpy(temp, argv[0], 10);
    v_ar = _mm_shuffle_pd(v_a, v_a, _MM_SHUFFLE2 (0,1));
  }
}

这段代码只是作为示例提供,当您运行它时,它会导致段错误。重点在于它无法编译。编译使用以下命令进行:

/Applications/Xcode.app/Contents/Developer/usr/bin/gcc test.c -arch x86_64 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk -mmacosx-version-min=10.7 -fopenmp

 Undefined symbols for architecture x86_64:
"___builtin_ia32_shufpd", referenced from:
    _main.omp_fn.0 in ccJM7RAw.o
"___builtin_object_size", referenced from:
    _main.omp_fn.0 in ccJM7RAw.o
ld: symbol(s) not found for architecture x86_64
collect2: ld returned 1 exit status

当不使用-fopenmp标记编译代码时,代码可以正常编译。我在谷歌上搜到了解决第一个与memcpy有关的问题的方法,即将-fno-builtin-D_FORTIFY_SOURCE=0添加到gcc参数列表中。但是我无法解决第二个问题(sse内部函数)。
请问有人可以帮我解决吗?以下是我的问题:
  • 最重要的:如何摆脱“___builtin_ia32_shufpd”错误?
  • 什么是导致memcpy问题的确切原因? -D_FORTIFY_SOURCE=0标记最终会做什么?

@Walter 谢谢。段错误不是问题,代码只是一个例子 - 当然是错误的。你使用的是gcc 4.7.1,而不是Xcode编译器,对吗?你能用我给出的命令行编译吗?在这里改变优化级别没有帮助... - angainor
1
这是Xcode附带的llvm-gcc编译器中的一个错误。它是一个带有GCC前端的LLVM编译器。OpenMP阶段正在生成一些内置函数,而后端无法识别。由于Xcode正逐步完全替换GCC为clang,因此该错误可能永远不会得到修复。只需从源代码或其他方法安装真正的GCC并使用它来编译OpenMP代码即可。 - Hristo Iliev
@HristoIliev 那可能是我要做的事情。但问题是为什么相同的内置函数在没有OpenMP的情况下也能工作?似乎gcc只用于OpenMP代码。此外,我没有直接在我的代码中使用内置函数。我使用memcpy_mm_shuffle_pd。我猜这些应该被clang支持。无论如何,感谢您的回复。 - angainor
Clang目前还不支持OpenMP。 _mm_suhffle_pd被实现为调用__builtin_ia32_shufpd的内联函数。其他内置函数来自于大多数Unix上启用了FORTIFY_SOURCE。它将某些不安全的函数替换为更安全的检查变体,这些变体可以是内联实现或由树转换产生。 - Hristo Iliev
我今天晚些时候会在我的 Mac 上检查一件事情,并给你写一个更完整的答案。 - Hristo Iliev
显示剩余3条评论
1个回答

15

这是由于苹果的基于 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 并安装了 Walter 先前提到的 macports gcc 4.7。它没有出现任何问题。唯一让人烦恼的是,为了使用 macports,您仍然需要安装 Xcode 命令行工具,并且您会得到一组新的编译器。真是太神奇了。 - angainor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接