![](/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.