¿Por qué no hay un registro que contiene los bytes más altos de EAX?

%AX = (%AH + %AL)

Entonces, ¿por qué no %EAX = (%SOME_REGISTER + %AX) para algún registro %SOME_REGISTER ?

Solo por alguna aclaración. En los primeros días del microprocesador de la década de 1970, las CPU tenían solo un pequeño número de registros y un conjunto de instrucciones muy limitado. Típicamente, la unidad aritmética solo podría operar en un único registro de CPU, a menudo denominado “acumulador”. El acumulador en los procesadores 8080 y Z80 de 8 bits se llamaba “A”. Había otros 6 registros de 8 bits de propósito general: B, C, D, E, H y L. Estos seis registros podrían emparejarse para formar 3 registros de 16 bits: BC, DE y HL. Internamente, el acumulador se combinó con el registro de Flags para formar el registro AF de 16 bits.

Cuando Intel desarrolló la familia 8086 de 16 bits, querían poder portar el código 8080, por lo que mantuvieron la misma estructura básica de registro:

 8080/Z80 8086 A AX BC BX DE CX HL DX IX SI IY DI 

Debido a la necesidad de portar código de 8 bits, necesitaban poder referirse a las partes individuales de 8 bits de AX, BX, CX y DX. Estos se llaman AL, AH para los bytes bajos y altos de AX y así sucesivamente para BL / BH, CL / CH y DL / DH. IX y IY en el Z80 solo se usaban como registros de puntero de 16 bits, por lo que no era necesario acceder a las dos mitades de SI & DI.

Cuando se lanzó el 80386 a mediados de la década de 1980, crearon versiones “extendidas” de todos los registros. Entonces, AX se convirtió en EAX, BX se convirtió en EBX, etc. No hubo necesidad de acceder a los 16 bits superiores de estos nuevos registros extendidos, por lo que no crearon un pseudoregistro EAXH.

AMD aplicó el mismo truco cuando produjeron los primeros procesadores de 64 bits. La versión de 64 bits del registro AX se llama RAX. Entonces, ahora tienes algo que se ve así:

 |63..32|31..16|15-8|7-0| |AH.|AL.| |AX.....| |EAX............| |RAX...................| 

En los viejos días de 8 bits, estaba el registro A.

En los días de 16 bits, estaba el registro AX de 16 bits, que se dividió en dos partes de 8 bits, AH y AL, para aquellos momentos en los que aún deseaba trabajar con valores de 8 bits.

En los días de 32 bits, se introdujo el registro EAX de 32 bits, pero se mantuvieron todos los registros AX, AH y AL. Los diseñadores no consideraron necesario introducir un nuevo registro de 16 bits que abordara los bits 16 a 31 de EAX.

Hay muchas respuestas publicadas aquí, pero ninguna responde realmente la pregunta: ¿Por qué no hay un registro que codifique directamente los 16 bits altos de EAX, o los 32 bits altos de RAX? La respuesta se reduce a las limitaciones de la encoding de la instrucción x86.

Lección de historia de 16 bits

Cuando Intel diseñó el 8086, usaron un esquema de encoding de longitud variable para muchas de las instrucciones. Esto significaba que ciertas instrucciones extremadamente comunes, como POP AX , podrían representarse como un solo byte (58), mientras que las instrucciones raras (pero potencialmente útiles) como MOV CX, [BX*4+BP+1023] aún podrían representarse , incluso si tomó varios bytes almacenarlos (en este ejemplo, 8B 8C FF 03).

Esto puede parecer una solución razonable, pero cuando lo diseñaron, completaron la mayor parte del espacio disponible . Entonces, por ejemplo, había ocho instrucciones POP para los ocho registros individuales (AX, CX, DX, BX, SP, BP, SI, DI), y completaron los códigos de operación 58 a 5F, y el código de operación 60 era algo completamente diferente ( PUSHA ), como era el opcode 57 ( PUSH DI ). No queda espacio para nada después o antes de eso. Incluso empujar y hacer estallar los registros del segmento, que es conceptualmente casi idéntico a empujar y hacer estallar los registros de propósito general, tuvo que codificarse en una ubicación diferente (alrededor de 06 / 0E / 16 / 1E) simplemente porque no había espacio al lado el rest de las instrucciones push / pop.

Del mismo modo, el byte “mod r / m” utilizado para una instrucción compleja como MOV CX, [BX*4+BP+1023] solo tiene tres bits para codificar el registro, lo que significa que solo puede representar ocho registros en total. Eso está bien si solo tienes ocho registros, pero presenta un problema real si quieres tener más.

