簡體   English   中英

gcc -O0仍然優化了“未使用”的代碼。是否有一個編譯標志來改變它?

[英]gcc -O0 still optimizes out “unused” code. Is there a compile flag to change that?

當我提出這個問題時 ,gcc正在移除(是的,使用-O0 )一行代碼_mm_div_ss(s1, s2); 大概是因為結果沒有保存。 但是,這應該觸發浮點異常並引發SIGFPE,如果刪除調用則不會發生這種情況。

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

我試過的事情:

$ 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_ss 定義__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郵件列表的一些討論。 最終,我認為一切都按預期工作。

為什么gcc沒有發出指定的指令?

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

怎么能把它擊敗?

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

由於這是微基准測試中經常遇到的問題,我建議你看看(例如)Google-Benchmark如何解決這個問題。 benchmark_api.h我們得到:

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

這種語法的細節很無聊,為了我們的目的,我們只需要知道:

  • "g"(value)表示該value用作語句的輸入
  • "memory"是編譯時讀/寫屏障

所以,我們可以將代碼更改為:

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

__m128 result = _mm_div_ss(s1, s2);

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

哪一個:

  • 強制編譯器認為s1s2可能在初始化和使用之間被修改過
  • 強制編譯器考慮使用操作的結果

不需要任何標志,它應該在任何優化級別上工作(我在https://gcc.godbolt.org/- -O3上測試它)。

海灣合作委員會不會在這里“優化”任何東西。 它只是不會生成無用的代碼。 似乎有一種非常普遍的錯覺,即編譯器應該生成一些純粹的代碼形式,並且對它的任何更改都是“優化”。 哪有這回事。

編譯器創建一些表示代碼含義的數據結構,然后它對該數據結構應用一些轉換,然后生成匯編程序,然后編譯成指令。 如果你在沒有“優化”的情況下進行編譯,那只意味着編譯器只會盡可能少地生成代碼。

在這種情況下,整個語句是無用的,因為它沒有做任何事情並且立即被拋棄(在擴展內聯之后以及內置函數意味着它等同於編寫a/b;不同之處在於寫入a/b;將發出關於statement with no effect的警告statement with no effect而內置函數可能不會被相同的警告處理)。 這不是一個優化,編譯器實際上不得不花費額外的努力來為無意義的語句創造意義,然后偽造一個臨時變量來存儲該語句的結果然后扔掉它。

您正在尋找的不是禁用優化的標志,而是禁用標志。 我不認為任何編譯器開發人員浪費時間實現這樣的標志。 除了可能是一個愚人節的笑話。

我不是gcc內部的專家,但似乎你的問題不在於通過一些優化傳遞去除死代碼。 編譯器很可能甚至不考慮首先生成此代碼。

讓我們將您的示例從編譯器特定的內在函數減少到一個簡單的舊增加:

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

沒有生成+ 77代碼

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

當其中一個操作數有副作用時, 只評估該操作數 仍然沒有在組裝中添加。

但是將此結果保存到(甚至未使用的)變量會強制編譯器生成用於添加的代碼

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內聯匯編)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM