![](/img/trans.png)
[英]Variable references in Intel style inline assembly and AT&T style, C++
[英]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 $0x3ff0000000000000
和pushq $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.