(Hay un excelente mapa de todas estas asignaciones de bytes en la architecture x86 aquí: http://sofes.miximages.com/assembly/xfeWv.png . Observe cómo no queda espacio en el mapa principal, con algunas instrucciones que se superponen a los bytes, e incluso cómo gran parte del mapa secundario “0F” se usa ahora gracias a las instrucciones MMX y SSE).

Hacia 32 y 64 bits

Entonces, incluso para permitir que el diseño de la CPU se extendiera de 16 bits a 32 bits, ya tenían un problema de diseño, y lo resolvieron con bytes de prefijos : Al agregar un byte especial “66” delante de todos los estándar de 16 bits instrucciones, la CPU sabe que quiere la misma instrucción, pero la versión de 32 bits (EAX) en lugar de la versión de 16 bits (AX). El rest del diseño permaneció igual: solo había ocho registros generales de propósito general en la architecture general de la CPU.

Hackeo similar tuvo que hacerse para extender la architecture a 64 bits (RAX y amigos); allí, el problema se resolvió añadiendo otro conjunto de códigos de prefijo ( REX , 40-4F) que significaba “64 bits” (y agregó efectivamente otros dos bits al campo “mod r / m”), y descartando también los extraños viejas instrucciones que nadie usó nunca y reutilizando sus códigos de bytes para cosas más nuevas.

Además de los registros de 8 bits

Una de las preguntas más importantes, entonces, es cómo diablos cosas como AH y AL alguna vez funcionó en primer lugar si realmente hay espacio en el diseño para ocho registros. La primera parte de la respuesta es que no hay nada como ” PUSH AL “: ¡algunas instrucciones simplemente no pueden funcionar en los registros del tamaño de un byte en absoluto! Los únicos que pueden son algunas rarezas especiales (como AAD y XLAT ) y versiones especiales de las instrucciones “mod r / m”: al tener un bit muy específico volteado en el byte “mod r / m”, esas “instrucciones extendidas” “podría voltearse para operar en los registros de 8 bits en lugar de los de 16 bits. Ocurre que también hay exactamente ocho registros de 8 bits: AL, CL, DL, BL, AH, CH, DH y BH (en ese orden), y eso se alinea muy bien con las ocho ranuras de registro disponibles en el byte “mod r / m”.

Intel señaló en ese momento que se suponía que el diseño 8086 era “compatible con la fuente” con el 8080/8085: había una instrucción equivalente en el 8086 para cada una de las instrucciones 8080/8085, pero no usaba los mismos códigos de bytes. (ni siquiera están cerca), y tendrías que recomstackr (volver a montar) tu progtwig para que use los nuevos códigos de bytes. Pero la “fuente compatible” era un camino a seguir para el viejo software, y permitía que los registros individuales A, B, C, etc. del 8085 y combo “BC” y “DE” siguieran funcionando en el nuevo procesador, incluso si ahora estuvieran llamado “AL” y “BL” y “BX” y “DX” (o lo que sea que el mapeo fue).

Así que esa es realmente la verdadera respuesta: no es que Intel o AMD intencionalmente “dejen fuera” un alto registro de 16 bits para EAX, o un registro alto de 32 bits para RAX: es que los registros de 8 bits son un rest histórico extraño anomalía, y la replicación de su diseño en tamaños de bits más altos sería realmente difícil dado el requisito de que la architecture sea compatible con versiones anteriores.

Una consideración de rendimiento

Hay otra consideración sobre por qué esos “registros altos” no se han agregado, ya que: dentro de las architectures de procesadores modernos, por razones de rendimiento, los registros de tamaño variable en realidad no se superponen de manera real: AH y AL aren ‘ t parte de AX, y AX no es parte de EAX, y EAX no es parte de RAX: todos son registros separados bajo el capó, y el procesador establece una bandera de invalidación sobre los demás cuando manipula uno de ellos para que sepa que tendrá que copiar los datos cuando lees de los demás.

(Por ejemplo: si configura AL = 5, el procesador no actualiza AX. Pero si luego lee de AX, el procesador copiará rápidamente ese 5 de AL en los bits inferiores de AX).

Al mantener los registros separados, la CPU puede hacer todo tipo de cosas inteligentes como el cambio de nombre de registro invisible para hacer que el código se ejecute más rápido, pero eso significa que su código se ejecuta más lento si utiliza el antiguo patrón de tratar los registros pequeños como piezas de mayor tamaño registra, porque el procesador tendrá que detener y actualizarlos. Para evitar que toda esta contabilidad interna se salga de control, los diseñadores de CPU sabiamente optaron por agregar registros separados en los procesadores más nuevos en lugar de agregar más registros superpuestos.

(Y sí, eso significa que realmente es más rápido en los procesadores modernos que explícitamente ” MOVZX EAX, value ” que hacerlo de la manera más vieja y más descuidada de ” MOV AX, value / use EAX “).

Conclusión

Con todo lo dicho, ¿podrían Intel y AMD agregar más registros “superpuestos” si realmente quisieran? Por supuesto. Hay formas de atraparlos si hay suficiente demanda. Pero dado el importante bagaje histórico, las limitaciones arquitectónicas actuales, las notables limitaciones de rendimiento y el hecho de que la mayoría de los códigos actualmente son generados por comstackdores optimizados para registros que no se superponen, es muy poco probable que agreguen tales cosas en el corto plazo.