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