我试图制作一个程序来使用 Armv7 Assembly 来闪烁 RPI3 b+,并注意到它无法使用此代码来实现延迟功能
delay:
b loop
loop:
add r10, r10, #1
cmp r10, r4
bne loop
beq return
return:
mov r10, #0
bx lr
r10 是用于计数器的寄存器,r4 包含 r10 需要到达才能停止并返回主代码。 看了教程后,我发现他们对计数器寄存器进行了异或运算,我添加了更正,现在代码如下所示。
delay:
eor r10, r10, r10
b loop
loop:
add r10, r10, #1
cmp r10, r4
bne loop
beq return
return:
mov r10, #0
bx lr
我已经编译,将其加载到 rpi3 中,现在它可以工作了,但是为什么我必须添加该行,我知道这些是什么异或门,但如果两个输入相等,它将返回完全相同的值。 这个操作的意义是什么?
TL:DR: 异或相同,相同类似于
sub same,same
,产生零。
本教程不好,ARM 或任何 RISC ISA 上的异或归零也不好。仅在 x86 asm(和 8080)中使用它,不能在其他 ISA 的 asm 中使用,也不能在高级语言中使用。
但是如果两个输入相等,它将返回完全相同的值。
不,那是常规的非排他或。 XOR 给出不同的位。当两个输入相同时,结果为
0
。
异或归零仅在 x86 上很好。 (请参阅 在 x86 汇编中将寄存器设置为零的最佳方法是什么:xor、mov 或 and? 了解详细原因)。 这些原因都不适用于 ARM:
mov reg, #0
的机器代码大小与 eor reg,reg,reg
相同,因此没有历史原因支持 EOR 作为现代 CPU 特有的“归零习惯用法”。
(即使在 Thumb 代码中也是如此,尽管在这种情况下您需要
movs reg, #0
进行较小的编码,至少对于 r0-r7。r8-r14 需要 4 字节 Thumb2 编码,无论是否设置标志。)
事实上,ARM CPU 在架构上甚至不允许优化eor dst, same,same
来打破错误的依赖关系,因为内存依赖关系排序规则需要 EOR 和其他操作来承载依赖关系。 (例如,使用
std::memory_order_consume
负载的结果。)并不是说他们会费心花费晶体管并为其供电,因为当
mov reg, #0
工作得很好时,ARM 机器代码没有理由首先使用它。
所以eor r10, r10, r10
明显比
mov r10, #0
差。永远不要使用它,除非你
想要一个依赖于R10旧值的0
。如果你不知道这意味着什么,那么你就不会想要它;它仅在加载结果(如
data_ready
标志)上的多线程代码中有用,或者在微基准实验中有用,以测试无序调度,或通过生成具有数据依赖于某些结果的常量值来测试延迟与吞吐量。
mov ax, 0
相比,它节省了一个字节的机器代码大小,在 32 位模式下节省了 3 个字节,因此现实世界的代码在任何地方都使用它。后来的 CPU 不断发展,即使在乱序执行的情况下也仍然高效,否则读取寄存器的旧值作为输入将成为问题。 (与
mov reg, 0
不同,即使没有任何特殊支持,我们也希望不会有错误的依赖关系。
mov
总是打破依赖关系;xor same,same 在 x86 上的特殊大小写只是使其以这种方式相等。xor-在 x86 上,归零在其他方面更好。)
考虑到该错误(缺少将循环计数器归零)和两个无用的
b next_instruction
指令,这不是一个高效代码的示例。即使您不
b
或
beq return
,执行也会继续执行下一条指令。大多数条件分支应该只是一个比较分支,而另一条执行路径是失败分支。对于初学者代码来说,将具有相反条件的另一个分支一个接一个地放置在某种程度上是一种反模式。或者使循环的底部成为
while(1) { if(cond)break }
而不是仅仅
do{}while(cond);
- 在循环中至少无用的分支位于循环之外。但这是一个延迟循环,它的存在只是为了浪费时间,所以实际上它只是浪费代码大小并改变每计数周期延迟因子。如果在这两种情况下都需要执行到其他地方(即两个可能的目标都在应该落入其中的其他代码之后),那么第二个分支应该是无条件的
b
。而且你应该永远不编写一个按源代码顺序跳转到下一条指令的分支,因为即使没有分支,执行也会转到那里。