为什么在 ARMv8 上可以使用 xzr 寄存器而不是文字 0?
Why might one use the xzr register instead of the literal 0 on ARMv8?
我正在阅读来自 ARM 的 SVE whitepaper 并且遇到了一些让我感到奇怪的东西(在非 SVE 示例中):
mov x8, xzr
我不知道这个 xzr
寄存器是什么,所以我查了一下,发现 some content from ARM 说明它在许多情况下是零的同义词。
所以看起来 x8
被初始化为零,这是有道理的,因为它是在 x8
用作循环计数器的循环之前执行的。
我不明白的是,为什么不使用文字 0
而不是 xzr
?例如:
mov x8, 0
总而言之,我的问题是:为什么在这里使用 xzr
寄存器而不是文字 0
?
TL;DR
将 64 位文字加载到寄存器需要多条指令,但使用 zxr 设置为 0 只需一条指令。因此代码更短更快。
要将文字移动到寄存器,您可以使用 MOVL 指令,请参阅 arm 参考:
MOVL pseudo-instruction
Load a register with either:
A 32-bit or 64-bit immediate value.
Any address.
MOVL generates either two or four instructions... a MOV, MOVK pair.
因此将文字加载到寄存器是一个多步骤过程。如果您只想清除寄存器,那么他们有一个快捷方式。 zxr 是一个始终读取零的伪寄存器,这是您需要的常用值,并且可以在一条指令中完成将一个寄存器移动到另一个寄存器。
在 Microchip 组装中,他们有类似的概念。要将寄存器设置为文字,您可以执行以下操作:
MOVLW 10 (Move 10 to the working register)
MOVWF 0x1234 (Move the working register to address 0x1234)
但是要设置为零,他们有指令:
CLRF 0x1234 (Set 0x1234 to zero)
mov x8,xzr
mov x8,#0
mov x8,0
生产
0000000000000000 <.text>:
0: aa1f03e8 mov x8, xzr
4: d2800008 mov x8, #0x0 // #0
8: d2800008 mov x8, #0x0 // #0
除了它允许没有井号的立即数之外,没有什么真正令人惊讶的。这不是指令大小问题(同样不足为奇,对于 x86,例如 xor rax,rax 比 mov rax,0 便宜),也许有流水线性能增益(尽管普遍认为指令需要一个以上的时钟开始完成) .
很可能这是个人喜好,我们有这个很酷的 mips,就像总是零寄存器的东西一样,我们只是为了好玩而使用它。
这两条指令在效果和预期性能方面应该是相同的。
它们实际上都是更通用指令的别名。
mov x8, 0
编码为 orr x8, xzr, 0
mov x8, xzr
编码为 orr x8, xzr, xzr
别名很有用,因为它们使 ASM 更具可读性。
第二种编码演示了为什么使用零寄存器 xzr
会很有用。因为我们知道 xzr 始终为零,所以我们可以为 mov
重用 orr
指令。没有它,mov
将需要不同的编码,因此会浪费编码 space.
这个答案不是 "on all fours" 给 OP 的。
XZR 可用于丢弃结果;例如,"ldr xzr, [sp], 16"。请参阅下面的 GDB
0x7fffffef40: 0x00000000 0x00000000 0x00400498 0x00000000
0x7fffffef50: 0x00000000 0x00000000 0x00000000 0x00000000
ldr x0,=0xdead
(gdb)
ldr x1,=0xc0de
(gdb)
stp x0, x1, [sp, #-16]!
(gdb) x/8x $sp
0x7fffffef30: 0x0000dead 0x00000000 0x0000c0de 0x00000000
0x7fffffef40: 0x00000000 0x00000000 0x00400498 0x00000000
ldr xzr, [sp], #16
(gdb) x/8x $sp
0x7fffffef40: 0x00000000 0x00000000 0x00400498 0x00000000
0x7fffffef50: 0x00000000 0x00000000 0x00000000 0x00000000
还要记住,在 ARMv8 中,堆栈应该是四字对齐的或 SP mod 16 = 0。
因此,您可以使用 "pushed" 或 "popped" 对寄存器之一的 XZR。
stp x1, xzr, [sp, #-16]!
ldp x10, xzr, [sp], #16
我认为 mov x8, xzr
与 mov x8, #0
的比较是在转移注意力。
如@old_timer 的回答所示,没有编码增益,而且很可能(虽然我没有检查过)很少或没有管道性能增益。
然而,xzr
给了我们什么——除了根据@InfinitelyManic 的回答的虚拟寄存器——是访问零值操作数 ,而无需加载和占用真实寄存器。这具有减少一条指令和多一个可用于保存 'real' 数据的寄存器的双重好处。
我认为这是原文'some content from ARM' referred to in the OP忽略指出的重要特征
这就是我所说的 mov x8, xzr
与 mov x8, #0
的意思。如果我们将 x8
置零,然后修改它,那么使用 xzr
或 #0
是相当随意的(尽管我倾向于支持 #0
,因为明显的)。但是,如果我们将 x8
归零纯粹是为了向后续指令提供零操作数,那么我们最好使用 - 在允许的情况下 - xzr
而不是 x8
作为that 指令中的操作数,根本不归零 x8
。
我正在阅读来自 ARM 的 SVE whitepaper 并且遇到了一些让我感到奇怪的东西(在非 SVE 示例中):
mov x8, xzr
我不知道这个 xzr
寄存器是什么,所以我查了一下,发现 some content from ARM 说明它在许多情况下是零的同义词。
所以看起来 x8
被初始化为零,这是有道理的,因为它是在 x8
用作循环计数器的循环之前执行的。
我不明白的是,为什么不使用文字 0
而不是 xzr
?例如:
mov x8, 0
总而言之,我的问题是:为什么在这里使用 xzr
寄存器而不是文字 0
?
TL;DR
将 64 位文字加载到寄存器需要多条指令,但使用 zxr 设置为 0 只需一条指令。因此代码更短更快。
要将文字移动到寄存器,您可以使用 MOVL 指令,请参阅 arm 参考:
MOVL pseudo-instruction
Load a register with either:
A 32-bit or 64-bit immediate value. Any address.
MOVL generates either two or four instructions... a MOV, MOVK pair.
因此将文字加载到寄存器是一个多步骤过程。如果您只想清除寄存器,那么他们有一个快捷方式。 zxr 是一个始终读取零的伪寄存器,这是您需要的常用值,并且可以在一条指令中完成将一个寄存器移动到另一个寄存器。
在 Microchip 组装中,他们有类似的概念。要将寄存器设置为文字,您可以执行以下操作:
MOVLW 10 (Move 10 to the working register)
MOVWF 0x1234 (Move the working register to address 0x1234)
但是要设置为零,他们有指令:
CLRF 0x1234 (Set 0x1234 to zero)
mov x8,xzr
mov x8,#0
mov x8,0
生产
0000000000000000 <.text>:
0: aa1f03e8 mov x8, xzr
4: d2800008 mov x8, #0x0 // #0
8: d2800008 mov x8, #0x0 // #0
除了它允许没有井号的立即数之外,没有什么真正令人惊讶的。这不是指令大小问题(同样不足为奇,对于 x86,例如 xor rax,rax 比 mov rax,0 便宜),也许有流水线性能增益(尽管普遍认为指令需要一个以上的时钟开始完成) .
很可能这是个人喜好,我们有这个很酷的 mips,就像总是零寄存器的东西一样,我们只是为了好玩而使用它。
这两条指令在效果和预期性能方面应该是相同的。
它们实际上都是更通用指令的别名。
mov x8, 0
编码为 orr x8, xzr, 0
mov x8, xzr
编码为 orr x8, xzr, xzr
别名很有用,因为它们使 ASM 更具可读性。
第二种编码演示了为什么使用零寄存器 xzr
会很有用。因为我们知道 xzr 始终为零,所以我们可以为 mov
重用 orr
指令。没有它,mov
将需要不同的编码,因此会浪费编码 space.
这个答案不是 "on all fours" 给 OP 的。
XZR 可用于丢弃结果;例如,"ldr xzr, [sp], 16"。请参阅下面的 GDB
0x7fffffef40: 0x00000000 0x00000000 0x00400498 0x00000000
0x7fffffef50: 0x00000000 0x00000000 0x00000000 0x00000000
ldr x0,=0xdead
(gdb)
ldr x1,=0xc0de
(gdb)
stp x0, x1, [sp, #-16]!
(gdb) x/8x $sp
0x7fffffef30: 0x0000dead 0x00000000 0x0000c0de 0x00000000
0x7fffffef40: 0x00000000 0x00000000 0x00400498 0x00000000
ldr xzr, [sp], #16
(gdb) x/8x $sp
0x7fffffef40: 0x00000000 0x00000000 0x00400498 0x00000000
0x7fffffef50: 0x00000000 0x00000000 0x00000000 0x00000000
还要记住,在 ARMv8 中,堆栈应该是四字对齐的或 SP mod 16 = 0。 因此,您可以使用 "pushed" 或 "popped" 对寄存器之一的 XZR。
stp x1, xzr, [sp, #-16]!
ldp x10, xzr, [sp], #16
我认为 mov x8, xzr
与 mov x8, #0
的比较是在转移注意力。
如@old_timer 的回答所示,没有编码增益,而且很可能(虽然我没有检查过)很少或没有管道性能增益。
然而,xzr
给了我们什么——除了根据@InfinitelyManic 的回答的虚拟寄存器——是访问零值操作数 ,而无需加载和占用真实寄存器。这具有减少一条指令和多一个可用于保存 'real' 数据的寄存器的双重好处。
我认为这是原文'some content from ARM' referred to in the OP忽略指出的重要特征
这就是我所说的 mov x8, xzr
与 mov x8, #0
的意思。如果我们将 x8
置零,然后修改它,那么使用 xzr
或 #0
是相当随意的(尽管我倾向于支持 #0
,因为明显的)。但是,如果我们将 x8
归零纯粹是为了向后续指令提供零操作数,那么我们最好使用 - 在允许的情况下 - xzr
而不是 x8
作为that 指令中的操作数,根本不归零 x8
。