LLVM编译器优化bug还是什么?

Jac*_*ack 9 c++ xcode llvm compiler-optimization

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

背景是:

  • 在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;
}
Run Code Online (Sandbox Code Playgroud)

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

sx = 648
tx = 649
w = 768
vs = 60
Run Code Online (Sandbox Code Playgroud)

(这些值直接来自XCode中的locals表,我无法查询lldb,vs因为我猜它会得到优化.)

那么第一个分支就是if (648 < 120 && ...没有办法接受它,但它实际上发生了.如果我用-O0编译,那么bug就会消失.

另一个有趣的事情是,for sx = 647tx = 648bug不会发生.

事情是两件事:或者我错过了一些非常明显的事情,以至于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
Run Code Online (Sandbox Code Playgroud)

如果我放置一个打印,例如printf("%d < %d - %d",sx,vs*2,sx < vs*2)在if子句之前,那么bug就会消失.

这个简单的测试用例解决了这个问题:

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
...
Run Code Online (Sandbox Code Playgroud)

EDIT2

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

Test.h

#ifndef TEST_H_
#define TEST_H_

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

#endif
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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

...
Run Code Online (Sandbox Code Playgroud)

OUTPUT

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
Run Code Online (Sandbox Code Playgroud)

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

Oli*_*rth 5

首先,您的测试用例意味着它实际上错误地占用了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
Run Code Online (Sandbox Code Playgroud)

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

但是,这是错误地实现的.比较(3)设置Z,这意味着不触发条件(3)(它需要N!=V),因此不执行条件(4).好到目前为止.但触发条件(4)(它需要)足够了(Z==1) || (N!=V),导致你看到的问题.

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

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


*虽然现在是12:40,所以我可能会弄错...

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