x86_64 registra rax / eax / ax / al sobrescribir los contenidos completos del registro

Como se anuncia ampliamente, los procesadores x86_64 modernos tienen registros de 64 bits que se pueden usar de manera compatible con versiones anteriores como registros de 32 bits, registros de 16 bits e incluso registros de 8 bits, por ejemplo:

0x1122334455667788 ================ rax (64 bits) ======== eax (32 bits) ==== ax (16 bits) == ah (8 bits) == al (8 bits) 

Tal esquema se puede tomar literalmente, es decir, uno siempre puede acceder solo a la parte del registro que usa un nombre designado para fines de lectura o escritura, y sería altamente lógico. De hecho, esto es cierto para todo hasta 32 bits:

 mov eax, 0x11112222 ; eax = 0x11112222 mov ax, 0x3333 ; eax = 0x11113333 (works, only low 16 bits changed) mov al, 0x44 ; eax = 0x11113344 (works, only low 8 bits changed) mov ah, 0x55 ; eax = 0x11115544 (works, only high 8 bits changed) xor ah, ah ; eax = 0x11110044 (works, only high 8 bits cleared) mov eax, 0x11112222 ; eax = 0x11112222 xor al, al ; eax = 0x11112200 (works, only low 8 bits cleared) mov eax, 0x11112222 ; eax = 0x11112222 xor ax, ax ; eax = 0x11110000 (works, only low 16 bits cleared) 

Sin embargo, las cosas parecen ser bastante incómodas tan pronto como llegamos a las cosas de 64 bits:

 mov rax, 0x1111222233334444 ; rax = 0x1111222233334444 mov eax, 0x55556666 ; actual: rax = 0x0000000055556666 ; expected: rax = 0x1111222255556666 ; upper 32 bits seem to be lost! mov rax, 0x1111222233334444 ; rax = 0x1111222233334444 mov ax, 0x7777 ; rax = 0x1111222233337777 (works!) mov rax, 0x1111222233334444 ; rax = 0x1111222233334444 xor eax, eax ; actual: rax = 0x0000000000000000 ; expected: rax = 0x1111222200000000 ; again, it wiped whole register 

Tal comportamiento parece ser muy ridículo e ilógico para mí. Parece que tratar de escribir cualquier cosa en eax por cualquier medio conduce a borrar 32 bits de registro de rax .

Entonces, tengo 2 preguntas:

  1. Creo que este comportamiento incómodo debe documentarse en alguna parte, pero parece que no puedo encontrar una explicación detallada (de cómo se eliminan exactamente 32 bits de registro de 64 bits) en cualquier lugar. ¿Tengo razón en que escribir a eax siempre borra el rax , o es algo más complicado? ¿Se aplica a todos los registros de 64 bits o hay algunas excepciones?

    Una pregunta fuertemente relacionada menciona el mismo comportamiento, pero, lamentablemente, tampoco hay referencias exactas a la documentación.

    En otras palabras, me gustaría un enlace a la documentación que especifique este comportamiento.

  2. ¿Soy solo yo o todo esto parece ser realmente extraño e ilógico (es decir, eax-ax-ah-al, rax-ax-ah-al teniendo un comportamiento y rax-eax teniendo otro)? Puede ser que me falta algún tipo de punto vital aquí sobre por qué se implementó así?

    Una explicación sobre “por qué” sería muy apreciada.

El modelo de procesador que se documenta en el manual del procesador Intel / AMD es un modelo bastante imperfecto para el motor de ejecución real de un núcleo moderno. En particular, la noción de los registros del procesador no coincide con la realidad, no existe un registro EAX o RAX.

Un trabajo principal del decodificador de instrucciones es convertir las instrucciones heredadas x86 / x64 en microoperaciones , instrucciones de un procesador similar a RISC. Pequeñas instrucciones que son fáciles de ejecutar al mismo tiempo y que permiten aprovechar múltiples subunidades de ejecución. Permitiendo hasta 6 instrucciones para ejecutar al mismo tiempo.

Para que funcione, la noción de registros de procesador también se virtualiza. El decodificador de instrucciones asigna un registro de un gran banco de registros. Cuando se retira la instrucción, el valor de ese registro asignado dinámicamente se escribe de nuevo en cualquier registro que actualmente tenga el valor de, por ejemplo, RAX.

Para hacer que funcione de manera uniforme y eficiente, permitiendo que muchas instrucciones se ejecuten simultáneamente, es muy importante que estas operaciones no tengan una interdependencia. Y lo peor que puede tener es que el valor de registro depende de otras instrucciones. El registro de EFLAGS es notorio, muchas instrucciones lo modifican.

Mismo problema con la forma en que te gusta que funcione. Gran problema, requiere que se fusionen dos valores de registro cuando se retira la instrucción. Crear una dependencia de datos que va a obstruir el núcleo. Al forzar la parte superior de 32 bits a 0, esa dependencia desaparece al instante, ya no es necesario fusionar. Warp 9 velocidad de ejecución.