繁体   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