避免在内存访问时刷新寄存器(gcc inline asm)

时间:2015-08-15 12:14:11

标签: gcc x86 inline-assembly

在GCC手册的第6.43.2.5 Extended Asm - Clobbers节中,在“内存”修补程序的解释中,提到了避免刷新寄存器的技巧:

  

如果你知道在编译时访问的内存大小,你可以指定一个这样的内存输入:

{"m"( ({ struct { char x[10]; } *p = (void *)ptr ; *p; }) )}
     

(假设您访问的是10个字节的字符串。)

我想我理解这个想法,但我不清楚如何使用它以及这个技巧有什么含义 - 除了向GCC提供更多类型信息。 最重要的是出现了三个问题:

  • 我是否也可以将其用作输出或输入/输出操作数并修改数据?
  • 如果我使用这个技巧,我还需要“记忆”clobber吗? 我想我没有,因为我宣称内存块为输入/输出,但我不确定。
  • 我是否可以安全地删除仅访问内存时所需的volatile限定符? 我想我可以,因为它会被声明为输出。

因为我们喜欢这样的例子:这段代码是否有意义并且合法吗?它似乎有效。

#include <iostream>
#include <cstdint>

void add_assembly(std::uint64_t * x) {
  struct memory { std::uint64_t data[2]; } * p = reinterpret_cast<memory*>(x);
  __asm__ (
        "addq $1, %[x] \t\n"
        "addq $5, 8%[x] \t\n"
        : [x] "+m" (*p) // Bonus question: Why don't I need a "&" here?
        : "m" (*p)
        : "cc"
  );
}

int main() {
  std::uint64_t x[2];
  x[0] = 3000;
  x[1] = 7253;
  std::cout << "before: " << x[0] << " " << x[1] << std::endl;
  add_assembly(&x[0]); // add 1 to x[0], add 5 to x[1]
  std::cout << "after:  " << x[0] << " " << x[1] << std::endl;
  return 0;
}

1 个答案:

答案 0 :(得分:1)

我对这个答案中的所有内容都不是100%肯定,但我最近自己也看过这个问题。

  

我是否也可以将其用作输出或输入/输出操作数并修改数据?

是的,但您可能会因单独的r(注册)或m(可能是复杂的寻址模式)约束而感觉更好。这是特别的。如果要在循环中递增指针,则为true,因此需要在寄存器中使用它。 m约束可以使%0扩展为(%rsi, %rdx, 4)或其他内容。

  

如果我使用这个技巧,我还需要&#34;内存&#34;揍?

没有。这告诉gcc哪些内存可能被修改。 "memory"表示所有内存都可能被修改。

  

我是否可以安全地删除仅访问内存时所需的volatile限定符?

你的意思是asm volatile (...)?是。仅当周围的代码不使用其输出(或者它没有任何输出)时,才需要volatile来使gcc不移动或消除asm语句。只要你告诉gcc你的asm在内存中产生的结果,并使用那个结果,就不应该使用volatile,所以gcc可以像任何其他黑盒函数一样进行优化。 (当然,假设你的asm是一个纯粹的&#34;函数,只取决于它所声明的输入。)

例子很好。 :)你的看起来不错。

我认为你指定一个读写输入操作数是不够的;你需要指定它两次,作为输入和输出。 +约束的措辞使我认为&#34;阅读和写作&#34;意味着gcc将您留在其中的值视为变量的新值,但我认为情况并非如此。

我认为在x86上,m约束等同于o(可抵消)约束。但要注意语法;你不希望8%[x]变成88(%rsp)。也许8 + %[x]?不确定。留一个空格,所以它是一个语法错误而不是一个不同的数字。

\t\n很傻。您希望选项卡位于每行的开头。

asm (
    "inc      %[x]   \n\t"   // inc is shorter than `add`
    "addq $5, 8 %[x] \n\t"   // this `add` writes all flags, preventing partial-flag stalls / slowdowns
    : [x] "+m" (*p) // Bonus question: Why don't I need a "&" here?
         // because you aren't writing a temporary to the output before reading the input
    : "m" (*p)
    : "cc"
);