Saltar al contenido

¿Cuántas formas de poner un registro a cero?

Bienvenido a nuestro sitio web, aquí vas a encontrar la respuesta que buscabas.

Solución:

Hay muchas posibilidades de cómo mover 0 en ax bajo IA32 …

    lea eax, [0]
    mov eax, 0FFFF0000h         //All constants form 0..0FFFFh << 16
    shr eax, 16                 //All constants form 16..31
    shl eax, 16                 //All constants form 16..31

Y quizás el más extraño ... 🙂

@movzx:
    movzx eax, byte ptr[@movzx + 6]   //Because the last byte of this instruction is 0

y...

  @movzx:
    movzx ax, byte ptr[@movzx + 7]

Editar:

Y para el modo de cpu x86 de 16 bits, no probado ...:

    lea  ax, [0]

y...

  @movzx:
    movzx ax, byte ptr cs:[@movzx + 7]   //Check if 7 is right offset

los cs: prefix es opcional en caso de que el ds El registro de segmento no es igual al registro de segmento cs.

Vea esta respuesta para mejor camino a cero registros: xor eax,eax (ventajas de rendimiento y codificación más pequeña).


Consideraré solo las formas en que una sola instrucción puede poner a cero un registro. Hay demasiadas formas si permite cargar un cero desde la memoria, por lo que en su mayoría excluiremos las instrucciones que se cargan desde la memoria.

Encontré 10 instrucciones únicas diferentes que ponen a cero un registro de 32 bits (y, por lo tanto, el registro completo de 64 bits en modo largo), sin condiciones previas ni cargas de ninguna otra memoria. Esto sin contar las diferentes codificaciones del mismo insn, o las diferentes formas de mov. Si cuenta la carga desde la memoria que se sabe que tiene un cero, o desde registros de segmento o lo que sea, hay un montón de formas. También hay un trillón de formas de cero registros vectoriales.

Para la mayoría de estos, las versiones eax y rax son codificaciones separadas para la misma funcionalidad, ambas poniendo a cero los registros completos de 64 bits, ya sea poniendo a cero la mitad superior implícitamente o escribiendo explícitamente el registro completo con un REX.W prefix.

Registros enteros:

# Works on any reg unless noted, usually of any size.  eax/ax/al as placeholders
and    eax, 0         ; three encodings: imm8, imm32, and eax-only imm32
andn   eax, eax,eax   ; BMI1 instruction set: dest = ~s1 & s2
imul   eax, any,0     ; eax = something * 0.  two encodings: imm8, imm32
lea    eax, [0]       ; absolute encoding (disp32 with no base or index).  Use [abs 0] in NASM if you used DEFAULT REL
lea    eax, [rel 0]   ; YASM supports this, but NASM doesn't: use a RIP-relative encoding to address a specific absolute address, making position-dependent code

mov    eax, 0         ; 5 bytes to encode (B8 imm32)
mov    rax, strict dword 0   ; 7 bytes: REX mov r/m64, sign-extended-imm32.    NASM optimizes mov rax,0 to the 5B version, but dword or strict dword stops it for some reason
mov    rax, strict qword 0   ; 10 bytes to encode (REX B8 imm64).  movabs mnemonic for AT&T.  normally assemblers choose smaller encodings if the operand fits, but strict qword forces the imm64.

sub    eax, eax         ; recognized as a zeroing idiom on some but maybe not all CPUs
xor    eax, eax         ; Preferred idiom: recognized on all CPUs

@movzx:
  movzx eax, byte ptr[@movzx + 6]   //Because the last byte of this instruction is 0.  neat hack from GJ.'s answer

.l: loop .l             ; clears e/rcx... eventually.  from I. J. Kennedy's answer.  To operate on only ECX, use an address-size prefix.
; rep lodsb             ; not counted because it's not safe (potential segfaults), but also zeros ecx

