GoAsm x64 - unexpected conversion mov -> lea

Started by beatrix, September 06, 2008, 08:59:09 AM

Previous topic - Next topic

beatrix

Hi,
I have a question about an "instruction conversion". I write :

mov rax, offset Title

When I compile it with GoAsm x64, I get the "lea" instruction (opcode 8Dh) in the .obj file :

lea rax, [0000000000402000h]

Why can't we have a "mov" instruction as expected :

48 B8 0020400000000000  (REX.W) mov rax, imm64

Thanks



jorgon

#1
Hi Beatrix

Well, since the LEA version does the same thing but is a bit smaller in size, I thought I might as well use that instead.  Here is the full explanation in the GoAsm 64 bit help file:-

QuoteRIP-Relative addressing

Some instructions in the AMD64 processor which address data or code, use RIP-Relative addressing to do so. The relative address is contained in a dword which is part of the instruction. When using this type of addressing, the processor adds three values: (a) the contents of the dword containing the relative address (b) the length of the instruction and (c) the value of RIP (the current instruction pointer) at the beginning of the instruction. The resulting value is then regarded as the absolute address of the data and code to be addressed by the instruction. Since the relative address can be a negative value, it is possible to address data or code earlier in the image from RIP as well as later. The range is roughly ±2GB, depending on the instruction size. Since relative addressing cannot address outside this range, this is the practical size limit of 64-bit images.
RIP-relative addressing happens "behind the back" of the user. The processor uses it if the opcodes contain certain values (in the ModRM byte, the Mod field equals 00 binary, and the r/m field equals 101 binary). You cannot control this except by changing the type of instructions you use. Generally here are the rules which govern whether or not an instruction uses RIP-relative addressing:-


  • Addresses in data cannot use RIP-relative addressing since the value of RIP cannot be known at the time when those addresses are set. Instead, an absolute address for insertion is calculated at link-time. So for example the following instructions do not use RIP-relative addressing but instead use absolute addresses:-
    MyDataLabel1 DQ MyDataLabel3   ;address of data label in data
    MyDataLabel2 DQ MyCodeLabel    ;address of code label in data
    MyDataLabel3 DQ $                     ;using current data pointer
    MyDataLabel4 DD MyDataLabel3   ;address of data label in data
    MyDataLabel5 DT MyCodeLabel    ;address of code label in data
    MyDataLabel6 DD $                     ;using current data pointer

    Note that in practice, the absolute address is contained in a dword and not in a qword. This is why in the above examples data and code addresses can be contained within a dword data declaration. This restriction is feasible because the practical image size is limited to 4GB anyway because of the restrictions imposed by RIP-relative addressing.

[li]Offsets converted to immediate values either at assemble-time or at link-time use absolute addressing rather than relative addressing. For example the following instructions do not use RIP-relative addressing but instead use absolute addresses:-
MOV RAX,ADDR MyDataLabel3        ;address of data label put in register
MOV MM0,ADDR MyCodeLabel        ;address of code label put in register
MOV Q[RSP],ADDR MyDataLabel3   ;address of data label put in memory location
MOV Q[RSP],ADDR MyCodeLabel    ;address of code label put in memory location[/li]

[li]GoAsm actually codes MOV RAX,ADDR MyDataLabel3 and similar instructions using the shorter LEA instruction, which does use RIP-relative addressing.
Here are examples of other instructions which use RIP-relative addressing:-
MOV RAX,[MyDataLabel3+55h]       ;address of data label
RCL Q[MyDataLabel3],1                  ;address of data label
MOV Q[MyDataLabel3],20h             ;address of data label
PAVGUSB MM3,[MyDataLabel3]       ;a 3DNow! instruction
CALL ExitProcess                            ;address of code label (system API)
JMP InternalCodeLabel                   ;address of code label inside the module
CALL InternalCodeLabel                  ;address of code label inside the module
CALL ExternalCodeLabel                  ;address of code label outside the module
PUSH [MyData]                              ;saving the contents of a data label
POP [MyData]                                ;restoring the contents of a data label

Note in the case of an external call, the relative address points to the Import Address Table. Since the table is now enlarged to 64-bits, it is possible to call a code label anywhere in memory.[/li]

[li]LEA uses RIP-relative addressing, for example:-
LEA RBX,MyDataLabel3                 ;load into RBX address of data label[/li]

