web-dev-qa-db-fra.com

Combien de façons de mettre un registre à zéro?

Je suis curieux de savoir combien de façons existe-t-il de définir un registre à zéro dans x86 Assembly. En utilisant une instruction. Quelqu'un m'a dit qu'il avait réussi à trouver au moins 10 façons de le faire.

Ceux que je peux penser sont:

xor ax,ax
mov ax, 0
and ax, 0
26
user173973

Il y a beaucoup de possibilité de faire bouger 0 dans hache sous 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

Et peut-être le plus étrange ... :)

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

et...

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

Modifier:  

Et pour le mode processeur 16 bits x86, non testé ...:

    lea  ax, [0]

et...

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

Le préfixe cs: est facultatif si le registre de segments ds n'est pas égal au registre de segments cs.

12
GJ.

Voir cette réponse pour les best registres de cheminement à zéro: xor eax,eax (avantages en termes de performances et codage réduit).


J'examinerai simplement les moyens par lesquels une seule instruction peut mettre à zéro un registre. Si vous autorisez le chargement d'un zéro dans la mémoire, les possibilités sont beaucoup trop nombreuses. Nous allons donc exclure les instructions qui se chargent de la mémoire.

J'ai trouvé 10 instructions simples différentes qui remettent à zéro un registre 32 bits (et donc le registre complet 64 bits en mode long), sans conditions préalables ni charges à partir d'une autre mémoire. Cela ne tient pas compte des différents encodages du même insn, ni des différentes formes de mov. Si vous comptez le chargement de la mémoire connue comme contenant un zéro, ou des registres de segments ou autres, il existe une multitude de chemins. Il existe également une multitude de façons de mettre à zéro les registres de vecteurs.

Pour la plupart d'entre elles, les versions eax et rax sont des codages distincts pour la même fonctionnalité, remettant à zéro les registres 64 bits complets, soit remettant à zéro la moitié supérieure de manière implicite ou écrivant explicitement le registre complet avec un préfixe REX.W.

Registres entiers:

# 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

"Décaler tous les bits d'un côté" n'est pas possible pour les registres GP de taille normale, mais uniquement pour les registres partiels. Les nombres de postes shl et shr sont masqués: count &= 31;, ce qui équivaut à count %= 32;. (Mais 286 et les versions antérieures ne sont que de 16 bits, donc ax est un registre "complet". La forme shr r/m16, imm8 du compte de variables variable ayant été ajoutée à l'instruction 286, il existait donc des CPU dans lesquelles une équipe pouvait mettre à zéro un registre entier.)

Notez également que les décomptes pour les vecteurs saturent au lieu d'être enveloppés.

# 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

En fonction d'autres conditions existantes (autre qu'un zéro dans un autre registre):

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

vecteur regs:

Certaines de ces instructions entières SSE2 peuvent également être utilisées sur les registres MMX (mm0 - mm7). Encore une fois, le meilleur choix est une forme de xor. PXOR/VPXOR, ou XORPS/VXORPS.

AVX vxorps xmm0,xmm0,xmm0 remet à zéro le ymm0/zmm0 complet et vaut mieux que vxorps ymm0,ymm0,ymm0 sur les processeurs AMD . Ces instructions de remise à zéro ont trois codages: SSE hérité, AVX (préfixe VEX) et AVX512 (préfixe EVEX), bien que la version SSE ne remette que le 128 en bas, qui n'est pas le registre complet sur les processeurs prenant en charge AVX ou AVX512. Quoi qu'il en soit, en fonction de la manière dont vous comptez, chaque entrée peut être composée de trois instructions différentes (le même opcode, toutefois, juste des préfixes différents). À l'exception de vzeroall, qu'AVX512 n'a pas modifié (et ne réinitialise pas 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 et similaire ne fonctionneront pas car NaN-NaN = NaN, pas zéro.

