x86アセンブリでレジスタをゼロに設定する方法がいくつあるのか知りたいです。1 つの命令を使用します。少なくとも 10 通りの方法を見つけることができたという人もいました。
私が考えることができるものは次のとおりです。
xor ax,ax
mov ax, 0
and ax, 0
IA32 で 0 を ax に移動する方法はたくさんあります...
lea eax, [0]
mov eax, 0FFFF0000h //All constants form 0..0FFFFh << 16
shr ax, 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
また、32ビットモードでも(より長い命令は、最終(最上位)アドレスバイトを後で置きます)...
@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 セグメント レジスタと等しくない場合のオプションです。
レジスタをゼロにする最良の方法については、この回答をxor eax,eax参照してください: (パフォーマンス上の利点と小さいエンコーディング)。
1 つの命令でレジスタをゼロにする方法だけを検討します。メモリからゼロをロードできるようにする方法が多すぎるため、メモリからロードする命令はほとんど除外します。
前提条件や他のメモリからのロードなしで、32 ビット レジスタ (したがって、ロング モードでは完全な 64 ビット レジスタ) をゼロにする 10 の異なる単一命令を見つけました。これは、同じ 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
; 2 same-size encodings each: r/m, r vs. r, r/m
@movzx:
movzx eax, byte ptr[@movzx + 6] //Assuming the high byte of the absolute address is 0. Not position-independent, and x86-64 RIP+rel32 would load 0xFF
.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
のような命令xor reg,reg は、2 つの異なる方法でエンコードできます。GAS AT&T 構文では、アセンブラーが選択するオペコードを要求できます。これは、両方の形式を許可する reg,reg integer 命令、つまり 8086 までさかのぼる命令にのみ適用されます。したがって、SSE/AVX には適用されません。
{load} xor %eax, %eax # 31 c0
{store} xor %eax, %eax # 33 c0
「すべてのビットを一方の端にシフトする」ことは、通常サイズの GP レジスタでは不可能であり、部分的なレジスタのみです。 shlまた、shrシフト カウントはマスクされます(286 以降では)。count & 31;つまり、mod 32 です。
(即時カウント シフトは 186 で新しくなった (以前は CL と暗黙の 1 のみ)。そのため、マスクされていない即時シフトを持つ CPU があります (NEC V30 も含まれます)。また、286 以前は 16 ビットのみであるためax、「フル」です)シフトによって完全な整数レジスタをゼロにすることができる CPU がありました。)
また、ベクトルのシフト カウントは、ラッピングではなく飽和することに注意してください。
# 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
他の既存の条件に応じて (別の 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
これらの SSE2 整数命令の一部は、MMX レジスタ ( mm0- mm7) でも使用できます。 それを個別に示すつもりはありません。
繰り返しますが、最良の選択は何らかの形式の xor です。/ 、またはPXOR/ 。x86アセンブリでレジスタをゼロに設定する最良の方法は何ですか: xor、mov、またはand? 詳細については。VPXORXORPSVXORPS
AVXvxorps xmm0,xmm0,xmm0は完全な ymm0/zmm0 をゼロにし、AMD CPUよりも優れてvxorps ymm0,ymm0,ymm0います。
これらのゼロ化命令には、レガシー SSE、AVX (VEX プレフィックス)、および AVX512 (EVEX プレフィックス) の 3 つのエンコーディングがありますが、SSE バージョンは、AVX または AVX512 をサポートする CPU の完全なレジスターではない下位 128 のみをゼロ化します。とにかく、数え方によっては、各エントリが 3 つの異なる命令になる可能性があります (同じオペコードですが、プレフィックスが異なるだけです)。ただしvzeroall、AVX512 は変更されませんでした (zmm16-31 をゼロにしません)。
PXOR xmm0, xmm0 ;; recommended
XORPS xmm0, xmm0 ;; or this
XORPD xmm0, xmm0 ;; longer encoding for zero benefit
PXOR mm0, mm0 ;; MMX, not show for the rest of the integer insns
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
;; SSE4.1
INSERTPS xmm0, xmm1, 0x0F ; imm[3:0] = zmask = all elements zeroed.
DPPS xmm0, xmm1, 0x00 ; imm[7:4] => inputs = treat as zero -> no FP exceptions. imm[3:0] => outputs = 0 as well, for good measure
DPPD xmm0, xmm1, 0x00 ; inputs = all zeroed -> no FP exceptions. outputs = 0
VZEROALL ; AVX1 x/y/zmm0..15 not zmm16..31
VPERM2I/F128 ymm0, ymm1, ymm2, 0x88 ; imm[3] and [7] zero that output lane
# 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 for the same VCMPPD xmm,xmm,xmm, imm8 instruction. Same exception behaviour as LT_OQ.
SUBPS xmm0, xmm0NaN-NaN = NaN であり、ゼロではないため、同様の方法は機能しません。
また、FP 命令は NaN 引数で例外を発生させる可能性があるため、CMPPS/PD でさえ、例外がマスクされていることがわかっている場合にのみ安全であり、MXCSR で例外ビットを設定する可能性を気にしません。述語の選択肢が拡張された AVX バージョンでさえ、#IASNaN で発生します。「quiet」述語は#IA、QNaN のみを抑制します。CMPPS/PD も Denormal 例外を発生させる可能性があります。( AVX512 EVEX エンコーディングは、丸めモードをオーバーライドするとともに、512 ビット ベクトルの FP 例外を抑制することができます)
( CMPPD の insn set ref エントリの表を参照してください。または、できれば Intel の元の PDF を参照してください。これは、HTML 抽出によってその表が破壊されるためです。)
上記の AVX1/2 および AVX512 EVEX 形式、PXOR のみ: これらはすべて完全な ZMM 宛先をゼロにします。PXOR には、VPXORD または VPXORQ の 2 つの EVEX バージョンがあり、dword または qword 要素によるマスキングが可能です。(XORPS/PD はすでにニーモニックで要素サイズを区別しているため、AVX512 はそれを変更しませんでした。従来の SSE エンコーディングでは、XORPD は常に、すべての CPU での XORPS に対してコード サイズ (より大きなオペコード) の無意味な浪費です。)
VPXOR xmm15, xmm0, xmm0 ; AVX1 VEX
VPXOR ymm15, ymm0, ymm0 ; AVX2 VEX, less efficient on some CPUs
VPXORD xmm31, xmm0, xmm0 ; AVX512VL EVEX
VPXORD ymm31, ymm0, ymm0 ; AVX512VL EVEX 256-bit
VPXORD zmm31, zmm0, zmm0 ; AVX512F EVEX 512-bit
VPXORQ xmm31, xmm0, xmm0 ; AVX512VL EVEX
VPXORQ ymm31, ymm0, ymm0 ; AVX512VL EVEX 256-bit
VPXORQ zmm31, zmm0, zmm0 ; AVX512F EVEX 512-bit
Intel の PXOR マニュアルエントリには、さまざまなベクトル幅が個別のエントリとしてリストされています。
任意のマスク レジスタでゼロ マスキングを使用できます(ただし、マージ マスキングは使用できません)。マスキングからゼロを取得するか、ベクトル命令の通常の出力からゼロを取得するかは問題ではありません。しかし、それは別の指示ではありません。例えば: VPXORD xmm16{k1}{z}, xmm0, xmm0
ここにはおそらくいくつかのオプションがありますが、今のところ、命令セットのリストを掘り下げてそれらすべてを探すほど興味はありません。
ただし、言及する価値のある興味深いものが 1 つあります。VPTERNLOGD/Qは、imm8 = 0xFF を使用して、代わりにレジスタをすべて 1に設定できます。(ただし、現在の実装では、古い値に誤った依存関係があります)。比較命令はすべてマスクに比較されるため、私のテストでは VPTERNLOGD が Skylake-AVX512 でベクトルをすべて 1 に設定する最良の方法のようですが、false を回避するために imm8=0xFF ケースを特別なケースにするわけではありません。依存関係。
VPTERNLOGD zmm0, zmm0,zmm0, 0 ; inputs can be any registers you like.
マスク レジスタ (k0..k7) のゼロ化: マスク命令、およびベクトルのマスク比較
kxorB/W/D/Q k0, k0, k0 ; narrow versions zero extend to max_kl
kshiftlB/W/D/Q k0, k0, 100 ; kshifts don't mask/wrap the 8-bit count
kshiftrB/W/D/Q k0, k0, 100
kandnB/W/D/Q k0, k0, k0 ; x & ~x
; compare into mask
vpcmpB/W/D/Q k0, x/y/zmm0, x/y/zmm0, 3 ; predicate #3 = always false; other predicates are false on equal as well
vpcmpuB/W/D/Q k0, x/y/zmm0, x/y/zmm0, 3 ; unsigned version
vptestnmB/W/D/Q k0, x/y/zmm0, x/y/zmm0 ; x & ~x test into mask
選択肢は 1 つだけです (古い値が無限大または NaN の場合、sub は機能しないため)。
FLDZ ; push +0.0
さらにいくつかの可能性があります:
sub ax, ax
movxz, eax, ah
movzx編集:すべてがゼロになるわけではないことに注意してくださいeax-ゼロだけですah(さらに、レジスタ自体としてアクセスできない上位16ビット)。
最速であるということは、メモリが機能する場合subとxor同等です。CPU 設計者が特別な最適化を追加するほど一般的であるため、(ほとんどの) 他のものよりも高速です。具体的には、法線subまたはxor結果は、レジスタ内の以前の値に依存します。CPUはxor-with-selfとsubtract-from-selfを特別に認識するため、依存関係チェーンがそこで壊れていることを認識します。それ以降の命令は以前の値に依存しないため、名前変更レジスタを使用して前後の命令を並行して実行できます。
特に古いプロセッサでは、余分な 16 ビットのデータがあるため、'mov reg, 0' が遅くなることが予想されます。また、ほとんどの初期のプロセッサ (特に 8088) は、主にメモリからストリームをロードする能力によって制限されていました - - 実際、8088 では、どんなリファレンス シートでもかなり正確に実行時間を見積もることができ、関係するバイト数に注意を払うだけです。divそれはandidivの命令で壊れますが、それだけです。OTOH、8088 はほとんど誰も興味を持っていないので (少なくとも 10 年間)、私はおそらく黙っているべきです。
もちろん、特定のケースでは、レジスタを 0 に設定する追加の方法があります。たとえばeax、正の整数に設定した場合、aを使用しedxて 0 に設定できますcdq/cltd(このトリックは、「安全でないプログラミング例によって」)。
レジスタ CX を 0 に設定できますLOOP $。