Почему clang не всегда выдает постоянное значение для одного и того же статического constexpr - PullRequest
4 голосов
/ 24 сентября 2019
static constexpr int
count_x(const char * str)
{
  int count{};
  for (;*str != 0; ++str) {
    count += *str == 'x';
  }
  return count;
}

#define STRx1 "123456789x"
#define STRx4 STRx1 STRx1 STRx1 STRx1
#define STRx8 STRx4 STRx4
#define STRx16 STRx8 STRx8

int test1() { return count_x(STRx4); }
int test2() { return count_x(STRx8); }
int test3() { return count_x(STRx16); }
int test4() { constexpr auto k = count_x(STRx16); return k; }

Учитывая приведенный выше код, clang выдает постоянное значение для test1, test2 и test4.Почему не для test3?

test1():                              # @test1()
        mov     eax, 4
        ret
test2():                              # @test2()
        mov     eax, 8
        ret
test3():                              # @test3()
        xor     eax, eax
        mov     dl, 49
        mov     ecx, offset .L.str.2+1
.LBB2_1:                                # =>This Inner Loop Header: Depth=1
        xor     esi, esi
        cmp     dl, 120
        sete    sil
        add     eax, esi
        movzx   edx, byte ptr [rcx]
        add     rcx, 1
        test    dl, dl
        jne     .LBB2_1
        ret
test4():                              # @test4()
        mov     eax, 16
        ret
.L.str.2:
        .asciz  "123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x123456789x"

gcc делает:

test1():
        mov     eax, 4
        ret
test2():
        mov     eax, 8
        ret
test3():
        mov     eax, 16
        ret
test4():
        mov     eax, 16
        ret

Используемые командные строки компиляции:

clang++ -Ofast -std=c++2a -S -o - -c src/test.cpp | grep -Ev $'^\t+\\.'
gcc9 -Ofast -std=c++2a -S -o - -c src/test.cpp | grep -Ev $'^\t+\\.'

Проводник компилятора: https://godbolt.org/z/V-3MEp

Ответы [ 2 ]

1 голос
/ 26 сентября 2019

Как уже упоминалось в комментарии Тарвена, по умолчанию * существует ограничение в 100 символов (или итераций).

Этот предел можно изменить с помощью действительно скрытой опции:

--scalar-evolution-max-iterations

Максимальное количество итераций SCEV будет символически выполнять константу производного цикла (MaxBruteForceIterations: Source )

Изменение команды на:

clang++ -Ofast -std=c++2a -S -o - -c src/test.cpp -mllvm --scalar-evolution-max-iterations=1000

производит:

test1():                              # @test1()
        mov     eax, 4
        ret
test2():                              # @test2()
        mov     eax, 8
        ret
test3():                              # @test3()
        mov     eax, 16
        ret
test4():                              # @test4()
        mov     eax, 16
        ret

Просмотр в Проводник компилятора

Этот предел и опция, помеченная как действительно скрытая, может быть, есть веская причина.Поэтому я бы не стал менять его и использовать в производстве без соответствующих знаний об его эффектах (если я найду что-нибудь, я обновлю этот ответ).

Для справки: здесь вИсходный код LLVM (ScalarEvolution :: computeLoadConstantCompareExitLimit) - это место, где предел используется для test3.

0 голосов
/ 24 сентября 2019

Функции Constexpr должны вызываться во время компиляции только в выражении, ожидающем значение времени компиляции.

Вы должны использовать:

int test4() {
    constexpr auto res = count_x(STRx16);
    return res;
}

для принудительного вычисления во время компиляции.

Иначе вы в основном полагаетесь на регулярную оптимизацию компилятора, которая дает ожидаемую оптимизацию для test1 и test2, но не для test3.

...