"Desplazar todos los bits en un extremo" no es posible para registros GP de tamaño normal, solo registros parciales. shl y shr Los recuentos de turnos están enmascarados (en 286 y posteriores): count & 31; es decir, mod 32.

(Los cambios de recuento inmediato eran nuevos en 186 (anteriormente solo CL e implícito-1), por lo que hay CPU con cambios inmediatos sin máscara (que también incluyen NEC V30). Además, 286 y anteriores son solo de 16 bits, por lo que ax es un registro "completo". Hubo CPU en las que un desplazamiento puede poner a cero un registro entero completo).

También tenga en cuenta que los recuentos de desplazamiento de los vectores se saturan en lugar de ajustarse.

# Zeroing methods that only work on 16bit or 8bit regs:
shl    ax, 16           ; shift count is still masked to 0x1F for any operand size less than 64b.  i.e. count %= 32
shr    al, 16           ; so 8b and 16b shifts can zero registers.

# zeroing ah/bh/ch/dh:  Low byte of the reg = whatever garbage was in the high16 reg
movxz  eax, ah          ; From Jerry Coffin's answer

Dependiendo de otras condiciones existentes (además de tener un cero en otro registro):

bextr  eax,  any, eax  ; if al >= 32, or ah = 0.  BMI1
BLSR   eax,  src       ; if src only has one set bit
CDQ                    ; edx = sign-extend(eax)
sbb    eax, eax        ; if CF=0.  (Only recognized on AMD CPUs as dependent only on flags (not eax))
setcc  al              ; with a condition that will produce a zero based on known state of flags

PSHUFB   xmm0, all-ones  ; xmm0 bytes are cleared when the mask bytes have their high bit set

reglas de vector:

Algunas de estas instrucciones enteras SSE2 también se pueden utilizar en registros MMX (mm0 - mm7). No voy a mostrar eso por separado.

Una vez más, la mejor opción es alguna forma de xor. Cualquiera PXOR / VPXOR, o XORPS / VXORPS. Consulte ¿Cuál es la mejor manera de establecer un registro en cero en el ensamblaje x86: xor, mov o and? para detalles.

AVX vxorps xmm0,xmm0,xmm0 pone a cero el ymm0 / zmm0 completo, y es mejor que vxorps ymm0,ymm0,ymm0 en las CPU AMD.

Estas instrucciones de puesta a cero tienen tres codificaciones cada una: SSE heredado, AVX (VEX prefix) y AVX512 (EVEX prefix), aunque la versión SSE solo pone a cero los 128 inferiores, que no es el registro completo en las CPU que admiten AVX o AVX512. De todos modos, dependiendo de cómo cuente, cada entrada puede ser de tres instrucciones diferentes (aunque el mismo código de operación, solo diferentes prefijos). Excepto vzeroall, que AVX512 no cambió (y no pone a cero zmm16-31).

PXOR       xmm0, xmm0     ;; recommended
XORPS      xmm0, xmm0     ;; or this
XORPD      xmm0, xmm0     ;; longer encoding for zero benefit
PXOR       mm0, mm0     ;; MMX, not show for the rest of the integer insns

ANDNPD    xmm0, xmm0
ANDNPS    xmm0, xmm0
PANDN     xmm0, xmm0     ; dest = ~dest & src

PCMPGTB   xmm0, xmm0     ; n > n is always false.
PCMPGTW   xmm0, xmm0     ; similarly, pcmpeqd is a good way to do _mm_set1_epi32(-1)
PCMPGTD   xmm0, xmm0
PCMPGTQ   xmm0, xmm0     ; SSE4.2, and slower than byte/word/dword

PSADBW    xmm0, xmm0     ; sum of absolute differences
MPSADBW   xmm0, xmm0, 0  ; SSE4.1.  sum of absolute differences, register against itself with no offset.  (imm8=0: same as PSADBW)

  ; shift-counts saturate and zero the reg, unlike for GP-register shifts
