簡體   English   中英

在Linux環境中運行內聯匯編(使用GCC / G ++)

[英]Running In-Line Assembly in Linux Environment (Using GCC/G++)

因此,我有一個非常基本的程序,用C(.c文件)編寫,帶有嵌入式匯編代碼部分。 我想將.c文件轉換為我知道但不知道如何為Linux環境編譯該代碼的程序集輸出。

將gcc或g ++用於.cpp文件時,出現無法識別asm指令的錯誤。

現在,除了將asm代碼的括號更改為括號之外,此代碼還可以在Visual Studio中按預期工作。 但是我仍然會出錯。 一堆未定義的變量引用。

我在工作代碼中所做的更改是將括號更改為括號,將匯編指令放在引號中(可在網上找到,可能是錯誤的)。

簡而言之,我希望下面的代碼能夠在Linux環境中使用命令gcc成功編譯。 我不知道語法,但是代碼有效,但不適用於linux /。

#include <stdio.h>
int main()

{

float num1, num2, sum, product;
float sum, product;
float f1, f2, f3, fsum, fmul;

printf("Enter two floating point numbers: \n");
scanf("%f %f", &num1, &num2);


__asm__ 
(
    "FLD num1;"
    "FADD num2;"
    "FST fsum;"
);

printf("The sum of %f and %f " "is" " %f\n", num1, num2, fsum);
printf("The hex equivalent of the numbers and sum is %x + %x = %x\n", num1, num2, fsum);

return 0;
}

GNU C內聯匯編程序被設計為不需要在asm語句的開始/結尾處的數據移動指令。 每當您將movfld或其他內容作為內聯匯編中的第一條指令編寫時,您就在破壞約束系統的目的。 您應該只要求編譯器首先將數據放在所需的位置。

另外,在2016年編寫新的x87代碼通常是浪費時間。 與執行FP數學的常規方法(xmm寄存器中的標量或矢量指令)相比,這很奇怪並且有很多不同。 如果它是針對不同的微體系結構手動調整的,或者沒有利用SSE指令,則可以通過將古老的asm代碼轉換為純C來獲得更好的結果。 如果您仍然想編寫x87代碼,請參閱標簽Wiki中的指南。

如果您想使用GNU C內聯匯編學習匯編,那就不要。 選擇任何其他學習asm的方法,例如編寫整個函數並從C調用它們。另請參見該答案的底部,以獲取有關編寫良好的GNU C內聯asm的鏈接的集合。


x87浮點操作數特殊的規則 ,因為x87寄存器棧不是隨機訪問的。 與“普通”物品相比,這使內聯自動裝箱變得更加難以使用。 獲得最佳代碼似乎比正常情況更困難。


在我們的例子中,我們知道我們將需要一個輸入操作數在FP堆棧的頂部,並在那里產生結果。 要求編譯器為我們執行此操作意味着除fadd之外,我們不需要任何其他說明。

  asm (
    "fadd %[num2]\n\t"
    : "=t" (fsum)                                  // t is the top of the register stack
    : [num1] "%0" (num1), [num2] "f" (num2)         // 0 means same reg as arg 0, and the % means they're commutative.  gcc doesn't allow an input and output to both use "t" for somre reason.  For integer regs, naming the same reg for an input and an output works, instead of using "0".
    : // "st(1)"  // we *don't* pop the num2 input, unlike the FYL2XP1 example in the gcc manual
      // This is optimal for this context, but in other cases faddp would be better
      // we don't need an early-clobber "=&t" to prevent num2 from sharing a reg with the output, because we already have a "0" constraint
  );

有關%0的說明,請參見文檔中的約束修飾符

fadd之前: num2%st(0) num1位於內存中或另一個FP堆棧寄存器中。 編譯器選擇哪個,並填寫寄存器名稱或有效地址。

希望這可以使編譯器在正確的時間后彈出堆棧。 (請注意,當輸出約束必須是FP堆棧寄存器時, fst %0太愚蠢了。它很可能最終像fst %st(0)一樣成為空操作。)

如果兩個FP值都已經在%st寄存器中,則我看不到一種優化方法來使用faddp的簡便方法。 例如,如果num1之前位於%st1 ,則faddp %st1將是理想選擇,但FP寄存器中仍不需要它。


