Question

I compiled this C function:

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

And I got this in calc.s, and I am annotating what is happening:

        .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

I understand everything up to the last two leal instructions. Why do you need two leal instructions for 19*z whereas 3*y is accomplished in one instruction.

Was it helpful?

Solution

leal is a way to perform a multiplication by a small constant on a cheap, if the constant is a power of two plus one. The idea is that leal without an offset is equivalent to "Reg1 = Reg2+Reg3*Scale". If Reg2 and Reg3 happen to match, that means "Reg1=Reg2*(Scale+1).

leal only supports scale factors up to 8, so to multiply by 19, you need two.

The effect of

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

is:

eax = eax + eax*2

which is to say, multiplication by three.

The second two leals together perform multiplication by 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)

OTHER TIPS

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

The reason for this that the lea instruction uses add and bitshifts and is faster then using mul for integer multiplication. Lea is limited though to multiplication factors of 1, 2, 4 and 8 - thus two instructions.

lea serves a double purpose one is to calculate addresses but it can also be used for arithmetic with some constraints, as you observe with your code. Two calls are needed because the scalar multiplier of lea is limited to 1, 2, 4 or 8 which means to get your multiplication by 19 requires two calls to lea:

[...]The scalar multiplier is limited to constant values 1, 2, 4, or 8 for byte, word, double word or quad word offsets respectively. This by itself allows for multiplication of a general register by constant values 2, 3, 4, 5, 8 and 9,[...]

so in your case you have:

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top