为什么这个IA32汇编代码有三个leal指令?

我编译了这个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 

我理解最后两个leal指令的一切。 为什么你需要两个19 * z的leal指令,而3 * y是在一个指令中完成的。

如果常数是2加1的幂,则leal是一种以便宜的方式执行乘以小常数的方法。 这个想法是没有偏移的leal相当于“Reg1 = Reg2 + Reg3 * Scale”。 如果Reg2和Reg3恰好匹配,则表示“Reg1 = Reg2 *(Scale + 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和bitshifts并且比使用mul进行整数乘法更快。 Lea受限于乘法因子1,2,4和8 – 因此有两条指令。

lea服务于双重目的是计算地址,但它也可以用于具有一些约束的算术,就像您在代码中观察到的那样。 需要两次调用,因为lea的标量乘数限制为48 ,这意味着乘以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