PSLLDQ    xmm0, 16       ;  left-shift the bytes in xmm0
PSRLDQ    xmm0, 16       ; right-shift the bytes in xmm0
PSLLW     xmm0, 16       ; left-shift the bits in each word
PSLLD     xmm0, 32       ;           double-word
PSLLQ     xmm0, 64       ;             quad-word
PSRLW/PSRLD/PSRLQ  ; same but right shift

PSUBB/W/D/Q   xmm0, xmm0     ; subtract packed elements, byte/word/dword/qword
PSUBSB/W   xmm0, xmm0     ; sub with signed saturation
PSUBUSB/W  xmm0, xmm0     ; sub with unsigned saturation

;; SSE4.1
INSERTPS   xmm0, xmm1, 0x0F   ; imm[3:0] = zmask = all elements zeroed.
DPPS       xmm0, xmm1, 0x00   ; imm[7:4] => inputs = treat as zero -> no FP exceptions.  imm[3:0] => outputs = 0 as well, for good measure
DPPD       xmm0, xmm1, 0x00   ; inputs = all zeroed -> no FP exceptions.  outputs = 0

VZEROALL                      ; AVX1  x/y/zmm0..15 not zmm16..31
VPERM2I/F128  ymm0, ymm1, ymm2, 0x88   ; imm[3] and [7] zero that output lane

# Can raise an exception on SNaN, so only usable if you know exceptions are masked
CMPLTPD    xmm0, xmm0         # exception on QNaN or SNaN, or denormal
VCMPLT_OQPD xmm0, xmm0,xmm0   # exception only on SNaN or denormal
CMPLT_OQPS ditto

VCMPFALSE_OQPD xmm0, xmm0, xmm0   # This is really just another imm8 predicate value for the same VCMPPD xmm,xmm,xmm, imm8 instruction.  Same exception behaviour as LT_OQ.

SUBPS xmm0, xmm0 y similares no funcionarán porque NaN-NaN = NaN, no cero.

Además, las instrucciones FP pueden generar excepciones en argumentos NaN, por lo que incluso CMPPS / PD solo es seguro si sabe que las excepciones están enmascaradas y no le importa establecer los bits de excepción en MXCSR. Incluso la versión AVX, con su amplia variedad de predicados, aumentará #IA en SNaN. Los predicados "silenciosos" solo suprimen #IA para QNaN. CMPPS / PD también puede generar la excepción Denormal. (Los Las codificaciones AVX512 EVEX pueden suprimir las excepciones de FP para vectores de 512 bits, además de anular el modo de redondeo)

(Consulte la tabla en la entrada insn set ref para CMPPD, o preferiblemente en el PDF original de Intel, ya que el extracto HTML destruye esa tabla).

AVX1 / 2 y AVX512 EVEX de las formas anteriores, solo para PXOR: todas ponen a cero el destino ZMM completo. PXOR tiene dos versiones EVEX: VPXORD o VPXORQ, lo que permite el enmascaramiento con elementos dword o qword. (XORPS / PD ya distingue el tamaño del elemento en el mnemónico, por lo que AVX512 no cambió eso. En la codificación SSE heredada, XORPD siempre es un desperdicio inútil de tamaño de código (código de operación más grande) frente a XORPS en todas las CPU).

VPXOR      xmm15, xmm0, xmm0      ; AVX1 VEX
VPXOR      ymm15, ymm0, ymm0      ; AVX2 VEX, less efficient on some CPUs
VPXORD     xmm31, xmm0, xmm0      ; AVX512VL EVEX
VPXORD     ymm31, ymm0, ymm0      ; AVX512VL EVEX 256-bit
VPXORD     zmm31, zmm0, zmm0      ; AVX512F EVEX 512-bit

VPXORQ     xmm31, xmm0, xmm0      ; AVX512VL EVEX
VPXORQ     ymm31, ymm0, ymm0      ; AVX512VL EVEX 256-bit
VPXORQ     zmm31, zmm0, zmm0      ; AVX512F EVEX 512-bit

