29

現在、 VC ++ 11(CTP Update 1)を使用してx86ターゲット用にコンパイルすると、ランダムな浮動小数点エラーが発生します。以下の短い例「test.cpp」を参照し、以下を使用してコンパイルしてください。

cl /GL /O2 /EHsc test.cpp /link /MACHINE:X86

出力はである必要がありますが、 (プログラム全体の最適化)が有効になっている場合10 == 10に生成されます。問題は、結果を浮動小数点スタックにプッシュすることのようですが、呼び出し元の関数は、SSEレジスタXMM0に結果を期待しています。10 == 0/GLget_scaling_factor()

質問:私は明らかな何かを見逃していますか、それともこれは本当にバグですか?もちろん、テストプログラムは簡素化されたテストケースであるため、意味がありません。

test.cpp:

#include <iostream>

template <typename T>
inline T get_scaling_factor(int units)
{
    switch (units)
    {
    case 0: return 1;  
    case 1: return 10;  
    case 2: return 100;  
    case 3: return 1000;  
    case 4: return 10000;  
    case 5: return 100000;  
    case 6: return 1000000;  
    case 7: return 10000000;  
    case 8: return 100000000;  
    case 9: return 1000000000; 
    default: return 1;
    }
}

template <int targetUnits, typename T>
inline T scale(T value, int sourceUnits)
{
    return value   * get_scaling_factor<T>(sourceUnits) 
                   / get_scaling_factor<T>(targetUnits);
}

__declspec(noinline)
double scale(double value, int units) 
{
    return scale<9>(value, units);
}

int main()
{
    std::cout << "10 = " << scale(1e9, 1) << std::endl;
}

アップデート

Microsoftによって確認された問題。これは、次のような単純なコードにも影響します。

#include <stdio.h>
double test(int a)
{
    switch (a)
    {
    case 0: return 1.0;
    case 1: return 10.0;
    case 2: return 100.0;
    case 3: return 1000.0;
    case 4: return 10000.0;
    case 5: return 100000.0;
    case 6: return 1000000.0;
    case 7: return 10000000.0;
    case 8: return 100000000.0;
    case 9: return 1000000000.0;
    default: return 1.0;
    }
}

void main()
{
    int nine = 9;
    double x = test(nine);
    x /= test(7);
    int val = (int)x;
    if (val == 100)
        printf("pass");
    else 
        printf("fail, val is %d", val);
}
4

2 に答える 2

23

はい、これは間違いなくコードオプティマイザのバグであり、問​​題なく再現できました。オプティマイザーのバグは通常、インライン化に関連していますが、ここではそうではありません。このバグは、新しい自動ベクトル化機能をサポートするVS2012でのコード生成の大幅な変更によって発生しました。

一言で言えば、get_scaling_factor()関数はFPUスタックに結果を返します。コードジェネレータは、スタックからそれを取得してXMMレジスタに格納するための命令を適切に発行します。ただし、オプティマイザが不適切な場合は、関数の結果がすでにXMM0に格納されていると想定しているかのように、そのコードが完全に削除されます。

回避策を見つけるのは困難です。double用のテンプレート関数を特殊化しても効果はありません。#pragmaoptimizeで最適化を無効にすると機能します。

#pragma optimize("", off)
__declspec(noinline)
double scale(double value, int units) 
{
    return scale<9>(value, units);
}
#pragma optimize("", on)

あなたの再現コードは非常に優れており、Microsoftはこれからこのバグを修正するのに問題はありません。この質問にリンクするだけで、connect.microsoft.comにフィードバックレポートを提出できます。または、お急ぎの場合は、Microsoftサポートに連絡してください。ただし、サービスパックを継続するための同じ回避策が提供されると思います。


更新:VS2013で修正されました。

于 2012-10-24T17:33:05.990 に答える
1

/GL設計上、デフォルトの呼び出し規約を無視します。LTCGを使用すると、コンパイラー/リンカーはコールグラフ全体を認識しているため、呼び出し元と呼び出し先を照合できます。SSEレジスタを使用することは、そのコンテキストでは奇妙ではありません。

get_scaling_factor()ただし、「結果を浮動小数点スタックにプッシュする」とはどういう意味か完全にはわかりません。コンパイラがインライン化に失敗したということですか?コールグラフには呼び出し元が1つしかないため、コンパイラーがそうすることを期待しています。(`get_scaling_factor(targetUnits)がインライン化されていることはわかっています。そうしないと、ゼロによる除算が発生するためです)

コンパイラが実際にインライン化に失敗した場合、get_scaling_factor()実際には2つのバグが見つかりました。1つはインライン化の失敗、もう1つはカスタム呼び出し規約の失敗です。

于 2012-10-24T16:53:17.047 に答える