【问题标题】:LLVM optimisation bug or undefined behaviour?LLVM 优化错误或未定义的行为?
【发布时间】:2013-03-07 19:55:20
【问题描述】:

在使用 clang 编译一个更大的项目时,我偶然发现了一个令人讨厌的错误。

考虑以下小例子:

unsigned long int * * fee();

void foo( unsigned long int q )
{
  unsigned long int i,j,k,e;
  unsigned long int pows[7];
  unsigned long int * * table;

  e = 0;
  for (i = 1; i <= 256; i *= q)
    pows[e++] = i;
  pows[e--] = i; 

  table = fee();  // need to set table to something unknown
                  // here, otherwise the compiler optimises
                  // parts of the loops below away
                  // (and no bug occurs)

  for (i = 0; i < q; i++)
    for (j = 0; j < e; j++)
      ((unsigned char*)(*table) + 5 )[i*e + j] = 0;   // bug here
}

据我所知,这段代码并没有以任何方式违反 C 标准,尽管最后一行看起来很别扭(在实际项目中,由于过度使用了预处理器宏,会出现这样的代码)。

在优化级别 -O1 或更高级别使用 clang(3.1 或更高版本)编译此代码会导致代码写入内存中的错误位置。

clang/LLVM 生成的汇编文件的关键部分如下: (这是 GAS 语法,所以对于那些习惯 Intel 的人来说:小心!)

    [...]
    callq   _fee
    leaq    6(%rbx), %r8          ## at this point, %rbx == e-1
    xorl    %edx, %edx
LBB0_4:
    [...]
    movq    %r8, %rsi
    imulq   %rdx, %rsi
    incq    %rdx
LBB0_6:
    movq    (%rax), %rcx          ## %rax == fee()
    movb    $0, (%rcx,%rsi)
    incq    %rsi
    [conditional jumps back to LBB0_6 resp. LBB0_4]
    [...]

换句话说,说明是这样的

(*table)[i*(e+5) + j] = 0;

而不是上面写的最后一行。 + 5 的选择是任意的,添加(或减去)其他整数会导致相同的行为。那么 - 这是 LLVM 优化中的错误还是这里发生了未定义的行为?

编辑: 另请注意,如果我在最后一行省略演员表(unsigned char*),该错误就会消失。通常,该错误似乎对任何更改都非常敏感。

【问题讨论】:

  • 在上面的汇编代码中看不到乘以 5 (但是我比 Intel 更习惯 ARM 汇编器,如果是 Intel :-)),但是,C 的最后一行代码转换为*((unsigned char*)(*table) + 5 + i*e + j),所以......你确定你在解释汇编器输出时正确地将这些大括号放在“e + 5”周围吗?
  • 是的,我很确定。这是 GAS 语法,而不是 Intel,所以 movq %r8, %rsiimulq %rdx, %rsi 意味着 %rsi 将持有 (%rbx+6) * %rdx = (e+5) * %rdx
  • 是的,现在我可以看到了。它看起来确实像一个优化器错误,因为即使有点奇怪,代码也足够干净(但是宏会产生奇怪的输出)。
  • @m_l:总是很难责怪编译器,但我认为在这种情况下你确实发现了编译器错误。恭喜!

标签: c llvm clang


【解决方案1】:

我很确定这是一个优化器错误。它在 LLVM-2.7 和 LLVM-3.1 中重现,这是我可以访问的唯一版本。

我将a bug 发布到 LLVM Bugzilla。

此 SSCCE 演示了该错误:

#include <stdio.h>

unsigned long int * table;

void foo( unsigned long int q )
{
  unsigned long int i,j,e;

  e = 0;
  for (i = 1; i <= 256; i *= q)
    e++;
  e--;

  for (i = 0; i < q; i++)
    for (j = 0; j < e; j++)
      ((unsigned char*)(table) + 13 )[i*e + j] = 0;   // bug here
}

int main() {
    unsigned long int v[8];
    int i;
    memset(v, 1, sizeof(v));

    table = v;
    foo(2);

    for(i=0; i<sizeof(v); i++) {
        printf("%d", ((unsigned char*)v)[i]);
    }
    puts("");
    return 0;
}

应该打印出来

1111111111111000000000000000011111111111111111111111111111111111

在 GCC 和“clang -O0”下。使用 LLVM 观察到的错误输出是

0000000011111111111110000000011111111111111111111111111111111111

感谢您注意到这一点!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-06-06
    • 2012-09-28
    • 2011-10-04
    • 2015-11-01
    • 2014-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多