使用 -fno-inline 对 memcmp 的 GCC 9(及更高版本)优化错误
Wrong GCC 9 (and higher) optimization of memcmp with -fno-inline
有一个小的 func
函数可以将内存块与静态 const 归零数组进行比较。这是一个说明问题的原始示例:
#include <cstring>
#include <memory>
#define MAX_BYTES (256)
inline int my_memcmp(const void * mem1, const void * mem2, const size_t size)
{
const auto *first = reinterpret_cast<const uint8_t *>(mem1);
const auto *second = reinterpret_cast<const uint8_t *>(mem2);
if (size < 8)
{
for (int i = 0; i < size; ++i) {
if (*first != *second) return (*first > *second) ? 1 : -1;
++first; ++second;
}
return 0;
}
return std::memcmp(mem1, mem2, size);
}
bool func(const uint8_t* in, size_t size)
{
size_t remain = size;
static const uint8_t zero_arr[MAX_BYTES] = { 0 };
while (remain >= MAX_BYTES)
{
if (my_memcmp(in, zero_arr, MAX_BYTES) != 0)
{
return false;
}
remain -= MAX_BYTES;
in += MAX_BYTES;
}
return true;
}
- 编译器:gcc 9.1 及更高版本
- 编译器标志:
-fno-inline -O3
- 神马拆解link:https://godbolt.org/z/P8vKGq
- Godbolt 程序执行link:https://godbolt.org/z/qr8f16
如果我使用 -fno-inline
编译器标志,编译器会尝试优化上面的代码并只为 my_memcmp
函数生成 2 行代码,但它似乎总是 returns 0:
my_memcmp(void const*, void const*, unsigned long) [clone .constprop.0]:
movzx eax, BYTE PTR [rdi]
ret
在我添加-fno-inline
之前无法重现问题(我在编译覆盖测试代码时遇到了问题,所以我需要添加no-inline以使报告更清楚。)另外我我发现 gcc 8 没有这样的问题。是否有合理的解释,或者这只是 GCC 9 和 10 中的错误?
这是 GCC 错误 95189,https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95189
基本上,如果缓冲区之一具有已知内容,GCC 可以为 memcmp 发出专用代码,但如果遇到零字节,则此专用代码无法正常工作(因为它对 strcmp 等其他函数是特殊的)。
它似乎已经在 GCC 主开发分支(主干)上得到修复,但该修复尚未移植到 9.x 和 10.x 发布分支。
C 中的最小重现在 -O2 处被错误编译,错误的评论中提到了类似的示例:
int f(const char *p)
{
return __builtin_memcmp(p, "[=10=][=10=][=10=]", 4);
}
有一个小的 func
函数可以将内存块与静态 const 归零数组进行比较。这是一个说明问题的原始示例:
#include <cstring>
#include <memory>
#define MAX_BYTES (256)
inline int my_memcmp(const void * mem1, const void * mem2, const size_t size)
{
const auto *first = reinterpret_cast<const uint8_t *>(mem1);
const auto *second = reinterpret_cast<const uint8_t *>(mem2);
if (size < 8)
{
for (int i = 0; i < size; ++i) {
if (*first != *second) return (*first > *second) ? 1 : -1;
++first; ++second;
}
return 0;
}
return std::memcmp(mem1, mem2, size);
}
bool func(const uint8_t* in, size_t size)
{
size_t remain = size;
static const uint8_t zero_arr[MAX_BYTES] = { 0 };
while (remain >= MAX_BYTES)
{
if (my_memcmp(in, zero_arr, MAX_BYTES) != 0)
{
return false;
}
remain -= MAX_BYTES;
in += MAX_BYTES;
}
return true;
}
- 编译器:gcc 9.1 及更高版本
- 编译器标志:
-fno-inline -O3
- 神马拆解link:https://godbolt.org/z/P8vKGq
- Godbolt 程序执行link:https://godbolt.org/z/qr8f16
如果我使用 -fno-inline
编译器标志,编译器会尝试优化上面的代码并只为 my_memcmp
函数生成 2 行代码,但它似乎总是 returns 0:
my_memcmp(void const*, void const*, unsigned long) [clone .constprop.0]:
movzx eax, BYTE PTR [rdi]
ret
在我添加-fno-inline
之前无法重现问题(我在编译覆盖测试代码时遇到了问题,所以我需要添加no-inline以使报告更清楚。)另外我我发现 gcc 8 没有这样的问题。是否有合理的解释,或者这只是 GCC 9 和 10 中的错误?
这是 GCC 错误 95189,https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95189
基本上,如果缓冲区之一具有已知内容,GCC 可以为 memcmp 发出专用代码,但如果遇到零字节,则此专用代码无法正常工作(因为它对 strcmp 等其他函数是特殊的)。
它似乎已经在 GCC 主开发分支(主干)上得到修复,但该修复尚未移植到 9.x 和 10.x 发布分支。
C 中的最小重现在 -O2 处被错误编译,错误的评论中提到了类似的示例:
int f(const char *p)
{
return __builtin_memcmp(p, "[=10=][=10=][=10=]", 4);
}