用C語言表達彙編指令

謝謝tsahoo在論壇中爲大家提供的原文 ,需要翻譯幫助的朋友請把原文貼在這裏

在彙編語言中使用asm,你也可以使用C語句表達。也就是說你不需要知道你要使用的數據在寄存器或存儲器中的位置。
你需要描述彙編指令象出現在機器中的那樣,爲每一個操作數加上一個強制的約束。
例如,這是使用68881's fsinx指令的方法: 
      asm ("fsinx %1,%0" : "=f" (result) : "f" (angle));
這 是用C語言表達當結果爲輸出操作數時的輸入操作數。每個都有“f”如它的操作數約束,也就是說需要浮點記錄器。顯示了操作數爲輸出;所有的輸出操作數必須 使用“=”。約束在機器中使用相同的語句(見約束)。每個操作數都被括號中在C語句之後的操作約束字符串所描述。一個冒號把第一個輸出操作數分開還有如果 存在最後一個的話同樣這樣處理。逗號在每個組中分隔開操作數。操作數的總和一般被限制在301以內;這個限制可能在GCC的將來版本中被打破。如果沒有輸 出而只有輸入的話,那麼需要在輸出處加兩個連續的冒號。
如 GCC 3.1 版,它也可能使用在彙編碼中可以參考的符號名來指定輸入和輸出操作數。這些名字在在約束字符串前的方括號內被指定,並且可以在彙編碼中使用%[name]代替操作數後的百分標記。使用命名操作數的例子如下:
     asm ("fsinx %[angle],%[output]"
          : [output] "=f" (result)
          : [angle] "f" (angle));
注意符號操作數的名字與其他C語言識別符沒有什麼聯繫。你可以使用任何你喜歡的名字,甚至那些現在有的標號,但是必須確保在同一個彙編程序中沒有兩個相同的符號名出現。
輸 出操作表達必須爲左值; 編譯器才能識別它。輸入操作數則不需要這樣。編譯器不能識別操作數對指令是否有合理的數據類型。它不能解析彙編程序指令並且不知道彙編指令的含義,甚至不 知道是否爲有效的彙編指令輸入。asm經常被用在自身不知道是否存在的機器指令編譯中。如果輸出操作表達不能直接尋址(例如:bit-field),那麼 約束中必須允許寄存器。在這種情況下,GCC將使用寄存器作爲asm 的輸出,然後存儲那個寄存器到輸出內。
當約束爲讀寫操作(或操作數在只有一 些位元需要轉化)時,需要允許寄存器, 你可能二者選一,理論上把函數一分我二,一個讀操作和一個寫操作。在指令執行時他們之間的關係通過他們需要在相同的位置表達出來。你可以對兩個操作都使用 C表達,或不同的表達。例如,這是我們寫的指令作爲讀操作源並且foo 作爲讀寫的目的地址:
asm ("combine %2,%0" : "=r" (foo) : "0" (foo), "g" (bar));
對操作數1的約束“0”要與操作數0佔用相同的存儲地址。在約束中的數必須在輸入中被允許並且它必須提及輸出操作數。
只有在約束中的一個操作數目可以保證一個操作數與另一個在相同的位置。事實上foo兩個操作數的值不能保證他們在彙編代碼中相同的位置。以下語句是不可靠的:
        asm ("combine %2,%0" : "=r" (foo) : "r" (foo), "g" (bar));
不同最佳化或重載將引起操作數0和1在不同的寄存器中;GCC不知道不這樣做的原因。例如,編譯器可能在寄存器中找到foo值的一個副本( 然後到自己地址複製它)。當然因爲對操作數1寄存器在彙編代碼中不被提及,結果將不會工作,但是GCC不會提示錯誤。
如GCC 3.1版,可以爲了匹配約束寫[name]來代替操作數。例如:
          asm ("cmoveq %1,%2,%[result]"
          : [result] "=r"(result)
          : "r" (test), "r"(new), "[result]"(old));
