【问题标题】:LLVM compiler optimization bug or what?LLVM 编译器优化错误还是什么?
【发布时间】:2013-07-05 07:46:45
【问题描述】:

我偶然发现了一个我无法理解的有趣问题。

背景是:

  • XCode 上的 LLVM 4.2 编译器
  • 使用 c++11 支持编译
  • -Os编译
  • 为 armv7/armv7s 架构编译

现在我意识到在启用优化的情况下编译时出现的某些代码存在问题。

代码是,逐字:

static int foo(int tx, int sx, int w)
{
  int vs = 60;

  if (sx < vs*2 && tx > w - vs*2)
    return (sx + w - tx);
  else if (sx > w - vs*2 && tx < vs*2)
    return -(w - sx + tx);
  else
    return sx - tx;
}

现在,通过使用 LLDB,我逐步执行代码来追踪一个奇怪的错误,这让我意识到 if 的第一个分支是通过输入获取的

sx = 648
tx = 649
w = 768
vs = 60

(这些值直接取自 XCode 中的 locals 表,我无法查询 lldb 关于 vs 的信息,因为我猜它得到了优化。)

然后第一个分支是if (648 &lt; 120 &amp;&amp; ...,所以应该没有办法接受它,但它确实发生了。如果我用 -O0 编译,那么这个错误就会消失。

另外一件有趣的事情是,sx = 647tx = 648 不会出现错误。

现在,事情有两个:或者我遗漏了一些非常明显的东西,以至于 10 小时的调试让我无法​​看到,或者在优化中存在某种错误。

有什么线索吗?

更多背景知识,这是生成的 ASM:

    .private_extern __ZN5Utils12wrapDistanceEiii
    .globl  __ZN5Utils12wrapDistanceEiii
    .align  2
    .code   16                      @ @_ZN5Utils12wrapDistanceEiii
    .thumb_func __ZN5Utils12wrapDistanceEiii
__ZN5Utils12wrapDistanceEiii:
    .cfi_startproc
Lfunc_begin9:
@ BB#0:
    @DEBUG_VALUE: wrapDistance:tx <- R0+0
    @DEBUG_VALUE: wrapDistance:sx <- R1+0
    @DEBUG_VALUE: wrapDistance:w <- R2+0
    @DEBUG_VALUE: vs <- 60+0
    sub.w   r3, r2, #120
    cmp r1, #119
    @DEBUG_VALUE: wrapDistance:tx <- R0+0
    @DEBUG_VALUE: wrapDistance:sx <- R1+0
    @DEBUG_VALUE: wrapDistance:w <- R2+0
    it  le
    cmple   r3, r0
Ltmp42:
    @DEBUG_VALUE: wrapDistance:tx <- R0+0
    @DEBUG_VALUE: wrapDistance:sx <- R1+0
    @DEBUG_VALUE: wrapDistance:w <- R2+0
    ittt    lt
    sublt   r0, r1, r0
Ltmp43:
    addlt   r0, r2
    @DEBUG_VALUE: vs <- 60+0
    bxlt    lr
Ltmp44:
    @DEBUG_VALUE: wrapDistance:tx <- R0+0
    @DEBUG_VALUE: wrapDistance:sx <- R1+0
    @DEBUG_VALUE: wrapDistance:w <- R2+0
    @DEBUG_VALUE: vs <- 60+0
    cmp r3, r1
    @DEBUG_VALUE: wrapDistance:tx <- R0+0
    @DEBUG_VALUE: wrapDistance:sx <- R1+0
    @DEBUG_VALUE: wrapDistance:w <- R2+0
    it  lt
    cmplt   r0, #119
Ltmp45:
    @DEBUG_VALUE: wrapDistance:tx <- R0+0
    @DEBUG_VALUE: wrapDistance:sx <- R1+0
    @DEBUG_VALUE: wrapDistance:w <- R2+0
    itttt   le
    suble   r1, r2, r1
Ltmp46:
    addle   r0, r1
Ltmp47:
    rsble   r0, r0, #0
    @DEBUG_VALUE: vs <- 60+0
    bxle    lr
Ltmp48:
    @DEBUG_VALUE: wrapDistance:tx <- R0+0
    @DEBUG_VALUE: wrapDistance:sx <- R1+0
    @DEBUG_VALUE: vs <- 60+0
    subs    r0, r1, r0
Ltmp49:
    @DEBUG_VALUE: vs <- 60+0
    bx  lr
Ltmp50:
Lfunc_end9:
    .cfi_endproc

如果我在 if 子句之前放置一个打印,例如 printf("%d &lt; %d - %d",sx,vs*2,sx &lt; vs*2),那么错误就会消失。

这个简单的测试用例存在问题:

for (int i = 0; i < 767; ++i)
{
  printf("test: %d, %d, %d",i,i+1,Utils::wrapDistance(i+1, i, 768))
}

...
test: 641, 642, -1
test: 642, 643, -1
test: 643, 644, -1
test: 644, 645, -1
test: 645, 646, -1
test: 646, 647, -1
test: 647, 648, -1
test: 648, 649, -769
test: 649, 650, -1
test: 650, 651, -1
test: 651, 652, -1
test: 652, 653, -1
test: 653, 654, -1
test: 654, 655, -1
...

EDIT2

我设法在一个独立程序中重现了这个错误,我只是创建了一个空的 iOS 项目,然后我定义了两次函数,一次在 AppDelegate.mm 中直接从同一个文件中调用,另一次在单独的文件中调用文件:

Test.h

#ifndef TEST_H_
#define TEST_H_

class Utils
{
  public:
    static int wrapDistance(int tx, int sx, int w);
};

#endif

Test.cpp

#include "Test.h"

int Utils::wrapDistance(int tx, int sx, int w)
{
  int vs = 60;

  if (sx < vs*2 && tx > w - vs*2)
    return (sx + w - tx);
  else if (sx > w - vs*2 && tx < vs*2)
    return -(w - sx + tx);
  else
    return sx - tx;
}

AppDelegate.mm

#import "AppDelegate.h"
#include "Test.h"

int wrapDistance(int tx, int sx, int w)
{
  int vs = 60;

  if (sx < vs*2 && tx > w - vs*2)
    return (sx + w - tx);
  else if (sx > w - vs*2 && tx < vs*2)
    return -(w - sx + tx);
  else
    return sx - tx;
}

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  ...

  for (int i = 0; i < 767; ++i)
  {
    NSLog(@"test inside: %d, %d, %d",i,i+1,wrapDistance(i+1, i, 768));
    NSLog(@"test outside: %d, %d, %d",i,i+1,Utils::wrapDistance(i+1, i, 768));
  }

    return YES;
}

