目标寄存器
Rn
第一个操作数的寄存器
shifter_operand
第二个操作数
其指令编码格式如下:
31-28
|
27-25
|
24-21
|
20
|
19-16
|
15-12
|
11-0 (12位)
|
cond
|
001
|
opcode
|
S
|
Rn
|
Rd
|
shifter_operand
|
当第2
个操作数的形式为:#immed_8r常数表达式时“该常数必须对应8位位图,即常数是由一个8位的常数循环移位偶数位得到的。”
其意思是这样:
#immed_8r在芯片处理时表示一个32位数,但是它是由一个8位数(比如:01011010,即0x5A)通过循环移位偶数位得到(1000
0000 0000 0000 0000 0000 0001 0110,就是0x5A通过循环右移2位(偶数位)的到的)。
而1010 0000 0000 0000 0000 0000 0001
0110,就不符合这样的规定,编译时一定出错。因为你可能通过将1011
0101循环右移位得到它,但是不可能通过循环移位偶数位得到。1011 0000 0000 0000 0000 0000 0001
0110,也不符合这样的规定,很明显:1 0110 1011 有9位。
为什么要有这样的规定?
要从指令编码格式来解释(这就是我为什么一开始讲的是指令编码格式),仔细看表格中的shifter_operand所占的位数:12位。要用一个12位的编码来表示任意的32位数是绝对不可能的(12位数有2^12种可能,而32位数有2^32种)。
但是又要用12位的编码来表示32位数,怎么办?
只有在表示数的数量上做限制。通过编码来实现用12位的编码来表示32位数。
在12位的shifter_operand中:8位存数据,4位存移位的次数。
8位存数据:解释了“该常数必须对应8位位图”。
4位存移位的次数:解释了为什么只能移偶数位。4位只有16种可能值,而32位数可以循环移位32次(32种可能),那就只好限制:只能移偶数位(两位两位地移,好像一个16位数在移位,16种移位可能)。这样就解决了能表示的情况是实际情况一半的矛盾。
所以对#immed_8r常数表达式的限制是解决指令编码的第二个操作数位数不足以表示32位操作数的无奈之举,但在我看来:这个可以说是聪明的做法。因为如果直接用12位数来表示32位操作数,只能表示0
到(2^12-1)。大于(2^12-1)的数就没办法表示了。而且细细想来“8位存数据,4位存移位的次数”,应该是最好的组合了(我并未想过所有的组合,只是顺便试了几个)。
ARM指令第二操作数#immed_8r详解
大多数ARM通用数据处理指令有一个灵活的第2操作数(flexible second
operand),这里这解释一下其中的一种格式,#immed_8r常量的表达式。常量必须对应于8位位图(pattern)。该位图在32位字中,被循环移位偶数位(0,2,4,...28,30)。合法常量0xff,0xff000,0xf000000f。非法常量:0x101,0xff04
ARM
在32位模式下,一条指令长度为32位,在上述数据处理指令中,操作数2为12位。所以像0x7f02这样的数,要两条指令才能完成。
MOV
R3, #0x7F00
;E3 A0 3C 7F 该指令自己完成0x7f移位
ORR
R1, R3, #2
所以直接是找不到0x7f02的
关于循环移位,其实arm中只有循环右移(ROR)。0x7f到0x7f00是通过循环右移24次才实现的,这里每次移动2位所以是12次(0xc)
在 ARM 数据处理指令中, 当参与操作的第二操作数为立即数时,
每个立即数都是采用一个8位的常数循环右移偶数位而间接得到,其中循环右移的位数有一个4位二进制的2倍表示. 则有效立即数可表示为:
:= immed_8; 循环右移(2×rotate_imm).
其中:
代表立即数,
immed_8
代表8位常数,
即所谓的'8位图',
rotate_imm
代表4位的循环右移值. 这样一来出现了一个问题: 尽管表示的范围变大了,
但是12位所能表现的数字的个数是一定的. 因此, ARM 规定并不是所有的32位常数都是合法的立即数,
只有通过上面的构造方法得到的才是合法的立即数, 编译的时候才不会报错.
举个例子吧.
0x3FC(0000 0000 0000 0000 0000 00
11 1111 1100) 是由 0xff
循环右移 2 位得到的;
200(0000 0000 0000 0000 0000 00
00 1100 1000) 是由 0xc8
循环右移 2 位得到的, 它们都是合法的.
而 0x1FE(0000 0000 0000 0000 0000 0001 1111 1110) 和
511(0000 0000 0000 0000 0000 0001 1111 1111)
无法看成是
8位的常数循环右移
偶数位而得到的,
因此是非法的.
指令操作数立即数时候,每个立即数由一个8位的常数循环右移偶数位得到。
= immed_8 循环右移( 2*rotate_imm)
打个比如:
1.立即数0xF200是由0xCF2间接表示的,即是由8位的0xF2循环右移24(2*12)得到的
immed_8 == 0xF2;
rotate_imm == 0xC
2.立即数0x3F0是由0xE3F间接表示的,即是由8位的0x3F循环右移28(2*14)得到的
immed_8 == 0x3F;
rotate_imm == 0xE
或者
立即数0x3F0是由0xFFC间接表示的,即是由8位的0xFC循环右移30(2*15)得到的
immed_8 == 0xFC;
rotate_imm == 0xF
表示方法有好几种
PS:其实你没必要一个一个的算,只要利用LDR伪指令就可以了,例如:
ldr r1, =12345678
编译器自然会给你做工作,现实的编程中应该也是这个居多吧
比较下来, 我们可以这样总结:
- 判断一个数是否符合8位位图的原则, 首先看这个数的二进制表示中1的个数是否不超过8个. 如果不超过8个,
再看这n个1(n<=8)是否能同时放到8个二进制位中, 如果可以放进去,
再看这八个二进制位是否可以循环右移偶数位得到我们欲使用的数. 如果可以, 则此数符合8位位图原理, 是合法的立即数. 否则,
不符合.
- 无法表示的32位数, 只有通过逻辑或算术运算等其它途径获得了. 比如0xffffff00,
可以通过0x000000ff按位取反得到.
因此以后的编程中, 时刻检查用到的第二操作数是否符合8位位图是一件千万不能疏忽的事.
下面给出两条判断依据,判断一个立即数是否合法
:
(1)首先把这个数用二进制表示出来,然后看这个数中“1”的最大间隔是多少,要看两次,一次是顺序看,一次是循环看,循环看是把16位或32寄存器的首尾连起来,越过首尾来看,两次中如果最大间隔都大于8(包含首尾的两个1),那这个数肯定是非法的。如果有一次小于等于8则有可能是合法的,可以进行下一步继续判断:
(2)此时又分为两种情况
- A.
如果顺序看时1的最大间隔等于8,此时可以看看,这个数最高位1的前面或者最低位1的后面是否有偶数个0,只要一种情况下有,这个数就是合法的。
- B.
如果循环看时1的最大间隔小于等于8,此时可以看看,循环看时,两端得到的间隔个数是否有一个为偶数,如果有一个是偶数,这个数就是合法的。
举例:
0xFF=0000 0000 0000 0000 0000 0000
1111
1111B,首尾的两个1间隔不大于8,符合(2)A的情况,是有效立即数。
0x104=0000 0000 0000 0000 0000 000
1 0000
0100B,也符合(2)A的情况,是有效立即数。
0xFF0、0xFF00、0xFF000、0xFF000000、0xF000000F都是有效立即数。
反例:
0x101=0000 0000 0000 0000 0000 000
1 0000
0001,首尾两个1间隔大于8(也就是说,包含这两个1的所有位数加起来是9个数,无法放到一个8位地址中),由条件(1)判断,是无效立即数。
0x102=0000 0000 0000 0000 0000 000
1 0000
0010,这个例子特殊,但是可以看出,由条件(2)A判断,是无效立即数。
0xFF1、0xFF04、0xFF003、0xFFFFFFFF、0xF000000F都是无效立即数。
还有一个要说的就是:
只要该数,可以通过0x00-0xFF中某个数,循环右移偶数位而产生,就是合法的mov的操作数,否则就是非法的mov的操作数。