Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do x86-64 instructions on 32-bit registers zero the upper part of the full 64-bit register?

People also ask

Why can a 32-bit processor be also known as x86 interchangeably but for 64-bit it's known as x64?

x86 started out as a 16-bit instruction set for 16-bit processors (the 8086 and 8088 processors), then was extended to a 32-bit instruction set for 32-bit processors (80386 and 80486), and now has been extended to a 64-bit instruction set for 64-bit processors.

Why is x86 x86 not x32?

That was firstly introduced for 16-bit machines later it was converted to 32-bit machines. Due to its designed quality and popularity in their architecture and size it was expanded and kept 86 at the end of model number so, that's why Windows x86 was called according to processor series as x86.

How many general purpose registers are in the 32-bit x86 architecture?

The x86 architecture contains eight 32-bit General Purpose Registers (GPRs). These registers are mainly used to perform address calculations, arithmetic and logical calculations.

What is x86-64 instruction set?

x86-64 (also known as x64, x86_64, AMD64, and Intel 64) is a 64-bit version of the x86 instruction set, first released in 1999. It introduced two new modes of operation, 64-bit mode and compatibility mode, along with a new 4-level paging mode.


I'm not AMD or speaking for them, but I would have done it the same way. Because zeroing the high half doesn't create a dependency on the previous value, that the CPU would have to wait on. The register renaming mechanism would essentially be defeated if it wasn't done that way.

This way you can write fast code using 32-bit values in 64-bit mode without having to explicitly break dependencies all the time. Without this behaviour, every single 32-bit instruction in 64-bit mode would have to wait on something that happened before, even though that high part would almost never be used. (Making int 64-bit would waste cache footprint and memory bandwidth; x86-64 most efficiently supports 32 and 64-bit operand sizes)

The behaviour for 8 and 16-bit operand sizes is the strange one. The dependency madness is one of the reasons that 16-bit instructions are avoided now. x86-64 inherited this from 8086 for 8-bit and 386 for 16-bit, and decided to have 8 and 16-bit registers work the same way in 64-bit mode as they do in 32-bit mode.


See also Why doesn't GCC use partial registers? for practical details of how writes to 8 and 16-bit partial registers (and subsequent reads of the full register) are handled by real CPUs.


It simply saves space in the instructions, and the instruction set. You can move small immediate values to a 64-bit register by using existing (32-bit) instructions.

It also saves you from having to encode 8 byte values for MOV RAX, 42, when MOV EAX, 42 can be reused.

This optimization is not as important for 8 and 16 bit ops (because they are smaller), and changing the rules there would also break old code.


Without zero extending to 64 bits, it would mean an instruction reading from rax would have 2 dependencies for its rax operand (the instruction that writes to eax and the instruction that writes to rax before it), this would result in a partial register stall, which starts to get tricky when there are 3 possible widths, so it helps that rax and eax write to the full register, meaning the 64-bit instruction set doesn't introduce any new layers of partial renaming.

mov rdx, 1
mov rax, 6
imul rax, rdx
mov rbx, rax
mov eax, 7 //retires before add rax, 6
mov rdx, rax // has to wait for both imul rax, rdx and mov eax, 7 to finish before dispatch to the execution units, even though the higher order bits are identical anyway

The only benefit of not zero extending is ensuring the higher order bits of rax are included, for instance, if it originally contains 0xffffffffffffffff, the result would be 0xffffffff00000007, but there's very little reason for the ISA to make this guarantee at such an expense, and it's more likely that the benefit of zero extension would actually be required more, so it saves the extra line of code mov rax, 0. By guaranteeing it will always be zero extended to 64 bits, the compilers can work with this axiom in mind whilst in mov rdx, rax, rax only has to wait for its single dependency, meaning it can begin execution quicker and retire, freeing up execution units. Furthermore, it also allows for more efficient zero idioms like xor eax, eax to zero rax without requiring a REX byte.


From a hardware perspective, the ability to update half a register has always been somewhat expensive, but on the original 8088, it was useful to allow hand-written assembly code to treat the 8088 as having either two non-stack-related 16-bit registers and eight 8-bit registers, six non-stack-related 16-bit registers and zero 8-bit registers, or other intermediate combinations of 16-bit and 8-bit registers. Such usefulness was worth the extra cost.

When the 80386 added 32-bit registers, no facilities were provided to access just the top half of a register, but an instruction like ROR ESI,16 would be fast enough that there could still be value in being able to hold two 16-bit values in ESI and switch between them.

With the migration to x64 architecture, the increased register set and other architectural enhancements reduced the need for programmers to squeeze the maximum amount of information into each register. Further, register renaming increased the cost of doing partial register updates. If code were to do something like:

    mov rax,[whatever]
    mov [something],rax
    mov rax,[somethingElse]
    mov [yetAnother],rax

register renaming and related logic would make it possible to have the CPU record the fact that the value loaded from [whatever] will need to be written to something, and then--so long as the last two addresses are different--allow the load of somethingElse and store to yetAnother to be processed without having to wait for the data to actually be read from whatever. If the third instruction were mov eax,[somethingElse, however, and it were specified as leaving the upper bits unaffaected, the fourth instruction couldn't store RAX until the first load was completed, and even allowing even the load of EAX to occur would be difficult, since the processor would have to keep track of the fact that while the lower half was available, the upper half wasn't.