6.47.2 Extended Asm - Assembler Instructions with C Expression Operands
使用擴展asm,您可以從匯編程序讀取和寫入 C 變量,并執行從匯編代碼到 C 標號的跳轉。擴展asm語法使用冒號(“:”)在匯編程序模板之后分隔操作數參數:
asm asm-qualifiers ( AssemblerTemplate : OutputOperands [ : InputOperands[ : Clobbers ] ])asm asm-qualifiers ( AssemblerTemplate : OutputOperands: InputOperands: Clobbers: GotoLabels)在后一種形式中,asm 限定符包含goto(在第一種形式中則沒有)。
asm關鍵字是 GNU 擴展。在編寫可以使用-ansi和各種-std選項進行編譯的代碼時,請使用__asm__而不是asm(請參見 Alternate Keywords)。
Qualifiers
-
volatile
擴展asm語句的典型用法是操縱輸入值以產生輸出值。但是,asm語句也可能會產生副作用。如果是這樣,您可能需要使用volatile限定符來禁用某些優化。 參見 Volatile。
-
inline
如果使用內聯限定符,則出于內聯目的,將asm語句的大小視為盡可能小的大小(請參見 Size of an asm)。
-
goto
該限定符通知編譯器,asm語句可能會跳轉到 GotoLabels 中列出的某個標號。 請參閱 GotoLabels。
Parameters
-
AssemblerTemplate
這是一個文字字符串,它是匯編程序代碼的模板,由固定文本和引用輸入、輸出及跳轉參數的標記組合而成。請參見 AssemblerTemplate。
-
OutputOperands
由匯編模板中的指令修改的以逗號分隔的 C 變量列表。允許使用空列表。請參見 OutputOperands。
-
InputOperands
由 AssemblerTemplate 中的指令讀取的以逗號分隔的 C 表達式列表。允許使用空列表。請參見 InputOperands。
-
Clobbers
除了輸出之外,由匯編模板更改的以逗號分隔的寄存器或其它值的列表。允許空列表。請參閱 InputOperands。
-
GotoLabels
當使用asm的goto形式時,此部分包含 AssemblerTemplate 中的代碼可能跳轉到的所有 C 標號的列表。請參閱 GotoLabels。
asm語句不能跳轉到其他asm語句中,只能跳轉到列出的 GotoLabels 中。GCC 的優化器不知道其他跳轉;因此在決定如何優化時不能考慮這些跳轉。
輸入+輸出+跳轉操作數的總數限制為30。
Remarks
asm語句允許您在 C 代碼中直接包含匯編指令。這可以幫助您最大限度地提高對時間敏感的代碼的性能,或者訪問 C 程序不容易獲得的匯編指令。
請注意,擴展asm語句必須在函數內部。只有基本asm可以在函數之外(請參閱 Basic Asm)。使用naked屬性聲明的函數也需要基本asm(請參見 Function Attributes)。
盡管asm的用法多種多樣,但將asm語句看作一系列將輸入參數轉換為輸出參數的低級指令可能會有所幫助。因此,一個使用asm的 i386的簡單示例(如果不是特別有用)可能像這樣:
int src = 1; int dst; asm ("mov %1, %0\n\t""add $1, %0": "=r" (dst) : "r" (src));printf("%d\n", dst);此代碼將src復制到dst,并將dst加1。
6.47.2.1 Volatile
如果 GCC 的優化器確定不需要輸出變量,則有時會丟棄asm語句。同樣,如果優化器認為代碼將始終返回相同的結果(即,兩次調用之間的輸入值均不變),則會將代碼移出循環。使用volatile限定詞將禁用這些優化。沒有輸出操作數的asm語句和asm goto語句是隱式易失的。
此 i386代碼演示了一個不使用(或不需要)volatile限定符的情況。如果它執行下面的斷言檢查,則此代碼使用asm執行驗證;否則,任何代碼都不會引用dwRes。結果,優化器可以丟棄asm語句,從而消除了對整個DoCheck例程的需求。通過在不需要時取消volatile限定符,可以使優化器生成盡可能高效的代碼。
void DoCheck(uint32_t dwSomeValue) {uint32_t dwRes;// Assumes dwSomeValue is not zero.asm ("bsfl %1,%0": "=r" (dwRes): "r" (dwSomeValue): "cc");assert(dwRes > 3); }下一個示例顯示了這樣一種情況:優化器可以識別出輸入(dwSomeValue)在函數執行期間從未改變,因此可以將asm移出循環以產生更有效的代碼。同樣,使用volatile限定符會禁用這種類型的優化。
void do_print(uint32_t dwSomeValue) {uint32_t dwRes;for (uint32_t x=0; x < 5; x++){// Assumes dwSomeValue is not zero.asm ("bsfl %1,%0": "=r" (dwRes): "r" (dwSomeValue): "cc");printf("%u: %u %u\n", x, dwSomeValue, dwRes);} }以下示例演示了需要使用volatile限定符的情況。它使用x86 rdtsc指令,該指令讀取計算機的時間戳記計數器。如果沒有volatile限定符,優化器可能會假定asm塊將始終返回相同的值,從而優化掉第二個調用。
uint64_t msr;asm volatile ( "rdtsc\n\t" // Returns the time in EDX:EAX."shl $32, %%rdx\n\t" // Shift the upper bits left."or %%rdx, %0" // 'Or' in the lower bits.: "=a" (msr): : "rdx");printf("msr: %llx\n", msr);// Do other work...// Reprint the timestamp asm volatile ( "rdtsc\n\t" // Returns the time in EDX:EAX."shl $32, %%rdx\n\t" // Shift the upper bits left."or %%rdx, %0" // 'Or' in the lower bits.: "=a" (msr): : "rdx");printf("msr: %llx\n", msr);GCC 的優化程序不會像前面示例中的非易失性代碼那樣處理此代碼。它們不會將其移出循環,也不會在假設前一次調用的結果仍然有效的前提下忽略它。
請注意,編譯器甚至可以相對于其他代碼移動volatile asm指令,包括跨跳轉指令。例如,在許多目標處理器中都有一個系統寄存器來控制浮點運算的舍入模式。像下面的 PowerPC 示例一樣,使用volatile asm語句進行設置并不可靠。
asm volatile("mtfsf 255, %0" : : "f" (fpenv)); sum = x + y;編譯器可能將加法移回volatile asm語句之前。為了使它按預期工作,請通過在后續代碼中引用變量向“asm”添加一個人工依賴項,例如:
asm volatile ("mtfsf 255,%1" : "=X" (sum) : "f" (fpenv)); sum = x + y;在某些情況下,GCC 可能會在優化時重復(或刪除重復的)匯編代碼。如果您的asm代碼定義了符號或標號,則可能導致在編譯期間出現意外的重復符號錯誤。 使用“%=”(請參閱 AssemblerTemplate)可以幫助解決此問題。
6.47.2.2 Assembler Template
匯編器模板是包含匯編器指令的文字字符串。編譯器替換模板中引用輸入、輸出和跳轉標號的標記,然后將結果字符串輸出到匯編器。該字符串可以包含匯編程序可識別的任何指令,包括偽指令。GCC 不會自行解析匯編程序指令,也不知道它們的含義,甚至不知道它們是否為有效的匯編程序輸入。但是,它會對語句進行計數(請參閱 Size of an asm)。
您可以將多個匯編程序指令放到一個asm字符串中,并以該系統中匯編代碼常用的字符分隔。在大多數情況下有效的組合是換行符和制表符(寫為“\n\t”)。一些匯編程序允許使用分號作為行分隔符。但是,請注意,某些匯編語法將分號作為注釋符。
即使編譯后使用volatile限定符,也不要期望匯編后的asm語句序列能完全保持連續。如果某些指令需要在輸出中保持連續,請將它們放在單個多指令asm語句中。
不使用輸入(輸出)操作數(例如通過直接從匯編模板使用全局符號)從 C 程序訪問數據可能無法按預期方式工作。同樣,直接從匯編器模板調用函數需要詳細了解目標匯編器和 ABI。
由于 GCC 不會解析匯編程序模板,因此它看不到所引用的任何符號。這可能導致 GCC 將這些符號作為未引用的符號丟棄,除非它們也作為輸入、輸出或跳轉操作數列出。
Special format strings
除了輸入、輸出和跳轉操作數所描述的標記外,這些標記在匯編程序模板中還有特殊的含義:
-
“%%”
將單個“%”輸出到匯編代碼中。
-
“%=”
輸出asm語句的每個實例在整個編譯中唯一的數字。在創建本地標號并在生成多個匯編指令的單個模板中多次引用它們時,此選項非常有用。
-
“%{”
-
“%|”
-
“%}”
將“{”、“|”和“}”字符分別輸出到匯編代碼中。不轉義時,這些字符在多種匯編語法中都具有特殊含義,如下所述。
Multiple assembler dialects in asm templates
在 x86等目標平臺上,GCC 支持多種匯編語法。-masm選項控制 GCC 內聯匯編程序的默認語法。-masm選項的特定目標文檔包含受支持的語法列表,以及未指定時的默認語法。理解這些信息可能很重要,因為使用一種語法進行編譯時可以正常工作的匯編代碼,如果按照另一種語法編譯可能會失敗。請參閱 x86 Options。
如果您的代碼需要支持多種匯編語法(例如,如果您正在編寫需要支持多種編譯選項的公共頭文件),請使用以下形式的構造:
{ dialect0 | dialect1 | dialect2... }當使用 #0語法編譯代碼時,此構造輸出dialect0;語法 1,輸出dialect1,等等。如果括號內的替代項少于編譯器支持的語法數量,則此構造不輸出任何內容。
例如,如果 x86編譯器支持兩種語法(“att”,“intel”),則匯編程序模板如下:
"bt{l %[Offset],%[Base] | %[Base],%[Offset]}; jc %l2"等價于其中之一
"btl %[Offset],%[Base] ; jc %l2" /* att dialect */ "bt %[Base],%[Offset]; jc %l2" /* intel dialect */使用相同的編譯器,以下代碼:
"xchg{l}\t{%%}ebx, %1"對應于
"xchgl\t%%ebx, %1" /* att dialect */ "xchg\tebx, %1" /* intel dialect */不支持嵌套語法替代項。
6.47.2.3 Output Operands
asm語句具有零個或多個輸出操作數,這些輸出操作數指示由匯編代碼修改的 C 變量的名稱。
在下面這個 i386示例中,old(在模板字符串中稱為%0)和*Base(稱為%1)是輸出,Offset(%2)是輸入:
bool old;__asm__ ("btsl %2,%1\n\t" // Turn on zero-based bit #Offset in Base."sbb %0,%0" // Use the CF to calculate old.: "=r" (old), "+rm" (*Base): "Ir" (Offset): "cc");return old;操作數之間用逗號分隔。每個操作數都有以下形式:
[ [asmSymbolicName] ] constraint (cvariablename)-
asmSymbolicName
指定操作數的符號名稱。在匯編程序模板中將名稱括在方括號中即可引用該名稱(即“%[Value]”)。名稱的作用域為包含定義的asm語句。任何有效的 C 變量名稱都是可接受的,包括周圍代碼中已定義的名稱。同一asm語句中的兩個操作數不能使用相同的符號名。
如果不使用 asmSymbolicName,請使用匯編模板中操作數列表中操作數(從零開始)的位置。例如,如果有三個輸出操作數,則在模板中使用“%0”引用第一個操作數,使用“%1”引用第二個操作數,“%2”表示第三個操作數。
-
constraint
一個字符串常量,它指定對所放置操作數的約束;有關詳細信息,請參見 Constraints。
輸出約束必須以“=”(覆蓋現有值的變量)或“+”(讀取和寫入時)開頭。使用“=”時,除非操作數綁定到輸入,否則不能假定該位置包含asm條目的現有值;請參閱 Input Operands。
在前綴之后,必須有一個或多個其他約束來描述值所在的位置(請參閱 Constraints)。常見的限制條件包括“r”代表寄存器,“m”代表內存。當您列出多個可能的位置(例如,“= rm”)時,編譯器將根據當前上下文選擇最有效的位置。如果您在asm語句允許的范圍內列出盡可能多的替代項,則可以允許優化器產生最佳的代碼。如果必須使用特定的寄存器,但是您的機器約束不能提供足夠的控制來選擇所需的特定寄存器,則可以使用局部寄存器變量來提供解決方案(請參閱 Local Register Variables)。
-
cvariablename
指定一個 C 左值表達式來保存輸出,通常是一個變量名。括號是語法的必需部分。
當編譯器選擇用于表示輸出操作數的寄存器時,它不使用任何被破壞的寄存器(請參見 Clobbers and Scratch Registers)。
輸出操作數表達式必須為左值。編譯器無法檢查操作數是否具有對于正在執行的指令合理的數據類型。對于不能直接尋址的輸出表達式(例如位字段),約束必須允許寄存器。在這種情況下,GCC 將寄存器用作asm的輸出,然后將該寄存器存儲到輸出中。
使用“+”約束修飾符的操作數計為兩個操作數(即輸入和輸出),每個asm語句最多可包含30個操作數。
對不能與輸入重疊的所有輸出操作數使用“&”約束修飾符(請參閱 Modifiers)。否則,GCC 假設匯編代碼在產生輸出之前會消耗其輸入,可能將輸出操作數分配到與不相關的輸入操作數相同的寄存器中。如果匯編代碼實際上由多條指令組成,那么這種假設可能是錯誤的。
如果一個輸出參數(a)允許寄存器約束,而另一個輸出參數(b)允許內存約束,則可能發生相同的問題。GCC 生成的訪問 b 中內存地址的代碼可能包含與 a 共享的寄存器,并且 GCC 認為這些寄存器是asm的輸入。如上所述,GCC 假定在寫入任何輸出之前就消耗了這些輸入寄存器。如果asm語句在使用 b 之前寫入 a,則此假設可能會導致錯誤的行為。在 a 上的寄存器約束中加入“&”修飾符可確保修改 a 時不會影響 b 引用的地址。否則,如果在使用 b 之前修改了 a,則 b 的位置未定義。
asm支持在操作數上應用操作數修飾符(例如,“%k2”而不是簡單的“%2”)。通常,這些限定符取決于硬件。可在 x86 Operand modifiers 中找到 x86支持的修飾符列表。
如果asm后面的 C 代碼沒有使用任何輸出操作數,請對asm語句使用volatile,以防止優化程序將asm語句作為不必要的代碼丟棄(請參見 Volatile)。
此代碼不使用可選的 asmSymbolicName。因此,它將第一個輸出操作數引用為%0(如果有第二個,則為%1,依此類推)。第一個輸入操作數的數字比最后一個輸出操作數的數字大1。在此 i386示例中,將Mask引用為%1:
uint32_t Mask = 1234; uint32_t Index;asm ("bsfl %1, %0": "=r" (Index): "r" (Mask): "cc");該代碼將覆蓋變量Index(“=”),并將值存儲在寄存器(“r”)中。使用通用的“r”約束而不是直接指定某個特定寄存器,可使編譯器選擇要使用的寄存器,從而可以提高代碼效率。如果匯編指令需要特定的寄存器,則可能無法實現。
以下 i386示例使用 asmSymbolicName 語法。它產生的結果與上面的代碼相同,但是有些人可能認為它更具可讀性或可維護性,因為在添加或刪除操作數時不需要對索引號進行重新排序。名稱aIndex和aMask僅在此示例中用于強調哪些名稱在何處使用。可以重用名稱Index和Mask。
uint32_t Mask = 1234; uint32_t Index;asm ("bsfl %[aMask], %[aIndex]": [aIndex] "=r" (Index): [aMask] "r" (Mask): "cc");下面是輸出操作數的更多示例。
uint32_t c = 1; uint32_t d; uint32_t *e = &c;asm ("mov %[e], %[d]": [d] "=rm" (d): [e] "rm" (*e));這里,d可以在寄存器中,也可以在內存中。由于編譯器可能已經在寄存器中擁有e指向的uint32_t位置的當前值,因此可以通過指定兩個約束使它為d選擇最佳位置。
6.47.2.4 Flag Output Operands
有些目標有一個特殊的寄存器,用于保存操作或比較結果的“標志”。通常,要么asm不修改該寄存器的內容,要么視為asm語句破壞了內容。
在某些目標上,存在一種特殊形式的輸出操作數,根據這種形式,標志寄存器中的條件可以是asm的輸出。支持的條件集是特定于目標的,但一般規則是輸出變量必須是標量整數,并且值是布爾值。當受支持時,目標定義預處理器符號__GCC_ASM_FLAG_OUTPUTS__。
由于標志輸出操作數的特殊性質,約束不能包括替代項。
通常,目標只有一個標志寄存器,因此是許多指令的隱含操作數。在這種情況下,不應在匯編模板中通過%0等引用操作數,因為匯編語言中沒有相應的文本。
-
ARM
-
AArch64
ARM 系列的標志輸出約束形式為“=@cc cond”,其中 cond 是 ARM 中為ConditionHolds定義的標準條件之一。
eq
Z 標志置位,或等于
ne
Z 標志清除或不相等
cs
hs
C 標志置位或無符號大于等于
cc
lo
C 標志清除或無符號小于
mi
N 標志置位或“減號”
pl
N 標志清除或“加號”
vs
V標志置位或有符號溢出
vc
V標志清除
hi
無符號大于
ls
無符號小于等于
ge
有符號大于等于
lt
有符號小于
gt
有符號大于
le
有符號小于等于在 thumb1模式下不支持標志輸出約束。
-
x86 系列
x86系列的標志輸出約束形式為“=@cc cond”,其中 cond 是 ISA 手冊中為jcc或setcc定義的標準條件之一。
a
“高于”或無符號大于
ae
“高于或等于”或無符號大于或等于
b
“以下”或無符號小于
be
“低于或等于”或無符號小于或等于
c
進位標志置位
e
z
“相等”或零標志置位
g
有符號大于
ge
有符號大于或等于
l
有符號小于
le
有符號小于或等于
o
溢出標志置位
p
奇偶校驗標志置位
s
符號標志置位
na
nae
nb
nbe
nc
ne
ng
nge
nl
nle
no
np
ns
nz
“not”標志,或上述標志的反向版本
6.47.2.5 Input Operands
輸入操作數使匯編代碼可使用 C 變量和表達式中的值。
操作數之間用逗號分隔。每個操作數具有以下格式:
[ [asmSymbolicName] ] constraint (cexpression)-
asmSymbolicName
指定操作數的符號名。在匯編程序模板中引用該名稱的方法是將其括在方括號中(即“%[Value]”)。名稱的作用域為包含其定義的asm語句。任何有效的 C 變量名稱都是可接受的,包括上下文代碼中已定義的名稱。同一asm語句中的兩個操作數都不能使用相同的符號名。
如果不使用 asmSymbolicName,請在匯編器模板的操作數列表中使用操作數(從零開始)的位置。例如,如果有兩個輸出操作數和三個輸入,則在模板中使用“%2”來引用第一個輸入操作數,第二個為“%3”,第三個為“%4”。
-
constraint
指定操作數位置約束的字符串常量;有關詳細信息,請參見 Constraints。
輸入約束字符串不能以“=”或“+”開頭。當您列出多個可能的位置(例如"irm")時,編譯器會根據當前上下文選擇效率最優的位置。如果必須使用特定的寄存器,但是您的機器約束不能提供足夠的控制來選擇所需的特定寄存器,則可以使用局部寄存器變量來解決(請參閱 Local Register Variables)。
輸入約束也可以是數字(例如,"0")。這表明在輸出約束列表中對應索引(從0開始)的輸出約束與該輸入共享同一位置。對輸出操作數使用 asmSymbolicName 語法時,可以使用這些名稱(用括號“[]”括起來)代替數字。
-
cexpression
這是作為輸入傳遞給asm語句的 C 變量或表達式。括號是語法的必需部分。
當編譯器選擇用于表示輸入操作數的寄存器時,它不使用所有被破壞的寄存器(請參見 Clobbers and Scratch Registers)。
如果沒有輸出操作數,但有輸入操作數,請在輸出操作數的位置放置兩個連續的冒號:
__asm__ ("some instructions": /* No outputs. */: "r" (Offset / 8));警告:請勿修改純輸入操作數的內容(綁定到輸出的輸入除外)。編譯器假定在退出asm語句時,這些操作數包含與執行該語句之前相同的值。無法使用 Clobber 通知編譯器這些輸入中的值正在更改。一種常見的解決方法是將變化的輸入變量綁定到永遠不會使用的輸出變量。但是請注意,如果asm語句后的代碼不使用任何輸出操作數,則 GCC 優化器可能會將asm語句視為不需要的內容而丟棄(請參見 Volatile)。
asm支持操作數上的操作數修飾符(例如“%k2”而不是簡單的“%2”)。通常,這些限定符依賴于硬件。x86 支持的修飾符列表位于 x86 Operand modifiers。
在本例中,使用虛構的combine指令,輸入操作數1的約束"0"表示它必須與輸出操作數0占據相同的位置。只有輸入操作數約束中可以使用數字,并且它們必須分別對應到一個輸出操作數。約束中只有數字(或符號匯編程序名稱)可以保證一個操作數與另一個操作數位于同一位置。僅foo是兩個操作數的值這一事實不足以保證它們在生成的匯編代碼中位于同一位置。
asm ("combine %2, %0" : "=r" (foo) : "0" (foo), "g" (bar));下面是一個使用符號名的示例。
asm ("cmoveq %1, %2, %[result]" : [result] "=r"(result) : "r" (test), "r" (new), "[result]" (old));6.47.2.6 Clobbers and Scratch Registers
盡管編譯器知道對輸出操作數列表項所做的更改,但內聯匯編代碼可能會修改的不僅僅是輸出。例如,計算可能需要額外的寄存器,或者處理器可能會由于特定匯編指令的副作用而覆蓋寄存器。為了將這些更改通知編譯器,請在 Clobber 列表中列出它們。Clobber 列表項可以是寄存器名稱,也可以是特殊的損壞部分(如下所示)。每個損壞列表項都是一個字符串常量,用雙引號引起來并用逗號分隔。
損壞描述不能以任何方式與輸入或輸出操作數重疊。例如,操作數不能使用損壞列表中列出的寄存器。聲明為存在于特定寄存器中的變量(請參閱 Explicit Register Variables)和用作asm輸入輸出操作數的變量,不能出現在 Clobber 描述中。特別地,如果不同時將輸入操作數指定為輸出操作數,就無法標識對其進行了修改。
當編譯器選擇使用哪個寄存器表示輸入和輸出操作數時,它不會使用任何被破壞的寄存器。因此,匯編代碼中可不受限制的使用損壞寄存器。
另一個限制是,損壞列表不應包含堆棧指針寄存器。這是因為編譯器要求在asm語句之后,堆棧指針的值必須與進入該語句時相同。但是,以前版本的 GCC 并未強制執行此規則,,并且允許堆棧指針出現在列表中,但語義不清楚。不建議使用此行為,并且在未來的 GCC 版本中列出堆棧指針可能會成為錯誤。
下面是 VAX 的一個實際示例,顯示了破壞性寄存器的使用:
asm volatile ("movc3 %0, %1, %2": /* No outputs. */: "g" (from), "g" (to), "g" (count): "r0", "r1", "r2", "r3", "r4", "r5", "memory");此外,還有兩個特殊的損壞參數:
- "cc"
"cc"損壞符表示匯編代碼修改了標志寄存器。在某些機器上,GCC 將條件代碼表示為特定的硬件寄存器。"cc"用于命名該寄存器。在其他機器上,條件代碼的處理有所不同,并且指定"cc"不起作用。但無論目標是什么,它都是有效的。
-
"memory"
"memory"損壞符告訴編譯器,匯編代碼對輸入和輸出操作數中未列出的項執行內存讀取或寫入操作(例如,訪問由一個輸入參數指向的內存)。為了確保內存中包含正確的值,GCC 可能需要在執行asm之前將特定的寄存器值刷新到內存中。此外,編譯器不會假設在asm之前從內存讀取的任何值在asm之后保持不變;它會根據需要重新加載它們。使用"memory"損壞項有效地構成了編譯器的讀/寫內存屏障。
請注意,此損壞項不會阻止處理器通過asm語句進行推測性讀取。為避免這種情況,您需要特定于處理器的柵欄指令。
將寄存器刷新到內存會影響性能,對于時間敏感的代碼可能是一個問題。您可以向 GCC 提供更好的信息來避免這種情況,如以下示例所示。至少,別名規則允許 GCC 知道不需要刷新哪些內存。
下面是一個虛擬的平方和指令,它使用兩個指針指向的內存中的浮點值,并產生浮點寄存器輸出。請注意,x和y都在asm參數中出現兩次,一次是指定要訪問的內存,另一次是指定asm使用的基址寄存器。通常,這樣做不會浪費寄存器,因為 GCC 可以將同一寄存器用于這兩個目的。但是,在這個asm中為x使用%1和%3并期望它們相同是不明智的。實際上,%3可能不是寄存器。它可能是對x指向的對象的符號內存引用。
asm ("sumsq %0, %1, %2": "+f" (result): "r" (x), "r" (y), "m" (*x), "m" (*y));以下是一個虛構的*z++ = *x++ * *y++指令。請注意,必須將x、y和z指針寄存器指定為輸入(輸出),因為asm會對它們進行修改。
asm ("vecmul %0, %1, %2": "+r" (z), "+r" (x), "+r" (y), "=m" (*z): "m" (*x), "m" (*y));一個 x86示例,其中字符串內存參數的長度未知。
asm("repne scasb": "=c" (count), "+D" (p): "m" (*(const char (*)[]) p), "0" (-1), "a" (0));如果您知道上面的命令只讀取一個10字節的數組,那么可以使用類似于"m" (*(const char (*)[10]) p)的內存輸入。
下面是一個在匯編中實現的 PowerPC 矢量縮放的示例,包括矢量和條件代碼損壞項,以及一些已初始化且經asm不變的偏移寄存器。
void dscal (size_t n, double *x, double alpha) {asm ("/* lots of asm here */": "+m" (*(double (*)[n]) x), "+&r" (n), "+b" (x): "d" (alpha), "b" (32), "b" (48), "b" (64),"b" (80), "b" (96), "b" (112): "cr0","vs32","vs33","vs34","vs35","vs36","vs37","vs38","vs39","vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47"); }與其通過損壞列表為asm語句分配固定寄存器用于暫存,還可以定義變量并將其設置為早期損壞輸出,如下面的示例中的a2和a3所示。這在寄存器分配上賦予了編譯器更多的自由。您還可以定義一個變量,并將其作為與輸入綁定的輸出,如a0和a1,分別與ap和lda綁定。當然,對于綁定輸出,asm不能在修改輸出寄存器后使用輸入值,因為它們是同一個寄存器。更重要的是,如果您省略了輸出上的早期損壞,那么如果 GCC 能夠證明它們在輸入asm時具有相同的值,那么 GCC 可能會將相同的寄存器分配給另一個輸入。這就是為什么a1具有早期損壞的原因??梢韵胂?#xff0c;lda的綁定輸入值為16,若沒有早期損壞標識,它與%11共享同一個寄存器。另一方面,ap不能與任何其他輸入相同,因此不需要對a0上進行早期提示。在這種情況下也是不可取的。 a0上的早期損壞會導致 GCC 為"m" (*(const double (*)[]) ap)輸入分配一個單獨的寄存器。請注意,設置由asm語句修改的初始化臨時寄存器的方法是將輸入綁定到輸出。GCC 假定沒有綁定到輸出的輸入是不變的,例如,下面的"b" (16)將%11設置為16,如果恰好需要值16,則 GCC 可以在以下代碼中使用該寄存器。如果在使用暫存器之前已消耗了可能共享同一寄存器的所有輸入,則甚至可以將普通的asm輸出用于暫存器。除非 GCC 對asm參數數量的限制,被asm語句破壞的 VSX 寄存器可能已經使用了此技術。
static void dgemv_kernel_4x4 (long n, const double *ap, long lda,const double *x, double *y, double alpha) {double *a0;double *a1;double *a2;double *a3;__asm__(/* lots of asm here */"#n=%1 ap=%8=%12 lda=%13 x=%7=%10 y=%0=%2 alpha=%9 o16=%11\n""#a0=%3 a1=%4 a2=%5 a3=%6":"+m" (*(double (*)[n]) y),"+&r" (n), // 1"+b" (y), // 2"=b" (a0), // 3"=&b" (a1), // 4"=&b" (a2), // 5"=&b" (a3) // 6:"m" (*(const double (*)[n]) x),"m" (*(const double (*)[]) ap),"d" (alpha), // 9"r" (x), // 10"b" (16), // 11"3" (ap), // 12"4" (lda) // 13:"cr0","vs32","vs33","vs34","vs35","vs36","vs37","vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47"); }6.47.2.7 Goto Labels
asm goto允許匯編代碼跳轉到一個或多個 C 標號。asm goto語句中的 GotoLabels 部分包含用逗號分隔的列表,其中列出了匯編代碼可能跳轉到的所有 C 標號。GCC 假定asm的執行會進入下一條語句(如果不是這種情況,請考慮在asm語句之后使用__builtin_unreachable內在函數)。通過使用hot和cold標號屬性(請參見 Label Attributes),可以改善asm goto的優化。
如果匯編代碼確實進行了任何修改,請使用"memory"損壞符強制優化器將所有寄存器值刷新到內存中,并在asm語句之后根據需要重新加載它們。
還要注意,asm goto語句總是隱式地被認為是易失性的。
在asm goto中若僅在某些可能的控制流路徑上設置輸出操作數時要小心。如果您沒有在給定路徑上設置輸出,并且從不在此路徑上使用它,那是可以的。否則,您應使用“ +”約束修飾符,這意味著操作數是輸入也是輸出。使用此修飾符,您將在asm goto`的所有可能路徑上獲得正確的值。
要在匯編器模板中引用標號,請在標號前添加“%l”(字母“L”的小寫),然后加上其在 GotoLabels 中(從零開始)位置與輸入操作數數量之和。例如,如果asm具有三個輸入并引用了兩個標號,則將第一個標號稱為“%l3”,將第二個標號稱為“%l4”)。
或者,您可以使用括號內的實際 C 標號名稱引用標號。例如,要引用名為carry的標號,可以使用“%l[carry]”。使用此方法時,標號仍必須在 GotoLabels 部分中列出。
以下是 i386的asm goto示例:
asm goto ("btl %1, %0\n\t""jc %l2": /* No outputs. */: "r" (p1), "r" (p2) : "cc" : carry);return 0;carry: return 1;以下示例顯示了使用一個內存損壞的asm goto。
int frob(int x) {int y;asm goto ("frob %%r5, %1; jc %l[error]; mov (%2), %%r5": /* No outputs. */: "r"(x), "r"(&y): "r5", "memory" : error);return y; error:return -1; }以下示例顯示了使用一個輸出的asm goto。
int foo(int count) {asm goto ("dec %0; jb %l[stop]": "+r" (count)::: stop);return count; stop:return 0; }以下人工示例顯示了一個asm goto,它僅在asm goto內部的一條路徑上設置輸出。 使用=約束修飾符而不是+是錯誤的,因為從asm goto的所有路徑上都使用factor。
int foo(int inp) {int factor = 0;asm goto ("cmp %1, 10; jb %l[lab]; mov 2, %0": "+r" (factor): "r" (inp):: lab); lab:return inp * factor; /* return 2 * inp or 0 if inp < 10 */ }6.47.2.8 x86 Operand Modifiers
擴展asm語句的匯編程序模板中對輸入、輸出和跳轉操作數的引用,在代碼輸出到匯編器時可以使用修飾符來影響操作數的格式化方式。例如,以下代碼對 x86使用“h”和“b”修飾符:
uint16_t num; asm volatile ("xchg %h0, %b0" : "+a" (num) );這些修飾符生成以下匯編代碼:
xchg %ah, %al本討論的其余部分使用以下代碼進行說明。
int main() {int iInt = 1;top:asm volatile goto ("some assembler instructions here": /* No outputs. */: "q" (iInt), "X" (sizeof(unsigned char) + 1), "i" (42): /* No clobbers. */: top); }如果沒有修飾符,這就是匯編器的“att”和“intel”語法的操作數輸出:
| %0 | %eax | eax |
| %1 | $2 | 2 |
| %3 | $.L3 | OFFSET FLAT:.L3 |
| %4 | $8 | 8 |
| %5 | %xmm0 | xmm0 |
| %7 | $0 | 0 |
下表顯示了受支持的修飾符及其效果。
| A | 打印絕對內存引用。 | %A0 | *%rax | rax |
| b | 打印寄存器的 QImode 名稱。 | %b0 | %al | al |
| B | 打印b的操作碼后綴。 | %B0 | b | |
| c | 需要一個常量操作數,并打印不帶標點符號的常量表達式。 | %c1 | 2 | 2 |
| d | 打印 AVX 指令的重復寄存器操作數。 | %d5 | %xmm0, %xmm0 | xmm0, xmm0 |
| E | 當目標為64位時,以雙整數(DImode)模式(8字節)打印地址。否則模式未指定(VOIDmode)。 | %E1 | %(rax) | [rax] |
| g | 打印寄存器的 V16SFmode 名稱。 | %g0 | %zmm0 | zmm0 |
| h | 打印“高”寄存器的 QImode 名稱。 | %h0 | %ah | ah |
| H | 向可偏移內存引用添加8個字節。在訪問高8字節的 SSE 值時非常有用。對于(%rax)中的 memref,它會生成 | %H0 | 8(%rax) | 8[rax] |
| k | 打印寄存器的 SImode 名稱。 | %k0 | %eax | eax |
| l | 打印不帶標點符號的標號名稱。 | %l3 | .L3 | .L3 |
| L | 打印l的操作碼后綴。 | %L0 | l | |
| N | 打印掩碼z。 | %N7 | {z} | {z} |
| p | 打印原始符號名稱(不帶特定于語法的前綴)。 | %p2 | 42 | 42 |
| P | 如果用于函數,則打印 PLT 后綴并生成 PIC 代碼。例如,為函數 foo() 發出foo@PLT而不是“foo”。 如果用于常量,則刪除所有特定于語法的前綴并發出裸常量。 參見上面的p。 | |||
| q | 打印寄存器的 DImode 名稱。 | %q0 | %rax | rax |
| Q | 打印q的操作碼后綴。 | %Q0 | q | |
| R | 打印嵌入式舍入和sae | %R4 | {rn-sae}, | , {rn-sae} |
| r | 僅打印sae。 | %r4 | {sae}, | , {sae} |
| s | 打印移位雙計數,然后使用匯編程序參數定界符打印s的操作碼后綴。 | %s1 | $2, | 2, |
| S | 打印s的操作碼后綴。 | %S0 | s | |
| t | 打印寄存器的 V8SFmode 名稱。 | %t5 | %ymm0 | ymm0 |
| T | 打印t的操作碼后綴。 | %T0 | t | |
| V | 打印不帶%的裸整數寄存器名。 | %V0 | eax | eax |
| w | 打印寄存器的 HImode 名稱。 | %w0 | %ax | ax |
| W | 打印w的操作碼后綴。 | %W0 | w | |
| x | 打印寄存器的 V4SFmode 名稱。 | %x5 | %xmm0 | xmm0 |
| y | 打印“st(0)”而不是“st”作為寄存器。 | %y6 | %st(0) | st(0) |
| z | 打印當前整數操作數大小的操作碼后綴b/w/l/q之一)。 | %z0 | l | |
| Z | 像z一樣,帶有 x87指令的特殊后綴。 |
6.47.2.9 x86 Floating-Point asm Operands
在 x86目標上,關于asm操作數中堆棧式寄存器的使用有幾個規則。這些規則僅適用于堆棧式寄存器的操作數:
給定一組在asm中消失的輸入寄存器,有必要知道asm隱式彈出了哪些輸入寄存器,以及哪些必須由GCC顯式彈出。
除非受約束以匹配輸出操作數,否則必須顯式以損壞修飾由asm隱式彈出的輸入寄存器。
對于由asm隱式彈出的任何輸入寄存器,必須知道如何調整堆棧以補償彈出。如果有任何未彈出的輸入比隱式彈出的寄存器更靠近寄存器堆棧的頂部,則將無法知道堆棧看起來是什么樣子,也不清楚堆棧的其余部分如何“向上滑動”。
所有隱式彈出的輸入寄存器必須比任何未隱式彈出的輸入寄存器更接近寄存器堆棧的頂部。
如果輸入在asm中消失,編譯器可能會使用輸入寄存器重新加載輸出??紤]以下示例:
asm ("foo" : "=t" (a) : "f" (b));這段代碼表示asm不會彈出輸入b,asm將結果推送到寄存器堆棧上,即asm之后的堆棧比之前更深一層。但是,重載可能認為它可以對輸入和輸出使用相同的寄存器。
為了防止這種情況的發生,如果任何輸入操作數使用“f”約束,則所有輸出寄存器約束都必須使用“&” 早期損壞修飾符。
上面的示例正確編寫為:
asm ("foo" : "=&t" (a) : "f" (b));一些操作數需要放在堆棧中的特定位置。所有輸出操作數都屬于這一類 GCC,沒有其他方法知道輸出出現在哪個寄存器中,除非您在約束中指出這一點。
輸出操作數必須明確指示asm后輸出在哪個寄存器中。不允許“=f”:操作數約束必須選擇具有單個寄存器的類。
不能在現有堆棧寄存器之間“插入”輸出操作數。由于 387 操作碼不使用讀/寫操作數,因此所有輸出操作數在asm之前都是無效的,并由asm推送。除了堆棧寄存器的頂部之外,將其推入其他任何地方都沒有意義。
輸出操作數必須從寄存器堆棧的頂部開始:輸出操作數不能“跳過”寄存器。
這個asm接受一個內部彈出的輸入,并產生兩個輸出。
asm ("fsincos" : "=t" (cos), "=u" (sin) : "0" (inp));該asm接受兩個輸入,這些輸入由fyl2xp1操作碼彈出,并將它們替換為一個輸出。要使編譯器知道fyl2xp1會同時彈出兩個輸入,st(1)損壞項是必需的。
asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)");6.47.2.10 MSP430 Operand Modifiers
略
參考資料:
- 6.47.2 Extended Asm - Assembler Instructions with C Expression Operands
- How to Use Inline Assembly Language in C Code(C語言內聯匯編)–continuing…
- gcc內嵌匯編詳解
- linux下gcc的嵌入匯編
- 內聯匯編
- Conditional execution
- Condition Codes 1: Condition Flags and Codes
- GNU 擴展:內聯匯編
- gcc x64 asm 內聯匯編嘗試
- 翻譯 | “擴展asm”——用C表示操作數的匯編程序指令
- 6.45.2擴展的Asm匯編器指令和C表達式操作數
- GCC擴展內聯匯編
- Android漫游記(5)—ARM GCC 內聯匯編烹飪書(附實例分析)
- ARM NEON優化(二)——NEON編程, 優化心得及內聯匯編使用心得
- 內存柵欄
- Inline assembly - start from scratch
- When to use earlyclobber constraint in extended GCC inline assembly?
- 2.2.3.1.2. Vector-Scalar Floating-Point Operations (VSX)
- Intel 80387 Militarity CoProcessor DataSheet
- Intel 8087
- 淺談ARM 匯編中的標號(Labels)
- What are some major dialects of assembly language?
- 匯編入門完全指北
- masm
- 匯編語言–Linux 匯編語言開發指南
- ATT匯編語言學習筆記
- 匯編語言-復習
- AT&T和Intel匯編語法
- x86_64匯編之一:AT&T匯編語法
- tolarian-academy/gcc-inline-asm.md
- 內聯匯編 - 從頭開始
- Memory Instructions: Load and Store
- 6.47.3.4 Constraints for Particular Machines
總結
以上是生活随笔為你收集整理的6.47.2 Extended Asm - Assembler Instructions with C Expression Operands的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【笔记补发20210402 第1次课】《
- 下一篇: Android 界面介绍与绘制优化