簡體   English   中英

在沒有 GCC 重新排序的情況下將“標記”指令插入到程序集中

[英]Inserting “marker” instructions into assembly without GCC reordering them

為了進行性能分析,能夠分辨出 C 代碼的哪一行與生成的匯編代碼的哪一行是很有用的。 一旦涉及到足夠數量的優化通道,這可能會非常困難,我設計了以下方案以使其更容易(盡管它有很多警告)。 我想我會使用內聯匯編來插入一個實際上是 nop 的指令,但編譯器很少或永遠不會自行生成。 然后,當我查看生成的代碼時,我可以推斷出現在插入的標記指令之間的匯編代碼可能來自位於內聯匯編語句之間的 C 代碼。

我想出了這些候選人:

// Force insertion of a instruction that will only clobber
// flags and that the compiler hardly ever uses itself. Lie and say
// that it alters memory to try to prevent the compiler from moving
// around. Mark it volatile so the compiler can't remove it entirely.
#define ASSEMBLY_MARKER_0()                 \
    __asm__ volatile ("cld" : /* no outputs */ : /* no inputs */ : "memory", "cc")

#define ASSEMBLY_MARKER_1()                 \
    __asm__ volatile ("xorl %%eax,0" : /* no outputs */ : /* no inputs */ : "memory", "cc")

然后我決定測試編譯器是否會跨越這些邊界移動指令。 clang 似乎完全符合我的要求,但 GCC 似乎並沒有被 memory 破壞或這個片段易失的事實所阻止。 無論如何它會重新排序指令? 有什么辦法可以防止這種情況發生嗎?

我知道即使我讓它工作,這種方法也有很多警告——我可能會嚴重影響圍繞標記生成的代碼。 但我堅持認為,它對於查找諸如 integer 寬度之間的意外隱式轉換以及其他“永遠不需要的等待......”類型問題仍然很有用。

您可以在此處查看 GCC 和 clang 之間的區別: https://godbolt.org/z/ZtUPc9

C 代碼:

int f(int x)
{
    __asm__ volatile ("xorl %%eax,0" : /* no outputs */ : /* no inputs */ : "memory", "cc");
    int j = x << 3;
    __asm__ volatile ("xorl %%eax,0" : /* no outputs */ : /* no inputs */ : "memory", "cc");
    return j;
}

GCC:

    xorl %eax,0
    xorl %eax,0
    lea     eax, [0+rdi*8]
    ret

Clang:

    xor     dword ptr [0], eax
    lea     eax, [8*rdi]
    xor     dword ptr [0], eax
    ret

編輯以回答評論中的問題:

為什么不是nops? 因為 gcc 經常插入那些本身。 關鍵是要堅持。

為什么不將代碼移動到自己的 function 中? 例如,如果您對 C++ 模板代碼進行此分析,則在生成實際進入可執行文件的 function 之前會發生許多內聯層,如果您關閉內聯(例如代碼可能是在假設常量折疊、死代碼限制等會擺脫瑣碎的事情的情況下編寫的)。

然后我決定測試編譯器是否會跨越這些邊界移動指令。 clang 似乎完全符合我的要求,但 GCC 似乎並沒有被 memory 破壞或這個片段易失的事實所阻止。 無論如何它會重新排序指令? 有什么辦法可以防止這種情況發生嗎?

並不真地。 關鍵是,這樣的 memory 屏障避免了對易失性(如易失性訪問或 asm 易失性)和/或 memory 訪問的內容進行重新排序。 或者在 x86 和 cc(條件代碼)的情況下,條件代碼的 de-use 鏈的部分不能移動。 這樣的障礙無論如何都不能避免在其上移動不相關的指令。

有時添加選項-save-temps -fverbose-asm有助於更好地理解匯編代碼及其與 C 的關系。 新版本的 GCC 將 C/C++ 代碼與匯編代碼一起轉儲(轉儲為 *.s)。 當您檢查匯編(而不是反匯編)時,注入 asm 注釋就足以顯示內聯 asm 的注入位置,無需添加實際指令。 通過禁用調試信息 ( -g0 ) 可以提高程序集的易讀性。

為了更好地理解代碼,您還可以禁用通常會導致大量指令重新排序的通道,例如指令調度( -fno-schedule-insns-fno-schedule-insns2 ),但這當然會對性能產生很大影響。

暫無
暫無

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

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