Сколько способов установить регистр в ноль?

Мне любопытно, как много способов установить регистр в ноль в сборке x86. Использование одной инструкции. Кто-то сказал мне, что ему удалось найти по меньшей мере 10 способов сделать это.

Я могу подумать:

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

Ответ 1

Есть много возможностей, как mov 0 in to ax под 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

И, возможно, самый странный...:)

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

и...

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

Изменить:

И для 16-битного режима x86 cpu, не проверено...:

    lea  ax, [0]

и...

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

Префикс cs: является необязательным, если регистр сегмента ds не равен регистру сегментов cs.

Ответ 2

См. этот ответ для наилучшего способа для нулевых регистров: xor eax,eax (преимущества производительности и меньшая кодировка).


Я рассмотрю только то, как одна команда может обнулить регистр. Есть слишком много способов, если вы разрешаете загружать нуль из памяти, поэтому мы будем главным образом исключать инструкции, которые загружаются из памяти.

Я нашел 10 разных одиночных инструкций, которые обнулили 32-битный регистр (и, следовательно, полный 64-битный регистр в длинном режиме), без предварительных условий или нагрузок из любой другой памяти. Это не считая разных кодировок одного и того же insn или разных форм mov. Если вы подсчитываете загрузку из памяти, которая, как известно, содержит нуль или регистры сегментов или что-то еще, есть лодка. Существует также миллион способов для нулевых векторных регистров.

Для большинства из них версии eax и rax представляют собой отдельные кодировки для одной и той же функциональности, обе обнуляющие полные 64-разрядные регистры, неинвертирующую верхнюю половину или явно записывая полный регистр с префиксом REX.W.

Целочисленные регистры:

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

.l: loop .l             ; clears e/rcx... eventually.  from I. J. Kennedy answer.  To operate on only ECX, use an address-size prefix.
; rep lodsb             ; not counted because it not safe (potential segfaults), but also zeros ecx

"Сдвиг всех битов на один конец" невозможно для регистров GP обычного размера, только частичные регистры. shl и shr значения сдвига маскируются: count &= 31;, что эквивалентно count %= 32;. (Но 286 и более ранние - только 16 бит, поэтому ax является "полным" регистром. Форма shr r/m16, imm8 переменной-счетчика команды была добавлена ​​286, поэтому были процессоры, в которых сдвиг может нулевать полный целочисленный регистр. )

Также обратите внимание, что сдвиговые значения для векторов насыщаются вместо обертывания.

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

В зависимости от других существующих условий (кроме нуля в другом регистре):

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

vector regs:

Некоторые из этих SSE2-целых инструкций могут также использоваться для регистров MMX (mm0 - mm7). Опять же, лучшим выбором является некоторая форма xor. Либо PXOR/VPXOR, либо XORPS/VXORPS.

AVX vxorps xmm0,xmm0,xmm0 нули полный ymm0/zmm0 и лучше, чем vxorps ymm0,ymm0,ymm0 для процессоров AMD. Эти команды обнуления имеют три кодировки: устаревшие SSE, AVX (префикс VEX) и AVX512 (префикс EVEX), хотя версия SSE только нули нижней 128, что не является полным регистром на процессорах, поддерживающих AVX или AVX512. Во всяком случае, в зависимости от того, как вы считаете, каждая запись может быть трех разных инструкций (такой же код операции, хотя и разные префиксы). За исключением vzeroall, который AVX512 не изменился (и не равен нулю 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 и подобное не будет работать, потому что NaN-NaN = NaN, а не ноль.

Кроме того, инструкции FP могут вызывать исключения из аргументов NaN, поэтому даже CMPPS/PD безопасен, если вы знаете, что исключения маскируются, и вам не нужно устанавливать биты исключений в MXCSR. Даже версия AVX с расширенным выбором предикатов поднимет #IA на SNaN. "Тихие" предикаты только подавляют #IA для QNaN. CMPPS/PD также может вызвать исключение Denormal.

(см. таблицу в запись insn set ref для CMPPD или, предпочтительно, в оригинальном PDF файле Intel, так как выдержка HTML управляет этой таблицей. )

AVX512:

Здесь, вероятно, есть несколько вариантов, но сейчас я не достаточно любопытен, чтобы перебирать список наборов инструкций, ища их всех.

Есть один интересный, который стоит упомянуть: VPTERNLOGD/Q может установить регистр all-ones вместо, с imm8 = 0xFF. (Но имеет ложную зависимость от старого значения, от текущих реализаций). Поскольку инструкции сравнения все сравниваются с маской, VPTERNLOGD, по-видимому, является лучшим способом установить вектор для всех-на Skylake-AVX512 в моем тестировании, хотя это не случайный случай imm8 = 0xFF, чтобы избежать ложной зависимости.

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

x87 FP:

Только один выбор (потому что sub не работает, если старое значение было бесконечным или NaN).

FLDZ    ; push +0.0

Ответ 3

Еще пара возможностей:

sub ax, ax

movxz, eax, ah

Изменить: я должен отметить, что movzx не нулевое все eax - он просто равен нулю ah (плюс верхние 16 бит, которые не доступны как сами регистры).

Что касается самого быстрого, если память поддерживает sub и xor, то они эквивалентны. Они быстрее, чем другие (другие), потому что они достаточно распространены, что разработчики процессоров добавили для них специальную оптимизацию. В частности, при нормальном sub или xor результат зависит от предыдущего значения в регистре. CPU распознает xor-with-self и вычитает из себя, поэтому он знает, что цепочка зависимостей там сломана. Любые инструкции после этого не будут зависеть от предыдущего значения, поэтому он может выполнять предыдущие и последующие инструкции параллельно с помощью регистров переименования.

Особенно на более старых процессорах мы ожидаем, что "mov reg, 0" будет медленнее просто потому, что у него есть дополнительные 16 бит данных, а большинство ранних процессоров (особенно 8088) были ограничены в первую очередь их способностью загружать поток из памяти - на самом деле, на 8088 вы можете довольно точно оценить время выполнения с любыми ссылочными листами и просто обратить внимание на количество задействованных байтов. Это нарушает инструкции div и idiv, но об этом. OTOH, я должен, вероятно, заткнуться, так как 8088 действительно мало интересует кого-либо (в течение по крайней мере десятилетия).

Ответ 4

Вы можете установить регистр CX на 0 с помощью LOOP $.

Ответ 5

Конечно, конкретные случаи имеют дополнительные способы установить регистр в 0: например. если у вас eax установлено положительное целое число, вы можете установить edx на 0 с помощью cdq/cltd (этот трюк используется на знаменитом 24-байтовом шеллкоде, который появляется в "Небезопасном программировании на примере" ).

Ответ 6

Этот поток старый, но несколько других примеров. Простые:

xor eax,eax

sub eax,eax

and eax,0

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

более сложные комбинации:

; 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

Ответ 7

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