...

输出

test inside: 644, 645, -1
test outside: 644, 645, -1
test inside: 645, 646, -1
test outside: 645, 646, -1
test inside: 646, 647, -1
test outside: 646, 647, -1
test inside: 647, 648, -1
test outside: 647, 648, -1
test inside: 648, 649, -1
test outside: 648, 649, -769
test inside: 649, 650, -1
test outside: 649, 650, -1
test inside: 650, 651, -1
test outside: 650, 651, -1
test inside: 651, 652, -1
test outside: 651, 652, -1

如您所见,在被调用的文件中定义的函数的行为是正确的,但同样的事情不适用于另一个,这显示了相同的错误。如果我强制不使用__attribute__ ((noinline)) 内联内部函数,那么这两个函数都会失败。我真的在黑暗中摸索。

【问题讨论】:

  • 您很可能会看到由代码中其他地方的问题引起的未定义行为。你能构建一个完整的测试用例吗?
  • 这是完整的测试用例,该函数不依赖任何外部输入,它是一个静态实用函数,仅用于计算包装环境中两个图块之间的距离。这些输入值总是会出现错误。我应该尝试将它与项目隔离或检查我猜的 ASM 代码。
  • “测试用例”是指SSCCE,它将包含驱动程序代码(即单元测试或展示行为所需的任何内容)。我相信您知道,一旦麻烦的代码与程序的其余部分隔离开来,许多错误都有自己修复的习惯;)
  • 当然,有可能(但不太可能)确实存在编译器错误。但我们需要证据(例如相应的 ASM 输出)...
  • 对@OliCharlesworth 的评论进行猜测,如果优化器将 120 放在一个常量表中,该表被其他代码中的错误指针位置写入所破坏,它可以解释您所看到的一切。

