2

I'm trying to understand how this line works:

lea (%eax, %eax, 4), %eax

So it looks like this essentially says:

%eax = 5* %eax

But does LEA treat as signed or unsigned?

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
krb686
  • 1,726
  • 9
  • 26
  • 46
  • 3
    It's all in the eyes of the beholder :) – 500 - Internal Server Error Apr 02 '14 at 11:35
  • For some reason I was under the impression that it wouldn't be the same result, but I guess that is untrue? – krb686 Apr 02 '14 at 11:38
  • 4
    all modern CPUs use 2's complement, so there won't be any difference between signed and unsigned operations – phuclv Apr 02 '14 at 12:34
  • @LưuVĩnhPhúc: MIPS has separate ADDI and ADDIU instructions, the only difference being that the signed one generates a trap on signed overflow. – ninjalj Apr 02 '14 at 13:08
  • 2
    No "integer" arithmetic instructions (well, maybe IMUL and IDIV) does anything but twos complement arithmetic. The beauty of this is that it gets the right answer, whether you interpret the operands as signed or unsigned. – Ira Baxter Sep 29 '14 at 20:55

4 Answers4

9
  1. LEA is not really intended to do arithmetic operations but to calculate the absolute address of a memory operation.

  2. There would be no difference between a signed and an unsigned operation; the result for a signed operation and an unsigned operation is the same in this case.

  3. As far as I know there is no possibility to multiply a register with a negative constant using the LEA instruction

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
Martin Rosenau
  • 17,897
  • 3
  • 19
  • 38
  • 4
    To expand on #1, I'm unaware of any architecture that uses negative memory addresses... :-) – Brian Knoblauch Apr 02 '14 at 12:53
  • The result of two's complement addition is the same for signed or unsigned numbers. The lower 32 bits of a product of two 32 bit operands is the same for signed or unsigned numbers. Negative constants can be used as immediate values with some assemblers (perhaps all). – rcgldr Apr 02 '14 at 14:43
  • 1
    Whether its intended or not; LEA is often used for arithmetic operations - either because its faster than an equivalent sequence of instructions, or because it doesn't modify flags. – Brendan Apr 06 '14 at 04:12
  • 1
    Point 1 is bogus; [`LEA` is a shift-and-add instruction that takes advantage of the CPUs ability to decode addressing modes.](https://stackoverflow.com/questions/46597055/using-lea-on-values-that-arent-addresses-pointers/46597375#46597375) For all we know, the intended purpose *is* to expose that functionality for use with arbitrary integers. In any case compilers think of it as just another ALU instruction (except for RIP-relative) , and so should humans. Pointer math in asm is just integer math, after all. – Peter Cordes Apr 25 '18 at 04:54
4

LEA does binary math, which is the same operation for unsigned or 2's complement signed. Just like the add and shl instructions. (Except lea doesn't set FLAGS, so you can't tell whether there was signed overflow (OF) or unsigned wrap-around (CF)).

For more about the general case of LEA as a shift-and-add instruction, see Using LEA on values that aren't addresses / pointers? - It's x86's only way to do a copy-and-add that writes the result into a different register, preserving the original, so compilers don't think about it as being "for addresses", and neither should you.


The only definite signedness is the way constants are encoded into machine code. A one-byte disp8 or 4-byte disp32 gets sign-extended when decoded. e.g.

lea  -4(%rdi), %eax        # int foo(int x){ return x-4; }

is the same instruction (with the same machine code: 8d 47 fc lea eax,[rdi-0x4]) as

lea  0xfffffffffffffffc(%rdi), %eax

Or to put it another way, assemblers can compress small-magnitude constants into 1 byte when (int8_t)c == c.


In 64-bit mode with 64-bit address-size, a disp32 or RIP+rel32 in machine code also gets sign-extended to 64-bit. (The only 64-bit absolute memory addressing is via a special encoding of mov; RIP+rel32 is far enough to reach anything in an executable or library with up to 2GiB of code+data, so it would be a waste of code footprint to use 8 byte displacements all the time.)


If you override the address-size to 32 and override the operand-size to 64, you effectively get zero-extension (like for unsigned), because 32-bit address works by truncation, not sign-extension. So addresses are contiguous from 0..4G, not the low and high 2GiB of 64-bit address space.

(Therefore it's silly to do that, just use 64-bit address size and 32-bit operand size so you don't need any prefixes; implicit zero-extension to 64-bit via writing a 32-bit register is exactly equivalent.)

# objdump -d output  for two equivalent instructions
   67 48 8d 47 fc          lea    -0x4(%edi),%rax    # address-size + REX.W + opcode + modrm + disp8
   8d 47 fc                lea    -0x4(%rdi),%eax    # no prefixes, same rest of the insn

I used 32-bit in 64-bit mode because other cases of a narrow result just merge, leaving the high bytes of the destination unchanged, so there's no zero or sign-extension. I guess I could have used 16-bit address-size in 32-bit mode, with 32-bit operand-size, like

lea  -1(%si), %eax    # zero-extend (uint16_t)(SI-1) into EAX

But lea -1(%esi), %ax is not equivalent to that, it only writes AX.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 1
    "...just use 64-bit address size and 32-bit address size..." I think you meant to write "32-bit operand size" – Sep Roland Aug 29 '21 at 20:58
1

This would be more appropriate as a comment, but comments don't have code formatting. As was hinted at by other comments and Martin's answer, you could do something like the following (gas syntax):

// %ecx has a size in it
neg %ecx
lea (%esp, %ecx, 4), %esp /* subtract %ecx*4 from stack ptr */
neg %ecx

... as compared with something like:

lea (,%ecx,4), %edx
sub %edx, %esp

... or:

lea (,%ecx,4), %edx
neg %edx
lea (%esp,%edx), %esp

I can't comment on the performance differences, but sub will alter flags and lea will not.

Brian Vandenberg
  • 4,011
  • 2
  • 37
  • 53
-2

It is treated as signed. Anything done using the bracket [] is signed. 2's complement is a way to represent signed numbers.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Alvo
  • 1
  • 1
  • 3
    You can use negative constants, like `lea eax, [rdi - 4]`, but that's exactly equivalent to writing `lea eax, [rdi + 0xfffffffffffffffc]`. Sign-extension of disp8 or disp32 constant displacements in the machine code when decoding is separate from the actual addition operation, and the question shows examples with only register operands. For registers operands, there's even less meaningful sense in which the operation can be called signed but not unsigned. (If you're working with signed numbers, you can of course use LEA or ADD, which both just do binary addition.) – Peter Cordes Aug 29 '21 at 01:31