一些指令破壞硬件寄存器。爲了描述這個情況,在輸入運算元之後寫第三個冒號,在硬件寄存器的名字後面(如字符串所給。這是一個爲VAX 例子:
asm volatile ("movc3 %0,%1,%2"
                   : /* no outputs */
                   : "g" (from), "g" (to), "g" (count)
                   : "r0", "r1", "r2", "r3", "r4", "r5");
你 可能在一定程度上對輸入或輸出操作數不會寫描述。例如,如果沒有在目錄中提及寄存器你可能不會描述操作數。在沒有指定輸出操作時沒有方法來指定輸入操作。 注意如果你敘述的所有輸出運算元爲這個給目的 ( 並且因此不用), 然後將需要爲 asm描述構造,同樣地在下面描述, 阻止GCC刪除asm。如果你在彙編碼中提及一個特殊的硬件寄存器,你可能需要在第三個冒號之後向編譯器指定寄存器記錄器的數值。在彙編程序中,寄存器的 名字從"%"開始;在彙編代碼中創建"%",必須在輸入中寫"%%".
如果你的彙編指令能夠改變條件碼寄存器,則把cc加入寄存器列表。 在一些機器上GCC 表現爲一個特殊的硬件寄存器條件碼; cc爲寄存器命名。在其他的機器上,條件碼不同方式地被處理,並且指定cc此時是沒有作用的。但無論對於什麼機器它是正確的。 如果你的彙編程序以不可預知方式修改存儲器,把存儲器加入寄存器列表。這將會導致GCC不保存在彙編指令對面的寄存器中被貯藏的存儲器的值。如果改變的存 儲器不在asm 的輸入或輸出中列出,你可能想要增加可變關鍵字,如存儲器忽略asm的邊際效應一樣。
你可以在一個單一asm中將多重寄存器指令 集合起來, 並使用系統中彙編代碼的正常字符將其分隔開。聯合大多數都回在新的一行開始工作,加一個"tab "將其移到指令的位置( 寫作: /n/ t). 有時可以使用分號,如果彙編程序允許分號作爲換行字符。注意有些彙編程序使用分號作爲註釋的開始.輸入操作要保證不使用寄存器,還有輸出操作數的地址,所 以你可以不限次數的讀寫寄存器.這是一個多重指令的例子;它假定子程序_foo接受寄存器9 和10 的獨立變量:
asm ("movl %0,r9/n/tmovl %1,r10/n/tcall _foo"
          : /* no outputs */
          : "g" (from), "g" (to)
          : "r9", "r10");
除非一個輸出運算元包含&強制約束脩正,GCC可能在同一個無關的輸入操作數記錄器中分配它,假設在輸出產生之前輸入被銷燬。如果彙編碼由多於一條指令組成這種假定可能是錯誤的.在這種情況時,對每個不可能重疊的輸出操作數使用,見修正量。
如果你想測試由編譯器產生的條件碼, 你必須在asm構造中包含一個分支和一個標誌, 如下:

     asm ("clr %0/n/tfrob %1/n/tbeq 0f/n/tmov #1,%0/n0:"
          : "g" (result)
          : "g" (input));
    
這假定了你的彙編程序支持本地標誌,就如GNU彙編程序和大多數的 Unix程序一樣。
說到標誌, 從一個asm跳轉到另外一個是不被支持的。編譯器的優化不識別這些跳轉,所以在優化的時候不會考慮這些跳轉。
通常使用這些asm 指令最方便的方法是象函數一樣壓縮它們,如下:
       #define sin(x)       /
     ({ double __value, __arg = (x);   /
        asm ("fsinx %1,%0": "=f" (__value): "f" (__arg));  /
        __value; })
這 裏變量__arg用來保證指令操作一個double值,並且只承認可以自動轉換的那些獨立變量x。另一種確定指令操作正確數據類型的方法是在asm中使用 一個計算。它不同與使用變量__arg的地方是它轉換更多的不同數據類型。例如,如果需要的是int,int函數中的獨立變量會無條件地承認一個指針,除 非調用者明確地指定它否則在分配一個int變量名的時候會出現警告。
如果asm有輸出運算, GCC承認指令除了改變輸出運算元之外沒有其他的操作。這並不是說有邊際效應的指令就不能使用了,但是必須小心的使用,因爲如果在不用是輸出運算時, 或把他們移出循環時,或他們有一個sub表達用一個代替了兩者時,編譯器可能消除他們。同時, 如果你的指令在變量上沒有邊際效應也不出現轉換,如果巧合在寄存器中發現變量,那麼它的初始值可能稍後被重新使用。
你可以通過在asm後寫關鍵字來阻止asm指令被刪除、移動、或聯合,例如:
    #define get_and_set_priority(new)              /
     ({ int __old;                                  /
        asm volatile ("get_and_set_priority %0, %1" /
                      : "=g" (__old) : "g" (new));  /
        __old; })
   如果你寫了一條沒有輸出的asm指令,GCC將會識別有其他作用並且不會在循環外刪除或移動它。關鍵字指出指令有重要的作用。如果它是可得到的, GCC將不刪除可變的asm ( 如果GCC能確定程序將不會執行到指令的位置,指令仍然會被刪除)。除此之外,GCC將不會在可變asm指令中重置指令。例如:
   *(volatile int *)addr = foo;
     asm volatile ("eieio" : : );
假定地址中包含了寄存器列表中存儲器的地址。 addr 包含存儲器圖表裝置記錄器的位址。PC的eieio指令(按順序地執行I/O操作)將指定CPU確保在其他的I/O之前將它存儲到設備寄存器中。
注 意,如果對編譯器無意義的話可變asm指令也可能移動,如跳轉指令。你不能認爲可變asm指令的順序性會保持地非常連續。 如果你想要連續的輸出,可以使用單一asm 。 同時, GCC將會爲asm指令執行一些優化; 當GCC遇到一個可變asm指令方式如一些其他的編譯器不會忘記每個操作。
一條沒有沒有任何操作的指令("舊風格"的asm)將會同樣地被處理 成可變asm指令。 這是尋找被彙編指令留下的條件碼的一個很自然的想法。然而,當我們試圖實現的時候,發現沒有任何方法能確保工作的可靠性。問題在於輸出可能需要重載,從而 導致需要額外的存儲指令。大多數的機器上,測試它的時間之前,這些指令會改變條件碼。問題的原因並不是普通的" 測試 "和" 比較 "指令,因爲他們沒有任何的輸出操作。如上述的原因一樣,不可能將先前指令留下的條件碼傳給彙編指令。如果你正在寫一個頭文件,應該包括ISO C 程序,使用__asm__ 代替asm。見預備關鍵字。
i386 浮點指針asm操作
有asm_operands中使用堆棧有一些規則。這些規則只適用於如堆棧一樣的運算元爲普通型的:
在asm_operands中給予一組輸出規則,有必要知道哪些是被asm退棧的,還有哪寫是被gcc明確退棧的。被asm捕獲的輸入規則必需被明確的識別,除非它是強制與輸出操作匹配的。
對於任何被asm退棧輸入信息,必需知道該如何調整堆棧爲退棧補償。如果任何的非退棧輸入比退棧信息更靠近棧頂,將不會知道堆棧如何就象不清楚堆棧是如何檢測的一樣。所有的非退棧輸入要比退棧信息更靠近棧頂。
在insn輸入可能被消除,可能使用輸入爲輸出重載,考慮下面的例子:
          asm ("foo" : "=t" (a) : "f" (b));
    程序中輸入B不被出棧,並且asm將結果如棧,也就是說棧比以前更深了一位。但是如果輸入B在insn中消除的話,重載可能考慮爲輸入和輸出使用相同的信息。
如果任何的輸入操作使用f強制約束, 那麼普所有輸出強制約束必須使用&earlyclobber。
語句應該如下:
          asm ("foo" : "=&t" (a) : "f" (b));
一些運算元需要在堆棧中特殊位置中。所有的輸出都屬於這個範疇--沒有其他方法可以知道輸出的出現除非用戶強制地指定它。
輸出運算必須明確地指出哪一個輸出在asm之後出現。 “=f”不承認: 操作數必須選擇一個類。
輸出運算不可能在現有的堆棧之間被嵌套。因爲沒有387運算碼使用讀/寫操作,在asm_operands之前所有的輸出運算都被消除,並且被asm_operands壓入堆棧,放在棧頂。
輸出運算必須在棧頂開始,它不可能跳過reg。
一些asm聲明可能因內部的計算需要額外的堆棧空間。這可以通過與輸入和輸出武官的寄存器來保證。
以下爲兩個合理的寫法:
這個有一個輸入,被出棧,產生兩個輸出:
     asm ("fsincos" : "=t" (cos), "=u" (sin) : "0" (inp));
   這個有兩個輸入,被fyl2xp1操作碼出棧,並且將他們與一個輸入做交換。用戶必須爲reg-stack.c 編碼st(1)從而知道將兩個輸入出棧:
     asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)”);

 

原帖: http://www.sf.org.cn/Article/base/200604/17413.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章