[li]RIP-relative addressing is not used where the data or code label is supplemented by an index register. Although this may seem odd, the reason appears to be that adding information about the register to the opcodes means that the processor can no longer recognise the instruction as one which uses RIP-relative addressing (in the ModRM byte, the Mod field no longer equals 00 binary, and the r/m field no longer equals 101 binary). This means that the following instructions use absolute addresses rather than RIP-relative ones:-
MOV RAX,[ESI+MyData]
RCL Q[EBX+MyData],1
MOV Q[RSI*2+MyData],44444444h
PAVGUSB MM3,[R12+MyData]
LEA RBX,MyData+RSI
CALL [MyCall+RDI]
JMP [MyJump2+RDI]
PUSH [MyCall+RSI]
POP [MyCall+R12][/li]
[/list]

Bearing in mind that the image size is limited to 4GB by the above arrangements, it might be thought that the advantages of RIP-relative addressing are somewhat limited. This seems to be the case. It appears that the only advantage is that it lessens the number of relocations which would need to be carried out by the loader if a DLL is loaded at an address which is unexpected. The loader then would need to adjust all absolute addresses to suit the actual image base, but relative addresses would not have to be altered since they refer to other parts of the virtual image of the executable itself. However, it is good practice for the programmer to choose a suitable image base at link-time to avoid the need for relocations in a DLL in the first place. A good example of this is the system DLLs themselves. They all have a different image base which effectively avoids any prospective clashes of the image in memory which would require relocation at load-time.
Author of the "Go" tools (GoAsm, GoLink, GoRC, GoBug)

beatrix

thanks for your answer and sorry, I haven't read your documentation in detail...gloups. Now...
LEA is shorter because of relative addressing mode. ok. But I stay on my first idea :) If I write a MOV in asm, I expect a MOV during execution. In my opinion, if we write program in asm, code optimisation is "coder dependent" and not "compiler dependent". If I want to use hardcoded addresses in my library (even if we force the system to make hundreds of relocations), I expect hardcoded addresses in my program. Actually, I have to code my MOV with db, it is not very handy.

jorgon

QuoteIf I write a MOV in asm, I expect a MOV during execution.

My answer is that "MOV" is merely a mnemonic.  It denotes to the programmer that the processor will move something from one place to another.  This is also what happens with LEA.  It moves an address into a register.  Other MOVs are dealt with normally.

Author of the "Go" tools (GoAsm, GoLink, GoRC, GoBug)

Mark Jones

Also, a specific byte sequence could be declared literally. i.e.


.code
    DB 01h,02h,03h  ; equivalent to some instruction
"To deny our impulses... foolish; to revel in them, chaos." MCJ 2003.08

Rockoon

In this case there could be a performance difference between the two, depending on the processor and surrounding code... LEA doesnt necessarily share the same execution units/ports as MOV does.. this isnt like going from a more complex instruction to a less complex one (SHL eax, 1 -> ADD eax, eax for example) where it is certainly more acceptable.. this is the opposite of that.

Also, the fact that there are different in size could have a performance impact that is hard to identify. if I expect a 10 byte instruction because I described one, I dont necessarily want a 6 byte one being thrown in as a replacement (I could be relying on 10-bytes for alignment reasons)

Perhaps make these sorts of translations dependent on a command line switch, or make them well documented (shouldn't have to look through a listing or debugger to find this stuff out)
When C++ compilers can be coerced to emit rcl and rcr, I *might* consider using one.

MichaelW

I would think a programmer coding for a specific instruction length would be depending on a disassembly to verify the length.
eschew obfuscation

Rockoon

Quote from: MichaelW on November 29, 2008, 01:12:38 PM
I would think a programmer coding for a specific instruction length would be depending on a disassembly to verify the length.

I would think a programmer would expect an assembler to emit what he told it to. If he wanted lea, he can use lea.

This isnt like choosing between two instructions based on syntax ambiguity (shifting by const 1 vs shifting by const n, for instance.)

The syntax here has no ambiguity. Instead, the syntax now has a hole in it where there now exists a mov instruction the programmer can no longer emit in a traditional manner.
When C++ compilers can be coerced to emit rcl and rcr, I *might* consider using one.

MichaelW

My comment was a response to this:
Quote
Also, the fact that there are different in size could have a performance impact that is hard to identify. if I expect a 10 byte instruction because I described one, I dont necessarily want a 6 byte one being thrown in as a replacement (I could be relying on 10-bytes for alignment reasons)

I think most programmers do not have a reliably accurate idea of the length of more than a few instructions, even for a specific assembler, and for this reason are unlikely to be blindly coding to a specific length.

eschew obfuscation