2012-08-22 4 views
17

を有効:このコードは、排他的または2のを実行することになっている奇妙な行動は、私はこのコードの小さなスニペット(これは私が持っている問題の最小限の実施例である)を持つ

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

void xorBuffer(unsigned char* dst, unsigned char* src, int len) 
{ 
    while (len != 0) 
    { 
     *dst ^= *src; 
     dst++; 
     src++; 
     len--; 
    } 
} 

int main() 
{ 
    unsigned char* a = malloc(32); 
    unsigned char* b = malloc(32); 
    int t; 

    memset(a, 0xAA, 32); 
    memset(b, 0xBB, 32); 

    xorBuffer(a, b, 32); 

    printf("result = "); 
    for (t = 0; t < 32; t++) printf("%.2x", a[t]); 
    printf("\n"); 

    return 0; 
} 

を32 (概念的には、a = a^bを実行する必要があります)。 0xAA^0xBB = 0x11以降、 "11"は32回印刷されるはずです。

私はこの問題をMinGW-GCC(Windows)でコンパイルすると完全にデバッグモード(最適化なし)で動作しますが、-O3から始まる最適化が有効になっているときにxorBufferループの途中でSIGILLでクラッシュします。また、私が問題のループにprintfを置くと、それは再び完全に動作します。私はスタックの破損を疑うが、私はここで間違っているのか分からない。

最適化を有効にしてGDBを使ってデバッグしようとすると、すべての変数に対して「GDBが最適化されている」と表示されるため、失われてしまいます(もちろん、変数を出力しようとすると突然動作します)。

ここで何が起こっているのか誰かが知っていますか?私はこの問題に関してあまりにも長い住居を過ごしてきました。私の推測では、基本的なCポインタの知識が欠落していますが、私にとってはコードが正しいように見えます。それはバッファのインクリメントから来るかもしれませんが、私が知る限り、sizeof(unsigned char) == 1なので、バイトごとに1つずつ進むべきです。

コードは私のLinuxマシン上のGCCでの最適化を行っても機能します。

ここには何がありますか?ありがとう! -O2で

:プログラム全体の要求、アセンブリ出力として

clicky

-03付:clicky

私は(と実行しているGCC 4.6.2でこの動作を確認MinGWの)私のコメントから

+3

「O2」の使用はどうですか? 'O3'はかなり危険です。 SIGILLは不正な命令によって発生したシグナルを意味します。コンパイラのバグのように見えます。または私は何かが欠けている。 –

+1

これはgcc 4.4.3で完璧に動作します。もし誰も気づかなければ、gccによって生成されたアセンブリコードを 'O3'と' O2'(どちらも正常に動作すればそうでなければ、最適化の低レベル)で表示しようとするかもしれません。 –

+0

@KirilKirovこのバグもともと高性能ライブラリで発見されたので、O2に行くのは本当に最適な解決策ではありません。もちろん、コンパイラのバグであれば修正が出るまでO2に落とします。最適化を行わず-O3で生成したアセンブリを投稿します。 – Thomas

答えて

8

馬コンパイラがターゲットアーキテクチャに関する正しい情報を持っていることを確認してください。 -O3の出力から、コンパイラがSIMD最適化を設定しているようですが、実際にはベクトル命令(movdqaなど)を使用してコードをより並列化しています。ターゲットプロセッサが、コンパイラがコードを発行しているものと100%一致しない場合、違法な命令が出る可能性があります。

+0

ありがとう、それはそれでした(詳細は私自身の「拡張の答え」を参照してください)。 – Thomas

8

私はUnwindの答えの拡張子としてこれを追加しています(これは私が正しい軌道に乗っているので受け入れます)。

最適化されたコードを調べた結果、私はAVX命令に気づいた。最初は、私のプロセッサがAVX命令セットをサポートしていることを考慮すれば、問題は発生しないと考えました。しかしAVX1とAVX2という2つの異なるAVXバージョンがあることが判明しました。私のプロセッサはAVX1しかサポートしていませんが、gccはプロセッサが2つのバージョンのいずれかをサポートしている限り無差別にAVX2オペコードを使用します(llvmは同じ間違いを犯しています)。bug reportsです。これは、私が考える限り、誤った操作とコンパイラのバグです。

結果はAVX1システム上のAVX2コードであり、明らかに不正な命令につながります。32ビットより小さい入力(256ビットのレジスタ幅のため)で失敗しないコードから、SSE3に限られたCPUサポートを持つ仮想マシンである私のLinuxボックスで動作するコードまで、多くのことが説明されています。

修正点は、-O3を無効にして-O2に戻ります。ここでgccは単純なコードを最適化するためのハードコアSIMD命令に頼らないか、volatileキーワードを使用して強制的にバッファは、バイトごとにバイト、苦労し、そのよう:

*(unsigned char volatile *)dst ^= *(unsigned char volatile *)src; 

もちろん、これは非常に遅く、ちょうど(プログラム全体の影響を無視して)-O2を使用するよりもおそらく悪いですが、それは、バッファを経由することで回避することができますintをintで置き換え、最後に余白を入れてください。速度に関しては十分です。

また、このバグのないgccのバージョンにアップグレードすることもできます(このバージョンはまだ存在しないかもしれませんが、私はチェックしていません)。

編集:究極の修正は、GCCで-mno-avxフラグを投げて、AVXオペコードをすべて無効にして、コードを変更しないでバグを完全に無効にすることです(そしてパッチを当てたコンパイラのバージョンが利用可能)。

どのようなperverseコンパイラのバグ。

+0

+1の話は完全な話です。この奇妙な部分を狩ったことをお祝いします。 –

関連する問題