Сколько способов установить регистр в ноль? - PullRequest
28 голосов
/ 28 января 2011

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

Я могу вспомнить:

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

Ответы [ 7 ]

12 голосов
/ 28 января 2011

Существует множество возможностей для перемещения 0 в топор под 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, не тестировалось ...:

    lea  ax, [0]

и...

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

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

7 голосов
/ 20 сентября 2015

См. этот ответ для лучших регистров нуля: 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.'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

«Переместить все биты на один конец» невозможнодля регистров 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's 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

векторные регистры:

Некоторые из этих целочисленных инструкций 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 для CMPPD или, предпочтительно, в исходном PDF-файле Intel, поскольку извлечение HTML портит эту таблицу.)

AVX512:

Возможно, здесь есть несколько вариантов, но мне сейчас не интересно, чтобы копаться в списке наборов инструкций и искать их все.

Однако стоит упомянуть один интересный: VPTERNLOGD / Q может вместо этого установить регистр в all-one , с 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
4 голосов
/ 28 января 2011

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

sub ax, ax

movxz, eax, ah

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

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

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

3 голосов
/ 31 января 2011

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

1 голос
/ 13 января 2015

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

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
1 голос
/ 28 января 2011

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

0 голосов
/ 13 июня 2017
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
...
...