assembly x86 sign-extension

assembly - ¿Cuándo y por qué firmamos extender y usar cdq con mul/div?



x86 sign-extension (1)

Use cdq / idiv para la cdq / idiv de 32 bits / 32 bits con cdq / idiv => 32 bits,
xor edx,edx / div para unsigned.

Si pone a cero EDX / RDX en lugar de extender la señal a EDX: EAX antes de idiv , puede obtener un gran resultado positivo para -5 / 2, por ejemplo .

Es posible utilizar la "potencia máxima" de 64/32 bits => división de 32 bits, pero no es seguro a menos que sepa que el divisor es lo suficientemente grande como para que el cociente no se desborde. (es decir, en general no puede implementar (a*b) / c con solo mul / div y un temporal de 64 bits en EDX: EAX).

La división genera una excepción (#DE) en el desbordamiento del cociente. En Unix / Linux, el núcleo ofrece SIGFPE para excepciones aritméticas, incluidos errores de división. Con signo normal o división de extensión cero, el desbordamiento solo es posible con idiv de INT_MIN / -1 (es decir, el caso especial del complemento 2 del número más negativo).

Como puede ver en el manual de referencia de insn (enlace en el wiki de la etiqueta x86 ):

  • mul / imul un operando: edx:eax = eax * src
  • imul dos operandos: dst *= src . por ejemplo, imul ecx, esi no lee ni escribe eax o edx.
  • div / idiv : divide edx:eax por el src. cociente en eax , resto en edx . No hay forma de div / idiv que ignore edx en la entrada.
  • cdq sign-extend eax en edx:eax , es decir, transmite el bit de signo de eax en cada bit de edx . No debe confundirse con cdqe , la instrucción de 64 bits que hace movsx rax, eax con menos bytes insn.

    Originalmente (8086), solo había cbw ( ax = sign_extend(al) ) y cwd ( dx:ax = sign_extend(ax) ). Las extensiones de x86 a 32 bits y 64 bits han hecho que los mnemónicos sean un poco ambiguos (pero recuerde, aparte de cbw , las versiones dentro de eax siempre terminan con una e para Extender). No hay instrucciones dl = sign_bit (al) porque 8bit mul y div son especiales, y usa ax lugar de dl:al .

Como las entradas a [i]mul son registros únicos, nunca es necesario hacer nada con edx antes de multiplicar.

Si su entrada está firmada, la firma y la extiende para llenar el registro que está utilizando como entrada para la multiplicación, por ejemplo, con movsx o cwde ( eax = sign_extend(ax) ). Si su entrada no está firmada, su extensión cero. (Con la excepción de que si solo necesita los 16 bits bajos del resultado de la multiplicación, por ejemplo, no importa si los 16 bits superiores de una o ambas entradas contienen basura ).

Para una división, siempre debe poner a cero o firmar extender eax en edx. La extensión cero es lo mismo que simplemente poner a cero incondicionalmente edx, por lo que no hay instrucciones especiales para ello. Solo xor edx,edx .

cdq existe porque es mucho más corto que mov edx, eax / sar edx, 31 para transmitir el bit de signo de eax a cada bit en edx. Además, los cambios con conteo> 1 no existían hasta 286, por lo que en 8086, necesitaría un bucle si cwd no existiera (la versión de 16 bits de cdq ).

En el modo de 64 bits, el signo y el cero que extienden los valores de 32 bits a 64 bits es común. El ABI permite la basura en los 32 bits altos de un registro de 64 bits que contiene un valor de 32 bits, por lo que si se supone que su función solo debe mirar los 32 bits bajos de edi , no puede simplemente usar [array + rdi] para indexar la matriz.

Entonces, ve muchos movsx rdi, edi (extensión de signo) o mov eax, edi (extensión cero, y sí, es más eficiente usar un registro de destino diferente, porque Intel mov-elimination no funciona con mov same,same )

Tuve una prueba hoy y la única pregunta que no entendí fue convertir una palabra doble en una palabra cuádruple.

Eso me hizo pensar, ¿por qué / cuándo firmamos extender para multiplicación o división? Además, ¿cuándo usamos instrucciones como cdq?