這是一個完整的版本,實際上可以編譯,並且甚至在64位模式下也可以工作,因為我為您編寫了一種類型校正的包裝器函數。 對於將FP寄存器中的某些FP args傳遞給varargs函數的任何ABI,這都是必需的。

#include <stdio.h>
#include <stdint.h>

uint32_t pun(float x) {
  union fp_pun {
    float single;
    uint32_t u32;
  } xu = {x};
  return xu.u32;
}


int main()
{
  float num1, num2, fsum;

  printf("Enter two floating point numbers: \n");
  scanf("%f %f", &num1, &num2);

  asm (
    "fadd %[num2]\n\t"
    : "=t" (fsum)
    : [num1] "%0" (num1), [num2] "f" (num2)  // 0 means same reg as arg 0, and the % means it's commutative with the next operand.  gcc doesn't allow an input and output to both use "t" for some reason.  For integer regs, naming the same reg for an input and an output works, instead of using "0".
    : // "st(1)"  // we *don't* pop the num2 input, unlike the FYL2XP1 example in the gcc manual
      // This is optimal for this context, but in other cases faddp would be better
      // we don't need an early-clobber "=&t" to prevent num2 from sharing a reg with the output, because we already have a "0" constraint
  );

  printf("The sum of %f and %f is %f\n", num1, num2, fsum);
  // Use a union for type-punning.  The %a hex-formatted-float only works for double, not single
  printf("The hex equivalent of the numbers and sum is %#x + %#x = %#x\n",
         pun(num1), pun(num2), pun(fsum));

  return 0;
}

Godbolt編譯器資源管理器上查看其編譯方式。

在使用SSE進行FP數學運算的普通代碼中,取出-m32可以看到將數據僅進行一次加法運算就將數據放入x87寄存器是多么愚蠢。 (特別是因為在scanf給我們單精度之后,它們還必須轉換為printf雙精度。)

gcc最終還為32bit編寫了一些效率很低的x87代碼。 它最終在regs中同時包含兩個arg,因為它是從單精度加載​​的,以准備存儲為double。 由於某種原因,它會在FP堆棧上復制該值,而不是在執行fadd之前將其存儲為double。

因此,在這種情況下, "f"約束比"m" "f"約束產生更好的代碼,並且我看不到使用AT&T語法為內存操作數指定單精度操作數大小而不破壞寄存器操作數的asm的簡便方法。 fadds %st(1)不會匯編,但是fadd (mem)不會與clang一起匯編。顯然,GNU默認為單精度內存操作數。)使用Intel語法,修改后的操作數大小將附加到內存操作數,如果編譯器選擇了內存操作數,則該操作數將存在。

無論如何,此序列將比gcc發出的序列更好,因為它避免了fld %st(1)

    call    __isoc99_scanf
    flds    -16(%ebp)
    subl    $12, %esp      # make even more space for args for printf beyond what was left after scanf
    fstl    (%esp)         # (double)num1

    flds    -12(%ebp)
    fstl    8(%esp)        # (double)num2

    faddp  %st(1)          # pops both inputs, leaving only fsum in %st(0)
    fsts    -28(%ebp)      # store the single-precision copy
    fstpl   16(%esp)       # (double)fsum
    pushl   $.LC3
    call    printf

但是,顯然,gcc並不打算這樣做。 編寫內聯asm以使用faddp使gcc在faddp之前執行額外的fld %st(1) ,而不是說服它在執行添加之前為printf存儲double args。

更好的情況是,如果設置了單精度存儲以使它們可以用作類型雙精度的printf的args,而不必為此再次進行復制。 如果手動編寫函數,我將用scanf將結果存儲到可用作printf的args的位置。

GCC中的嵌入式程序集按字面意義轉換為生成的程序集源。 由於變量不存在於程序集中,因此您編寫的內容無法正常工作。

使它起作用的方法是使用擴展程序集 ,該程序集帶有在編譯源時GCC將用來翻譯程序集的修飾符來注釋程序集。

__asm__
(
  "fld %1\n\t"
  "fadd %2\n\t"
  "fst %0"
  : "=f" (fsum)
  : "f" (num1), "f" (num2)
  :
);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM