为什么Clang会优化掉这段代码?

51
代码的目的是查找表示0到1之间的值的32位浮点位模式的总数。看起来这应该有效,但由于某种原因,来自Clang的汇编输出基本等同于return 0;
我使用-std=c++1y -Wall -Wextra -pedantic -O2-std=c++1y -Wall -Wextra -pedantic -O3对Clang 3.3和Clang 3.4.1进行了编译。
Clang 3.4使用-O2和-O3优化掉了所有内容。
Clang 3.3仅使用-O3优化掉了所有内容。
“优化掉所有内容”指的是程序的汇编输出:
main:                                   # @main
    xorl    %eax, %eax
    ret
#include <limits>
#include <cstring>
#include <cstdint>

template <class TO, class FROM>
inline TO punning_cast(const FROM &input)
{
    TO out;
    std::memcpy(&out, &input, sizeof(TO));
    return out;
}

int main()
{
    uint32_t i = std::numeric_limits<uint32_t>::min();
    uint32_t count = 0;

    while (1)
    {
        float n = punning_cast<float>(i);
        if(n >= 0.0f && n <= 1.0f)
            count++;
        if (i == std::numeric_limits<uint32_t>::max())
            break;
        i++;
    }

    return count;
}

9
为什么要这么麻烦,不直接使用std::next_after呢?(但是因为你在开始编写代码之前就明确了你的目标,所以我给你点赞。) - Kerrek SB
1
是 punning_cast 被优化掉了吗? - harmic
3
这是我的第一个猜测,但我在这个程序中没有看到任何无效的东西,而且使用-fsanitize = undefined,clang也没有报告任何问题。像这样使用memcpy不违反别名规则。 - user743382
6
这似乎是编译器的一个错误。这段代码没有任何未定义的行为... - Kerrek SB
2
可能相关:http://llvm.org/bugs/show_bug.cgi?id=17288 - Kerrek SB
显示剩余49条评论
3个回答

62

以下是指出这是编译器错误的简单测试用例:

http://coliru.stacked-crooked.com/a/58b3f9b4edd8e373

#include <cstdint>

int main()
{
    uint32_t i = 0;
    uint32_t count = 1;

    while (1)
    {
        if( i < 5 )
            count+=1;

        if (i == 0xFFFFFFFF)
            break;
        i++;
    }

    return count; // should return 6
}

这段程序表明其输出结果为1而不是6。它也没有认为这是一个无限循环,因为在这种情况下该程序不会从主函数返回。


很好的例子。当我将它转换为 for 循环时,它似乎再次做了正确的事情。因此,看起来 clang 的优化器在无限 while 循环上会出现错误。 - Adam
1
感谢提供简化的测试用例。由于目前可能没有更有用的贡献,我将把这个标记为答案。唯一剩下的事情就是使用这个示例提交错误报告。 - Chris_F
@Adam:你所做的将它转换为for循环的变换是很有趣的,值得了解。 - R.. GitHub STOP HELPING ICE
@Chris_F:你能在这里发布一下你提交错误报告的链接吗? - R.. GitHub STOP HELPING ICE
2
@R.. for (uint32_t i = 0; i != 0xFFFFFFFF; i++) - Adam
@Adam,那个for循环不会运行0xFFFFFFFF的值,而while循环会。 - noggin182

4

这不是一个答案,而是一个太大以不能成为评论的数据点。

有趣的是,如果你在返回之前打印count,那么clang仍然会使用-O3输出0,而使用-O0则输出1065353218。(注意,echo $?报告返回值始终为2,无论实际返回值如何)。对我来说,这看起来像是编译器的一个bug。

如果将while改为for

for (uint32_t i = std::numeric_limits<uint32_t>::min(); i != std::numeric_limits<uint32_t>::max(); ++i)
{
    float n = punning_cast<float>(i);
    if(n >= 0.0f && n <= 1.0f)
        count++;
}

对于两种优化级别,得到的答案是相同的。如果你打印输出,那肯定是正确的,虽然我没有查看汇编代码,但在未打印输出时也可能是正确的,因为它需要时间才能完成。(clang 3.4)

我之前在LLVM中发现过错误 (有趣的模板问题导致clang崩溃),如果你提供了一个清晰明确的错误示例,他们会很快响应并修复它。我建议您将此提交为错误报告。


3

使用mukunda上面的示例,在clang 3.4中启用-O2优化后,问题似乎出现在矢量化阶段。向量化代码入口处跳过了向量化代码:

br i1 true, label %middle.block, label %vector.ph

