【问题标题】:Setting a float/double to a constant value in AT&T inline assembly在 AT&T 内联汇编中将 float/double 设置为常量值
【发布时间】:2015-06-02 01:58:24
【问题描述】:

我正在考虑提高我编写和分析的 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: %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" = 这条指令的分段错误。

我们将不胜感激,并提前感谢您的宝贵时间。

【问题讨论】:

  • 我看到了那个帖子,但它在声明const1: dq 1.2345 时仍然使用.data 部分,我无法在内联汇编中访问它。除非我误解了某些东西(这很可能)。不过感谢您的回复。
  • 尝试阅读第一条回复:stackoverflow.com/a/6514824/2702398
  • 你指的是push的建议吗?还是使用扩展程序集的那个? - 我试图避免扩展组装,push 会导致段错误。
  • push - 这就是 C 编译器的工作方式。

标签: c++ c gcc assembly inline


【解决方案1】:

您无法将内联汇编代码移植到 Microsoft 的 C/C++ 编译器,原因有两个。首先是 asm 语句的语法太不同了。微软的编译器需要类似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,更不用说微软的编译器了。让我们看看我在打开优化的情况下编译它时得到了什么:

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's intrinsics,GCC、Microsoft C/C++ 以及 Clang 和英特尔自己的编译器都支持它们。这是一个例子:

#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 常量。

【讨论】:

  • 该程序集还包括:.LC0: .long 0 .long 1072693248 .text。您提到它使用存储在 ADDSD 指令中的预设值,这对 1.0.0 是有意义的。您能否更详细地了解.LC0(%rip) 寻址的机制?我之前看到我的编译器这样做并且完全困惑。再次感谢您抽出宝贵时间发帖,非常感谢!
  • 我在第二个和第三个汇编输出示例中省略了常量定义以节省空间。 .LC0(%rip) 表示使用 RIP 相对寻址来访问存储在 .LC0 中的值。这与在此上下文中仅使用 .LC0 相同,只是它使代码位置独立。汇编程序不使用.LC0 的地址,而是使用.LC0 与指令的距离或.LC0 - .,换句话说,其中. 是指令的地址。因为. 也是指令执行时 RIP 的值,所以你得到.LC0 - . + %rip = .LC0
【解决方案2】:

如果有人对我的问题的确切答案感兴趣,我也将其发布在这里,因为我以某种方式设法通过纯粹的运气和尝试/错误来解决这个问题。这样做的重点是学习简单的组装。

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"
   );
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-28
    • 1970-01-01
    • 1970-01-01
    • 2021-11-25
    • 2012-09-20
    • 2018-10-29
    • 1970-01-01
    相关资源
    最近更新 更多