标签: c++ xcode llvm compiler-optimization


【解决方案1】:

首先,您的测试用例暗示它实际上错误地采用了else if 分支。

但我收回了;生成的 ASM 中似乎确实存在错误。*

这里是您的 ASM 的格式化/注释版本,用于失败的测试:

% r0 = tx = 649
% r1 = sx = 648
% r2 = w  = 768

% w - vs*2
sub.w   r3, r2, #120         % r3 = 648

% if (sx < vs*2)
cmp r1, #119
it  le                       % (1) Not taken
  % if ((w - vs*2) < tx)
  cmple   r3, r0
ittt    lt                   % (2) Not taken
  % return (sx + w - tx)
  sublt   r0, r1, r0
  addlt   r0, r2
  bxlt    lr

% if ((w - vs*2) < sx)
cmp r3, r1
it  lt                       % (3) Not taken
  % if (tx < vs*2)
  cmplt   r0, #119
itttt   le                   % (4) Taken!  <<<<<<<<<<<<<
  % return -(w - sx + tx)
  suble   r1, r2, r1
  addle   r0, r1
  rsble   r0, r0, #0
  bxle    lr

% return sx - tx
subs    r0, r1, r0
bx  lr

条件 (3) 和 (4) 应该一起工作以实现两个子表达式的逻辑与。理论上,只有在块 (3) 被执行并且随后的比较设置了适当的状态标志时,块 (4) 才会被执行。**

但是,这执行不正确。比较(3)设置Z,表示不触发条件(3)(需要N!=V),所以不执行条件(4)。到目前为止还好。但是足以触发条件(4)(需要(Z==1) || (N!=V)),导致你看到的问题。

总而言之,这里有四种可能性:

  1. 针对 ARM7 的 LLVM 后端确实存在错误。
  2. 您提供的 C 代码实际上并不是您正在编译的代码。
  3. 您在其他地方有一些无效的 C 代码触发了未定义的行为,从而导致生成无意义的 ASM 作为副作用。
  4. 我上面的分析不正确!


* 虽然现在是凌晨 12 点 40 分,所以我可能记错了...

** http://blogs.arm.com/software-enablement/206-condition-codes-1-condition-flags-and-codes/

【讨论】:

  • 感谢您的分析,从未直接使用过 arm asm,因此我需要更多时间来查看分支中使用的指令和一般架构。现在我总是倾向于排除编译器错误,因为 99.9999% 的时间是开发人员的错误,但我会排除假设 2 和 4。因此,一些无效代码可能会触发未定义的行为,但我看不出这是如何产生的错误的二进制?否则这真的是一个错误。
  • @Jack:编译器可以假设您的代码是有效的,并相应地进行优化。如果您的代码因某种原因无效,则这些优化可能不再有意义(因此未定义行为)。不过,我想说这样的问题不太可能出现在这样的独立函数中。 (不过,我也认为不太可能存在编译器错误......)
  • 我已经能够在一个空项目中重现该错误。我需要做的就是将函数放在一个单独的文件中,然后从应用程序委托的applicationDidFinishLaunching: 调用它。奇怪的是:如果我将函数保存在委托的同一个文件中,那么这个错误就不会发生。如果我将它移动到它自己的文件上,那么它就会发生。检查我的编辑。
  • 我发现修复该错误的唯一方法是提供不同的算法实现,该算法使用不同的代码产生相同的结果。这修复了优化错误。无论如何,我向苹果公司报告了这种奇怪的行为。感谢您的帮助!
  • 好的,我提交了一个错误报告,他们实际上接受并修复了它,所以它确实是一个错误。你知道,99.99% 的时间都是开发者的错,但这次不是。
猜你喜欢
  • 2017-08-31
  • 2014-01-07
  • 2020-07-24
  • 1970-01-01
  • 2012-09-28
  • 1970-01-01
  • 2014-06-07
  • 1970-01-01
  • 2013-10-07
相关资源
最近更新 更多