【问题标题】:Inline gcc assembly and local variables (double)内联 gcc 程序集和局部变量(双)
【发布时间】:2013-07-05 08:50:51
【问题描述】:

我正在尝试使用 gcc/g++ 的内联 asm 指令(我不得不说我之前一直在 MSVC 上使用英特尔语法,那就是微风)。 我正在玩 double 值,以下 my_func2 似乎在执行后崩溃:

  #include <iostream>

  void my_func(const double *in, double *out) {
    asm("mov %0, %%r8" : : "r"(in));
    asm("movupd (%%r8), %%xmm0" :);
    asm("movupd (%%r8), %%xmm1" :);
    asm("addpd %%xmm1, %%xmm0" :);
    asm("movupd %%xmm0, (%0)" : : "r"(out) : "%r8", "%xmm0", "%xmm1");
  }

  double my_func2(const double *in) {
    double  ret = 0.0;

    asm("mov %0, %%r8" : : "r"(in));
    asm("movupd (%%r8), %%xmm0" :);
    asm("movupd (%%r8), %%xmm1" :);
    asm("addpd %%xmm1, %%xmm0" :);
    asm("movupd %%xmm0, %0" : "=m"(ret) : : "memory", "%r8", "%xmm0", "%xmm1");

    return ret;
 }

  int main(int argc, char *argv[]) {
    const double    a = 1.0;
    double      b = 0.0;
    my_func(&a, &b);
    std::cout << "b:" << b << std::endl;
    b = my_func2(&a);
    std::cout << "b:" << b << std::endl;
  }

我得到的错误具体是(当我使用 gdb 运行时):

程序收到信号 SIGBUS,总线错误。 0x00000000004008e1 in main (argc=, argv=) at asm_test.cpp:28 28 b = my_func2(&a);

我做错了什么?在 my_func2 的最后一行我已经指定 memory 也被破坏了,我不明白...... 我在哪里可以找到如何使用臭名昭著的 AT&T 语法的好指南?
我编译:g++ -g -o asm_test asm_test.cppg++ 版本 g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 on Ubuntu Linux scv 3.2.0-48 -generic #74-Ubuntu SMP Thu Jun 6 19:43:26 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

我找到了 http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.htmlhttp://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html ,您有什么更多推荐的吗?

谢谢,
埃玛

【问题讨论】:

  • 不要需要这些标志来编译并使其工作。

标签: c++ gcc assembly inline-assembly att


【解决方案1】:

这里的错误是使用movupd时必须小心。使用此指令,您实际上复制了 128 位内存,输入

碰巧第一个函数也可以复制这些值,但第二个函数在ret 变量中只有 64 位空间。正如预期的那样,这会破坏堆栈,导致未定义的行为?
movupd 替换为movlpd(或movhpd),事情就会发挥作用魅力

我还在破坏正确的寄存器吗?

以下代码在使用g++ -O3 -o asm_test asm_test.cpp 编译时正常

  void my_func(const double *in, double *out) {
    asm ("mov %0, %%r8" : : "r"(in));
    asm ("movhpd (%%r8), %%xmm0" :);
    asm ("movhpd (%%r8), %%xmm1" :);
    asm ("addpd %%xmm1, %%xmm0" :);
    asm ("movhpd %%xmm0, (%0)" : : "r"(out) : "memory", "%r8", "%xmm0", "%xmm1");
  }

  double my_func2(const double *in) {
    double  ret;

    asm("mov %0, %%r8" : : "r"(in));
    asm("movlpd (%%r8), %%xmm0" :);
    asm("movlpd (%%r8), %%xmm1" :);
    asm("addpd %%xmm1, %%xmm0" :);
    asm("movlpd %%xmm0, %0" : "=m"(ret) : : "memory", "%r8", "%xmm0", "%xmm1");

    return ret;
  }

