为什么在 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, xzrmov x8, #0 的比较是在转移注意力。

如@old_timer 的回答所示,没有编码增益,而且很可能(虽然我没有检查过)很少或没有管道性能增益。

然而,xzr 给了我们什么——除了根据@InfinitelyManic 的回答的虚拟寄存器——是访问零值操作数 ,而无需加载和占用真实寄存器。这具有减少一条指令和多一个可用于保存 'real' 数据的寄存器的双重好处。

我认为这是原文'some content from ARM' referred to in the OP忽略指出的重要特征

这就是我所说的 mov x8, xzrmov x8, #0 的意思。如果我们将 x8 置零,然后修改它,那么使用 xzr#0 是相当随意的(尽管我倾向于支持 #0,因为明显的)。但是,如果我们将 x8 归零纯粹是为了向后续指令提供零操作数,那么我们最好使用 - 在允许的情况下 - xzr 而不是 x8 作为that 指令中的操作数,根本不归零 x8