![](/img/trans.png)
[英]Explanation for clobber list in extended GCC inline assembly executing the x87 FPATAN operation
[英]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
必須也是一個內存位置,數據來自哪里。 現在,回答我的問題:最終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
(即堆棧的頂部)。
看着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的操作數中使用類似堆棧的寄存器。 這些規則僅適用於類似堆棧的寄存器:
給定一組在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.