2

私のオープンソースソフトウェアプロジェクトでは、gccアトミックビルトインを呼び出します:__ sync_add_and_fetchと__sync_sub_and_fetchは、特定の変数にアトミックインクリメントとデクリメントを実装します。コードをコンパイルしようとしている人から定期的にメールが届きますが、次のリンカーエラーが発生します。

refcountobject.cpp:(.text+0xb5): undefined reference to `__sync_sub_and_fetch_4'
refcountobject.cpp:(.text+0x115): undefined reference to `__sync_add_and_fetch_4'

少し掘り下げた後、根本的な原因を絞り込んで、古いバージョンのgcc(4.1)がデフォルトでi386のターゲットアーキテクチャになっているという事実に絞り込みました。そして明らかに、gccは実際には80386でのアトミック加算に固有のものを持っていないので、その場所に未定義の__sync_add_and_fetch_4呼び出しを暗黙的に挿入します。これがどのように機能するかについての素晴らしい説明はここにあります。

ここで説明するように、簡単な回避策は、コンパイラフラグの1つとして-march=pentiumを追加するようにMakefileを変更するように指示することです。そして、すべてが良いです。

では、ユーザーがMakefileを手動で修正する必要がないように、長期的な修正とは何でしょうか。

私はいくつかのアイデアを検討しています:

-march=pentiumをコンパイラフラグとしてMakefileにハードコーディングしたくありません。Intelベースではないものなら何でも壊れると思います。しかし、Makefileにデフォルトのターゲットがi386であることを検出するルールがあれば、確かにそれを追加することができます。私は、gcc -dumpmachineを呼び出し、最初のトリプレットを解析するスクリプトであるルールをMakefileに含めることを考えています。文字列がi386の場合、コンパイラフラグが追加されます。私は、80386マシン用に実際に構築している人はいないと思います。

もう1つの方法は、リンカーがフォールバックするための__sync_add_and_fetch_4の実装を実際に提供することです。定義されているGCC_HAVE_SYNC_COMPARE_AND_SWAPマクロの存在に基づいて条件付きでコンパイルすることもできます。グローバルpthread_mutexを使用して実装のプロトタイプを作成しました。最高のパフォーマンスではない可能性がありますが、問題はうまく機能して解決します。x86用にコンパイルする場合は、実装用に「lockxadd」を呼び出すようにインラインアセンブリを自分で作成することをお勧めします。

4

2 に答える 2

1

これは私の他の実用的な解決策です。特定の状況で使用できる可能性がありますが、上記のmakefile+scriptソリューションを選択しました。

この解決策は、_sync_add_and_fetch_4、_sync_fetch_and_add_4、_sync_sub_and_fetch_4、および_sync_fetch_and_sub_4のローカル定義を別のソースファイルで提供することです。それらは、コンパイラーがそれらをネイティブに生成できなかった場合にのみリンクされます。いくつかの組み立てが必要でしたが、すべての場所のウィキペディアには、私が参照できる合理的な実装がありました。(また、コンパイラーが通常生成するものを分解して、他のすべてが正しいかどうかを推測しました)。

#if defined(__i386) || defined(i386) || defined(__i386__)
extern "C" unsigned int xadd_4(volatile void* pVal, unsigned int inc)
{

    unsigned int result;
    unsigned int* pValInt = (unsigned int*)pVal;

    asm volatile( 
        "lock; xaddl %%eax, %2;"
        :"=a" (result) 
        : "a" (inc), "m" (*pValInt) 
        :"memory" );

    return (result);

}

extern "C" unsigned int __sync_add_and_fetch_4(volatile void* pVal, unsigned int inc)
{
    return (xadd_4(pVal, inc) + inc);
}

extern "C" unsigned int __sync_sub_and_fetch_4(volatile void* pVal, unsigned int inc)
{
    return (xadd_4(pVal, -inc) - inc);
}

extern "C" unsigned int __sync_fetch_and_add_4(volatile void* pVal, unsigned int inc)
{
    return xadd_4(pVal, inc);
}

extern "C" unsigned int __sync_fetch_and_sub_4(volatile void* pVal, unsigned int inc)
{
    return xadd_4(pVal, -inc);
}

#endif
于 2013-02-24T01:41:15.987 に答える
0

返事がなかったので、私は自分で解決するためにそれを打ちました。

考えられる解決策は2つあり、そのうちの1つです。

まず、次のスクリプトgetfixupflags.shをMakefileと同じディレクトリに追加します。このスクリプトは、コンパイラがi386をターゲットにしている可能性があるかどうかを検出し、ターゲットにしている場合は、出力として「-march=pentium」をエコーアウトします。

#!/bin/bash

_cxx=$1
_fixupflags=
_regex_i386='^i386'

if [[  ! -n $_cxx ]]; then echo "_cxx var is empty - exiting" >&2; exit; fi

 _target=`$_cxx -dumpmachine`
if [[ $_target =~ $_regex_i386 ]]; then 
    _fixupflags="$_fixupflags -march=pentium"
fi

if [[ -n $_fixupflags ]]; then echo $_fixupflags; fi

次に、このスクリプトを使用するようにMakefileを修正します。Makefileに次の行を追加します

FIXUP_FLAGS := $(shell getfixupflags.sh $(CXX))

次に、Makefileのコンパイラ指令を変更して、コードのコンパイル時にFIXUP_FLAGSを含めます。例えば:

%.o: %.cpp
    $(COMPILE.cpp) $(FIXUP_FLAGS) $^
于 2013-02-24T01:24:27.940 に答える