Разъяснение развертывания цикла компилятора C

#c #msvcrt #loop-unrolling

#c #msvcrt #развертывание цикла

Вопрос:

У меня возникли проблемы с пониманием того, как компилятор MSVC разворачивает следующий цикл (извините за мое плохое понимание языка ассемблера):

 #define NUM_ITERATIONS (1000 * 1000 * 1000)
double dummySum = 0;

for (int x = 0; x < NUM_ITERATIONS; x  ) {
    if (x amp; 1) 
       dummySum  = x;
}
 

Это сгенерированная сборка:

 00007FF7B4511070  xorps       xmm1,xmm1  
        double dummySum = 0;
00007FF7B4511073  mov         ecx,2  
00007FF7B4511078  nop         dword ptr [rax rax]  
        if (x amp; 1) 
00007FF7B4511080  lea         eax,[rcx-2]  
00007FF7B4511083  mov         r8d,eax  
00007FF7B4511086  and         r8d,1  
00007FF7B451108A  je          someTest 28h (07FF7B4511098h)  
            dummySum  = x;
00007FF7B451108C  movd        xmm0,eax  
00007FF7B4511090  cvtdq2pd    xmm0,xmm0  
00007FF7B4511094  addsd       xmm1,xmm0  
        if (x amp; 1) 
00007FF7B4511098  lea         edx,[rcx-1]  
00007FF7B451109B  and         edx,1  
00007FF7B451109E  je          someTest 3Fh (07FF7B45110AFh)  
            dummySum  = x;
00007FF7B45110A0  lea         eax,[rcx-1]  
00007FF7B45110A3  movd        xmm0,eax  
00007FF7B45110A7  cvtdq2pd    xmm0,xmm0  
00007FF7B45110AB  addsd       xmm1,xmm0  
00007FF7B45110AF  test        r8d,r8d  
        if (x amp; 1) 
00007FF7B45110B2  je          someTest 50h (07FF7B45110C0h)  
            dummySum  = x;
00007FF7B45110B4  movd        xmm0,ecx  
00007FF7B45110B8  cvtdq2pd    xmm0,xmm0  
00007FF7B45110BC  addsd       xmm1,xmm0  
00007FF7B45110C0  test        edx,edx  
        if (x amp; 1) 
00007FF7B45110C2  je          someTest 63h (07FF7B45110D3h)  
            dummySum  = x;
00007FF7B45110C4  lea         eax,[rcx 1]  
00007FF7B45110C7  movd        xmm0,eax  
00007FF7B45110CB  cvtdq2pd    xmm0,xmm0  
00007FF7B45110CF  addsd       xmm1,xmm0  
00007FF7B45110D3  test        r8d,r8d  
        if (x amp; 1) 
00007FF7B45110D6  je          someTest 77h (07FF7B45110E7h)  
            dummySum  = x;
00007FF7B45110D8  lea         eax,[rcx 2]  
00007FF7B45110DB  movd        xmm0,eax  
00007FF7B45110DF  cvtdq2pd    xmm0,xmm0  
00007FF7B45110E3  addsd       xmm1,xmm0  
00007FF7B45110E7  test        edx,edx  
        if (x amp; 1) 
00007FF7B45110E9  je          someTest 8Ah (07FF7B45110FAh)  
            dummySum  = x;
00007FF7B45110EB  lea         eax,[rcx 3]  
00007FF7B45110EE  movd        xmm0,eax  
00007FF7B45110F2  cvtdq2pd    xmm0,xmm0  
00007FF7B45110F6  addsd       xmm1,xmm0  
00007FF7B45110FA  test        r8d,r8d  
        if (x amp; 1) 
00007FF7B45110FD  je          someTest 9Eh (07FF7B451110Eh)  
            dummySum  = x;
00007FF7B45110FF  lea         eax,[rcx 4]  
00007FF7B4511102  movd        xmm0,eax  
00007FF7B4511106  cvtdq2pd    xmm0,xmm0  
00007FF7B451110A  addsd       xmm1,xmm0  
00007FF7B451110E  test        edx,edx  
        if (x amp; 1) 
00007FF7B4511110  je          someTest 0B1h (07FF7B4511121h)  
            dummySum  = x;
00007FF7B4511112  lea         eax,[rcx 5]  
00007FF7B4511115  movd        xmm0,eax  
00007FF7B4511119  cvtdq2pd    xmm0,xmm0  
00007FF7B451111D  addsd       xmm1,xmm0  
00007FF7B4511121  test        r8d,r8d  
        if (x amp; 1) 
00007FF7B4511124  je          someTest 0C5h (07FF7B4511135h)  
            dummySum  = x;
00007FF7B4511126  lea         eax,[rcx 6]  
00007FF7B4511129  movd        xmm0,eax  
00007FF7B451112D  cvtdq2pd    xmm0,xmm0  
00007FF7B4511131  addsd       xmm1,xmm0  
00007FF7B4511135  test        edx,edx  
        if (x amp; 1) 
00007FF7B4511137  je          someTest 0D8h (07FF7B4511148h)  
            dummySum  = x;