因此,count 的值保持不变,从其初始化开始。
*** IR Dump Before Combine redundant instructions ***
; Function Attrs: nounwind readnone ssp uwtable
define i32 @main() #0 {
entry:
  br i1 true, label %middle.block, label %vector.ph

vector.ph:                                        ; preds = %entry
  br label %vector.body

vector.body:                                      ; preds = %vector.body, %vector.ph
  %index = phi i32 [ 0, %vector.ph ], [ %index.next, %vector.body ]
  %vec.phi = phi <4 x i32> [ <i32 1, i32 0, i32 0, i32 0>, %vector.ph ], [ %4, %vector.body ]
  %vec.phi8 = phi <4 x i32> [ zeroinitializer, %vector.ph ], [ %5, %vector.body ]
  %broadcast.splatinsert = insertelement <4 x i32> undef, i32 %index, i32 0
  %broadcast.splat = shufflevector <4 x i32> %broadcast.splatinsert, <4 x i32> undef, <4 x i32> zeroinitializer
  %induction = add <4 x i32> %broadcast.splat, <i32 0, i32 1, i32 2, i32 3>
  %induction7 = add <4 x i32> %broadcast.splat, <i32 4, i32 5, i32 6, i32 7>
  %0 = icmp ult <4 x i32> %induction, <i32 5, i32 5, i32 5, i32 5>
  %1 = icmp ult <4 x i32> %induction7, <i32 5, i32 5, i32 5, i32 5>
  %2 = zext <4 x i1> %0 to <4 x i32>
  %3 = zext <4 x i1> %1 to <4 x i32>
  %4 = add <4 x i32> %2, %vec.phi
  %5 = add <4 x i32> %3, %vec.phi8
  %6 = icmp eq <4 x i32> %induction, <i32 -1, i32 -1, i32 -1, i32 -1>
  %7 = icmp eq <4 x i32> %induction7, <i32 -1, i32 -1, i32 -1, i32 -1>
  %8 = add <4 x i32> %induction, <i32 1, i32 1, i32 1, i32 1>
  %9 = add <4 x i32> %induction7, <i32 1, i32 1, i32 1, i32 1>
  %index.next = add i32 %index, 8
  %10 = icmp eq i32 %index.next, 0
  br i1 %10, label %middle.block, label %vector.body, !llvm.loop !1

middle.block:                                     ; preds = %vector.body, %entry
  %resume.val = phi i32 [ 0, %entry ], [ 0, %vector.body ]
  %trunc.resume.val = phi i32 [ 0, %entry ], [ 0, %vector.body ]
  %rdx.vec.exit.phi = phi <4 x i32> [ <i32 1, i32 0, i32 0, i32 0>, %entry ], [ %4, %vector.body ]
  %rdx.vec.exit.phi9 = phi <4 x i32> [ zeroinitializer, %entry ], [ %5, %vector.body ]
  %bin.rdx = add <4 x i32> %rdx.vec.exit.phi9, %rdx.vec.exit.phi
  %rdx.shuf = shufflevector <4 x i32> %bin.rdx, <4 x i32> undef, <4 x i32> <i32 2, i32 3, i32 undef, i32 undef>
  %bin.rdx10 = add <4 x i32> %bin.rdx, %rdx.shuf
  %rdx.shuf11 = shufflevector <4 x i32> %bin.rdx10, <4 x i32> undef, <4 x i32> <i32 1, i32 undef, i32 undef, i32 undef>
  %bin.rdx12 = add <4 x i32> %bin.rdx10, %rdx.shuf11
  %11 = extractelement <4 x i32> %bin.rdx12, i32 0
  %cmp.n = icmp eq i32 0, %resume.val
  br i1 %cmp.n, label %while.end, label %scalar.ph

scalar.ph:                                        ; preds = %middle.block
  br label %while.body
while.body:                                       ; preds = %while.body, %scalar.ph
  %i.0 = phi i32 [ %trunc.resume.val, %scalar.ph ], [ %inc, %while.body ]
  %count.0 = phi i32 [ %11, %scalar.ph ], [ %add.count.0, %while.body ]
  %cmp = icmp ult i32 %i.0, 5
  %add = zext i1 %cmp to i32
  %add.count.0 = add i32 %add, %count.0
  %cmp1 = icmp eq i32 %i.0, -1
  %inc = add i32 %i.0, 1
  br i1 %cmp1, label %while.end, label %while.body, !llvm.loop !4

while.end:                                        ; preds = %middle.block, %while.body
  %add.count.0.lcssa = phi i32 [ %add.count.0, %while.body ], [ %11, %middle.block ]
  ret i32 %add.count.0.lcssa
}

优化器稍后会删除不可达和无效的代码 - 这几乎是整个函数体。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接