在 AT&T 内联装配体中将浮点/双精度设置为常量值
Setting a float/double to a constant value in AT&T inline assembly
我正在考虑提高我编写并分析的c++库的运行时性能。我是非常新的汇编(和内联汇编),并有一个非常基本的问题要问。
如何将xmm寄存器的值(xmm, ymm, zmm等)设置为使用内联汇编的常量浮点数或双精度值?我强烈建议不要使用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: %lfn", val);
return 0;
}
void foo(double *val) {
// return *val + 1.0.
__asm__ (
"movq -8(%rbp), %raxnt" // move pointer from stack to rax.
"movq (%rax), %xmm1nt" // dereference pointer and move to xmm1.
"?????????????" // somehow move 1.0 to xmm0.
"addsd %xmm1, %xmm0nt" // add xmm1 to xmm0.
"movsd %xmm0, (%rax)nt" // move result back val.
);
}
我尝试使用push $0x3ff0000000000000
和pushq $0x3ff0000000000000
将值移动到堆栈,然后可能将其移动到xmm0,结果如下:
"pushq $0x3ff0000000000000nt"
= "Error: ' push'操作数类型不匹配".
"push $0x3ff00000nt"
=此指令出现分段错误。
任何帮助将是感激的,并提前感谢您的时间。
不能将内联汇编代码移植到Microsoft的C/c++编译器中有两个原因。首先是asm语句的语法差别太大。微软的编译器期望类似于asm { mov rax, [rbp + 8] }
而不是asm("movq -8(%rbp), %raxnt")
。第二,微软64位编译器不支持内联汇编。
因此,您不妨正确地使用GCC的扩展语法。因为你的内联程序集是非常脆弱的。您不能依赖val
位于-8(%rbp)
。编译器甚至可能不会把它放在堆栈上。您也不能假定编译器不会介意您丢弃RAX、XMM0和XMM1。
所以你需要告诉编译器你想要使用什么变量,你要丢弃什么寄存器。此外,您还可以让编译器处理将1.0加载到XMM寄存器中的问题。像这样:
asm ("movq (%0), %%xmm1nt"
"addsd %1, %%xmm1nt"
"movsd %%xmm1, (%0)nt"
: /* no output operands */
: "r" (val), "x" (1.0)
: "xmm1", "memory");
"r" (val)
输入操作数告诉编译器将val
放入通用寄存器中,然后将该寄存器名替换到%0
中出现在字符串中的位置。类似地,"x" (1.0)
告诉编译器将1.0放入XMM寄存器,将其替换为%1
。处理器告诉编译器,该语句修改了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,更不用说微软的编译器了。让我们看看当我打开优化功能编译它时得到了什么:
foo:
movsd .LC0(%rip), %xmm0
/APP
movq (%rcx), %xmm1
addsd %xmm0, %xmm1
movsd %xmm1, (%rcx)
/NO_APP
ret
看这个函数是多么简短和甜蜜。编译器已经消除了设置堆栈框架的所有不必要的模板代码。此外,由于val
在RCX中被传递给函数,编译器直接在内联程序集中使用该寄存器。不需要将其存储在堆栈中,只需要立即将其加载回另一个寄存器。
*val + 1.0
。要做到这一点,你需要使用Intel的intrinsic, 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常量
如果有人对我的问题的确切答案感兴趣,我也把它贴在这里,因为我以某种方式设法弄清楚了纯粹的运气和试验/错误。这样做的目的是为了学习简单的组装。
void foo(double *in) {
__asm__ (
"movq -8(%rbp), %raxnt"
"movq (%rax), %xmm1nt"
"movq $0x3FF0000000000000, %rbxnt"
"movq %rbx, %xmm0nt"
"addsd %xmm1, %xmm0nt"
"movsd %xmm0, (%rax)nt"
);
}
- 如何防止 c++ 在从浮点型转换为双精度型(不适用于 IO)时添加额外的小数?
- 正在将csv文件读取为双精度矢量
- 如何解决gcc编译器优化导致的centos双编译器设置中的分段错误
- 我可以信任表示整数的浮点或双精度来保持精度吗
- 如何在C++中的同一函数中使用字符串和双精度
- 特征::矩阵<双精度,1,3> 结构类型函数中的返回类型函数
- 检查是否以特定精度给出双精度
- 将双精度变量设置为另一个变量的值
- 位设置为浮点数或双精度值 c++
- 保证最大输出长度为双精度,并设置默认输出格式的精度
- 使用词法强制转换在'string to double'中设置双精度变量的精度
- 无法获得固定的设置精度双精度以显示小数位
- 在不使用流(ios_base::precision)的情况下设置双精度
- 将双精度值的格式设置为小数点后 1 位
- 如何在C++中使用双精度/浮点类型时设置精度
- 如何在C++中正确设置双精度
- 使用 fstream 设置精度以输出文件 - 格式为双精度
- 如何通过 JNI 设置 java 类的双精度/整数类型的值
- vc.net:无法设置双精度变量的值
- 在 AT&T 内联装配体中将浮点/双精度设置为常量值