启用优化程序时GCC浮点错误

时间:2016-02-11 04:51:58

标签: c gcc compiler-optimization

我正在寻找GCC优化器错误的解决方法。这个bug在v4.5中,仍然存在于v5.3.0中,唉。这是问题C代码片段(类似printf的函数的一部分):

d *= factor;
if ((d > 0) && (d < 1))     /* 9.9e-1 instead of 0.99e0 */
{
    exp--;
    d *= 10;
}
else if (!sci && (d >= 10)) /* 1e0 instead of 10e-1 */
{
    exp++;
    d /= 10;
}

使用-O1或-O2时,此代码不会产生正确的结果。但是,如果我在“d * = factor”之后和“if”之前插入一个函数调用,如srand()或类似函数,那么代码编译正确并产生预期的结果。

我已经尝试插入其他东西来代替函数调用,试图将编译器推送到不同的状态而没有错误,但到目前为止只有函数调用似乎有效。

这引出了一个问题:对解决方法有任何更好的建议吗?

我无法为此生成一个小测试用例,或者我会将其报告为GCC错误。如果我从较大的函数中提取上面的代码段,它可以正常工作;它只会在长期复杂的整体功能中失败。

这是另一个与第一个形式非常相似的片段,它也有同样的问题。如果我在赋值和if之间插入函数调用,则问题就会消失。

gl = gc->mgr * pat_round(l / gc->mgr);
dl = l - gl; 
if (((dl * gc->last_dl) < 0) &&
        ((gl == gc->last_gl) || ((gl * gc->last_gl) < 0)))
{
    ...
}

这两种情况都是双倍的比较。

这是第一个片段的黑客版本的汇编程序,其中插入了一个srand(0)调用以使其工作(我标记了用'*'更改的指令):

.L461:
    fldl    (%esp)
    fmull   24(%esp)
    fstpl   (%esp)
    subl    $12, %esp
    .cfi_def_cfa_offset 4252
    pushl   $0
    .cfi_def_cfa_offset 4256
    call    srand
    addl    $16, %esp
    .cfi_def_cfa_offset 4240
    fldz
    fldl    (%esp)
    fucomi  %st(1), %st
    fstp    %st(1)
    jbe     .L559
    fld1
    fucomip %st(1), %st
    jbe     .L560
    decl    %ebp
    fmuls   .LC2
    fstpl   (%esp)
    jmp     .L457

并且删除了srand(0)也是同样的事情 - 这是非工作版本:

.L461:
    fldl    (%esp)
    fmull   24(%esp)
    fstl    (%esp)
    fldz
    fxch    %st(1)
    fucomi  %st(1), %st
    fstp    %st(1)
    jbe     .L559
    fld1
    fucomip %st(1), %st
    jbe     .L560
    decl    %ebp
    fmuls   .LC2
    fstpl   (%esp)
    jmp     .L457

1 个答案:

答案 0 :(得分:4)

一些细节在这里会有所帮助,例如对意外行为的简要描述。由于缺乏这一点,我们不得不依靠心灵感应和水晶球,这是众所周知的不可靠。

根据我的ouija董事会,您的问题是,在该片段终止时,您希望d处于半封闭范围[1,10]。但事实证明d实际上是10.当您插入函数调用时,d会神秘地更改为正确的值。

这可能发生,并且它不是一个优化错误。尽管double具有固定的精度,并且在整个计算过程中名义上使用,但允许编译器以更高的精度执行中间计算,这可能导致变量在其各个点处看起来更精确。寿命。

现在,让我们通过心灵感应确定产品d * factor是否略小于1,如果精确计算的话。实际上它非常接近1,如果它被舍入到53位精度,它将会舍入到1.0。但它恰好在那一点上有64位精度,所以它稍微少一些。现在我们乘以10,(因为它小于1,根据测试)并将结果舍入为53位,因为我们不再将值保存在寄存器中。圆形值将变为10,违背了预期(除了精神世界的期望,他们一直都知道它。)

插入函数调用将强制编译器将值保存在浮点寄存器中,因此在与1进行比较之前将其校正为53位,因此将比较相等,而不是更少。

当然,上述所有内容只是一种幻想,因为它在报告的证据中没有任何依据。如果事实证明与现实有任何相似之处,那将只是其中一个难以理解的巧合。

在该假设情况下,强制编译器使用SSE进行浮点运算将避免过多的精度计算,因为SSE寄存器仅为64位。或者,您可以告诉GCC更加努力地避免使用80位中间体。请参阅-mfpmath=sse选项,-ffloat-store-fexcess-precision=standard(SSE通常最好。)