投稿人:Jacob Bramley,2010 年 7 月 16 日
原文链接:http://blogs.arm.com/software-enablement/206-condition-codes-1-condition-flags-and-codes/
现实中,每一种通用计算架构都存在有条件执行某些代码的机制。此类机制用于实施 C 语言中的 if 结构,也有其他一些不那么明显的情形。
if
ARM 与许多其他架构一样,使用存储前一运算相关状态信息的一组标志来实施有条件执行。在这篇博文中,我想就这些标志的运作进行一些探讨。当然,架构参考手册是权威的信息来源,若要了解一些此处未予涵盖的特例,您需要查阅该手册。
考虑下面这段简单的 C 代码:
for (i = 10; i != 0; i--) {
do_something();
}
编译器可能会按照如下所示实施该结构:
mov r4, #10
loop_label:
bl do_something
sub r4, r4, #1
cmp r4, #0
bne loop_label
最后两条指令尤令人注意。cmp (比较)指令将 r4 与 0 对比,而 bne 指令仅仅是一个 b (分支)指令,只有 cmp 指令的结果是“不相等”时才会执行。该代码可以运作,因为 cmp 设置了一些表明运算各种属性的全局标志。bne 指令实际上只是一条带有 ne 条件代码后缀的 b (分支)指令,它读取这些标志来判断是否要进行分支 1。
cmp
r4
0
bne
b
ne
下列代码实施更高效的解决方案:
subs r4, r4, #1
添加 s 后缀到 sub 使它可以根据运算的结果对标志自身进行更新。此后缀可以添加到许多(但不是全部)算术和逻辑运算 2。
s
sub
在本文的其余部分中,我将说明条件标志是什么,它们存储在哪里,以及如何使用条件代码测试它们。
如果您有 ARM 平台(或模拟器),可以使用随附的 ccdemo 应用程序试验本文所述的运算。该应用程序允许您取一个运算和两个运算对象,显示生成的标志,以及相匹配的条件代码列表。在编写汇编代码时,它也是一款比较有用的开发工具。
ccdemo
设置条件标志的最简单方式是使用比较运算,如 cmp。这一机制对许多处理器架构都通用,而 cmp 的语义(如果不是细节)可能也熟悉。此外,我们也已看到,许多指令(如示例中的 sub )可以通过添加 s 后缀进行修改,以根据结果来更新条件标志。那当然没错,但存储的信息是什么,我们如何访问呢?
额外的信息存储在 APSR(应用处理器状态寄存器)中的四个条件标志位中(或者,如果您习惯使用 ARMv7 前术语 3, 4 的话,叫做 CPSR (当前处理器状态寄存器))。这些标志表示简单的属性,如结果是否为负,并在各种组合中用于检测更为高级的关系,如“大于”和同类。我说完标志后,就会说明它们如何映射到条件代码(如前例中的 ne)。
APSR
CPSR
N
如果结果为负,则某一指令会设置 N 标志。在实践中,N 设为结果的二进制补码符号位(第 31 位)。
Z
如果标志设置指令的结果为零,则设置 Z 标志。
C
如果无符号运算溢出 32 位结果寄存器,则设置 C 标志。例如,此位可用于实施 64 位无符号运算。
V
V 标志的运作原理与 C 标志相同,但用于有符号运算。例如,0x7fffffff 是 32 位中可以表示的最大二进制补码正整数,因此 0x7fffffff + 0x7fffffff 将触发有符号溢出,而不是无符号溢出(或进位):其结果 0xfffffffe 在解释为无符号数时是正确的,而在解释为有符号数时则表示负值 (-2)。
0x7fffffff
0x7fffffff + 0x7fffffff
0xfffffffe
-2
请考虑以下示例:
ldr r1, =0xffffffff
ldr r2, =0x00000001
adds r0, r1, r2
运算结果应该是 0x100000000,但最高位由于无法装入 32 位目标寄存器而丢失,因此实际的结果是 0x00000000。此情形中,标志将设置为如下:
0x100000000
0x00000000
标志
说明
N = 0
结果为 0(视为正数),所以 N (负)位设置为 0。
Z = 1
结果为 0,所以 Z (零)位设置为 1。
1
C = 1
我们丢失了一些数据,因为结果无法装在 32 位中,因此处理器通过将 C (进位)设置为 1 来表示。
V = 0
从二进制补码有符号运算角度而言,0xffffffff 实际含义是 -1,所以我们做的运算实际上是 (-1) + 1 = 0。该运算显然不会溢出,所以 V (溢出)设置为 0。
0xffffffff
-1
(-1) + 1 = 0
如果喜欢的话,您可以用 ccdemo 应用程序进行检查。输出可能会如下所示:
$ ./ccdemo adds 0xffffffff 0x1
The results (in various formats):
Signed: -1 adds 1 = 0
Unsigned: 4294967295 adds 1 = 0
Hexadecimal: 0xffffffff adds 0x00000001 = 0x00000000
标志:
N (negative): 0
Z (zero) : 1
C (carry) : 1
V (overflow): 0
Condition Codes:
EQ: 1 NE: 0
CS: 1 CC: 0
MI: 0 PL: 1
VS: 0 VC: 1
HI: 0 LS: 1
GE: 1 LT: 0
GT: 0 LE: 1
我们已讨论了如何设置标志,但这如何能够有条件地执行某些代码呢?如果您不能对此作出回应,能够设置标志就毫无意义。
测试标志的最常用方式是使用有条件执行代码。此机制与其他架构中使用的机制相似;因此,如果熟悉其他机器的话,您可能会认识下列模式,其与 C 的 if/else 结构正好对应:
if/else
cmp r0, #20
bhi do_something_else
do_something:
@ This code runs if (r0 <= 20).
b continue @ Prevent do_something_else from executing.[JY1]
b continue @ Prevent do_something_else from executing.
do_something_else:
@ This code runs if (r0 > 20).
continue:
@ Other code.
实际上,将其中一个条件代码附加到某条指令上,如果条件为真,会导致其执行。否则,它不执行任何运算,基本上是 nop。
nop
下表罗列了可用的条件代码、其含义(标志通过 cmp 或subs 指令设置),以及所测试的标志:
subs
代码
含义(对于 cmp 或 subs)
测试的标志
eq
等于。
Z==1
不等于。
Z==0
cs or hs
cs
hs
无符号大于或等于(或设置进位)。
C==1
cc or lo
cc
lo
无符号小于(或清除进位)。
C==0
mi
负值。助记符代表“负号”。
N==1
pl
正数或零。助记符代表“正号”。
N==0
vs
有符号溢出。助记符代表“设置 V”。
V==1
vc
无符号溢出。助记符代表“清除 V”。
V==0
hi
无符号大于。
(C==1) && (Z==0)
ls
无符号小于或等于。
(C==0) || (Z==1)
ge
有符号大于或等于。
N==V
lt
有符号小于。
N!=V
gt
有符号大于。
(Z==0) && (N==V)
le
有符号小于或等于。
(Z==1) || (N!=V)
al (or omitted)
al
始终执行。
未测试。
前几个的运作方式比较明显,因为它们测试单个的标志,其余则取决于具体的标志组合。在实践中,您极少需要确切了解发生的状况;助记符隐藏了比较的复杂性。
这里,再次提供之前给过的 -loop 代码的示例:
现在应当足够容易地了解此处的确切情况了:
subs 指令根据“r4-1”的结果设置标志。特别是,如果结果是 0,则设置 Z 标志,而结果为任何其他值时则清除该标志。
r4-1”
只有 ne 条件为真时,才会执行 bne 指令。如果 Z 被清除,则该条件为真,因此 bne 将迭代该循环,直到设置了 Z (所以 r4 为 0)。
cmp 指令(我们在第一个示例中看到的)可以被视为不存储其结果的 sub 指令:如果两个运算对象相等,则相减的结果为零,因而在eq 和 Z 标志之间映射。当然,我们可以仅将 sub 指令用于一个虚拟寄存器,但只有存在空余寄存器时您才能这么做。因此,专用比较指令常被使用。
实际上有四个可用的专用比较指令,它们执行的运算如下表所述:
指令
作用与 subs 相似,但不存储结果。
cmn
作用与 adds 相似,但不存储结果。
adds
tst
作用与 ands 相似,但不存储结果。
ands
teq
作用与 eors 相似,但不存储结果。
eors
请注意,专用比较运算不要求 s 后缀;它们仅仅更新标志,因此后缀是多余的。
尽管条件标志机制原理比较简单,但需要考虑许多细节,查看一些实际示例或许很有用处!我决定在未来的博文中展示一些实际运用的例子。
ccdemo.tar.gz (4.4K) 下载数: 801
2http://community.arm.com/docs/DOC-8527
从技术角度而言,大多数指令都可有条件执行,而不仅仅是分支指令。不过,我将在其他文章中更加详细地探讨此类条件执行。
2
指令集快速参考卡总结了各个指令的标志设置能力。架构参考手册中包含有关到底如何为各个指令更新标志的详细信息。
3
尽管各有不同名字,APSR 和 CPSR 在 ARMv7 上其实是相同的;不过,对于 APSR,只为其定义了条件代码和一两个其他位。这些其他位不应以任何方式直接访问,因此重新命名基本上是对旧式混合访问 CPSR 的清理。但请注意,GCC(至少 4.3.3)不接受 APSR;因此,如果您要访问,必须在汇编源代码中使用 CPSR。
4
通常,极少情况下才需要直接访问 APSR ,因为条件代码已经给予您通常需要从中获得的功能。然而,如果确实要查看其中有些什么,您可以通过 msr 和 mrs 指令访问。这实际上是 ccdemo 应用程序用来提供指定指令相关信息的方式。
msr
mrs
本篇文章的短链接:http://bit.ly/9XZAC0