Why can't I move directly a byte to a 64 bit register?
Why can't I directly move a byte from memory to a 64-bit register in Intel x86-64 assembly?
For instance, this code:
extern printf
global main
segment .text
main:
enter 2, 0
mov byte [rbp - 1], 'A'
mov byte [rbp - 2], 'B'
mov r12, [rbp - 1]
mov r13, [rbp - 2]
xor rax, rax
mov rdi, Format
mov rsi, r12
mov rdx, r13
call printf
leave
ret
segment .data
Format: db "%d %d", 10, 0
prints:
65 16706
I need to change the move byte to registers r12 and r13 to this in order to make the code work properly:
xor rax, rax
mov al, byte [rbp - 1]
mov r12, rax
xor rax, rax
mov al, byte [rbp - 2]
mov r13, rax
Now, it prints what is intended:
65 66
Why do we need to do this?
Is there a simpler way of doing this?
Thanks.
Solution 1:
Use move with zero or sign extension as appropriate.
For example: movzx eax, byte [rbp - 1]
to zero-extend into RAX.
movsx rax, byte [rbp - 1]
to sign-extend into RAX.
Solution 2:
Expanding 8-bit registers to 64-bit when assigning values
You can use the movzx instruction to move a byte to the 64-bit register.
In your case, it would be
movzx r12, byte ptr [rbp - 1]
movzx r13, byte ptr [rbp - 2]
Another way to avoid addressing memory to time would have been
mov ax, word ptr [rbp - 2]
movzx r12, al
movzx r13, ah
but the last instruction would not be compiled. See http://www.felixcloutier.com/x86/MOVZX.html "In 64-bit mode, r/m8 can not be encoded to access the following byte registers if the REX prefix is used: AH, BH, CH, DH."
So we have to make the following:
mov ax, word ptr [rbp - 2]
movzx r12, al
mov al, ah
movzx r13, al
But just two movxz'es like in the first example may be faster (the processor may optimize memory access) - the speed depends on a larger context and should be tested in complex.
You can take benefit of the fact that in 64-bit mode, modifying 32-bit registers also clears highest bits (63-32), but, anyway, you cannot encode the ah
register with movzx instruction under 64-bit even to a 32-bit part of a new register appeared in 64-bit mode (movzx r13d, ah
would not work).
Using 8-bit, 16-bit, and 32 parts of 64-bit rNN registers
You can use 8-bit, 16-bit, and 32 parts of 64-bit rNN registers the following way:
rNNb - byte rNNw - word rNNd - dword
for example, r10b, r10w, r10d. Here are the examples within the code
xor r8d,dword ptr [r9+r10*4]
.....
xor r8b, al
.....
xor eax, r11d
Please note: The 'h' parts of the rNN registers are not available, they are only available for four first registers: ah, bh, ch and dh.
Another note: when modifying 32-bit parts of 64-bit registers, higher 32 bits are automatically set to zero.
The fastest way of working with the registers
The fastest way of working with the registers is to always clear the highest bits, to remove false dependency on previous content of the registers. This is the way recommended by Intel, and will allow better Out-of-Order Execution (OOE) and Register Renaming (RR). Besides that, working with full registers rather with with their lower parts is faster on modern processors: Knights Landing and Cannonlake. So this is the code that will run faster on these processors (it will use OOE and RR):
movzx rax, word ptr [rbp - 2]
movzx r12, al
shr rax, 8
mov r13, rax
As about Knights Landing and future mainstream processors like CannonLake - Intel is explicit that instructions on 8-bit and 16-bit registers would be much slower than on 32-bit or 64-bit registers on CannonLake and so they are now on Knights Landing.
If you write with OOB and RR in mind, your assembly code will be much faster.