【问题标题】:Debug GCC warning during LTO constant propagation在 LTO 常量传播期间调试 GCC 警告
【发布时间】:2021-05-25 18:25:09
【问题描述】:

我们正在使我们的项目兼容 GCC。 启用 LTO 后,链接需要很长时间,并且这些警告显示:

../src/xenia/base/memory.h: In function ‘copy_and_swap.constprop’:
../src/xenia/base/memory.cc:105: warning: iteration 4611686018427387903 
invokes undefined behavior [-Waggressive-loop-optimizations]
  105 |     dest[i] = byte_swap(src[i]);
      |
../src/xenia/base/memory.cc:104: note: within this loop
  104 |   for (; i < count; ++i) {  // handle residual elements
      |
../src/xenia/base/memory.cc:124: warning: iteration 4611686018427387903 
invokes undefined behavior [-Waggressive-loop-optimizations]
  124 |     dest[i] = byte_swap(src[i]);
      |
../src/xenia/base/memory.cc:123: note: within this loop
  123 |   for (; i < count; ++i) {  // handle residual elements
      |

这是我们第一次看到这些函数存在问题(通常使用 MSVC/Clang)。它们包括 向量内在函数。

如何调试此问题?如何获取调用 GCC 的编译时堆栈跟踪 正在尝试优化?

编辑:

这是有问题的代码

inline uint32_t byte_swap(uint32_t value) { return __builtin_bswap32(value); }

void copy_and_swap_32_aligned(void* dest_ptr, const void* src_ptr,
                              size_t count) {
  assert_zero(reinterpret_cast<uintptr_t>(dest_ptr) & 0xF);
  assert_zero(reinterpret_cast<uintptr_t>(src_ptr) & 0xF);

  auto dest = reinterpret_cast<uint32_t*>(dest_ptr);
  auto src = reinterpret_cast<const uint32_t*>(src_ptr);
  __m128i shufmask =
      _mm_set_epi8(0x0C, 0x0D, 0x0E, 0x0F, 0x08, 0x09, 0x0A, 0x0B, 0x04, 0x05,
                   0x06, 0x07, 0x00, 0x01, 0x02, 0x03);

  size_t i;
  for (i = 0; i + 4 <= count; i += 4) {
    __m128i input = _mm_load_si128(reinterpret_cast<const __m128i*>(&src[i]));
    __m128i output = _mm_shuffle_epi8(input, shufmask);
    _mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
  }
  for (; i < count; ++i) {  // handle residual elements
    dest[i] = byte_swap(src[i]);
  }
}

没有内在函数的平台不变版本的函数(仅循环完整的数组和单独的字节交换)不会引发 gcc 警告。

【问题讨论】:

  • 我不确切知道 gcc 将什么视为 UB,但我已经用一个可能更接近您的代码试图做的示例更新了我的帖子。

标签: gcc compiler-errors linker lto


【解决方案1】:

有问题的数组是如何声明的?

gcc 编译器通常会根据以下概念推断循环可能执行的次数,即如果标准不对构造的行为施加任何要求,即使这是因为委员会预计普通实现会所有的处理都是一样的,没有任何可能的行动会比其他任何行动更好或更差。

以代码 sn-p 为例:

unsigned foo[32770];
unsigned mul_mod_65536(unsigned short x, unsigned short y)
{
    return (x*y) & 0xFFFFu;
}
void test(unsigned short n)
{
    unsigned sum = 0;
    for (unsigned short i=32768; i<n; i++)
        sum += mul_mod_65536(i, 65535);
    if (n < 32770)
        foo[n] = sum & 32767;
}

GCC 会将 test() 函数处理成等价于:

void test(unsigned short n)
{
    foo[n] = 0;
}

这样的转换是符合标准的:标准的作者表示,他们期望普通编译器会扩展语言的语义,以在比标准规定的更多情况下以相同的方式处理有符号和无符号整数数学,但他们没有需要这样的行为。该标准允许编译器任意偏离常见行为,因为这种偏离在目标平台上有意义,或者出于编译器作者认为合适的任何其他原因。

您引用的警告通常是 gcc 用更“高效”但无用的代码替换有用代码的结果。认为自己很幸运,您收到有关此类“优化”的警告。在某些情况下[就像我上面发布的示例],gcc 会默默地更改代码,从而将普通的因果律抛到窗外。在大多数平台上,除了产生一个可能毫无意义的值之外,尝试将 32769 乘以 65535 会产生任何副作用,并且在上面的代码中,这种乘法可能产生的任何值最终都会被忽略,这并没有什么特别的原因如果乘法没有任何奇怪的副作用。然而,GCC 会注意到任何大于 32769 的 n 值都会导致整数溢出。即使溢出发生在标准的作者期望普通实现表现得有意义的情况下,gcc 反而推断它可以认为任何 n 超过 32769 的情况都是无意义的。

附录:

也许以下代码 sn-p 可能有助于在 Godbolt 中使用:

struct foo { int a[2][2]; };
int bump(struct foo *p, unsigned short n)
{
    int total=0;
    int i=0;
    for (i=0; i < n+2; i++)
    {
        p->a[0][i] += 1;
    }
    return i;
}
struct foo foo[4];
int test2(unsigned char n)
{
    return bump(foo, n+1);
}

尝试更改数组维度,并将调用中的第二个参数替换为不同的常量 n 或 n+1,您会注意到 gcc 有时会:

  1. 生成能够索引整个数组的代码
  2. 有时会以静默方式生成代码,其行为就像循环范围仅限于一维一样
  3. 有时会产生警告并生成代码,这些代码只是从函数末尾掉入内存中恰好跟在它后面的任何代码中,并且
  4. 有时会默默地生成代码,这些代码会从函数末尾落入内存中的任何内容。

我对 C++ 不够熟悉,无法确切知道您的示例与此类事物相比如何,但 gcc 对内部数组维度非常激进。至少在 C 语言中,它似乎扩展了语义语言,以允许指针算术运算+ 超出内部数组的范围(标准的作者几乎可以肯定地打算这样做,但未能授权,因为当y 不小于内部数组的长度时对arr[x][y] 的访问被明确表征为UB,但它也被明确表征为等同于*((&amp;arr[x][0]) + y),这意味着后者的构造将是UB相同的情况)。

【讨论】:

  • 非常有趣但也令人担忧。数组作为函数参数进来。通常它只是没有上下文的内存地址或指向结构的指针。我在原始问题中添加了一个代码 sn-p
  • 感谢您添加的示例。似乎需要真正避免这样的代码......我能够查明我的问题并认为这是一个编译器错误。 godbolt.org/z/vGvKqfvzr
  • @JoelLinn:我不太明白你在做什么,但看起来编译器无法正确计算我在进入最后一个循环时将拥有的值?跨度>
  • 类似的东西,是的。也许将其标记为未使用(因为在发出警告的情况下,计数是 4 的倍数)并丢弃一些内部状态或其他东西。
猜你喜欢
  • 2020-04-24
  • 2017-04-06
  • 2012-11-27
  • 1970-01-01
  • 1970-01-01
  • 2013-11-02
  • 1970-01-01
  • 2014-05-16
  • 1970-01-01
相关资源
最近更新 更多