![](/img/trans.png)
[英]When to use a particular operand constraint in extended GCC inline assembly?
[英]GCC inline assembly: “g” constraint and parameter size
我知道使用內聯匯編解決以下問題是個壞主意。 我目前正在學習內聯匯編作為linux內核類的一部分,這是該類的一個賦值的一部分。
下面的開頭是一段幾乎正確的代碼片段,而不是段錯誤。 它是一個函數,副本的子src
開始於索引s_idx
和結束(僅僅)在索引e_idx
到預分配的dest
僅使用內聯組件。
static inline char *asm_sub_str(char *dest, char *src, int s_idx, int e_idx) {
asm("addq %q2, %%rsi;" /* Add start index to src (ptrs are 64-bit) */
"subl %k2, %%ecx;" /* Get length of substr as e - s (int is 32-bit) */
"cld;" /* Clear direction bit (force increment) */
"rep movsb;" /* Move %ecx bytes of str at %esi into str at %edi */
: /* No Ouputs */
: "S" (src), "D" (dest), "g" (s_idx), "c" (e_idx)
: "cc", "memory"
);
return dest;
}
此代碼的問題是第二個輸入參數的約束。 使用gcc
的默認優化和-ggdb
編譯時,會生成以下程序集:
Dump of assembler code for function asm_sub_str:
0x00000000004008e6 <+0>: push %rbp
0x00000000004008e7 <+1>: mov %rsp,%rbp
0x00000000004008ea <+4>: mov %rdi,-0x8(%rbp)
0x00000000004008ee <+8>: mov %rsi,-0x10(%rbp)
0x00000000004008f2 <+12>: mov %edx,-0x14(%rbp)
0x00000000004008f5 <+15>: mov %ecx,-0x18(%rbp)
0x00000000004008f8 <+18>: mov -0x10(%rbp),%rax
0x00000000004008fc <+22>: mov -0x8(%rbp),%rdx
0x0000000000400900 <+26>: mov -0x18(%rbp),%ecx
0x0000000000400903 <+29>: mov %rax,%rsi
0x0000000000400906 <+32>: mov %rdx,%rdi
0x0000000000400909 <+35>: add -0x14(%rbp),%rsi
0x000000000040090d <+39>: sub -0x14(%rbp),%ecx
0x0000000000400910 <+42>: cld
0x0000000000400911 <+43>: rep movsb %ds:(%rsi),%es:(%rdi)
0x0000000000400913 <+45>: mov -0x8(%rbp),%rax
0x0000000000400917 <+49>: pop %rbp
0x0000000000400918 <+50>: retq
這與第二個輸入參數的約束設置為"m"
而不是"g"
時生成的程序集相同,這使我相信編譯器正在有效地選擇"m"
約束。 在使用gdb執行這些指令時,我發現有問題的指令是+35
,它將起始偏移索引s_idx
到%rsi
的src
指針。 問題當然是s_idx
只有32位,而靜態上該位置的64位整數的高4字節不一定是0.在我的機器上,它實際上是非零的並且導致添加混亂%rsi
高4字節,導致指令+43
。
當然,上面的解決方案是將參數2
的約束更改為"r"
以便將其置於其自己的64位寄存器中,其中前4個字節被正確歸零並將其稱為一天。 相反,我的問題是為什么當表達式"%q2"
表示參數2
的值將用作64位值時,gcc將"g"
約束解析為"m"
而不是"r"
?
我不知道很多有關GCC如何解析內聯匯編,我知道有沒有真的在裝配打字的感覺,但我認為,GCC可以識別的有效隱式轉換s_idx
到long
時,它被用來作為64第一個內聯指令中的位值。 FWIW,如果我明確地將"g" (s_idx)
改為"g" ((long) s_idx)
,則gcc "g" ((long) s_idx)
"g"
約束解析為"r"
因為(long) s_idx
是臨時值。 我認為gcc也可以隱含地做到這一點?
但我認為gcc可以在第一個內聯指令中用作64位值時,將
s_idx
的有效隱式s_idx
為long
。
不,GCC只着眼於約束,而不是asm
編譯周圍的代碼時模板串上 。 填充%
模板操作數的gcc部分與周圍代碼的寄存器分配和代碼生成完全分開。
沒有什么可以檢查是否理智或理解正在使用模板操作數的上下文。也許你有一個16位輸入並希望用vmovd %k[input], %%xmm0
/ vpbroadcastw %%xmm0, %%ymm0
將其復制到向量寄存器vpbroadcastw %%xmm0, %%ymm0
。 高16位被忽略,因此您不希望gcc浪費時間零或為您進行符號擴展。 但是你絕對想要使用vmovd
而不是vpinsrw $0, %[input], %%xmm0
,因為那會更多vpinsrw $0, %[input], %%xmm0
並且具有錯誤的依賴性。 對於所有gcc知道或關心,您可以在asm注釋行中使用操作數,例如"# low word of input = %h2 \\n
#low "# low word of input = %h2 \\n
。
GNU C inline asm的設計使得約束可以告訴編譯器它需要知道的一切。 因此,您需要手動將s_idx
為long
。
您不需要為ECX轉換輸入,因為sub
指令將隱式地對結果進行零擴展(進入RCX)。 您的輸入是簽名類型,但可能您希望差異始終是正面的。
必須始終假定寄存器輸入具有超出輸入類型寬度的高垃圾。 這類似於x86-64 System V調用約定中的函數args 可以具有高位32位的垃圾 ,但是(我假設)沒有關於擴展到32位的未寫規則。 (請注意,在函數內聯之后,你的asm語句的輸入可能不是函數args。你不想使用__attribute__((noinline))
,正如我所說它無論如何也無濟於事。)
讓我相信編譯器正在有效地選擇“m”約束。
是的, gcc -O0
在每個C語句之間將所有內容溢出到內存中(因此如果在斷點處停止,則可以使用調試器更改它)。 因此,內存操作數是編譯器最有效的選擇。 它需要一個加載指令才能將其恢復到寄存器中。 即,數值是在內存中的前asm
聲明,在-O0
。
(clang在多選項約束下很糟糕,即使在-O3
時也會選擇內存,即使這意味着先溢出,但gcc沒有那個問題。)
當輸入是數字文字常量時, gcc -O0
(和clang
)將使用立即數作為g
約束,例如"g" (1234)
。 在你的情況下,你得到:
...
addq $1234, %rsi;
subl $1234, %ecx;
rep movsb
...
像"g" ((long)s_idx)
這樣的輸入即使在-O0
也會使用寄存器,就像x+y
或任何其他臨時結果一樣(只要s_idx
不long
)。 有趣的是,偶數(unsigned)
導致了一個寄存器操作數,即使int
和unsigned
的大小相同,而且強制轉換沒有指令。 此時你正好看到gcc -O0
優化程度,因為你得到的更多依賴於gcc內部設計的方式而不是有意義或有效的方式。
如果要查看有趣的asm,請啟用優化編譯 。 請參閱如何從GCC /鏗鏘聲組件輸出中刪除“噪音”? ,特別是Matt Godbolt的CppCon2017關於查看編譯器輸出的鏈接。
雖然在沒有優化的情況下檢查asm對於內聯asm來說也是好的; 你可能沒有意識到與使用問題q
覆蓋,如果它只是注冊,但它仍然是一個問題。 檢查它在-O3
如何內聯到幾個不同的調用者也是有用的(特別是如果你使用一些編譯時常量輸入進行測試)。
除了上面討論的高垃圾問題,你修改輸入操作數寄存器而不告訴編譯器。
通過使其中一些"+"
讀/寫輸出來解決此問題意味着默認情況下您的asm語句不再是volatile
,因此如果輸出未使用,編譯器將對其進行優化。 (這包括在函數內聯之后,因此return dest
對於獨立版本是足夠的,但是如果調用者忽略返回值則不在內聯之后。)
你確實使用了"memory"
clobber,因此編譯器會假設你讀/寫內存。 你可以告訴它你閱讀和寫作哪個內存,因此它可以在你的副本更有效地優化。 請參閱內聯GNU匯編程序中的獲取字符串長度 :您可以使用虛擬內存輸入/輸出約束,如"m" (*(const char (*)[]) src)
char *asm_sub_str_fancyconstraints(char *dest, char *src, int s_idx, int e_idx) {
asm (
"addq %[s_idx], %%rsi; \n\t" /* Add start index to src (ptrs are 64-bit) */
"subl %k[s_idx], %%ecx; \n\t" /* Get length of substr as e - s (int is 32-bit) */
// the calling convention requires DF=0, and inline-asm can safely assume it, too
// (it's widely done, including in the Linux kernel)
//"cld;" /* Clear direction bit (force increment) */
"rep movsb; \n\t" /* Move %ecx bytes of str at %esi into str at %edi */
: [src]"+&S" (src), [dest]"+D" (dest), [e_idx]"+c" (e_idx)
, "=m" (*(char (*)[]) dest) // dummy output: all of dest
: [s_idx]"g" ((long long)s_idx)
, "m" (*(const char (*)[]) src) // dummy input: tell the compiler we read all of src[0..infinity]
: "cc"
);
return 0; // asm statement not optimized away, even without volatile,
// because of the memory output.
// Just like dest++; could optimize away, but *dest = 0; couldn't.
}
格式化:請注意在每行末尾使用\\n\\t
以提高可讀性; 否則asm指令全部在一行上,僅由;
。 (如果你正在檢查你的asm模板是如何工作的,它會很好地組合,但不是很容易閱讀。)
這將編譯(使用gcc -O3)
asm_sub_str_fancyconstraints:
movslq %edx, %rdx # from the (long long)s_idx
xorl %eax, %eax # from the return 0, which I changed to test that it doesn't optimize away
addq %rdx, %rsi;
subl %edx, %ecx; # your code zero-extends (e_idx - s_idx)
rep movsb;
ret
我用gcc + clang在Godbolt編譯器資源管理器上放了這個+其他幾個版本 。 一個更簡單的版本修復了錯誤,但仍然使用"memory"
clobber + asm volatile
來獲得正確性,編譯時優化成本高於告訴編譯器讀取和寫入哪個內存的版本。
早期的破壞 :注意"+&S"
約束:
如果由於一些奇怪的原因,編譯器知道src
地址和s_idx
相等,它可以對兩個輸入使用相同的寄存器( esi/rsi
)。 這將導致在sub
中使用之前修改s_idx
。 聲明保持src
的寄存器早期被破壞(在最后一次讀取所有輸入寄存器之前)將強制編譯器選擇不同的寄存器。
請參閱上面的Godbolt鏈接,了解如果沒有&
為早期破壞導致破壞的呼叫者。 (但只有無意義的src = (char*)s_idx;
)。 早期刪除聲明通常是多指令asm語句所必需的,以防止更真實的破壞可能性,因此請務必記住這一點,並且只有當您確定任何只讀輸入與共享寄存器時才能將其保留。輸出或輸入/輸出操作數。 (當然使用特定寄存器約束限制了這種可能性。)
我在ecx
省略了e_idx
中的早期clobber聲明,因為唯一的“free”參數是s_idx
,並且將它們放在同一個寄存器中將導致sub same,same
和rep movsb
根據需要運行0次迭代。
讓編譯器進行數學運算當然會更有效率,並且只需要在正確的寄存器中請求rep movsb
的輸入。 特別是如果兩個e_idx
和s_idx
是編譯時間常數,這是愚蠢的,強制編譯器mov
立即到寄存器,然后減去另一個立竿見影。
或者甚至更好,不要使用內聯asm。 (但如果你真的想要rep movsb
來測試它的性能,那么內聯asm就是一種方法.gcc還有調整選項來控制memcpy
聯的方式,如果有的話。)
如果您可以避免,請不要建議你https://gcc.gnu.org/wiki/DontUseInlineAsm ,內聯asm答案是完整的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.