De plus, les instructions FP peuvent déclencher des exceptions sur les arguments NaN. Par conséquent, même CMPPS/PD n'est sécurisé que si vous savez que les exceptions sont masquées et que vous ne vous inquiétez pas de la possibilité de définir les bits d'exception dans MXCSR. Même la version AVX, avec son choix étendu de prédicats, lèvera #IA sur SNaN. Les prédicats "quiet" suppriment uniquement #IA pour QNaN. CMPPS/PD peut également déclencher l'exception Denormal.

(Voir le tableau dans l'entrée de référence insn set pour CMPPD , ou de préférence dans le PDF d'origine d'Intel puisque l'extrait HTML modifie ce tableau.)

AVX512:

Il y a probablement plusieurs options ici, mais je ne suis pas assez curieux pour le moment de fouiller dans la liste d'instructions en les cherchant toutes.

Il y en a un intéressant à noter, cependant: VPTERNLOGD/Q peut définir un registre sur all-one à la place, avec imm8 = 0xFF. (Mais a une dépendance fausse sur l'ancienne valeur, sur les implémentations actuelles). Puisque les instructions de comparaison se comparent toutes dans un masque, VPTERNLOGD semble être le meilleur moyen de définir un vecteur à un entier sur Skylake-AVX512 dans mes tests, bien que il ne faut pas cas spécial imm8 = 0xFF à éviter une fausse dépendance .

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

x87 FP:

Un seul choix (car sub ne fonctionne pas si l'ancienne valeur était infini ou NaN).

FLDZ    ; Push +0.0
6
Peter Cordes

Quelques autres possibilités:

sub ax, ax

movxz, eax, ah

Edit: Je devrais noter que le movzxne remet pas tous les eaxà zéro - il ne remet que le ah(plus les 16 premiers bits qui ne sont pas accessibles en tant que registres).

Pour être le plus rapide, si les services de la mémoire sont remplacés, subet xorsont équivalents. Ils sont plus rapides que les autres, car ils sont suffisamment répandus pour que les concepteurs de CPU leur apportent une optimisation particulière. Spécifiquement, avec un subou xornormal, le résultat dépend de la valeur précédente dans le registre. La CPU reconnaît le xor-with-self et soustrait-de-soi spécialement pour savoir que la chaîne de dépendance y est rompue. Toute instruction ultérieure ne dépendra d'aucune valeur précédente, il pourra donc exécuter les instructions précédentes et suivantes en parallèle à l'aide de registres de renommage.

Sur les anciens processeurs, nous nous attendons à ce que «mov reg, 0» soit plus lent simplement parce qu'il contient 16 bits de données supplémentaires, et la plupart des premiers processeurs (en particulier le 8088) étaient principalement limités par leur capacité à charger le flux à partir de la mémoire - - en fait, sur un 8088, vous pouvez estimer le temps d’exécution avec assez de précision avec toutes les feuilles de référence, et faites attention au nombre d’octets impliqués. Cela ne fonctionne pas pour les instructions divet idivname__, mais c'est à peu près tout. OTOH, je devrais probablement me taire, car le 8088 n’a vraiment que peu d’intérêt pour quiconque (depuis au moins une décennie maintenant).

4
Jerry Coffin

Vous pouvez définir le registre CX sur 0 avec LOOP $.

3
I. J. Kennedy

Bien entendu, des cas spécifiques disposent de moyens supplémentaires pour définir un registre sur 0: par exemple. Si vous avez défini eax sur un entier positif, vous pouvez définir edx sur 0 avec un cdq/cltd (cette astuce est utilisée sur un célèbre shellcode de 24 octets, qui apparaît dans "Programmation non sécurisée par exemple").

1
ninjalj

Ce fil est vieux mais quelques autres exemples. Les plus simples:

xor eax,eax

sub eax,eax

and eax,0

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

combinaisons plus complexes:

; 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
Bartosz Wójcik
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
ARISTOS