почему оптимизатору не разрешено сворачивать в "постоянном контексте"? - PullRequest
5 голосов
/ 21 июня 2019

__builtin_is_constant_evaluated - это встроенная функция, используемая для реализации std::is_constant_evaluated в стандартной библиотеке для clang и gcc.

Код, который недопустим в постоянном контексте, также часто сложнее оптимизатору для постоянного сворачивания.

например:

int f(int i) {
    if (__builtin_is_constant_evaluated())
        return 1;
    else {
        int* ptr = new int(1);
        int i = *ptr;
        delete ptr;
        return i;
    }
}

испускается 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 foldэто константа, но это потому, что оптимизатор clang может удалить ненужное динамическое выделение, а не потому, что он использовал __builtin_is_constant_evaluated() == 1.

. Я знаю, что это приведет к тому, что возвращаемое значение реализации __builtin_is_constant_evaluated() будет определено, поскольку оптимизация будет отличаться отодин компилятор другому.но is_constant_evaluated уже следует использовать только в том случае, если оба пути имеют одинаковое наблюдаемое поведение.

почему оптимизатор не использует __builtin_is_constant_evaluated() == 1 и откат до __builtin_is_constant_evaluated() == 0, если он не смог сложить?

1 Ответ

5 голосов
/ 21 июня 2019

Per [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)
}

[...]

Вызов (3) является основным выражением-константой, но реализация не требуется для его оценки при компиляциивремя.Поэтому мы указываем, что это вызывает std::is_constant_evaluated() для получения false.Соблазнительно не указывать, производится ли true или false в этом случае, но это вызывает серьезные семантические проблемы: тогда ответ может стать непоследовательным на разных этапах компиляции.Например:

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, теперь мы имеем ситуацию, когда важная проверка во время выполнения обойдена.

...