Is it possible to access the higher part of the 32-bit and 64-bit registers? If so, which ones?
It is impossible to access the higher parts of the
RAX registers, or of any other 32 and 64-bit registers, directly. You'll have to use indirect instruction sequences if you're interested in doing that. This is because there are no encodings to access those parts in any instruction.
Assuming it is not possible, what are the reasons behind this?
As noted by @nordwald in the comments below, it is simply defined so in the manuals. To get a more detailed official answer, we would need to ask members of the specification definition. We can safely assume the core reason is that the cost of providing access to all available register fractions exceeds the benefit.
I will try listing a few possible reasons for why the cost exceeds the benefit now but didn't in the past:
Benefit of more available register space
During the time of 16 bit registers registers were scarce (due to chip manufacturing costs) and chips were boasting the number of registers made available to the user. There was a demand for more registers. Exposing half-registers allowed better utilization of the available (precious and expensive) register real-estate. As time passed more registers were made available, the need of better usage of the exact register size decreased. Nowadays, you can use 64 bit registers for only their lower byte or word without "worrying" about not having enough registers.
Cost of increasing instruction set
Since the available instruction set was relatively small and there were only a few registers, enough of bits were available to encode the different half of registers without increasing the size of the instruction set. In the original 16-bit register days most of the available instruction set space was used. Moving to 32 (and then again to 64-bit), instead of re-implementing the same instructions with different register sizes prefixes were introduced (
0x66 for 32 bit registers, for example), keeping the rest of the instruction in tact. This made it trivial to support 32-bit registers on top of the 16-bit original instruction set, but accessing the higher parts of those registers required a more complex design. You can notice there were only 8 fully sized registers (
DI) as well as only 8 half sized registers (
DH), so that only 3 bits were required to specify the required register.
Nowadays, encoding all portions of the registers would increase the instruction set and overall CPU complexity.
Supporting legacy code
When Intel made the first 16-bit processors (the 8086 processor family) they wanted to keep what they called source compatibility. This meant 8-bit processor (Z80/8080) code could be assembled to a 16-bit processor with no code/source changes, although the underlying binary representation of instructions was allowed to change. Thus, the 8-bit registers were bound to stay from the early 8-bit processors even though the instruction binary representation was completely redesigned and 8-bit mode was not supported by the new CPUs (unlike 32 and 64-bit modes which are binary backwards compatible). The Z80 processor family had six 8-bit registers that could have been accessed together to form a 16-bit word.
Additionally, in transition from 16 to 32-bit processors and then from 32 to 64, maintaining backward compatibility and supporting legacy execution modes was desired. For that reason, maintaining the same binary encoding was achieved by keeping the same instruction set as well as the same binary encoding for multiple instructions, "forcing" the inclusion of half-registers as part of the available instruction set.
Cost of register access synchronization
As pipelining optimizations became more and more popular the burden of synchronization register access (among other things) increased, making it harder for the CPU to keep track of register access.
In modern CPUs the half-registers are not actually overlapping with the full registers and instead, the CPU sets a special invalidation flag on all others when one is changed, so it'll know to read the updated register when if others are accessed. This allows advanced CPU level optimizaions (such as register renaming, for example) but makes execution actually slower when half-sized registers are used interchangeably (which is no longer that frequent).
Accessing half-sized register is only partially available to begin with
Although you might assume otherwise, accessing or using half-sized registers is not always possible. For example, you cannot push half a register (there's no "Push AL"). This was done to only support the minimum needed to make the half-registers useful, but they are not treated as full-blow registers.
Obviously, some reasons can carry more weight than others, some may be unrelated to the original decision, some original reasons may be missing here, etc. Those are merely educated guesses and YMMV