Почему существуют три инструкции для этого кода сборки IA32?

Я скомпилировал эту функцию C:

int calc(int x, int y, int z) { return x + 3*y + 19*z; } 

И я получил это в calc.s, и я аннотирую то, что происходит:

  .file "calc.c" .text .globl calc .type calc, @function calc: pushl %ebp //Save paramaters movl %esp, %ebp //Move stack pointer into %ebp movl 12(%ebp), %eax //Move y into %eax movl 16(%ebp), %ecx //Move z into %ecx leal (%eax,%eax,2), %eax //%eax = 3*y addl 8(%ebp), %eax //%eax = x+3y leal (%ecx,%ecx,8), %edx // ? leal (%ecx,%edx,2), %edx // ? addl %edx, %eax //%eax = (x+3*y)+(19*z) popl %ebp //Pop the previous pointer ret .size calc, .-calc .ident "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3" .section .note.GNU-stack,"",@progbits 

Я все понимаю до двух последних инструкций. Зачем вам нужны две инструкции для 19 * z, тогда как 3 * y выполняется в одной инструкции.

leal – способ выполнить умножение на небольшую константу на дешевой, если константа равна мощности плюс плюс. Идея заключается в том, что leal без смещения эквивалентен «Reg1 = Reg2 + Reg3 * Scale». Если Reg2 и Reg3 совпадают, это означает «Reg1 = Reg2 * (Масштаб + 1).

leal поддерживает только масштабные коэффициенты до 8, поэтому для умножения на 19 вам нужно два.

Эффект

 leal (%eax,%eax,2), %eax 

является:

 eax = eax + eax*2 

т. е. умножение на три.

Второе два leal вместе умножают на 19:

 leal (%ecx,%ecx,8), %edx // edx = ecx+ecx*8 leal (%ecx,%edx,2), %edx // edx = ecx+edx*2 (but edx is already z*9) 
 leal (%ecx,%ecx,8), %edx # edx = ecx + 8*ecx = 9*ecx = 9 * z leal (%ecx,%edx,2), %edx # edx = ecx + 2*edx = ecx + 2 * (ecx + 8*ecx) = z + 2 * 9 * z = 19 * z 

Причина этого в том, что инструкция lea использует add и bithifts и быстрее, чем использование mul для целочисленного умножения. Lea ограничена, хотя коэффициенты умножения 1, 2, 4 и 8 – таким образом, две инструкции.

lea служит для двойной цели – вычислять адреса, но также может использоваться для арифметики с некоторыми ограничениями, как вы это видите в коде. Требуется два вызова, потому что скалярный множитель lea ограничен 1 , 2 , 4 или 8 что означает, что для умножения на 19 требуется два вызова lea :

[…] Скалярный множитель ограничен постоянными значениями 1, 2, 4 или 8 для байтов, слов, двойных слов или сдвигов слова четвертого слова соответственно. Это само по себе позволяет умножать общий регистр на постоянные значения 2, 3, 4, 5, 8 и 9, […]

поэтому в вашем случае у вас есть:

 leal (%ecx,%ecx,8), %edx // edx = ecx + ecx*8 which is z*8 + z = z*9 leal (%ecx,%edx,2), %edx // edx = ecx + edx*2 which gives us (z*9)*2 + z // for a total of 19z