Visual C++ 2012 (x86) 中可能的编译器错误?

Posted

技术标签:

【中文标题】Visual C++ 2012 (x86) 中可能的编译器错误?【英文标题】:Possible compiler bug in Visual C++ 2012 (x86)? 【发布时间】:2012-10-24 15:02:13 【问题描述】:

我目前在使用 VC++ 11 (CTP Update 1) 为 x86 目标编译时遇到随机浮点错误。请参阅下面的简短示例“test.cpp”,并使用以下命令进行编译:

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

输出应该是10 == 10,但是当启用/GL(全程序优化)时,它会产生10 == 0。问题似乎是 get_scaling_factor() 将结果推送到浮点堆栈上,但调用函数期望它在 SSE 寄存器 XMM0 中。

问题:我是否遗漏了一些明显的东西,或者这真的是一个错误?当然,测试程序没有意义,因为它是一个精简的测试用例。

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;


更新

问题confirmed by 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);

【问题讨论】:

static_cast 要键入的文字 T? @Steve-o: 否 - 即使您将所有文字包装在 T(...) 中,它也会失败。无论如何都没有必要,编译器会将它们提升为正确的类型(如果可以的话)。请注意,即使在最高警告级别下,代码也会编译没有警告。 【参考方案1】:

是的,这绝对是一个代码优化器错误,我可以毫无问题地重现它。优化器错误通常与内联相关,但此处并非如此。这个错误是由 VS2012 中支持新的自动矢量化功能的大量代码生成更改引入的。

简而言之,get_scaling_factor() 函数在 FPU 堆栈上返回结果。代码生成器正确地发出指令以从堆栈中检索它并将其存储在 XMM 寄存器中。但是优化器不恰当地完全删除了该代码,就好像它假设函数结果已经存储在 XMM0 中一样。

解决方法很难找到,将模板函数专门用于 double 无效。使用#pragma optimize 禁用优化:

#pragma optimize("", off)
__declspec(noinline)
double scale(double value, int units) 

    return scale<9>(value, units);

#pragma optimize("", on)

您的复制代码非常好,Microsoft 可以轻松地修复此错误。您可以在 connect.microsoft.com 提交反馈报告,只需链接到此问题即可。或者,如果您赶时间,那么您可以联系 Microsoft 支持,尽管我想他们会为您提供相同的解决方法,让您最后使用 Service Pack。


更新:已在 VS2013 中修复。

【讨论】:

汉斯,感谢您提供的有用分析。我听取了您的建议并提交了bug report 跟进您的解决方法:实际上重写get_scaling_factor() 并引入变量T result; 并让switch 语句在返回之前为其分配正确的返回值就足够了。然后编译器产生正确的结果。但是,由于我不知道是什么触发了这个错误,我不得不假设它可能随时随地咬我。这基本上意味着 LTCG 不能用于生产代码 已确认。只需添加一个局部变量并对其进行初始化就足以使函数突然使用 xmm0 而不是 FPU 作为优化代码中的返回值。看起来真正的错误是优化器丢失了使用 FPU 或 SSE 的函数。【参考方案2】:

/GL 在设计上忽略了默认调用约定。使用 LTCG,编译器/链接器知道整个调用图,因此它可以匹配调用者和被调用者。在这种情况下使用 SSE 寄存器并不奇怪。

不过,我不完全确定“get_scaling_factor() 将结果推送到浮点堆栈”是什么意思。您的意思是编译器无法内联它吗?我希望编译器这样做,因为调用图只有一个调用者。 (我们知道 `get_scaling_factor(targetUnits) 是内联的,否则会导致除以零)

如果编译器确实无法内联get_scaling_factor(),那么您实际上发现了两个错误:一个内联失败,一个自定义调用约定失败。

【讨论】:

感谢您的回答。编译器半内联get_scaling_factor():它内联开关的默认分支,但不内联其余部分。文字被压入浮点堆栈(使用fld),但调用函数希望它们位于xmm0

以上是关于Visual C++ 2012 (x86) 中可能的编译器错误?的主要内容,如果未能解决你的问题,请参考以下文章

在 Microsoft Visual Studio 中的 x86 C++ 应用程序中集成犰狳

Win7 64位无法安装上Microsoft Visual C++ 2010(x86) Redistributable

如何用visual studio2012编c++程序?

为什么Microsoft Visual C++ 2005 SP1 Redistributable Package (x86)不能安装,装了之后其他的还是不能装

如何在 Visual C++ 2010 中使用 C++ 库 [重复]

如何将 x86 和 x64 asm 文件包含到单个 Visual Studio 项目中?