1

Here's the C code:

int baz(int a, int b)
{
    return a * 11;
}

That is compiled to the following set of assembly instructions (with -O2 flag):

baz(int, int):
        lea     eax, [rdi+rdi*4]
        lea     eax, [rdi+rax*2]
        ret

The lea instruction computes the effective address of the second operand (the source operand) and stores it in the first operand. To me, it seems that the first instruction should load an address to the EAX register, but, if so, multiplying RAX by 2 does not make sense in the second lea instruction, so I infer that these two lea instructions do not do quite the same thing.

I was wondering if someone could clarify what exactly is happening here.

Sep Roland
  • 33,889
  • 7
  • 43
  • 76
Ka Kkoi
  • 13
  • 2
  • 2
    Basically, forget about "addresses" and look at what `lea` actually does: simple arithmetic, shifts and adds. And it happens to let you do those in combinations that often require fewer total instructions than the equivalent sequence of `shl/add` instructions, so when such a combination is what you need, you can optimize like the compiler has done here. Nobody is forcing you to use the result as an address. – Nate Eldredge May 14 '23 at 15:12

2 Answers2

6

The function argument for a is stored in rdi. There is no need to load anything from memory.

lea eax, [rdi+rdi*4] is not calculating the address for any memory location to retrieve data from. Instead the compiler is just repurposing the instruction to do a multiplication. It stores a + a*4 to eax. Let's call that value t.

lea eax, [rdi+rax*2] then effectively stores a + t*2 to eax.

rax is also the register in which the function's return value is returned.

So the return value will be a + t*2 which is a + (a + a*4)*2 which is a + a*5*2 which is a*11.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • I see, so these two instructions do the same thing, which is storing the value calculated in the square brackets in the eax register. However, shouldn't lea instruction load the address into the eax register, by definition? – Ka Kkoi May 14 '23 at 15:02
  • 2
    @KaKkoi It computes (loads) the effective address that the address operand specifies. It doesn't load anything from the computed effective address as e.g. `mov` with the same address operand would. – user17732522 May 14 '23 at 15:05
4

Linux uses the System V AMD64 ABI calling convention which passes the first integer parameter in the register RDI and the return value in RAX. Here EAX is sufficient, because it returns a 32-bit value. The second parameter is unused.

LEA was intended for address calculations first on 8086 processors, but is also used for integer arithmetic with a constant factor, which is the case here. The constant factor is encoded using the scale value of the SIB byte in the instruction encoding. It can be 1,2,4 or 8.

So, the code could be explained by

baz(RDI, RSI):            ; a, b
lea     eax, [rdi+rdi*4]  ; RAX = 1*a + 4*a   = 5*a
lea     eax, [rdi+rax*2]  ; RAX = 1*a + 2*RAX = 1*a + 2*(5*a)
ret                       ; return RAX/EAX = 11*a

The upper half of RAX(64-bit value) is automatically cleared by the first LEA, see this SO question.

zx485
  • 28,498
  • 28
  • 50
  • 59
  • I see, but might I ask how exactly does CPU decide if the lea instruction is meant to calculate the address or the integer using integer arithmetic with a constant factor? – Ka Kkoi May 14 '23 at 15:06
  • 1
    It doesn't. Both are just integer values. Originally, the scale factor was probably intended to calculate the addresses for bytes(8, factor 1), words(16, factor 2), double words(32, factor 4), quad words(64, factor 8). This comes handy for calculating addresses in arrays, i.e. `lea eax,[base+5*4]` can calculate the address of the fifth(5) double word(4) entry of an array with the base address "base", all in one instruction. – zx485 May 14 '23 at 15:12
  • That would calculate the address of the sixth. (+0*4 = first, +1*4 = second, etc.) – ikegami May 14 '23 at 15:38
  • @ikegami: Thanks for correcting. Of course arrays on memory level are zero based. – zx485 May 14 '23 at 17:50
  • 1
    @zx485 `lea eax,[base+5*4]` is a bad example because the `lea` would be needed more if the `5` was rather a register instead of a constant. – ecm May 14 '23 at 18:27
  • If you're using LEA for actual addresses, you should be using 64-bit operand-size, like `lea rax, [rel base+5*4]` for a RIP-relative addressing mode. If you aren't using RIP-relative addressing, then there's no point in using LEA if no registers are involved, use `mov eax, base+5*4` (e.g. in a "small" code model like non-PIE on Linux, where static addresses are in the low 31 bits of virtual address space, allowing 32-bit zero-extended or sign-extended.) – Peter Cordes May 14 '23 at 18:35