为什么不允许优化器在“恒定上下文”中折叠?

时间:2019-06-21 08:52:41

标签: c++ optimization c++20

__builtin_is_constant_evaluated是用于在clang和gcc的标准库中实现std::is_constant_evaluated的内置函数。

在恒定上下文中无效的代码通常也使优化程序难以恒定折叠。

例如:

int f(int i) {
    if (__builtin_is_constant_evaluated())
        return 1;
    else {
        int* ptr = new int(1);
        int i = *ptr;
        delete ptr;
        return i;
    }
}
{p>由gcc -O3发出为:

f(int):
        sub     rsp, 8
        mov     edi, 4
        call    operator new(unsigned long)
        mov     esi, 4
        mov     rdi, rax
        call    operator delete(void*, unsigned long)
        mov     eax, 1
        add     rsp, 8
        ret

因此优化程序使用了__builtin_is_constant_evaluated() == 0

clang将其折叠为常数,但这是因为clang的优化器可以删除不需要的动态分配,而不是因为它使用了__builtin_is_constant_evaluated() == 1

我知道这将定义__builtin_is_constant_evaluated()实现的返回值,因为优化因一个编译器而异。但是只有在两个路径具有相同的可观察行为时,才应该使用is_constant_evaluated。

为什么优化器无法折叠,为什么它不使用__builtin_is_constant_evaluated() == 1并退回到__builtin_is_constant_evaluated() == 0呢?

1 个答案:

答案 0 :(得分:5)

[meta.const.eval]

constexpr bool is_constant_evaluated() noexcept;
     

返回: true当且仅当调用的评估发生在对表达式或转换的评估明显地发生时   常量评估([expr.const])。

f永远不能在常量求值的表达式或转换中调用,因此std::is_constant_evaluated()返回false。这是由编译器决定的,与优化器无关。

当然,如果优化器可以证明分支是等效的,则它可以进行恒定折叠。但这毕竟是优化-超出了C ++语言本身的范围。

但是为什么会这样呢?引入了std::is_constant_evaluated的提案是P0595。它很好地解释了这个想法:

constexpr double power(double b, int x) {
  if (std::is_constant_evaluated() && x >= 0) {
    // A constant-evaluation context: Use a
    // constexpr-friendly algorithm.
    double r = 1.0, p = b;
    unsigned u = (unsigned)x;
    while (u != 0) {
      if (u & 1) r *= p;
      u /= 2;
      p *= p;
    }
    return r;
  } else {
    // Let the code generator figure it out.
    return std::pow(b, (double)x);
  }
}

// ...
double thousand() {
  return power(10.0, 3);  // (3)
}
     

[...]

     

Call(3)是核心常量表达式,但实现不是   需要在编译时对其进行评估。因此,我们指定它   使std::is_constant_evaluated()产生false。它的   试图将其指定为truefalse   在这种情况下,但这引起了重大的语义问题:   这样一来,答案可能会在各个阶段间变得不一致   汇编。例如:

int *p, *invalid;
constexpr bool is_valid() {
  return std::is_constant_evaluated() ? true : p != invalid;
}
constexpr int get() { return is_valid() ? *p : abort(); }
     

此示例尝试依靠constexpr评估这一事实   检测未定义的行为以避免对constexpr不友好的调用   abort()在编译时。但是,如果std::is_constant_evaluated()   可以返回true,我们现在遇到了这样一种情况:   运行时检查被绕过。