I was trying to understand how Address Computation Instruction works, especially with leaq
command. Then I get confused when I see examples using leaq
to do arithmetic computation. For example, the following C code,
long m12(long x) {
return x*12;
}
In assembly,
leaq (%rdi, %rdi, 2), %rax
salq $2, $rax
If my understanding is right, leaq should move whatever address (%rdi, %rdi, 2)
, which should be 2*%rdi+%rdi
, evaluate to into %rax
. What I get confused is since value x is stored in %rdi
, which is just memory address, why does times %rdi by 3 then left shift this memory address by 2 is equal to x times 12? Isn't that when we times %rdi
by 3, we jump to another memory address which does not hold value x?
leaq
doesn't have to operate on memory addresses, and it computes an address, it doesn't actually read from the result, so until a mov
or the like tries to use it, it's just an esoteric way to add one number, plus 1, 2, 4 or 8 times another number (or the same number in this case). It's frequently "abused"† for mathematical purposes, as you see. 2*%rdi+%rdi
is just 3 * %rdi
, so it's computing x * 3
without involving the multiplier unit on the CPU.
Similarly, left shifting, for integers, doubles the value for every bit shifted (every zero added to the right), thanks to the way binary numbers work (the same way in decimal numbers, adding zeroes on the right multiplies by 10).
So this is abusing the leaq
instruction to accomplish multiplication by 3, then shifting the result to achieve a further multiplication by 4, for a final result of multiplying by 12 without ever actually using a multiply instruction (which it presumably believes would run more slowly, and for all I know it could be right; second-guessing the compiler is usually a losing game).
†: To be clear, it's not abuse in the sense of misuse, just using it in a way that doesn't clearly align with the implied purpose you'd expect from its name. It's 100% okay to use it this way.
So, if we pass in x as 1. Assume register is 4 bit, %rdi will be 0001 or 0x1 ? (If we ignore type long)
I'd argue that's not an abuse of LEA, copy-and-add is one of the intended purposes of exposing the CPU's address-generation ability through the
lea
instruction. See my answer.@ZhiyuanRuan yes, types like
int/short/long/...
are in common x86-64 ABIs passed by value, the value itself is in register when calling some function in ABI conforming way. No memory address is involved in your original assembly from compiler.@PeterCordes: "abuse" is mostly related to the terminology used to describe the instruction (load effective address); it's designed for address generation, but registers are registers, and the math is the same either way. I'm not saying it's bad to use
lea
, just not what the instruction's name would lead you to believe was the purpose.That's where I disagree. I think of it as being designed to expose the address-generation functionality of the hardware for use for arbitrary purposes. That's how compilers think of it, and so should humans. The naming is just connected to the fact that it uses addressing-mode syntax and machine-encoding, not the "intended" purpose. (I don't really know what Intel had in mind, as I said in my answer, but I think explaining it to beginners this way makes it sound normal to use LEA, because it is normal. That's why I dislike the term "abuse", but that's a fair justification for using it.)