2016-10-03 93 views
45

正如我在this question提出的,gcc正在刪除(是的,用-O0)一行代碼_mm_div_ss(s1, s2);,可能是因爲結果沒有保存。但是,這個應該觸發一個浮點異常,並提出SIGFPE,如果刪除該調用不會發生。gcc -O0仍然優化了「未使用」的代碼。有沒有編譯標誌來改變它?

問題:是否有一個標誌或多個標誌傳遞給gcc,以便代碼按原樣編譯?我在想像fno-remove-unused,但我沒有看到類似的東西。理想情況下,這將是一個編譯器標誌,而不必更改我的源代碼,但如果不支持,那麼是否有一些gcc屬性/編譯指示可用?

事情我已經嘗試:

$ gcc --help=optimizers | grep -i remove 

沒有結果。

$ gcc --help=optimizers | grep -i unused 

沒有結果。

,並明確禁用所有死碼/消除標誌 - 注意,這是毫無無用的代碼警告:

$ gcc -O0 -msse2 -Wall -Wextra -pedantic -Winline \ 
    -fno-dce -fno-dse -fno-tree-dce \ 
    -fno-tree-dse -fno-tree-fre -fno-compare-elim -fno-gcse \ 
    -fno-gcse-after-reload -fno-gcse-las -fno-rerun-cse-after-loop \ 
    -fno-tree-builtin-call-dce -fno-tree-cselim a.c 
a.c: In function ‘main’: 
a.c:25:5: warning: ISO C90 forbids mixed declarations and code [-Wpedantic] 
    __m128 s1, s2; 
    ^
$ 

源程序

#include <stdio.h> 
#include <signal.h> 
#include <string.h> 
#include <xmmintrin.h> 

static void sigaction_sfpe(int signal, siginfo_t *si, void *arg) 
{ 
    printf("%d,%d,%d\n", signal, si!=NULL?1:0, arg!=NULL?1:0); 
    printf("inside SIGFPE handler\nexit now.\n"); 
    exit(1); 
} 

int main() 
{ 
    struct sigaction sa; 

    memset(&sa, 0, sizeof(sa)); 
    sigemptyset(&sa.sa_mask); 
    sa.sa_sigaction = sigaction_sfpe; 
    sa.sa_flags = SA_SIGINFO; 
    sigaction(SIGFPE, &sa, NULL); 

    _mm_setcsr(0x00001D80); 

    __m128 s1, s2; 
    s1 = _mm_set_ps(1.0, 1.0, 1.0, 1.0); 
    s2 = _mm_set_ps(0.0, 0.0, 0.0, 0.0); 
    _mm_div_ss(s1, s2); 

    printf("done (no error).\n"); 

    return 0; 
} 

編譯上述程序給出

$ ./a.out 
done (no error). 

更改線路

_mm_div_ss(s1, s2); 

s2 = _mm_div_ss(s1, s2); // add "s2 = " 

產生預期的結果:

$ ./a.out 
inside SIGFPE handler 

編輯更多的細節。

這似乎與_mm_div_ssdefinition上的__always_inline__屬性有關。

$ cat t.c 
int 
div(int b) 
{ 
    return 1/b; 
} 

int main() 
{ 
    div(0); 
    return 0; 
} 


$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out 
$ 

(無警告或錯誤)

$ ./t.out 
Floating point exception 
$ 

VS下方(同樣除了功能屬性)

$ cat t.c 
__inline int __attribute__((__always_inline__)) 
div(int b) 
{ 
    return 1/b; 
} 

int main() 
{ 
    div(0); 
    return 0; 
} 

$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out 
$ 

(無警告或錯誤)

$ ./t.out 
$ 

添加函數屬性__warn_unused_result__至少給出了一個有用的信息:

$ gcc -O0 -Wall -Wextra -pedantic -Winline t.c -o t.out 
t.c: In function ‘main’: 
t.c:9:5: warning: ignoring return value of ‘div’, declared with attribute warn_unused_result [-Wunused-result] 
    div(0); 
    ^

編輯:

gcc mailing list一些討論。最終,我認爲一切都按預期工作。

+0

嘗試使用'__attribute __((used))'所涉及的變量。 –

+2

也許聲明s1和s2爲volatile ... – Malkocoglu

回答

22

GCC不會「優化」任何東西。它只是不會產生無用的代碼。這似乎是一種非常普遍的錯覺,即編譯器應該生成一些純粹的代碼形式,並且對其進行的任何更改都是「優化」。哪有這回事。

編譯器創建了一些代表代碼含義的數據結構,然後它對該數據結構應用了一些轉換,然後生成彙編器,然後彙編到指令中。如果你沒有進行「優化」編譯,這隻意味着編譯器只會儘可能少地生成代碼。

在這種情況下,整個語句是無用的,因爲它沒有做任何事情,並立即扔掉(在擴展內聯和內建的意味着它相當於寫a/b;,不同之處在於寫a/b;將發射關於statement with no effect的警告,而內建的可能不會被相同的警告處理)。這不是一種優化,編譯器實際上不得不花費額外的努力去爲無意義的語句發明意義,然後僞造一個臨時變量來存儲此語句的結果,然後將其丟棄。

你在找什麼不是標誌來禁用優化,但pessimization標誌。我不認爲任何編譯器開發者浪費時間來實現這樣的標誌。除了可能作爲一個愚人節玩笑。

+0

如何顯示有關語句的警告但不起作用?因爲'WALL -Wextra -pedantic'沒有顯示任何東西。 – BurnsBA

+2

@BurnsBA我也無法讓它產生警告。這可能是gcc中的一個錯誤。向他們發送一個錯誤報告。 – Art

+7

這裏的誤解實際上是將一個除法的SIGFPE提升爲零是一個已定義的效果,因此優化調用會除去可觀察的已定義行爲。 – Random832

10

我不是gcc內部專家,但似乎你的問題不是通過一些優化通過去除死代碼。最有可能的是,編譯器甚至不會考慮生成這個代碼。

讓我們減少一個普通的舊此外,從編譯器特定的內在您的例子:

int foo(int num) { 
    num + 77; 
    return num + 15; 
} 

No code for + 77 generated

foo(int): 
     push rbp 
     mov  rbp, rsp 
     mov  DWORD PTR [rbp-4], edi 
     mov  eax, DWORD PTR [rbp-4] 
     add  eax, 15 
     pop  rbp 
     ret 

當一個操作數有副作用,only that operand gets evaluated。儘管如此,組裝中還沒有增加。

但保存此結果爲(即使未使用)變量強制編譯器generate code for addition

int foo(int num) { 
    int baz = num + 77; 
    return num + 15; 
} 

大會:

foo(int): 
    push rbp 
    mov  rbp, rsp 
    mov  DWORD PTR [rbp-20], edi 
    mov  eax, DWORD PTR [rbp-20] 
    add  eax, 77 
    mov  DWORD PTR [rbp-4], eax 
    mov  eax, DWORD PTR [rbp-20] 
    add  eax, 15 
    pop  rbp 
    ret 

以下僅僅是一個猜測,但我與編譯器的經驗構造,不生成未使用表達式的代碼更自然,而不是稍後消除此代碼。

我的建議是明確您的意圖,並將表達式的結果放入 volatile(因此,優化程序不可移除)變量。

@Matthieu M指出,防止預先計算價值是不夠的。因此,除了使用信號播放外,您應該使用記錄的方式來執行所需的確切指令(可能爲volatile內聯彙編)。

+1

不幸的是,僅僅將結果放在volatile中並不足以防止編譯器在沒有實際發出所需指令的情況下對其進行預計算,因爲所有參數都是在編譯時出現的。 –

+0

確實;將零點放入易失性存儲器並再次讀出將關閉優化器。 – Joshua

+0

@MatthieuM。我完全同意它並不總是足夠的,但在這種情況下,它是([最後一個鏈接](https://godbolt.org/g/sE78jL))。此外,編譯器*必須*更喜歡在通常情況下更快的代碼來「發出所需的指令」,以便有用。這就是應該使用內聯彙編的情況。 – deniss

32

爲什麼gcc不發出指定的指令?

編譯器生成的代碼必須具有標準指定的可觀察行爲。任何不可觀察的東西都可以隨意更改(和優化),因爲它不會改變程序的行爲(如指定的那樣)。

你怎麼能打敗它提交?

訣竅是讓編譯器相信特定代碼段的行爲實際上是可觀察的。

由於這是一個在微基準測試中經常遇到的問題,所以我建議您看一下(例如)Google-Benchmark如何解決這個問題。從benchmark_api.h我們得到:

template <class Tp> 
inline void DoNotOptimize(Tp const& value) { 
    asm volatile("" : : "g"(value) : "memory"); 
} 

this syntax細節是枯燥的,我們的目的,我們只需要知道:

  • "g"(value)告訴value作爲輸入的語句
  • "memory"是編譯時讀/寫屏障

所以,我們可以改變co德到:

asm volatile("" : : : "memory"); 

__m128 result = _mm_div_ss(s1, s2); 

asm volatile("" : : "g"(result) :); 

其中:

  • 強制編譯器認爲s1s2可能已被他們的初始化之間修改和使用
  • 強制編譯器認爲該操作的結果被使用

不需要任何標誌,它應該在任何優化級別工作(I t在-03處將其設置爲https://gcc.godbolt.org/)。

+0

你有什麼來源gcc決定觀察行爲或不行?如果在沒有警告的情況下刪除此語句是一個錯誤,意外或有意,我試圖縮小範圍。 – BurnsBA

+1

@BurnsBA:C++標準是什麼是或不是可觀察行爲的參考。它也很難閱讀和充滿角落的情況......一般來說,對於單線程程序,可觀察行爲是影響程序輸出(I/O)的任何事情。對於多線程程序來說,事情變得更復雜,因爲許多交錯是可能的輸出。 –