00007FF7B4511139  lea         eax,[rcx 7]  
00007FF7B451113C  movd        xmm0,eax  
00007FF7B4511140  cvtdq2pd    xmm0,xmm0  
00007FF7B4511144  addsd       xmm1,xmm0  

    for (int x = 0; x < NUM_ITERATIONS; x  ) {
00007FF7B4511148  add         ecx,0Ah  
00007FF7B451114B  lea         eax,[rcx-2]  
00007FF7B451114E  cmp         eax,3B9ACA00h  
00007FF7B4511153  jl          someTest 10h (07FF7B4511080h)  
    }
 

Я понимаю эту часть (начало цикла):

 // if (x % 2 == 0) jump over the sumation

00007FF7B4511073  mov         ecx,2                          // ecx/rcx = 2
00007FF7B4511080  lea         eax,[rcx-2]                    // eax = rcx - 2
00007FF7B4511083  mov         r8d,eax                        // r8d = eax
00007FF7B4511086  and         r8d,1                          // r8x amp; 1
00007FF7B451108A  je          someTest 28h (07FF7B4511098h)  // jump if zero

// add double 

00007FF7B451108C  movd        xmm0,eax  
00007FF7B4511090  cvtdq2pd    xmm0,xmm0  
00007FF7B4511094  addsd       xmm1,xmm0  
 

Но я не понимаю, как последующие инструкции перехода, похоже, пропускают следующую lea инструкцию, если я смотрю на адреса (предполагается, что происходит переход) — обратите внимание, что я пропустил инструкции между переходами из приведенного выше списка:

 00007FF7B45110C0  test        edx,edx  
00007FF7B45110C2  je          someTest 63h (07FF7B45110D3h) 

... addresses in between omitted ...

00007FF7B45110D3  test        r8d,r8d  
00007FF7B45110D6  je          someTest 77h (07FF7B45110E7h)  

... addresses in between omitted ...

00007FF7B45110E7  test        edx,edx  
00007FF7B45110E9  je          someTest 8Ah (07FF7B45110FAh)  

... addresses in between omitted ...

00007FF7B45110FA  test        r8d,r8d  
00007FF7B45110FD  je          someTest 9Eh (07FF7B451110Eh)  

... addresses in between omitted ...

00007FF7B451110E  test        edx,edx  
00007FF7B4511110  je          someTest 0B1h (07FF7B4511121h)  
 

Если выполняется каждый переход, кажется, что это будет просто чередование test r8d,r8d test edx,edx инструкций и, без загрузки следующего значения.

Что я здесь неправильно интерпретирую?

Комментарии:

1. Последняя часть ( test edx,edx материал) отсутствует в списке. Здесь чего-то не хватает.

2. @MichaelWalz: не могли бы вы уточнить? Какая именно адресная строка? Я проверил адреса, и они, похоже, совпадают (если я не ошибся). Т.Е. Если вы начинаете с адреса 00007FF7B45110C0 в списке и выполняете je переходы.

3. Хорошо, я понимаю, вам следует опубликовать раздел «прыжки через эту часть …», который вы удалили, это, безусловно, важно.

4. @MichaelWalz: это в оригинальном списке, это была просто попытка показать (если я не ошибаюсь), что эти инструкции, похоже, пропускаются, если выполняется переход.

5. Вы видите эти lea eax,[rcx X] инструкции? Вы видите, насколько X в тех инструкциях все по-другому? Это может быть как-то связано с этим. Вы также должны запустить отладчик и пошагово просмотреть ассемблерный код (не исходный код).

Ответ №1:

Хорошо, понял, я прошел через дизассемблирование шаг за шагом; компилятор довольно умный. Цикл развертывается для выполнения 10 раз за итерацию, и эти инструкции расположены так, что r8d и edx загружаются только один раз за итерацию:

 lea         eax,[rcx-2]  
mov         r8d,eax  
and         r8d,1        // r8d is 0 here
...
lea         edx,[rcx-1]  
and         edx,1        // edx is 1 here
 

После этого эти регистры больше не загружаются для остальной части итерации, потому что компилятор, очевидно, понял, что amp; 1 на каждом нечетном шаге вычисляется значение true:

 00007FF7B45110C0  test        edx,edx  // always 1
00007FF7B45110C2  je          someTest 63h (07FF7B45110D3h) 

... addresses in between omitted ...

00007FF7B45110D3  test        r8d,r8d  // always 0
00007FF7B45110D6  je          someTest 77h (07FF7B45110E7h)  

... addresses in between omitted ...

00007FF7B45110E7  test        edx,edx  // always 1
00007FF7B45110E9  je          someTest 8Ah (07FF7B45110FAh)  

... addresses in between omitted ...

00007FF7B45110FA  test        r8d,r8d  // always 0
00007FF7B45110FD  je          someTest 9Eh (07FF7B451110Eh)  

... addresses in between omitted ...

00007FF7B451110E  test        edx,edx  // always 1
00007FF7B4511110  je          someTest 0B1h (07FF7B4511121h)