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


Tengo curiosidad por saber cuántas formas hay de poner un registro a cero en el ensamblado x86. Usando una instrucción. Alguien me dijo que se las arregló para encontrar al menos 10 maneras de hacerlo.

Los que se me ocurren son:

xor ax,ax
mov ax, 0
and ax, 0
Author: GJ., 2011-01-28

7 answers

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 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

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

 12
Author: GJ.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-28 20:14:29

Ver esta respuesta para el 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 maneras si permite cargar un cero desde la memoria, por lo que en su mayoría excluiremos las instrucciones que se cargan desde la memoria.

He encontrado 10 instrucciones individuales diferentes que ponen cero un registro de 32 bits (y por lo tanto el registro completo de 64 bits en modo largo), sin precondiciones ni cargas de ninguna otra memoria. Esto no está contando las diferentes codificaciones del mismo insn, o las diferentes formas de mov. Si cuentas la carga desde la memoria que se sabe que contiene un cero, o desde registros de segmentos 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 prefijo.

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

"Shift all the bits out one end" no es posible para registros GP de tamaño regular, solo registros parciales. shl y shr los recuentos de desplazamiento están enmascarados: count &= 31;, equivalente a count %= 32;. (Pero 286 y anteriores son solo de 16 bits, por lo que ax es un registro "completo". La forma de cuenta variable shr r/m16, imm8 de la instrucción se agregó 286, por lo que hubo CPU donde un shift puede poner a cero un registro entero completo.)

También tenga en cuenta que los recuentos de desplazamiento para los vectores saturan en lugar de envolver.

# 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 (aparte de tener un cero en otro reg):

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 del vector:

Algunas de estas instrucciones de enteros SSE2 también se pueden usar en registros MMX (mm0 - mm7). Una vez más, la mejor opción es alguna forma de xor. O bien PXOR / VPXOR, o XORPS / VXORPS.

AVX vxorps xmm0,xmm0,xmm0 ceros el ymm0/zmm0 completo, y es mejor que vxorps ymm0,ymm0,ymm0 en CPU AMD. Estas instrucciones de puesta a cero tienen tres codificaciones: legacy SSE, AVX (prefijo VEX) y AVX512 (prefijo EVEX), aunque la versión SSE solo cera el 128 inferior, 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 tres instrucciones diferentes (el mismo opcode, sin embargo, solo prefijos diferentes). Excepto vzeroall, que AVX512 no cambió (y no pone cero zmm16-31).

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

PXOR       xmm0, xmm0
XORPD      xmm0, xmm0
XORPS      xmm0, xmm0

VZEROALL

# 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 fro the same VCMPPD xmm,xmm,xmm, imm8 instruction.  Same exception behaviour as LT_OQ.

SUBPS xmm0, xmm0 y similar no funcionará 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 selección de predicados, elevará #IA en SNaN. Los predicados "silenciosos" solo suprimen #IA para QNaN. CMPPS / PD también puede elevar la Denormal salvedad.

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

AVX512:

Probablemente hay varias opciones aquí, pero no soy lo suficientemente curioso en este momento para ir a cavar a través de la lista de instrucciones en busca de todos ellos.

Hay una interesante que vale la pena mencionar, sin embargo: VPTERNLOGD / Q puede establecer un registro en todos-unos en su lugar, con imm8 = 0xFF. (Pero tiene una dependencia falsa en el valor antiguo, en 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 en all-ones en Skylake-AVX512 en mis pruebas, aunque no es un caso especial el caso imm8=0xFF para evitar una dependencia falsa.

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

X87 FP:

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

FLDZ    ; push +0.0
 6
Author: Peter Cordes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-28 14:39:37

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 just solo pone a cero ah (más los 16 bits superiores que no son accesibles como un registro en sí mismos).

En cuanto a ser el más rápido, si la memoria sirve, sub y xor son equivalentes. Son más rápidos que (la mayoría) los demás porque son lo suficientemente comunes como para que los diseñadores de CPU agregaran una optimización especial para ellos. Específicamente, con un sub o xor normal el resultado depende de el valor anterior en el registro. La CPU reconoce el xor-with-self y resta-de-self especialmente para saber que la cadena de dependencias está rota allí. Cualquier instrucción posterior no dependerá de ningún valor anterior, por lo que puede ejecutar instrucciones anteriores y posteriores en paralelo utilizando rename registers.

Especialmente en procesadores más antiguos, esperamos que el 'mov reg, 0' sea más lento simplemente porque tiene un extra de 16 bits de datos, y la mayoría de los primeros procesadores (especialmente el 8088) fueron limitado principalmente por su capacidad para cargar el flujo desde la memoria in de hecho, en un 8088 puede estimar el tiempo de ejecución con bastante precisión con cualquier hoja de referencia, y solo preste atención al número de bytes involucrados. Eso se descompone para las instrucciones div y idiv, pero eso es todo. OTOH, probablemente debería callarme, ya que el 8088 realmente es de poco interés para muchos (desde hace al menos una década).

 4
Author: Jerry Coffin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-28 16:06:13

Puede establecer register CX a 0 con LOOP $.

 3
Author: I. J. Kennedy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-31 17:32:23

Por supuesto, los casos específicos tienen formas adicionales de establecer un registro en 0: por ejemplo, si tiene eax establecido en un entero positivo, puede establecer edx a 0 con un cdq/cltd (este truco se usa en un famoso código shell de 24 bytes, que aparece en "Programación insegura por ejemplo").

 1
Author: ninjalj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-28 19:10:07

Este hilo es viejo, pero algunos otros ejemplos. Simples:

xor eax,eax

sub eax,eax

and eax,0

lea eax,[0] ; it doesn't look "natural" in the binary

Combinaciones más complejas:

; flip all those 1111... bits to 0000
or  eax,-1  ;  eax = 0FFFFFFFFh
not eax     ; ~eax = 0

; XOR EAX,-1 works the same as NOT EAX instruction in this case, flipping 1 bits to 0
or  eax,-1  ;  eax = 0FFFFFFFFh
xor eax,-1  ; ~eax = 0

; -1 + 1 = 0
or  eax,-1 ;  eax = 0FFFFFFFFh or signed int = -1
not eax    ;++eax = 0
 1
Author: Bartosz Wójcik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-22 13:44:11
mov eax,0  
shl eax,32  
shr eax,32  
imul eax,0 
sub eax,eax 
xor eax,eax   
and eax,0  
andn eax,eax,eax 

loop $ ;ecx only  
pause  ;ecx only (pause="rep nop" or better="rep xchg eax,eax")

;twogether:  
push dword 0    
pop eax

or eax,0xFFFFFFFF  
not eax

xor al,al ;("mov al,0","sub al,al",...)  
movzx eax,al
...
 0
Author: ARISTOS,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-13 19:32:54