【问题标题】:Why is Clang optimizing this code out?为什么 Clang 优化这段代码?
【发布时间】:2014-07-13 08:52:59
【问题描述】:

代码的目的是找到代表 0 到 1 之间值的 32 位浮点位模式的总数。在我看来这应该可行,但由于某种原因,Clang 的汇编输出基本上是相当于return 0;

我用 Clang 3.3 和 Clang 3.4.1 编译了这个,使用 -std=c++1y -Wall -Wextra -pedantic -O2-std=c++1y -Wall -Wextra -pedantic -O3

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;
}

【问题讨论】:

  • 为什么这么参与,而不只是使用std::next_after? (但是 +1 是为了在开始编写代码之前明确你的目标。)
  • @KerrekSB 我想我可以试试那个方法,但我仍然认为更好的问题是为什么 Clang 认为这段代码没有任何用处。
  • @bolov 这也是我的第一个猜测,但我没有在这个程序中看到任何无效的内容,并且clang 没有使用-fsanitize=undefined 报告任何内容。像这样使用memcpy 并不违反别名规则。
  • 它看起来像一个编译器错误。代码中没有任何 UB...

标签: c++ clang compiler-optimization


【解决方案1】:

这是一个更简单的测试用例,指出这是一个编译器错误:

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。它也不认为这是一个无限循环,在这种情况下,程序集不会从 main 返回。

【讨论】:

  • 很好的例子。当我把它变成for 时,它似乎又做了正确的事情。所以看起来clang的优化器在无限while循环上出错了。
  • 感谢简化的测试用例。我将继续并将其标记为答案,因为此时可能没有什么更有用的可以在这里贡献了。剩下要做的就是用这个例子提交一个错误报告。
  • @Adam:您为将其转换为for 循环所做的转换会很有趣。
  • @Chris_F:提交后,您能否在此处发布错误报告的链接?
  • @R..for (uint32_t i = 0; i != 0xFFFFFFFF; i++)
【解决方案2】:

这不是一个答案,而是一个太大而无法评论的数据点。

有趣的是,如果您在返回之前打印count,那么clang 将仍然优化所有内容并使用-O31065353218-O0 打印0。 (请注意,echo $? 报告返回值总是 2,无论实际返回是什么)。对我来说,这使它看起来像一个编译器错误。

如果您将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 示例,在带有 -O2 的 clang 3.4 中,问题似乎出在矢量化阶段。向量化代码在输入时跳转到向量化代码之前:

    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
    }
    

    优化器稍后会删除无法访问和无效的代码——这几乎是整个函数体。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-03-05
      • 2014-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多