Comment beaucoup de façons de mettre un registre à zéro?
je suis curieux de savoir combien de façons sont là pour mettre un registre à zéro dans x86 assembly. En utilisant une seule instruction. Quelqu'un m'a dit qu'il a réussi à trouver au moins 10 façons de le faire.
ceux auxquels je pense sont:
xor ax,ax
mov ax, 0
and ax, 0
7 réponses
il y a beaucoup de possibilités de mov 0 dans la 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]
Edit:
et en mode cpu 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 dans le cas où le registre de segment ds n'est pas égal au registre de segment cs.
Voir cette réponse pour le meilleur de façon à zéro des registres: xor eax,eax
(les avantages de performance, et les plus petites de l'encodage).
je vais considérer juste les façons dont une seule instruction peut zéro un registre. Il y a beaucoup trop de façons si vous autorisez le chargement d'un zéro depuis la mémoire, donc nous exclurons principalement les instructions qui chargent depuis la mémoire.
j'ai trouvé 10 différentes instructions simples qui zéro un registre de 32 bits (et donc le registre complet de 64 bits en mode long), sans pré-conditions ou charges de toute autre mémoire. Il ne s'agit pas de compter les différents encodages du même insn, ou les différentes formes de mov
. Si vous comptez le chargement de la mémoire qui est connu pour contenir un zéro, ou des registres de segment ou autre, il y a une cargaison de moyens. Il existe également un zillion de façons de zéro des registres vectoriels.
Pour la plupart de ces, les versions EAX et rax sont des codages séparés pour la même fonctionnalité, les deux mettant à zéro les registres complets de 64 bits, soit mettant à zéro la moitié supérieure implicitement ou écrivant explicitement le registre complet avec un REX.W préfixe.
Integer registres:
# 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
"Maj tous les bits à une fin" n'est pas possible pour le format standard GP registres, seulement partielle des registres. Les nombres de postes shl
et shr
sont masqués: count &= 31;
, équivalent à count %= 32;
. (Mais les numéros 286 et antérieurs sont de 16 bits seulement, de sorte que ax
est un registre" complet". La forme shr r/m16, imm8
de comptage de variables de l'instruction a été ajoutée 286, donc il y avait des CPU où un décalage peut zéro un registre entier complet.)
aussi noter que le nombre de déplacements pour les vecteurs saturent au lieu de les envelopper.
# 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
zéro dans un autre 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
vecteur regs:
certaines de ces instructions D'entier SSE2 peuvent également être utilisées sur les registres MMX ( mm0
- mm7
). Encore une fois, le meilleur choix est une certaine forme de xor. Soit PXOR
/ VPXOR
, soit XORPS
/ VXORPS
.
AVX vxorps xmm0,xmm0,xmm0
zéros le plein ymm0/ zmm0, et est meilleur que vxorps ymm0,ymm0,ymm0
sur AMD CPUs . Ces instructions de mise à zéro ont trois encodages: SSE legacy, AVX (préfixe VEX), et AVX512 (préfixe EVEX), bien que la version SSE zéros seulement le 128 inférieur, qui n'est pas le registre complet sur les CPU qui supportent AVX ou AVX512. Quoi qu'il en soit, selon la façon dont vous comptez, chaque entrée peut être trois instructions différentes (même opcode, cependant, juste des préfixes différents). Sauf vzeroall
, qui AVX512 n'a pas changé (et ne fait pas zéro 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 fonctionnera pas parce que NaN-NaN = NAN, pas zéro.
aussi, les instructions FP peuvent soulever des exceptions sur les arguments NaN, donc même CMPPS/PD n'est sûr que si vous savez que les exceptions sont masquées, et que vous ne vous souciez pas de définir éventuellement les bits d'exception dans MXCSR. Même la version AVX, avec son choix élargi de prédicats, soulèvera #IA
sur SNaN. Le" calme "prédit seulement supprimer #IA
pour QNaN. Le CMPPS / PD peut également élever le Dénormal exception.
(Voir le tableau dans la section l'insn set ref entrée pour CMPPD , ou, de préférence, d'Intel d'origine PDF depuis le code HTML d'extraire mangles la table.)
AVX512:
il y a probablement plusieurs options ici, mais je ne suis pas assez curieux en ce moment d'aller creuser à travers la liste des instructions les recherchant toutes.
il y en a un intéressant qui vaut la peine d'être mentionné, bien que: VPTERNLOGD / Q peut définir un registre à all-ones à la place, avec imm8 = 0xff. (Mais il a une fausse dépendance sur l'ancienne valeur, sur les implémentations actuelles). Puisque les instructions de comparaison toutes comparent dans un masque, VPTERNLOGD semble être la meilleure façon de définir un vecteur à tous les autres sur Skylake-AVX512 dans mes tests, bien que il n'est pas Spécial-cas le cas imm8=0xFF pour éviter une fausse dépendance .
VPTERNLOGD zmm0, zmm0,zmm0, 0 ; inputs can be any registers you like.
x87 FP:
un seul choix (parce que sub ne fonctionne pas si l'ancienne valeur était infinity ou NaN).
FLDZ ; push +0.0
deux autres possibilités:
sub ax, ax
movxz, eax, ah
Edit: je dois noter que le movzx
ne Zéro pas tout de eax
-- c'est juste zéro ah
(plus les 16 premiers bits qui ne sont pas accessibles comme un registre en eux-mêmes).
comme pour être le plus rapide, si la mémoire sert le sub
et xor
sont équivalents. Ils sont plus rapides que (la plupart) d'autres parce qu'ils sont assez communs que les concepteurs de CPU ont ajouté spécial optimisation pour eux. Plus précisément, avec un sub
ou xor
normal, le résultat dépend de la valeur précédente dans le registre. Le CPU reconnaît le xor-avec-soi et soustrait-du-soi spécialement pour qu'il sache que la chaîne de dépendances y est brisée. Toutes les instructions qui suivent ne dépendront d'aucune valeur antérieure, de sorte qu'il peut exécuter les instructions précédentes et suivantes en parallèle en utilisant les registres de renommage.
en particulier sur les processeurs plus anciens, nous attendons le " mov reg, 0' à être plus lent, tout simplement parce qu'il a un supplément de 16 bits de données, et la plupart des premiers processeurs (en particulier le 8088) ont été principalement limitée par leur capacité à charger le flux de la mémoire -- en fait, sur un 8088 vous pouvez estimer les temps d'exécution assez correctement avec toutes les feuilles de référence à tous, et il suffit de payer l'attention sur le nombre d'octets impliqués. Cela ne se décomposent pour les instructions div
et idiv
, mais c'est à peu près tout. OTOH, je devrais probablement me taire, puisque le 8088 est vraiment de peu d'intérêt pour la plupart des gens (depuis au moins une décennie maintenant).
bien sûr, les cas spécifiques ont des façons supplémentaires de définir un registre à 0: par exemple, si vous avez eax
défini à un entier positif, vous pouvez définir edx
à 0 avec un cdq/cltd
(cette astuce est utilisée sur un shellcode célèbre de 24 octets, qui apparaît sur"la programmation non sécurisée par exemple").
ce fil est ancien mais quelques autres exemples. 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
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
...