簡體   English   中英

在AT&T內聯匯編中將float / double設置為恆定值

[英]Setting a float/double to a constant value in AT&T inline assembly

我正在尋找提高我編寫和分析的C ++庫的運行時性能的方法。 我是組裝(和內聯匯編)的新手,有一個非常基本的問題要問。

如何使用內聯匯編將xmm寄存器的值(xmm,ymm,zmm等)設置為恆定的float或double值? 我強烈不希望使用GCC的擴展程序集使代碼更易於移植到MSVC。 使用-S進行編譯時,我看到GCC使用了.data節,但是,我認為我不能在內聯代碼中使用該節。

為了簡單起見,假設我要在以下C代碼中實現foo函數:

#include <cstdio>

void foo(double *val);
int main(int argc, char **argv) {
   double val = 0.0;

   foo(&val);
   printf("val: %lf\n", val);
   return 0;
}

void foo(double *val) {
   // return *val + 1.0.
   __asm__ (
      "movq -8(%rbp), %rax\n\t"   // move pointer from stack to rax.
      "movq (%rax), %xmm1\n\t"    // dereference pointer and move to xmm1.
      "?????????????"             // somehow move 1.0 to xmm0.
      "addsd %xmm1, %xmm0\n\t"    // add xmm1 to xmm0.
      "movsd %xmm0, (%rax)\n\t"   // move result back val.
   );
 }

我嘗試使用push $0x3ff0000000000000pushq $0x3ff0000000000000將值移到堆棧,然后可能將其移到xmm0,結果如下:

"pushq $0x3ff0000000000000\\n\\t" =”錯誤:'push'的操作數類型不匹配。“

"push $0x3ff00000\\n\\t" =該指令出現分段錯誤。

任何幫助將不勝感激,並提前感謝您的時間。

不能將內聯匯編代碼移植到Microsoft的C / C ++編譯器中有兩個原因。 首先是asm語句的語法太不同了。 微軟的編譯器期望使用asm { mov rax, [rbp + 8] }而不是asm("movq -8(%rbp), %rax\\n\\t") 第二點是Microsoft 64位編譯器不支持內聯匯編。

因此,您也可以正確使用GCC的擴展語法。 因為它是內聯程序集,所以非常脆弱。 您不能認為val位於-8(%rbp) 編譯器甚至可能沒有將其放在堆棧上。 您也不能假設編譯器不會介意您破壞RAX,XMM0和XMM1。

因此,要正確執行此操作,您需要告訴編譯器要使用哪些變量以及要破壞的寄存器。 另外,您還可以讓編譯器處理將1.0加載到XMM寄存器中的問題。 像這樣:

asm ("movq (%0), %%xmm1\n\t"
     "addsd %1, %%xmm1\n\t"
     "movsd %%xmm1, (%0)\n\t"
     : /* no output operands */
     : "r" (val), "x" (1.0)
     : "xmm1", "memory");

"r" (val)輸入操作數告訴編譯器將val放入通用寄存器中,然后將該寄存器名稱替換為%0出現在字符串中的任何位置。 類似地, "x" (1.0)告訴編譯器將1.0放入XMM寄存器,用%1代替。 Clobbers告訴編譯器XMM1寄存器由該語句以及內存中的某些內容進行了修改。 您可能還會注意到,我已經在ADDSD上交換了操作數,因此該語句僅修改了一個寄存器。

這是編譯我在計算機上安裝的GCC版本時生成的程序集:

foo:
    pushq   %rbp
    movq    %rsp, %rbp
    movq    %rcx, 16(%rbp)
    movq    16(%rbp), %rax
    movsd   .LC2(%rip), %xmm0

/APP
    movq (%rax), %xmm1
    addsd %xmm0, %xmm1
    movsd %xmm1, (%rax)
/NO_APP

    popq    %rbp
    ret

.LC2:
    .long   0
    .long   1072693248

看來我的GCC版本決定將val存儲在16(%rbp)而不是-8(%rbp) 您的代碼甚至無法移植到其他版本的GCC,更不用說Microsoft的編譯器了。 讓我們看看在啟用優化的情況下進行編譯時得到的結果:

foo:
    movsd   .LC0(%rip), %xmm0

/APP
    movq (%rcx), %xmm1
    addsd %xmm0, %xmm1
    movsd %xmm1, (%rcx)
/NO_APP

    ret

看看該功能有多簡短。 編譯器消除了設置堆棧框架的所有不必要的樣板代碼。 同樣,由於將val傳遞給RCX中的函數,因此編譯器僅直接在內聯匯編中使用該寄存器。 無需將其存儲在堆棧中,只需立即將其加載回另一個寄存器即可。

當然,就像您自己的代碼一樣,這些都不能與Microsoft的編譯器遠程兼容。 他們使其兼容的唯一方法是根本不使用內聯匯編。 幸運的是,這是一個選擇,我不僅僅是使用*val + 1.0 為此,您需要使用Intel的內在函數 ,GCC,Microsoft C / C ++,Clang和Intel自己的編譯器均支持Intel的內在函數 這是一個例子:

#include <emmintrin.h>

void foo(double *val) {
    __m128d a = _mm_load_sd(val);
    const double c = 1.0;
    __m128d b = _mm_load_sd(&c);
    a = _mm_add_sd(a, b);
    _mm_store_sd(val, a);
}

在不進行優化的情況下進行編譯時,我的編譯器對此做了一些令人毛骨悚然的事情,但是在進行優化時,它看起來像這樣:

foo:
    movsd   (%rcx), %xmm0
    addsd   .LC0(%rip), %xmm0
    movlpd  %xmm0, (%rcx)
    ret

編譯器非常聰明,知道它可以直接在ADDSD指令中使用存儲在內存中的1.0常量。

如果有人對我的問題的確切答案感興趣,我也將其張貼在這里,因為我以某種方式設法通過運氣和審判/錯誤來弄清楚了它。 這樣做的全部目的是學習簡單的組裝。

void foo(double *in) {
   __asm__ (
      "movq -8(%rbp), %rax\n\t"
      "movq (%rax), %xmm1\n\t"
      "movq $0x3FF0000000000000, %rbx\n\t" 
      "movq %rbx, %xmm0\n\t"
      "addsd %xmm1, %xmm0\n\t"
      "movsd %xmm0, (%rax)\n\t"
   );
}

暫無
暫無

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

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