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));
  }
}

コードは例として提供されているだけで、実行すると segfault が発生します。ポイントは、コンパイルしないことです。コンパイルは次の行を使用して行われます

/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

フラグ toを使用しない場合、コードは問題なくコンパイルされます。今、私はぐるぐる回って、 、または引数リストに を追加することに関連する最初の問題の解決策を見つけました。2 番目の問題 (sse 固有) を解決できませんでした。-fopenmpgccmemcpy-fno-builtin-D_FORTIFY_SOURCE=0gcc

誰でもこれを解決するのを手伝ってもらえますか? 質問:

  • 最も重要なこと: "___builtin_ia32_shufpd" エラーを取り除くには?
  • memcpy問題の正確な理由は何-D_FORTIFY_SOURCE=0ですか?フラグは最終的に何をしますか?
4

1 に答える 1

15

これは、Apple の LLVM ベースの GCC ( llvm-gcc) が OpenMP 領域を変換し、その内部のビルトインへの呼び出しを処理する方法のバグです。この問題は、中間ツリー ダンプを調べることで診断できます (-fdump-tree-all引数を に渡すことで取得できますgcc)。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 つの引数を取る関数呼び出しです。1 つの整数が に%eax、2 つの XMM 引数が%xmm0および%xmm1にあり、結果が返され%xmm0ます (SysV AMD64 ABI 関数呼び出し規約に従って)。対照的に、それなしで生成されたコード-fopenmpは、それが起こるはずの組み込みの命令レベルの展開です。

LBB1_3:
    movapd  -64(%rbp), %xmm0
    shufpd  $1, %xmm0, %xmm0
    movapd  %xmm0, -80(%rbp)

パスする-D_FORTIFY_SOURCE=0memcpy、「強化された」チェック バージョンに置き換えられず、通常の呼び出しmemcpyが代わりに使用されます。これにより、への参照が削除さobject_sizeれますが、組み込み__memcpy_chkへの呼び出しを削除することはできません。ia32_shufpd

これは明らかにコンパイラのバグです。コードをコンパイルするために本当に Apple の 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]);
  }
}

parallel1 つの追加関数呼び出しのオーバーヘッドは、領域に出入りするオーバーヘッドと比較して無視できます。内部で OpenMP プラグマを使用できます。領域funcの動的スコープにより機能しますparallel

GCCをClangに置き換えるという彼らのコミットメントを考えると、Appleは将来的に修正されたコンパイラを提供するかもしれません.

于 2012-10-18T18:32:32.177 に答える