在 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 [=14=]x3ff0000000000000
和 pushq [=15=]x3ff0000000000000
将值移动到堆栈,然后可能将其移动到 xmm0,结果如下:
"pushq [=16=]x3ff0000000000000\n\t"
= "Error: operand type mismatch for `push'".
"push [=17=]x3ff00000\n\t"
= 该指令的段错误。
如有任何帮助,我们将不胜感激,并提前感谢您的宝贵时间。
您不能使内联汇编代码可移植到 Microsoft 的 C/C++ 编译器,原因有二。首先是 asm 语句的语法差异太大。 Microsoft 的编译器需要 asm { mov rax, [rbp + 8] }
而不是 asm("movq -8(%rbp), %rax\n\t")
。二是微软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 中的函数,编译器直接在内联汇编中使用该寄存器。无需将其存储在堆栈中,只需立即将其加载回另一个寄存器即可。
当然,就像你自己的代码一样,none这个是远程兼容微软的编译器的。他们使其兼容的唯一方法是根本不使用内联汇编。幸运的是,这是一个选项,我的意思不仅仅是使用 *val + 1.0
。为此,您需要使用 Intel's intrinsics,GCC、Microsoft C/C++ 以及 Clang 和 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常量
如果有人对我的问题的确切答案感兴趣,我也会将其张贴在这里,因为我以某种方式设法凭借运气和 trial/error 弄清楚了。这样做的重点是学习简单的组装。
void foo(double *in) {
__asm__ (
"movq -8(%rbp), %rax\n\t"
"movq (%rax), %xmm1\n\t"
"movq [=10=]x3FF0000000000000, %rbx\n\t"
"movq %rbx, %xmm0\n\t"
"addsd %xmm1, %xmm0\n\t"
"movsd %xmm0, (%rax)\n\t"
);
}
我正在考虑提高我编写和分析的 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 [=14=]x3ff0000000000000
和 pushq [=15=]x3ff0000000000000
将值移动到堆栈,然后可能将其移动到 xmm0,结果如下:
"pushq [=16=]x3ff0000000000000\n\t"
= "Error: operand type mismatch for `push'".
"push [=17=]x3ff00000\n\t"
= 该指令的段错误。
如有任何帮助,我们将不胜感激,并提前感谢您的宝贵时间。
您不能使内联汇编代码可移植到 Microsoft 的 C/C++ 编译器,原因有二。首先是 asm 语句的语法差异太大。 Microsoft 的编译器需要 asm { mov rax, [rbp + 8] }
而不是 asm("movq -8(%rbp), %rax\n\t")
。二是微软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 中的函数,编译器直接在内联汇编中使用该寄存器。无需将其存储在堆栈中,只需立即将其加载回另一个寄存器即可。
当然,就像你自己的代码一样,none这个是远程兼容微软的编译器的。他们使其兼容的唯一方法是根本不使用内联汇编。幸运的是,这是一个选项,我的意思不仅仅是使用 *val + 1.0
。为此,您需要使用 Intel's intrinsics,GCC、Microsoft C/C++ 以及 Clang 和 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常量
如果有人对我的问题的确切答案感兴趣,我也会将其张贴在这里,因为我以某种方式设法凭借运气和 trial/error 弄清楚了。这样做的重点是学习简单的组装。
void foo(double *in) {
__asm__ (
"movq -8(%rbp), %rax\n\t"
"movq (%rax), %xmm1\n\t"
"movq [=10=]x3FF0000000000000, %rbx\n\t"
"movq %rbx, %xmm0\n\t"
"addsd %xmm1, %xmm0\n\t"
"movsd %xmm0, (%rax)\n\t"
);
}