|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
如果说Intel指令中的立即数,相信大家都很熟悉。类似的,ARM指令中的“立即数”就是常数表达式。之所以称为常数表达式,而不称为立即数是有原因的。
' ]- i: O: v/ d/ HIntel指令属于CISC指令集,指令是不定长的,因此可以将任意32位立即数编码到指令内。" t7 ?; M! Z3 O% w4 a: c
+ g# C, {7 d8 l3 g
+ x; \; `+ x$ X; t& k) c- L; Q! d3 C
% B {# [$ r: J3 t# g" `6 ?
Arm指令属于RISC指令集,指令是定长的32字节。众所周知,指令中操作码是必须的字段,如果把32位立即数直接编码到指令内部,操作码就无“容身之地”了……( n+ y6 s! `7 P8 [/ U7 k
6 l4 x: l9 M, i- ?6 ~& j ^8 n% J( a& i3 ~, s$ \0 ^2 d
/ y, Z5 C0 p, w* T/ h0 R因此,Arm指令中“立即数”的位数必小于32位。那么如何在Arm指令中正常表示立即数呢?我们看看Arm的通用指令格式。
# X9 K& q7 f" l0 U
5 I5 E" p3 \! L6 W+ N2 o6 j" ?) A3 a& ~7 D4 H
, ^; W/ x; S/ M l" q0 m9 Z
Arm指令中,操作码(opcode)、目的操作数(Rd)、源操作数1(Rn)是必须的字段。条件码(cond)、符号位标记(s)源操作数2(oprand2)是可选的。其中Rd和Rn必须是寄存器,因此Arm的“立即数”只能存储在oprand2。1 p+ H5 y* N. b3 D5 x
- v6 D: }5 a+ i. U f* z
3 j8 F6 r9 u1 M' A
在Arm的指令编码内,使用“立即数”的指令为“立即数”提供了12bit的存储空间,也就是说Arm的“立即数”只能表示212=4096个数字。这显然无法表示所有的32位立即数,如果使用这12bit表示0~4095的数字,那么从4096~(232-1)的数组都不能表示了。考虑到这种“后果”,Arm指令集的设计者们使用了一个技巧,即使用常数表达式代替立即数的概念。
0 ^1 ]4 i1 ^. ?常数表达式表示一个常数,且该常数对应8位的位图,即可以由一个8位的常数通过循环移位偶数位得到。
# M. U4 A! L/ I+ _5 V$ N4 f. F: `, T* G
1 Y: i% r- y. O; ~( `9 w4 e( W比如0x3fc可以由8位常数0xff循环左移2位或循环右移30位得到,是常数表达式。再比如0x1fe,虽然可以有0xff循环左移1位或循环右移31位得到,但是移动的位数不是偶数,因此不是常数表达式。# ~6 j' q7 R _0 ` ?
根据常数表达式的定义,常数表达式只需要12bit的存储空间。
1 t: k y% o% G5 E8 q% ~! }
% n( m6 X3 Q- ~
/ U6 U+ S& S* J! B2 H3 ^) j+ K. d12bit空间中,低8字节存储8位位图,高4字节存储循环右移的次数。4字节只能表示24=16种移动次数,但是由于常数表达式定义中,移位限定为偶数次,因此这4个字节刚好能表示0、2、4、6、8…32位16个数字!6 y3 G# t) ` i
比如0x3fc的二进制表示为0xfff,即使用8位数字0xff循环右移0xf*2=30次得到。
9 J6 m* i! F( H( D+ n明确了常数表达式的含义后,可以通过简单的编程识别一个数字是否是常数表达式。$ l0 O' M' b. R% S: e: G
/** u$ \/ a+ c. U, `" z; h6 P$ C- g
循环左移两位
- N, I) N, l' Q/ z*/
! Q! }+ c1 u Q$ Z# w1 @void roundLeftShiftTwoBit(unsigned int& num)6 g2 U& _. O7 F5 ?1 p2 v/ q' i' O
{( I6 \4 l+ N" G, P3 u
unsigned int oveRFlow=num & 0xc0;//取左移即将溢出的两位
' ]& g" K" X4 b7 ?# _num=(num<<2) (overFlow>>30);//将溢出部分追加到尾部
; k; h3 L# K; `' j5 A/ x}: M) g. x' ~ Z8 Z4 i% i0 h# G
/*7 }" `% p; L5 R/ k4 z0 E
判断num是否是常数表达式,8位数字循环右移偶数位得到/ N/ [( J& J. J$ R6 }' k
*/
2 O3 y( j& m' t9 x( pbool constExpr(unsigned int num)/ |4 G6 H0 a7 a
{
2 u" ]( }0 ?% C1 ?for(int i=0;i<16;i++){
* J& a0 s! _, R9 J0 i, `* Y! Qif(num<=0xff)
0 A4 N/ F3 L6 a( x1 G" E6 y1 freturn true;//有效位图
: k+ h3 R6 S2 N1 M6 q9 i0 Lelse0 e" J9 I' l( |/ O# D1 N
roundLeftShiftTwoBit(num);//循环左移2位
* |# q- o: G) T5 k! i3 Z1 p}4 w/ @9 }. }' j4 @" `' F( p
}
( p: \+ d1 M; f& KroundLeftShiftTwoBit函数将一个无符号32位整数循环左移2位,constExpr反复将一个无符号32位整数循环左移2位,直到发现该数字在0到0xff之间为止,否则表示该数字不是常数表达式。
1 r# J. i4 u0 @6 X" O通过常数表达式,Arm指令便避免了“立即数”仅仅局限于0~4095的数字范围的问题了。但是常数表达式仍不能表示全部的32位整数,比如0x1fe。对于非常数表达式的数字,只能通过拆分来解决。
^7 l+ U8 E1 B& ?6 h" c( _, D比如Arm指令mov r0,#0x1fe不是合法的指令,因为0x1fe不是常数表达式。那么只能将0x1fe拆分,表示为两条指令,比如: E: z/ `& \- M+ ]' Y5 I6 H
mov r0,#0xfe
% C# v$ U7 W; Q- E: U" iadd r0,r0,#0x1002 n+ g% u7 B* J5 j5 h: r
除了拆分数字的方法,Arm的LDR宏使用临时变量的方式实现非常数表达式的数据传送,即:
* b! x @. z6 `3 w6 E* fLDR r0,=0x1fe
! z! m7 i4 c' y5 v; p- aldr宏指令不是Arm的真正指令,它由如下两条指令实现:
: u0 R! w6 k# O$ a2 R& |.tmp .word 0x1fe
0 V( [: q9 v4 Z4 Q8 [% ^ldr r0,.tmp8 A# R$ M5 W( c) M
ldr r0,[r0]
& k8 q8 ~0 d }3 y/ k1 h' SArm的ldr指令用于将内存的数据加载到寄存器。这里数字0x1fe被存储在ldr指令附近的位置.tmp处,ldr r0,.tmp被编译为ldr r0,[pc+offset]的形式,其中offset为符号.tmp相对ldr指令的偏移。这样ldr r0,.tmp获取了符号.tmp的地址,然后再次使用ldr r0,[r0]将.tmp地址处的数据取出,即0x1fe。
( R7 R7 ~' ^0 ~! ]: _2 U) J通过以上的描述,我们明确了Arm指令中常数表达式的真正含义,并基于此编写了一个验证常数表达式的函数。最后,我们解析了如何在Arm中实现非常数表达式数据的传送,并讨论了它的实现。希望对你理解Arm的常数表达式有所帮助。9 L: T/ Y, G1 l0 R% G
|
|