Los diferentes anchos de vector se enumeran con entradas separadas en la entrada manual PXOR de Intel.

usted pueden utilice el enmascaramiento cero (pero no el enmascaramiento combinado) con cualquier registro de máscara que desee; no importa si obtiene un cero del enmascaramiento o un cero de la salida normal de la instrucción vectorial. Pero esa no es una instrucción diferente. p.ej: VPXORD xmm16k1z, xmm0, xmm0

AVX512:

Probablemente haya varias opciones aquí, pero no tengo la curiosidad suficiente en este momento para buscar en la lista de conjuntos de instrucciones en busca de todas.

Sin embargo, hay uno interesante que vale la pena mencionar: VPTERNLOGD / Q puede configurar un registro para todos-uno en su lugar, con imm8 = 0xFF. (Pero tiene un false dependencia del valor anterior, de las implementaciones actuales). Dado que todas las instrucciones de comparación se comparan en una máscara, VPTERNLOGD parece ser la mejor manera de establecer un vector para todos en Skylake-AVX512 en mis pruebas, aunque no es un caso especial, el caso imm8 = 0xFF para evitar un false dependencia.

VPTERNLOGD zmm0, zmm0,zmm0, 0     ; inputs can be any registers you like.

Puesta a cero del registro de máscara (k0..k7): Instrucciones de máscara y comparación de vectores en máscara

kxorB/W/D/Q     k0, k0, k0     ; narrow versions zero extend to max_kl
kshiftlB/W/D/Q  k0, k0, 100    ; kshifts don't mask/wrap the 8-bit count
kshiftrB/W/D/Q  k0, k0, 100
kandnB/W/D/Q    k0, k0, k0     ; x & ~x

; compare into mask
vpcmpB/W/D/Q    k0, x/y/zmm0, x/y/zmm0, 3    ; predicate #3 = always false; other predicates are false on equal as well
vpcmpuB/W/D/Q   k0, x/y/zmm0, x/y/zmm0, 3    ; unsigned version

vptestnmB/W/D/Q k0, x/y/zmm0, x/y/zmm0       ; x & ~x test into mask      

x87 FP:

Solo una opción (porque sub no funciona si el valor anterior era infinito o NaN).

FLDZ    ; push +0.0

Un par de posibilidades más:

sub ax, ax

movxz, eax, ah

Editar: Debo señalar que el movzx no pone a cero todo eax - es solo cero ah (más los 16 bits principales que no son accesibles como un registro en sí mismos).

En cuanto a ser el más rápido, si la memoria le sirve a la sub y xor son equivalentes. Son más rápidos que (la mayoría) de los demás porque son lo suficientemente comunes como para que los diseñadores de CPU les hayan agregado una optimización especial. Específicamente, con un normal sub o xor el resultado depende del valor anterior en el registro. La CPU reconoce el xor-con-uno mismo y el resta-de-uno mismo especialmente para saber que la cadena de dependencia está rota allí. Las instrucciones posteriores no dependerán de ningún valor anterior, por lo que puede ejecutar instrucciones anteriores y posteriores en paralelo utilizando registros de cambio de nombre.

Especialmente en los procesadores más antiguos, esperamos que 'mov reg, 0' sea más lento simplemente porque tiene 16 bits adicionales de datos, y la mayoría de los primeros procesadores (especialmente el 8088) estaban limitados principalmente por su capacidad para cargar el flujo desde la memoria. - de hecho, en un 8088 puede estimar el tiempo de ejecución con bastante precisión con cualquier hoja de referencia, y simplemente prestar atención a la cantidad de bytes involucrados. Eso se rompe por el div y idiv instrucciones, pero eso es todo. OTOH, probablemente debería callarme, ya que el 8088 realmente le interesa poco a nadie (desde hace al menos una década).

Si haces scroll puedes encontrar las anotaciones de otros sys admins, tú incluso tienes el poder insertar el tuyo si te apetece.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *