参见this answer for the best way to zero registers: xor eax,eax(性能优势和更小的编码)。
我将只考虑单条指令可以将寄存器归零的方式。如果您允许从内存中加载零,则方法太多了,因此我们将主要排除从内存中加载的指令。
我发现了 10 条不同的单指令,它们将 32 位寄存器(以及长模式下的完整 64 位寄存器)归零,没有前置条件或从任何其他内存加载。这还不包括同一个insn的不同编码,或者mov的不同形式。如果您计算从已知为零的内存或段寄存器或其他任何内容加载,那么有很多方法。还有无数种方法可以将向量寄存器归零。
对于其中大多数,eax 和 rax 版本是相同功能的不同编码,都将完整的 64 位寄存器归零,zeroing the upper half implicitly 或使用 REX.W 前缀显式写入完整寄存器。
整数寄存器(NASM 语法):
# 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 can be encoded two different ways 的指令。在 GAS AT&T 语法中,我们可以请求汇编器选择哪个操作码。这仅适用于允许两种形式的 reg,reg 整数指令,即可以追溯到 8086。所以不适用于 SSE/AVX。
{load} xor %eax, %eax # 31 c0
{store} xor %eax, %eax # 33 c0
“将所有位移出一端”对于常规大小的 GP 寄存器是不可能的,只有部分寄存器是不可能的。 shl 和 shr 移位计数被屏蔽 (on 286 and later): count & 31; 即 mod 32。
(立即数移位是 186 中的新功能(以前只有 CL 和隐式 1),因此有些 CPU 具有未屏蔽的立即移位(也包括 NEC V30)。此外,286 和更早版本仅是 16 位,所以 @987654340 @ 是一个“完整”寄存器。在 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)。我不打算单独展示。
同样,最好的选择是某种形式的异或。 PXOR / VPXOR 或 XORPS / VXORPS。详情请见What is the best way to set a register to zero in x86 assembly: xor, mov or and?。
AVX vxorps xmm0,xmm0,xmm0 将完整的 ymm0/zmm0 归零,并且是 better than vxorps ymm0,ymm0,ymm0 on AMD CPUs。
这些归零指令各有三种编码:旧版 SSE、AVX(VEX 前缀)和 AVX512(EVEX 前缀),尽管 SSE 版本仅将底部 128 归零,这不是完整的在支持 AVX 或 AVX512 的 CPU 上注册。无论如何,根据您的计数方式,每个条目可以是三个不同的指令(尽管操作码相同,只是前缀不同)。除了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, xmm0 和类似的不起作用,因为 NaN-NaN = NaN,而不是零。
此外,FP 指令可以引发 NaN 参数的异常,因此即使 CMPPS/PD 只有在您知道异常被屏蔽并且您不关心是否可能在 MXCSR 中设置异常位时才是安全的。即使是 AVX 版本,具有扩展的谓词选择,也会在 SNaN 上引发#IA。 “安静”谓词仅抑制 QNaN 的 #IA。 CMPPS/PD 也可以引发非正规异常。 (AVX512 EVEX 编码可以抑制 512 位向量的 FP 异常,同时覆盖舍入模式)
(请参阅the insn set ref entry for CMPPD 中的表格,或者最好在英特尔的原始 PDF 中,因为 HTML 提取会破坏该表格。)
上面的 AVX1/2 和 AVX512 EVEX 形式,仅用于 PXOR:这些都为零完整的 ZMM 目标。 PXOR 有两个 EVEX 版本:VPXORD 或 VPXORQ,允许使用 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's PXOR manual entry 中以单独的条目列出。
您可以对任何您想要的掩码寄存器使用零掩码(但不能合并掩码);无论您是从掩码中获得零还是从向量指令的正常输出中获得零都没有关系。但这不是一个不同的指令。例如:VPXORD xmm16{k1}{z}, xmm0, xmm0
AVX512:
这里可能有几个选项,但我现在还没有足够的兴趣去挖掘指令集列表来寻找所有选项。
不过,有一个有趣的问题值得一提:VPTERNLOGD/Q 可以改为 set a register to all-ones,而 imm8 = 0xFF。 (但在当前实现上对旧值有错误的依赖)。由于比较指令都比较为掩码,因此在我的测试中,VPTERNLOGD 似乎是在 Skylake-AVX512 上将向量设置为全1的最佳方法,尽管it doesn't special-case the imm8=0xFF case to avoid a false dependency。
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
x87 FP:
只有一个选择(因为如果旧值是无穷大或 NaN,则 sub 不起作用)。
FLDZ ; push +0.0