如何防止 gcc 优化破坏 rep movsb 代码?

How to prevent gcc optimization breaking rep movsb code?

我尝试使用 rep movsb 指令创建我的 memcpy 代码。当禁用优化时,它可以完美地处理任何尺寸。但是,当我启用优化时,它并没有按预期工作。

问题

  1. 如何防止 gcc 优化破坏 rep movsb 代码?
  2. 我的代码是否有问题导致未定义的行为?

创建我自己的 memcpy 的动机:

我从 Intel® 64 and IA-32 Architectures Optimization Reference Manual 3.7.6 节 中读到关于 memcpy 的增强 movsb。我来到 libc 源代码,我看到 libc 的默认 memcpy 使用 SSE 而不是 movsb.

因此,我想比较 SSE 指令rep movsb 对 memcpy 的性能。但是现在,我发现它有问题。

重现问题的简单代码 (test.c)

#include <stdio.h>
#include <string.h>

inline static void *my_memcpy(
  register void *dest,
  register const void *src,
  register size_t n
) {
  __asm__ volatile(
    "mov %0, %%rdi;"
    "mov %1, %%rsi;"
    "mov %2, %%rcx;"
    "rep movsb;"
    :
    : "r"(dest), "r"(src), "r"(n)
    : "rdi", "rsi", "rcx"
  );
  return dest;
}

#define to_boolean_str(A) ((A) ? "true" : "false")

int main()
{
  char src[32];
  char dst[32];

  memset(src, 'a', 32);
  memset(dst, 'b', 32);

  my_memcpy(dst, src, 1);
  printf("%s\n", to_boolean_str(!memcmp(dst, src, 1)));

  my_memcpy(dst, src, 2);
  printf("%s\n", to_boolean_str(!memcmp(dst, src, 2)));

  my_memcpy(dst, src, 3);
  printf("%s\n", to_boolean_str(!memcmp(dst, src, 3)));

  return 0;
}

编译并运行

ammarfaizi2@integral:~$ gcc --version
gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

ammarfaizi2@integral:~$ gcc -O0 test.c -o test && ./test
true
true
true
ammarfaizi2@integral:~$ gcc -O1 test.c -o test && ./test
false
true
true
ammarfaizi2@integral:~$ gcc -O2 test.c -o test && ./test
false
true
true
ammarfaizi2@integral:~$ gcc -O3 test.c -o test && ./test
false
true
true
ammarfaizi2@integral:~$ 

总结

如果启用优化,

my_memcpy(dst, src, 1); 会导致错误行为。

正如所写,您的 asm 约束并未反映出 asm 语句可以修改内存,因此编译器可以根据在 dest 或 [=11= 处读取或写入内存的操作自由地重新排序它].您需要将 "memory" 添加到 clobber 列表中。

正如其他人指出的那样,您还应该编辑约束以避免 mov。如果你这样做,你还需要在约束中表示 asm 现在修改它的参数(例如,使它们都是双重的 input/output)并备份 dest 的值,这样你就可以 return它。因此,您可以跳过此改进,直到您开始使用它并了解约束的工作原理。