【讨论】:

  • asm("..." : ) 是一个小技巧,可以解决 gcc 将 抛出你的内联程序集 如果这样写(使用多个单独的 asm() 语句不是独立的)。重写这个可能会更好。
  • 我知道会是这种情况......也许我也应该使用volatile(这应该迫使编译器保持原样。或者你建议在一个语句中重写它。无论如何,使用movupd 而不是任何 lower / high 一半是 real 问题......顺便说一句,我已经做了一些分析为大型双数据集重写 sum 函数,当我查看生成的 asm 时,它符合预期(但我不得不说我已经将函数封装到 __attribute__ ((noinline)) 中)。
【解决方案2】:

gcc 内联汇编并不是特别喜欢它单独asm() 语句实际上并不独立。你最好把上面的代码写成这样:

#include <xmmintrin.h> // for __m128d

static  void my_func(const double *in, double *out) {
    asm("movupd %1, %%xmm0\n"
        "movupd %1, %%xmm1\n"
        "addpd %%xmm1, %%xmm0\n"
        "movupd %%xmm0, %0"
        : "=rm"(*(__m128d*)out)
        : "rm"(*(__m128d*)in)
        : "%xmm0", "%xmm1");
}

static double my_func2(const double *in) {
    double ret;
    asm("movupd %1, %%xmm0\n"
        "movupd %1, %%xmm1\n"
        "addpd %%xmm1, %%xmm0\n"
        "movlpd %%xmm0, %0"
        : "=xm"(ret)
        : "rm"(*(__m128d*)in)
        : "%xmm0", "%xmm1");
    return ret;
}

因为这让编译器可以选择放置东西的位置(mem 或 reg)。对于您的来源,这会将以下两个块内联到main()

 1c: 66 0f 10 44 24 10 movupd 0x10(%rsp),%xmm0
  22: 66 0f 10 4c 24 10 移动 0x10(%rsp),%xmm1
  28: 66 0f 58 c1 添加%xmm1,%xmm0
  2c: 66 0f 11 44​​ 24 20 移动 %xmm0,0x20(%rsp)
[ ... ]
  63: 66 0f 10 44 24 10 移动 0x10(%rsp),%xmm0
  69: 66 0f 10 4c 24 10 移动 0x10(%rsp),%xmm1
  6f: 66 0f 58 c1 addpd %xmm1,%xmm0
  73: 66 0f 13 44 24 08 movlpd %xmm0,0x8(%rsp)

这_不是最佳的,但是......如果你把它改成:

static  void my_func(const double *in, double *out) {
    asm volatile("movapd %1, %0\n"
                 "addpd %1, %0"
                 : "=xm"((__m128d*)out)
                 : "x"(*(__m128d*)in));
}

你把它留给编译器把变量放在哪里。编译器检测到它可以完全不进行加载/存储......因为这被简单地内联为:

 18: 66 0f 28 c1 movapd %xmm1,%xmm0
  1c: 66 0f 58 c1 addpd %xmm1,%xmm0
因为编译器识别出所有变量都在寄存器中/希望所有返回都在寄存器中。

虽然完全没有必要使用汇编来做到这一点;用一个像样的编译器(你的 gcc 会做)普通的 C/C++ 版本,

static void my_func(const double *in, double *out) {
    out[0] = in[0] + in[0];
    out[1] = in[1] + in[1];
}

很可能会变成效率不低的代码。

【讨论】:

  • 问题:你认为崩溃不是因为我应该在 128 位而不是 64 位上运行吗?此外,从一些测试来看,上述 C/C++ 循环可能仅在某些情况下是正确的,并且只有当我使用 -march=native 编译时。
  • 在您的具体示例中,是的,您确实通过将 128 位写入 ret 的位置导致崩溃,因为编译器很可能在堆栈上创建了一个内存布局,如 struct stk stk = { double ret; void *savedfp; void *retaddr },而您'一直在涂抹调用者的帧指针。从您的函数返回时,调用者 main 将在下一次相对于帧指针的内存访问时崩溃。
  • 感谢 FrankH,为了与所提问题保持一致,我会推广我的答案,但我会珍惜您的建议。
猜你喜欢
  • 2012-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多