En el Tour x86-64 de los manuales de Intel , leí
Quizás el hecho más sorprendente es que una instrucción como
MOV EAX, EBX
automáticamente pone a cero los 32 bits superiores delRAX
registro.
La documentación de Intel (3.4.1.1 Registros de propósito general en modo de 64 bits en la arquitectura básica manual) citada en la misma fuente nos dice:
- Los operandos de 64 bits generan un resultado de 64 bits en el registro de propósito general de destino.
- Los operandos de 32 bits generan un resultado de 32 bits, ampliado a cero a un resultado de 64 bits en el registro de propósito general de destino.
- Los operandos de 8 y 16 bits generan un resultado de 8 o 16 bits. Los 56 bits o 48 bits superiores (respectivamente) del registro de propósito general de destino no son modificados por la operación. Si el resultado de una operación de 8 bits o de 16 bits está destinado al cálculo de direcciones de 64 bits, extienda explícitamente el signo del registro a los 64 bits completos.
En el ensamblaje x86-32 y x86-64, instrucciones de 16 bits como
mov ax, bx
no muestre este tipo de comportamiento "extraño" de que la palabra superior de eax se ponga a cero.
Entonces: ¿cuál es la razón por la que se introdujo este comportamiento? A primera vista, parece ilógico (pero la razón podría ser que estoy acostumbrado a las peculiaridades del ensamblaje x86-32).
r32
operando de destino ponen a cero el 32 alto, en lugar de fusionarse. Por ejemplo, algunos ensambladores reemplazaránpmovmskb r64, xmm
conpmovmskb r32, xmm
, guardando un REX, porque la versión de destino de 64 bits se comporta de manera idéntica. Aunque la sección Operación del manual enumera las 6 combinaciones de fuente de 32 / 64bit dest y 64/128 / 256b por separado, la extensión cero implícita del formulario r32 duplica la extensión cero explícita del formulario r64. Tengo curiosidad por la implementación de HW ...xor eax,eax
oxor r8d,r8d
es la mejor manera de poner a cero RAX o R8 (guardar un prefijo REX para RAX, y XOR de 64 bits ni siquiera se maneja especialmente en Silvermont). Relacionado: ¿Cómo funcionan exactamente los registros parciales en Haswell / Skylake? Escribir AL parece tener una falsa dependencia de RAX, y AH es inconsistenteRespuestas:
No soy AMD ni hablo por ellos, pero lo habría hecho de la misma manera. Debido a que poner a cero la mitad alta no crea una dependencia del valor anterior, la CPU tendría que esperar. El mecanismo de cambio de nombre de registros esencialmente se anularía si no se hiciera de esa manera.
De esta manera, puede escribir código rápido utilizando valores de 32 bits en modo de 64 bits sin tener que romper explícitamente las dependencias todo el tiempo. Sin este comportamiento, cada instrucción de 32 bits en modo de 64 bits tendría que esperar a algo que sucedió antes, aunque esa parte alta casi nunca se usaría. (Hacer
int
64 bits desperdiciaría la huella de caché y el ancho de banda de la memoria; x86-64 admite tamaños de operandos de 32 y 64 bits de manera más eficiente )El comportamiento de los operandos de 8 y 16 bits es extraño. La locura de la dependencia es una de las razones por las que ahora se evitan las instrucciones de 16 bits. x86-64 heredó esto de 8086 para 8 bits y 386 para 16 bits, y decidió que los registros de 8 y 16 bits funcionaran de la misma manera en el modo de 64 bits que en el modo de 32 bits.
Consulte también ¿Por qué GCC no usa registros parciales? para obtener detalles prácticos sobre cómo las escrituras en registros parciales de 8 y 16 bits (y lecturas posteriores del registro completo) son manejadas por CPU reales.
fuente
Simplemente ahorra espacio en las instrucciones y el conjunto de instrucciones. Puede mover pequeños valores inmediatos a un registro de 64 bits mediante las instrucciones existentes (32 bits).
También le evita tener que codificar valores de 8 bytes para
MOV RAX, 42
cuándoMOV EAX, 42
se pueden reutilizar.Esta optimización no es tan importante para operaciones de 8 y 16 bits (porque son más pequeñas), y cambiar las reglas allí también rompería el código antiguo.
fuente
XOR EAX, EAX
porqueXOR RAX, RAX
necesitaría un prefijo REX.[rsi + edx]
no está permitido). Por supuesto, evitar falsas dependencias / bloqueos de registros parciales (la otra respuesta) es otra razón importante.Sin que el cero se extienda a 64 bits, significaría que una instrucción que lee
rax
tendría 2 dependencias para surax
operando (la instrucción que escribeeax
y la instrucción que escriberax
antes), esto significa que 1) el ROB debería tener entradas para múltiples dependencias para un solo operando, lo que significa que el ROB requeriría más lógica y transistores y ocuparía más espacio, y la ejecución sería más lenta esperando una segunda dependencia innecesaria que podría tardar años en ejecutarse; o alternativamente 2), que supongo que sucede con las instrucciones de 16 bits, la etapa de asignación probablemente se detiene (es decir, si la RAT tiene una asignación activa para unaax
escritura yeax
aparece una lectura, se detiene hasta que laax
escritura se retira).El único beneficio de no extenderse a cero es garantizar que
rax
se incluyan los bits de orden superior de , por ejemplo, si originalmente contiene 0xffffffffffffffff, el resultado sería 0xffffffff00000007, pero hay muy pocas razones para que la ISA haga esta garantía a tal costo, y es más probable que se requiera más el beneficio de la extensión cero, por lo que ahorra la línea adicional de códigomov rax, 0
. Al garantizar que siempre será cero extendido a 64 bits, los compiladores pueden trabajar con este axioma en mente mientras están dentromov rdx, rax
,rax
solo tienen que esperar su dependencia única, lo que significa que puede comenzar la ejecución más rápido y retirarse, liberando unidades de ejecución. Además, también permite modismos cero más eficientes, comoxor eax, eax
a cero,rax
sin requerir un byte REX.fuente
cmovbe
es 2 uops perocmovb
es 1). Pero ninguna CPU que cambie el nombre de un registro parcial lo hace de la manera que usted sugiere. En su lugar, insertan un uop de fusión si se cambia el nombre de un registro parcial por separado del registro completo (es decir, está "sucio"). Consulte ¿Por qué GCC no usa registros parciales? y ¿Cómo funcionan exactamente los registros parciales en Haswell / Skylake? Escribir AL parece tener una falsa dependencia de RAX, y AH es inconsistenteThis gives a delay of 5 - 6 clocks. The reason is that a temporary register has been assigned to AL to make it independent of AH. The execution unit has to wait until the write to AL has retired before it is possible to combine the value from AL with the value of the rest of EAX
No puedo encontrar un ejemplo de la 'fusión de uop' que se usaría para resolver esto, sin embargo, lo mismo para un estancamiento parcial de la banderamov al, [mem]
es una carga microfundida + ALU- fusionar, solo cambiar el nombre de AH, y un uop de combinación de AH todavía se emite solo. Los mecanismos de fusión de banderas parciales en estas CPU varían, por ejemplo, Core2 / Nehalem todavía se detiene para las banderas parciales, a diferencia del registro parcial.