|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
如果说Intel指令中的立即数,相信大家都很熟悉。类似的,ARM指令中的“立即数”就是常数表达式。之所以称为常数表达式,而不称为立即数是有原因的。! t6 B2 Q% F+ z" ]
Intel指令属于CISC指令集,指令是不定长的,因此可以将任意32位立即数编码到指令内。
3 `- V0 s. z, b) }' Q9 \, \$ l" v# p& H, C1 s
# ^- l/ D) a. H, M: h
5 I8 F9 v% `8 b& @$ \6 d% C* k8 _Arm指令属于RISC指令集,指令是定长的32字节。众所周知,指令中操作码是必须的字段,如果把32位立即数直接编码到指令内部,操作码就无“容身之地”了……
& _6 _! C( y7 H3 ~# J! r) n5 a9 G1 w/ D+ q! r
& i7 V4 O& i1 G+ W6 L
, E9 B' B* h, |因此,Arm指令中“立即数”的位数必小于32位。那么如何在Arm指令中正常表示立即数呢?我们看看Arm的通用指令格式。
1 q1 R! k- m8 E& C# R/ G' Y
% g. B+ R2 X: i+ P# h. U
3 F5 y$ a4 R% s5 x, h
6 J. P- w9 v% g! M, l( w# w2 N5 [3 }Arm指令中,操作码(opcode)、目的操作数(Rd)、源操作数1(Rn)是必须的字段。条件码(cond)、符号位标记(s)源操作数2(oprand2)是可选的。其中Rd和Rn必须是寄存器,因此Arm的“立即数”只能存储在oprand2。3 j* u' A, K. t( ~ f. d
: E& a9 `. Z# M% t
$ t# C V/ a' m* u8 C) g4 T. u在Arm的指令编码内,使用“立即数”的指令为“立即数”提供了12bit的存储空间,也就是说Arm的“立即数”只能表示212=4096个数字。这显然无法表示所有的32位立即数,如果使用这12bit表示0~4095的数字,那么从4096~(232-1)的数组都不能表示了。考虑到这种“后果”,Arm指令集的设计者们使用了一个技巧,即使用常数表达式代替立即数的概念。
9 `8 J: A! l; v- p% h3 w/ V常数表达式表示一个常数,且该常数对应8位的位图,即可以由一个8位的常数通过循环移位偶数位得到。
& ?7 @0 @1 c! Z7 f' _5 s$ N# D8 T
0 B% q1 r7 B2 V" V4 p2 T# M3 R. t s- m, C
比如0x3fc可以由8位常数0xff循环左移2位或循环右移30位得到,是常数表达式。再比如0x1fe,虽然可以有0xff循环左移1位或循环右移31位得到,但是移动的位数不是偶数,因此不是常数表达式。3 N! A/ Y' t+ v- _4 l. s# }- Q" T
根据常数表达式的定义,常数表达式只需要12bit的存储空间。( J$ b' s& Z) x1 i) I
: V; L, x3 l, S; _- P) o5 j3 ~' ~4 i; j' A4 Y6 q p% T
12bit空间中,低8字节存储8位位图,高4字节存储循环右移的次数。4字节只能表示24=16种移动次数,但是由于常数表达式定义中,移位限定为偶数次,因此这4个字节刚好能表示0、2、4、6、8…32位16个数字!
* `: p; c2 G9 b& l, X比如0x3fc的二进制表示为0xfff,即使用8位数字0xff循环右移0xf*2=30次得到。
/ q1 B( k0 f1 A! v& Q$ _9 |明确了常数表达式的含义后,可以通过简单的编程识别一个数字是否是常数表达式。4 P+ i5 C& w. I% b1 d% P; c8 E) H
/*2 L5 }, b' @8 }& j, f
循环左移两位- j) a5 J7 F# _$ ?, Q# X4 n9 J
*/6 v8 |: _ H T8 y! [
void roundLeftShiftTwoBit(unsigned int& num): R0 F. M- O7 S: N
{
+ A2 Y' k* @$ Z6 r# xunsigned int oveRFlow=num & 0xc0;//取左移即将溢出的两位9 d" _5 S4 h, {: R7 x/ X- e
num=(num<<2) (overFlow>>30);//将溢出部分追加到尾部
1 ]0 v A) c: p a}
' V; q! W) V% J$ u6 ?/ O; V- q( e7 Q r! k/*: d9 A4 o" X0 i( l% t- W4 k
判断num是否是常数表达式,8位数字循环右移偶数位得到2 Y* l9 z, Q# J1 P/ f3 @
*/& P+ ~* X8 p, e! a2 H& ]& z
bool constExpr(unsigned int num)
# |2 M6 q5 F5 x+ z{
- `, J5 a, C# C" Ffor(int i=0;i<16;i++){
. @; Q* |$ _( m8 L& a/ [9 v* Qif(num<=0xff)% F/ a9 b( X( r8 c
return true;//有效位图$ ^$ S+ F* _: b) R4 g, F
else
0 U# v0 M% o/ z) P C- mroundLeftShiftTwoBit(num);//循环左移2位6 }9 @3 I2 B! G y0 M: w
}
6 a7 _+ r8 c7 |}
7 E' r* e& M. D% o' ^0 u! U6 X5 CroundLeftShiftTwoBit函数将一个无符号32位整数循环左移2位,constExpr反复将一个无符号32位整数循环左移2位,直到发现该数字在0到0xff之间为止,否则表示该数字不是常数表达式。
& T, J: q7 R. Y通过常数表达式,Arm指令便避免了“立即数”仅仅局限于0~4095的数字范围的问题了。但是常数表达式仍不能表示全部的32位整数,比如0x1fe。对于非常数表达式的数字,只能通过拆分来解决。
% r) [# G3 C. h5 m/ q! x6 e. F; c比如Arm指令mov r0,#0x1fe不是合法的指令,因为0x1fe不是常数表达式。那么只能将0x1fe拆分,表示为两条指令,比如:/ e+ `; j3 g% H8 h5 w) E
mov r0,#0xfe7 \, d. N/ B/ ]
add r0,r0,#0x100, ]& A$ j9 E7 a [& ~4 M. b) y
除了拆分数字的方法,Arm的LDR宏使用临时变量的方式实现非常数表达式的数据传送,即:- ?2 I5 w/ D" Q' _. _0 k" ^% i
LDR r0,=0x1fe# n A! y* w3 b j
ldr宏指令不是Arm的真正指令,它由如下两条指令实现:( F8 L+ Z- t. @ W/ x& J7 s% _' Q% o
.tmp .word 0x1fe3 Q0 O0 l* a' ?- Y
ldr r0,.tmp
4 G: g/ I, y$ `, ?- \ kldr r0,[r0]
7 T! I {# e) g/ U6 C# e4 W4 ~Arm的ldr指令用于将内存的数据加载到寄存器。这里数字0x1fe被存储在ldr指令附近的位置.tmp处,ldr r0,.tmp被编译为ldr r0,[pc+offset]的形式,其中offset为符号.tmp相对ldr指令的偏移。这样ldr r0,.tmp获取了符号.tmp的地址,然后再次使用ldr r0,[r0]将.tmp地址处的数据取出,即0x1fe。
- L+ e, A z! U通过以上的描述,我们明确了Arm指令中常数表达式的真正含义,并基于此编写了一个验证常数表达式的函数。最后,我们解析了如何在Arm中实现非常数表达式数据的传送,并讨论了它的实现。希望对你理解Arm的常数表达式有所帮助。
. \2 g- N' J6 T2 p' q' ]7 d3 a& P |
|