2013-07-08 2 views
9

내가 이해할 수없는 재미있는 문제를 발견했습니다.LLVM 컴파일러 최적화 버그 또는 무엇?

배경은 다음과 같습니다

  • 지금하는 ARMv7/armv7s 아키텍처 용으로 컴파일 -Os

  • 로 컴파일 된 C++ 11을 지원하도록 컴파일 엑스 코드
    • LLVM 4.2 컴파일러 최적화가 활성화 된 상태에서 컴파일 할 때 나타나는 코드에 문제가 있음을 깨달았습니다. 나는 이상한 버그를 추적하는 코드를 강화 LLDB로 이동하여 지금

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

      , 이것은 첫 번째 분기가 입력을 가지고가는 경우에 실현하기 위해 나를 인도 :

      코드는 그대로입니다

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

      (이 값은 엑스 코드에서 지역 주민 테이블에서 직접에서 가져가, 나는 그것을 최적화됩니다 생각 때문에 약 vs lldb 쿼리 할 수 ​​아니에요.)

      첫 번째 지점은 if (648 < 120 && ...이므로 취할 방법이 없어야하지만 실제로 발생합니다. -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 
      

      그때 버그가 사라지는 경우 절하기 전에 인쇄, 예를 들어 printf("%d < %d - %d",sx,vs*2,sx < vs*2)을 배치합니다.

      이 간단한 테스트 케이스

      문제 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

      내가 독립 실행 형 프로그램의 버그를 재현 관리를, 난 그냥 빈 아이폰 OS 프로젝트를 만든 다음, 나는 정의 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 
      

      이다 OUTPUT 라는 것은 정확하지만 다른 것은 똑같은 것을 적용하지 않는다. 같은 버그. 강제로 __attribute__ ((noinline))으로 내부 함수를 인라인하지 않으면 두 함수가 실패합니다. 나는 어둠 속으로 정말로 들떠있다.

  • +0

    코드의 다른 곳에 문제가 있습니다. 완전한 테스트 케이스를 만들 수 있습니까? –

    +0

    이것은 완전한 테스트 케이스입니다.이 함수는 외부 입력에 의존하지 않고 래핑 된 환경에서 두 타일 간의 거리를 계산하는 데 사용되는 정적 유틸리티 함수입니다. 버그는 항상 이러한 입력 값과 함께 발생합니다. 내가 프로젝트에서 격리 시키거나 ASM 코드를 확인하려고 노력해야한다. – Jack

    +0

    "test-case"는 드라이버 코드 (예 : 단위 테스트 또는 동작을 표시하는 데 필요한 것)가 포함 된 [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)는 논리 AND의 달성하기 위해 함께 일하기로되어있다 두 개의 하위 표현식. 이론적으로, 블록 (4)는 블록 (3)이 실행되면 이 실행되고 이후의 비교는 적절한 상태 플래그를 설정합니다. **

    그러나 이것은 잘못 구현됩니다. 비교 (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/

    +0

    분석 해 주셔서 감사합니다. arm asm으로 직접 작업하지 않았으므로 지침과 분기에 사용 된 일반적인 아키텍처를 보는 데 더 많은 시간이 필요했습니다. 이제는 99.9999 %의 시간이 개발자 실수이지만 가설 2와 4를 제외하기 때문에 항상 컴파일러 오류를 제외하는 경향이 있습니다. 따라서 일부 잘못된 코드가 정의되지 않은 동작을 유발할 수 있지만 이것이 어떻게 생성되는지는 알 수 없습니다. 잘못된 바이너리? 그렇지 않으면 정말 버그입니다. – Jack

    +0

    @Jack : 컴파일러는 코드가 유효하다고 가정하고 이에 따라 최적화를 할 수 있습니다. 코드가 어떻게 든 무효 인 경우 이러한 최적화는 더 이상 의미가 없습니다 (따라서 정의되지 않은 동작). 하지만 이런 문제가 이와 같은 독립 실행 형 기능에서 나타날 수는 없다고 생각합니다. (그러나 컴파일러 버그가있을 가능성은 거의 없다고 생각했습니다 ...) –

    +0

    빈 프로젝트에서 버그를 재현 할 수있었습니다. 필자가해야 할 일은 분리 된 파일에 함수를 넣고 app delegate의'applicationDidFinishLaunching :'에서 호출하는 것뿐입니다. 이상한 점 : 함수를 델리게이트의 동일한 파일에 보관하면 버그가 발생하지 않습니다. 파일을 자체 파일로 옮기면됩니다. 내 편집을 확인하십시오. – Jack