簡體   English   中英

gcc vs clang:使用-fPIC內聯函數

[英]gcc vs clang: inlining a function with -fPIC

考慮以下代碼:

// foo.cxx
int last;

int next() {
  return ++last;
}

int index(int scale) {
  return next() << scale;
}

使用gcc 7.2進行編譯時:

$ g++ -std=c++11 -O3 -fPIC

這會發出:

next():
    movq    last@GOTPCREL(%rip), %rdx
    movl    (%rdx), %eax
    addl    $1, %eax
    movl    %eax, (%rdx)
    ret
index(int):
    pushq   %rbx
    movl    %edi, %ebx
    call    next()@PLT    ## next() not inlined, call through PLT
    movl    %ebx, %ecx
    sall    %cl, %eax
    popq    %rbx
    ret

但是,使用clang 3.9編譯具有相同標志的相同代碼時:

next():                               # @next()
    movq    last@GOTPCREL(%rip), %rcx
    movl    (%rcx), %eax
    incl    %eax
    movl    %eax, (%rcx)
    retq

index(int):                              # @index(int)
    movq    last@GOTPCREL(%rip), %rcx
    movl    (%rcx), %eax
    incl    %eax              ## next() was inlined!
    movl    %eax, (%rcx)
    movl    %edi, %ecx
    shll    %cl, %eax
    retq

gcc通過PLT調用next() ,clang內聯它。 兩者仍然從GOT查找last 對於在Linux上進行編譯,是否正確地進行優化並且gcc在簡單內聯中丟失,或者是否在進行優化時是錯誤的,還是純粹是QoI問題?

我不認為標准會涉及到那么多細節。 它只是說,如果符號在不同的翻譯單元中具有外部鏈接,則大致相同,它是相同的符號。 這使得clang的版本正確。

從那時起,據我所知,我們已超出標准。 編譯器的選擇因他們認為有用的-fPIC輸出而不同。

注意g++ -c -std=c++11 -O3 -fPIE輸出:

0000000000000000 <_Z4nextv>:
   0:   8b 05 00 00 00 00       mov    0x0(%rip),%eax        # 6 <_Z4nextv+0x6>
   6:   83 c0 01                add    $0x1,%eax
   9:   89 05 00 00 00 00       mov    %eax,0x0(%rip)        # f <_Z4nextv+0xf>
   f:   c3                      retq   

0000000000000010 <_Z5indexi>:
  10:   8b 05 00 00 00 00       mov    0x0(%rip),%eax        # 16 <_Z5indexi+0x6>
  16:   89 f9                   mov    %edi,%ecx
  18:   83 c0 01                add    $0x1,%eax
  1b:   89 05 00 00 00 00       mov    %eax,0x0(%rip)        # 21 <_Z5indexi+0x11>
  21:   d3 e0                   shl    %cl,%eax
  23:   c3                      retq

所以GCC 確實知道如何優化它。 它只是選擇不使用-fPIC 但為什么? 我只能看到一個解釋:可以在動態鏈接期間覆蓋符號,並一致地查看效果。 該技術稱為符號插入

在共享庫中,如果next index調用,因為next是全局可見的,gcc必須考慮next可能插入的可能性。 所以它使用PLT。 但是,當使用-fPIE時,不允許插入符號,因此gcc可以進行優化。

clang錯了嗎? 不。但gcc似乎為符號插入提供了更好的支持,這對於檢測代碼很方便。 如果使用-fPIC而不是-fPIE來構建他的可執行文件,它會以一些開銷為-fPIE


補充說明:

一篇 gcc開發人員的博客文章中 ,他提到,在帖子的末尾:

在將一些基准與clang進行比較時,我注意到clang實際上忽略了ELF插入規則。 雖然它是bug,但我決定將-fno-semantic-interposition標志添加到GCC以獲得類似的行為。 如果不希望插入,ELF的官方答案是使用隱藏的可見性,如果需要導出符號,則定義別名。 這並非總是實用的手工操作。

在此之后,我獲得了x86-64 ABI規范 在3.5.5節中,它確實要求所有調用全局可見符號的函數必須通過PLT(它根據內存模型定義要使用的確切指令序列)。

因此,雖然它沒有違反C ++標准,但忽略語義插入似乎違反了ABI。


最后一句話:不知道在哪里放這個,但你可能會感興趣。 我會省略轉儲,但我使用objdump和編譯器選項的測試表明:

在gcc方面:

  • gcc -fPIClast訪問通過GOT,對next()調用通過PLT。
  • gcc -fPIC -fno-semantic-interpositionlast通過GOT, next()是內聯的。
  • gcc -fPIE last是IP-relative, next()是內聯的。
  • -fPIE暗示-fno-semantic-interposition

在事情的鏗鏘一面:

  • clang -fPIC last通過GOT, next()是內聯的。
  • clang -fPIE last通過GOT, next()是內聯的。

以及編譯為IP相對的修改版本,在兩個編譯器上內聯:

// foo.cxx
int last_ __attribute__((visibility("hidden")));
extern int last __attribute__((alias("last_")));

int __attribute__((visibility("hidden"))) next_()
{
  return ++last_;
}
// This one is ugly, because alias needs the mangled name. Could extern "C" next_ instead.
extern int next() __attribute__((alias("_Z5next_v")));

int index(int scale) {
  return next_() << scale;
}

基本上,這明確標志着盡管它們在全球范圍內可用,但我們使用那些忽略任何類型插入的符號的隱藏版本。 無論傳遞的選項如何,兩個編譯器都會完全優化訪問。

暫無
暫無

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

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