2017-05-18 57 views
20

我已經用遍了我的代碼一個宏,在調試模式下的功能:如何根據斷言來指導GCC優化而無需運行時成本?

#define contract(condition) \ 
    if (!(condition)) \ 
     throw exception("a contract has been violated"); 

...但在釋放模式:

#define contract(condition) \ 
    if (!(condition)) \ 
     __builtin_unreachable(); 

這確實超過了assert()那是什麼,在釋放由於UB傳播,編譯器可以大量優化代碼。

例如,用下面的代碼測試:

int foo(int i) { 
    contract(i == 1); 
    return i; 
} 

// ... 

foo(0); 

...拋出在調試模式下的異常,但在釋放模式產生組件,用於無條件return 1;

foo(int): 
     mov  eax, 1 
     ret 

條件,所有依賴它的東西都被優化了。

我的問題出現在更復雜的條件下。當編譯器不能證明該條件沒有副作用,它不會優化它,這是一個runtme懲罰相比,不使用合約。

有沒有辦法表示合同中的條件沒有副作用,所以這是總是優化出來?

+0

註釋不是延長討論;這個談話已經[轉移到聊天](http://chat.stackoverflow.com/rooms/144609/discussion-on-question-by-lyingonthesky-how-to-guide-gcc-optimizations-based-on)。 – deceze

回答

2

沒有辦法強制優化代碼,因爲如果它是死代碼,因爲GCC必須始終對標準抱怨。

另一方面,通過使用屬性error可以檢查表達式沒有任何副作用,只要函數的調用不能被優化就會顯示錯誤。

,檢查任何被優化並確實UB傳播的宏的示例:

#define _contract(condition) \ 
    { 
     ([&]() __attribute__ ((noinline,error ("contract could not be optimized out"))) { 
      if (condition) {} // using the condition in if seem to hide `unused` warnings. 
     }()); 
     if (!(condition)) 
      __builtin_unreachable(); 
    } 

錯誤屬性不無優化工作(因此該宏只能用於釋放\優化模式編譯) 。 請注意,在鏈接過程中會顯示指示合同有副作用的錯誤。

A test that shows an error with unoptimizable contract.

A test that optimizes out a contract but, does UB propagation with it.

5

所以,不是一個答案,但一些有趣的結果可能導致某處。

我結束了以下玩具代碼:

#define contract(x) \ 
    if (![&]() __attribute__((pure, noinline)) { return (x); }()) \ 
     __builtin_unreachable(); 

bool noSideEffect(int i); 

int foo(int i) { 
    contract(noSideEffect(i)); 

    contract(i == 1); 

    return i; 
} 

您可以follow along at home,太;)

noSideEffect是,我們知道有沒有副作用的功能,但是編譯器不。
它是這樣的:

  1. GCC有__attribute__((pure))標記功能爲無副作用。

  2. 合格noSideEffectpure屬性完全刪除函數調用。太好了!

  3. 但我們不能修改noSideEffect的聲明。那麼如何將它的調用包裝在一個本身爲pure的函數中呢?而且由於我們試圖製造一個自包含的宏,lambda聽起來不錯。

  4. 令人驚訝的是,這是行不通的......除非我們將noinline添加到lambda!我想優化器會首先將lambda內聯,在考慮優化noSideEffect的呼叫之前,將丟失pure屬性。有了noinline,優化器可以以一種反直覺的方式將所有東西都清除掉。大!

  5. 但是,現在有兩個問題:使用noinline屬性,編譯器會爲每個lambda生成一個主體,即使他們從不使用。呃 - 連接器無論如何可能會把它們扔掉。
    但更重要的......實際上,我們失去了__builtin_unreachable()已使:(

概括起來的優化,你可以刪除或在上面的代碼片段放回noinline,並與其中的一個結束結果:

隨着noinline

; Unused code 
foo(int)::{lambda()#2}::operator()() const: 
     mov  rax, QWORD PTR [rdi] 
     cmp  DWORD PTR [rax], 1 
     sete al 
     ret 
foo(int)::{lambda()#1}::operator()() const: 
     mov  rax, QWORD PTR [rdi] 
     mov  edi, DWORD PTR [rax] 
     jmp  noSideEffect(int) 

; No function call, but the access to i is performed 
foo(int): 
     mov  eax, edi 
     ret 

沒有noinline

; No unused code 
; Access to i has been optimized out, 
; but the call to `noSideEffect` has been kept. 
foo(int): 
     sub  rsp, 8 
     call noSideEffect(int) 
     mov  eax, 1 
     add  rsp, 8 
     ret 
+1

我玩過這個玩具很多,我認爲這是GCC的一個錯誤,它在內聯時剝離了'pure'屬性,這要歸功於那裏的知識。 – LyingOnTheSky

4

是否有表達,在合同條件沒有任何副作用的一種方式,所以它總是優化掉了?

不太可能。

衆所周知,你不能接受大量的斷言,將它們變成假設(通過__builtin_unreachable)並期待好的結果(例如John Regehr的Assertions Are Pessimistic, Assumptions Are Optimistic)。

一些線索:

  • CLANG,而已經具有__builtin_unreachable內在介紹__builtin_assume正是爲了這個目的。

  • N4425 - Generalized Dynamic Assumptions(*)注意到:

    GCC沒有明確規定的一般假設設施,但一般的假設可以使用和控制流的組合__builtin_unreachable內在

    編碼...

    提供通用假設的現有實現在實現保留的標識符空間中使用一些關鍵字(__assume__builtin_assume等)。由於不評估表達式參數(副作用被丟棄),因此用特殊的庫函數(例如std::assume)來指定它似乎很困難。

  • 準則支持庫(GSL,由微軟託管的,但絕不是微軟專用)具有 「僅僅」 這樣的代碼:

    #ifdef _MSC_VER 
    #define GSL_ASSUME(cond) __assume(cond) 
    #elif defined(__clang__) 
    #define GSL_ASSUME(cond) __builtin_assume(cond) 
    #elif defined(__GNUC__) 
    #define GSL_ASSUME(cond) ((cond) ? static_cast<void>(0) : __builtin_unreachable()) 
    #else 
    #define GSL_ASSUME(cond) static_cast<void>(!!(cond)) 
    #endif 
    

    ,並指出:

    // GSL_ASSUME(cond) 
    // 
    // Tell the optimizer that the predicate cond must hold. It is unspecified 
    // whether or not cond is actually evaluated. 
    

*)論文rejected:EWG的指導是在擬議的合同設施內提供功能。

+0

我可以請知道「cond」做了什麼,它在哪裏宣佈? –

+1

@ jack_1729'cond'是類似函數宏的輸入參數(查看https://gcc.gnu.org/onlinedocs/cpp/Macro-Arguments.html)。 'GSL_ASSUME'需要一個布爾表達式/條件('cond')。 – manlio