pentium language instruction assembly x86 x86-16 tasm

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



list of x86 instructions (7)

Tengo curiosidad de cuántas maneras hay para establecer un registro a cero en el ensamblaje x86. Usando una instrucción. Alguien me dijo que logró encontrar al menos 10 formas de hacerlo.

Los que puedo pensar son:

xor ax,ax mov ax, 0 and ax, 0


Consulte esta respuesta para conocer la mejor forma de poner a cero los 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 cero un registro. Hay muchas maneras si permite cargar cero en la memoria, por lo que generalmente excluiremos las instrucciones que se cargan de la memoria.

He encontrado 10 instrucciones individuales diferentes que ponen a cero un registro de 32 bits (y por lo tanto el registro completo de 64 bits en el modo largo), sin precondiciones ni cargas desde ninguna otra memoria. Esto no cuenta las diferentes codificaciones del mismo insn, o las diferentes formas de mov . Si cuentas cargando desde la memoria que se sabe que tiene un cero, o desde registros de segmentos o lo que sea, hay un montón de maneras. También hay un trillón de formas de cero registros de vectores.

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

Registros de 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 hacia afuera en un extremo" no es posible para registros GP de tamaño normal, solo registros parciales. shr recuentos de shl y shr shift están enmascarados: count &= 31; , equivalente a count %= 32; . (Pero 286 y anteriores son de 16 bits, entonces ax es un registro "completo". La forma de la instrucción shr r/m16, imm8 de la instrucción se agregó 286, por lo que hubo CPUs donde un cambio puede cero un registro entero completo .)

También tenga en cuenta que los recuentos de turnos para vectores se 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 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 vectoriales:

Algunas de estas instrucciones enteras SSE2 también pueden usarse en registros MMX ( mm0 - mm7 ). De nuevo, la mejor opción es alguna forma de xor. Ya sea PXOR / VPXOR , o XORPS / VXORPS .

AVX vxorps xmm0,xmm0,xmm0 cero todo el ymm0 / zmm0, y es mejor que vxorps ymm0,ymm0,ymm0 en las CPU AMD . Estas instrucciones de ajuste a cero tienen tres codificaciones: SSE heredado, AVX (prefijo VEX) y AVX512 (prefijo EVEX), aunque la versión SSE solo cierra la parte inferior 128, 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 (el mismo código de operación, sin embargo, solo diferentes prefijos). Excepto vzeroall , que AVX512 no cambió (y no 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 similares no funcionarán porque NaN-NaN = NaN, no cero.

Además, las instrucciones de FP pueden generar excepciones en los argumentos de NaN, por lo que incluso CMPPS / PD solo es seguro si usted 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 elección ampliada de predicados, aumentará #IA en SNaN. Los predicados "silenciosos" solo suprimen #IA para QNaN. CMPPS / PD también puede generar la excepción Denormal.

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

AVX512:

Probablemente haya varias opciones aquí, pero no tengo la suficiente curiosidad ahora para ir a buscar en la lista de instrucciones para buscarlas todas.

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

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 anterior era infinito o NaN).

FLDZ ; push +0.0


Este hilo es antiguo, pero hay algunos otros ejemplos. Los más 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


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 tal vez lo 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 16 bit x86 cpu, 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 del segmento ds no sea igual al registro del segmento cs.


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 configurar edx en 0 con un cdq/cltd (este truco se utiliza en un famoso shellcode de 24 bytes, que aparece en "Programación insegura por ejemplo").


Puede configurar el registro CX en 0 con LOOP $ .


Un par de posibilidades más:

sub ax, ax movxz, eax, ah

Editar: Debo señalar que el movzx no movzx cero todos los eax , solo ah de cero (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, el sub y el 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 agreguen una optimización especial. Específicamente, con un sub o xor normal, el resultado depende del valor anterior en el registro. La CPU reconoce el xor-self y el restar de uno mismo especialmente para que sepa que la cadena de dependencia está rota allí. Cualquier instrucción posterior no dependerá de ningún valor previo, por lo que puede ejecutar instrucciones anteriores y posteriores en paralelo utilizando registros de cambio de nombre.

Especialmente en procesadores más antiguos, esperamos que el ''mov reg, 0'' sea más lento simplemente porque tiene 16 bits de datos adicionales, y la mayoría de los procesadores antiguos (especialmente el 8088) se limitaron principalmente por su capacidad de cargar la secuencia 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 preste atención al número de bytes involucrados. Eso se rompe por las instrucciones div e idiv , pero eso es todo. OTOH, probablemente debería callar, ya que el 8088 realmente es de poco interés para muchos (por lo menos una década).


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