簡體   English   中英

如何用擴展的gcc程序集指定x87 FPU堆棧的破壞底部?

[英]How to specify clobbered bottom of the x87 FPU stack with extended gcc assembly?

在我們的代碼庫中,我發現了這個代碼片段,用於在x87上進行快速,向負無限1舍入:

inline int my_int(double x)
{
  int r;
#ifdef _GCC_
  asm ("fldl %1\n"
       "fistpl %0\n"
       :"=m"(r)
       :"m"(x));
#else
  // ...
#endif
  return r;
}

我不是非常熟悉GCC擴展匯編語法,但是從我從文檔中收集到的內容:

  • r必須是一個記憶位置,我在寫回東西;
  • x必須也是一個內存位置,數據來自哪里。
  • 沒有clobber規范,因此編譯器可以放心,在代碼片段的末尾,寄存器就像他離開時一樣。

現在,回答我的問題:最終FPU堆棧是平衡的,但是如果所有8個位置都已經在使用並且我已經溢出呢? 編譯器如何知道它不能信任ST(7)到它的位置? 應該添加一些clobber嗎?

編輯我試圖在clobber列表中指定st(7) ,它似乎影響codegen,現在我將等待對此事實的一些確認。


作為旁注:在glibc和MinGW中查看准系統lrint的實現我看到類似的東西

__asm__ __volatile__ ("fistpl %0"
                      : "=m" (retval)
                      : "t" (x)
                      : "st");

我們要求輸入直接放在ST(0) (這避免了可能無用的fldl ); 什么是"st" clobber? 文檔似乎只提到了t (即堆棧的頂部)。


  1. 是的,它取決於當前的舍入模式,在我們的應用程序中應該總是“朝向負無窮大”。

看着glibc和MinGW中的准系統lrint的實現,我看到了類似的東西

 __asm__ __volatile__ ("fistpl %0" : "=m" (retval) : "t" (x) : "st"); 

我們要求輸入直接放在ST(0) (這避免了可能無用的fldl

這實際上是將所需代碼表示為內聯匯編的正確方法。

為了獲得最佳的代碼生成,您需要使用輸入和輸出。 不要硬編碼必要的加載/存儲指令,而是讓編譯器生成它們。 這不僅引入了消除可能不必要的指令的可能性,而且還意味着編譯器可以在需要時更好地調度這些指令(也就是說,它可以在先前的代碼序列中交織指令,通常最小化其成本)。

什么是"st" clobber? 文檔似乎只提到了t (即堆棧的頂部)。

"st" clobber是指st(0)寄存器, x87 FPU堆棧的頂部。 Intel / MASM表示法稱為st(0) ,AT&T / GAS表示法通常稱為st 而且,根據GCC的clobbers文檔,clobber列表中的項目是“注冊名稱或特殊符號”( "cc" (條件代碼/標志)和"memory" )。 所以這只是意味着內聯匯編崩潰(覆蓋) st(0)寄存器。 這個clobber是必要的原因是fistpl指令彈出堆棧的頂部,從而破壞了st(0)的原始內容。

關於此代碼,我唯一關心的是文檔中的以下段落:

Clobber描述可能不以任何方式與輸入或輸出操作數重疊。 例如,在clobber列表中列出該寄存器時,您可能沒有描述具有一個成員的寄存器類的操作數。 聲明存在於特定寄存器中的變量 (請參閱顯式寄存器變量 )並用作asm輸入或輸出操作數必須沒有在clobber描述中提及的部分。 特別是,沒有辦法指定輸入操作數被修改而不將它們指定為輸出操作數。

當編譯器選擇用於表示輸入和輸出操作數的寄存器時,它不使用任何被破壞的寄存器。 因此,破壞寄存器可用於匯編代碼中的任何用途。

如您所知, t 約束意味着x87 FPU堆棧的頂部。 問題是,這與st寄存器相同,並且文檔非常明確地說我們沒有一個clobber指定與輸入/輸出操作數之一相同的寄存器。 此外,由於文檔聲明編譯器禁止使用任何被破壞的寄存器來表示輸入/輸出操作數,因此這個內聯匯編使得一個不可能的請求 - 將該值加載到x87 FPU堆棧的頂部而不將其放入st

現在,我假設glibc的作者知道他們在做什么,並且比你或我更熟悉編譯器內聯匯編的實現,所以這段代碼可能合法且合法。

實際上,似乎x87的類似堆棧的寄存器的異常情況迫使clobbers和操作數之間的正常交互例外。 官方文件說:

在x86目標上,有一些規則在asm的操作數中使用類似堆棧的寄存器。 這些規則僅適用於類似堆棧的寄存器:

  1. 給定一組在asm中死亡的輸入寄存器,有必要知道哪些是由asm隱式彈出的,哪些必須由GCC顯式彈出。

    由asm隱式彈出的輸入寄存器必須明確地被破壞,除非它被約束為匹配輸出操作數。

這完全適合我們的情況。

官方文檔中出現的示例(鏈接部分的底部)提供了進一步的確認:

這個asm接受兩個輸入,由fyl2xp1操作碼彈出,並用一個輸出替換它們。 st(1) clobber是編譯器知道fyl2xp1彈出兩個輸入所必需的。

 asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)"); 

這里,clobber st(1)與輸入約束u相同,這似乎違反了上面提到的有關clobbers的文檔,但是使用和證明了"st"被用作你的clobber的相同原因原始代碼,因為fistpl彈出輸入。


所有這些說,現在你知道如何在內聯匯編中正確編寫代碼,我必須回應以前的評論者,他們建議最好的解決方案是不要使用內聯匯編。 只需調用lrint ,它不僅具有您想要的確切語義,而且在某些情況下也可以由編譯器進行更好的優化( 例如 ,當目標體系結構支持SSE時將其轉換為單個cvtsd2si指令)。

暫無
暫無

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

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