2013-07-08 56 views
9

我偶然发现了一个我无法理解的有趣问题。LLVM编译优化bug还是什么?

背景是:上的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去我走到码追查一个奇怪的错误,这使我认识到的第一个分支,如果采取输入

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

    (这些值是从直接取自XCode中当地人表,我不能查询LLDB约vs,因为我猜它得到优化。)

    然后第一个分支是if (648 < 120 && ...,所以应该没有办法采取它,但它实际上发生。如果我用-O0进行编译,那么这个bug就会消失。

    另一个有趣的事实是,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 
    

    如果我把打印,例如printf("%d < %d - %d",sx,vs*2,sx < vs*2) if子句在此之前的bug消失。

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

    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 
    

    测试。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))内联函数,那么这两个函数都会失败。我真的摸索着黑暗。

  • +0

    这极有可能是你看到引起未定义行为通过代码中的其他地方的问题。你能构建一个完整的测试用例吗? –

    +0

    这是一个完整的测试用例,该函数不依赖于任何外部输入,它是一个静态效用函数,用于计算包装环境中两个图块之间的距离。这些错误总是出现在这些输入值中。我应该尝试从项目中隔离它或检查ASM代码,我猜。 – Jack

    +0

    “测试用例”是指包含驱动程序代码(即单元测试或显示行为所需的任何内容)的[SSCCE](http://sscce.org)。正如我确信你知道的那样,许多错误有一个修复自己的习惯,一旦麻烦的代码与程序的其余部分分离开来;) –

    回答

    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 
    

    条件语句(3)和(4)都应该共同努力实现的一个逻辑与两个子表达式。理论上,块(4)仅在执行块(3)时执行后续比较设置适当的状态标志。 **

    但是,这是实施不正确。比较(3)设置Z,这意味着条件(3)没有被触发(它需要N!=V),所以条件(4)不被执行。迄今为止很好。但它足以触发条件(4)(它需要(Z==1) || (N!=V)),导致您看到的问题。

    总的来说,有四种可能的位置:

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


    *虽然这是上午12点四十零,现在,所以我可能是错误的...

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

    +0

    感谢您的分析,从未直接与arm asm一起工作,因此我将需要更多时间来查看分支中使用的指令和一般体系结构。现在我总是倾向于排除编译器错误,因为99.9999%的时间是开发者故障,但是我排除了假设2和4.因此,一些无效的代码可能会触发未定义的行为,但我不明白这是如何产生的二进制错误?否则,这真是一个错误。 – Jack

    +0

    @Jack:允许编译器假定你的代码是有效的,并相应地进行优化。如果你的代码在某种程度上是无效的,这些优化可能不再有意义(因此未定义的行为)。但是,我认为这样的问题不太可能在像这样的独立功能中体现出来。 (但是,我也认为这是不太可能的,可能会有一个编译器错误...) –

    +0

    我已经能够在一个空的项目中重现错误。我所需要做的就是将函数放在一个单独的文件中,并从应用程序委托的'applicationDidFinishLaunching:'中调用它。奇怪的是:如果我将该函数保存在委托的同一个文件中,那么该错误不会发生。如果我将它移动到它自己的文件上,那么它就会发生。检查我的编辑。 – Jack