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