如何将寄存器清零?有几种方法?

如何将寄存器清零?有几种方法?

请参考这个回答了解寄存器清零的最佳方法:xor eax,eax(具有性能优势和更小的编码)。

我将只考虑一条指令可以清零一个寄存器的方式。如果允许从内存中加载零值,那么方法就太多了,因此我们将大多数排除掉从内存中加载的指令。

我发现有10种不同的单个指令可以清零32位寄存器(因此在长模式下也会清零完整的64位寄存器),没有预先条件或从任何其他内存中加载。这不包括相同insn的不同编码,或mov的不同形式。如果计算从已知保存零值的内存或从段寄存器等加载,则有大量的方法。还有无数种方法可以清零向量寄存器。

对于其中大多数,eax和rax版本是相同功能的不同编码,两者都将全64位寄存器清零,隐式地清零上半部分或使用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可以有两种不同的编码方式(可参考此链接)。在GAS AT&T语法中,我们可以请求汇编器选择哪个操作码。这仅适用于允许两种形式的reg,reg整数指令,即追溯到8086年的指令。因此不适用于SSE / AVX。

{load} xor %eax, %eax # 31 c0

{store} xor %eax, %eax # 33 c0

"Shift all the bits out one end" 对于常规大小的 GP 寄存器不可能实现,只能对部分寄存器进行操作。在 286 及以后的处理器上,shl 和 shr 的移位计数会被掩码 (详见链接): count & 31; 即取模 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

根据其他现有条件(除了在另一个寄存器中为零):

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。有关详细信息,请参见在x86汇编中将寄存器设置为零的最佳方法:xor、mov还是and?。

AVX vxorps xmm0,xmm0,xmm0 可以将整个ymm0 / zmm0清零,并且在AMD CPU上比vxorps ymm0,ymm0,ymm0更好。

这些清零指令每个都有三种编码方式:传统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还可能引发Denormal异常。(AVX512 EVEX编码可以抑制FP异常,对于512位向量,同时覆盖舍入模式)

(请参见CMPPD指令集参考条目中的表格,或更好的是在英特尔原始PDF中查看,因为HTML提取会破坏该表格。)

AVX1/2 和 AVX512 EVEX 版本中的 PXOR 操作:这些操作都会将 ZMM 目标寄存器的所有位清零。PXOR 有两个 EVEX 版本:VPXORD 或 VPXORQ,允许使用 dword 或 qword 元素进行掩码操作。(XORPS/PD 已经在助记符中区分了元素大小,所以 AVX512 没有改变这一点。在传统的 SSE 编码中,对于所有 CPU,XORPD 都是一个毫无意义的代码浪费(较大的操作码),与 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可以使用imm8 = 0xFF将寄存器设置为全1, 但是在当前实现中对旧值存在虚假依赖。由于比较指令都会比较成掩码,根据我的测试,在Skylake-AVX512上,VPTERNLOGD似乎是将向量设置为全1的最佳方法,尽管它不会特殊处理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

x87浮点数:

只有一种选择(因为如果旧值为无穷大或NaN,则sub函数无法工作)。

FLDZ ; push +0.0

你可能也喜欢

书本简笔画图片教程 儿童学画
2024年中国体育类大学排名
曲面屏 vs 平面屏:5大核心对比解析你绝对不知道的真相