Complier, генерирующий MOV взад и вперед на EAX - PullRequest
5 голосов
/ 14 марта 2019
int test1(int a, int b) {
    if (__builtin_expect(a < b, 0))
        return a / b;
    return b;
}

был скомпилирован clang с -O3 -march=native в

test1(int, int):                             # @test1(int, int)
        cmp     edi, esi
        jl      .LBB0_1
        mov     eax, esi
        ret
.LBB0_1:
        mov     eax, edi
        cdq
        idiv    esi
        mov     esi, eax
        mov     eax, esi  # moving eax back and forth
        ret

, почему eax перемещается вперед и назад после того, как idiv?

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

GCC с -O3 -march=native соответствует код для

test1(int, int):
        mov     r8d, esi
        cmp     edi, esi
        jl      .L4
        mov     eax, r8d
        ret
.L4:
        mov     eax, edi
        cdq
        idiv    esi
        mov     r8d, eax
        mov     eax, r8d  #back and forth mov
        ret

Godbolt

Ответы [ 2 ]

2 голосов
/ 14 марта 2019

Это не полное решение головоломки, но оно должно дать некоторые подсказки.

Без __builtin_expect, clang генерирует:

test2(int, int):                             # @test2(int, int)
        mov     ecx, esi
        cmp     edi, esi
        jge     .LBB1_2
        mov     eax, edi
        cdq
        idiv    ecx
        mov     ecx, eax
.LBB1_2:
        mov     eax, ecx
        ret

Хотя распределение регистров здесь все еще странноПо крайней мере, имеет смысл: если ветвь берется, значение b в ecx передается в eax в качестве возвращаемого значения.Если оно не взято, результат деления (в eax) должен быть передан в ecx, чтобы быть в том же регистре, что и в другом случае.

Возможно, что __builtin_expect убеждает компилятор в особом случае, когда ветвь берется поздно в процессе компиляции, теряет метку .LBB1_2 и приводит к тому, что она в конечном итоге отсутствует в сборке.

1 голос
/ 14 марта 2019

idiv esi имеет размер 32-битного операнда, поэтому EAX уже расширен нулями для заполнения RAX. Поэтому копирование в ESI или R8D и обратно не влияет на значение в EAX. (И соглашение о вызовах в любом случае не требует расширения нуля или расширения знака до 64-битного; 32-битные типы возвращаются в 32-битных регистрах с возможным мусором в верхних 32-х.)

Это похоже на пропущенную оптимизацию. (Нет никакой микроархитектурной причины, по которой это тоже было бы хорошо.)

...