|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
如果说Intel指令中的立即数,相信大家都很熟悉。类似的,ARM指令中的“立即数”就是常数表达式。之所以称为常数表达式,而不称为立即数是有原因的。
- f# t! k4 W& Q4 cIntel指令属于CISC指令集,指令是不定长的,因此可以将任意32位立即数编码到指令内。
" _, c; j1 _9 i, X7 B: T
5 H2 `. `# j% t( j3 A3 c4 A/ w2 M+ O
0 P X1 [, v4 q' a& E9 y. \ S4 W0 H, v" }
Arm指令属于RISC指令集,指令是定长的32字节。众所周知,指令中操作码是必须的字段,如果把32位立即数直接编码到指令内部,操作码就无“容身之地”了……0 L" c: Z3 |' B. k' v9 t' m
( F% t2 p [8 e; u" W
' Y$ Z N3 v6 h# y( J6 G1 b( z7 b6 c8 c) H2 o0 Z9 m) g
因此,Arm指令中“立即数”的位数必小于32位。那么如何在Arm指令中正常表示立即数呢?我们看看Arm的通用指令格式。/ d1 U' _6 E% T+ j- l
) Z5 d4 ?& G" P- |
# l1 l# u% R3 _# z0 I. T$ N
) u7 f" W+ X+ O; }8 N2 sArm指令中,操作码(opcode)、目的操作数(Rd)、源操作数1(Rn)是必须的字段。条件码(cond)、符号位标记(s)源操作数2(oprand2)是可选的。其中Rd和Rn必须是寄存器,因此Arm的“立即数”只能存储在oprand2。
- t! Z6 z7 b. l& j- X2 `2 R( U! o ) U6 j' o; l: N2 A3 i
4 Y2 L W/ `3 g& Z9 ^* f$ ^+ v
在Arm的指令编码内,使用“立即数”的指令为“立即数”提供了12bit的存储空间,也就是说Arm的“立即数”只能表示212=4096个数字。这显然无法表示所有的32位立即数,如果使用这12bit表示0~4095的数字,那么从4096~(232-1)的数组都不能表示了。考虑到这种“后果”,Arm指令集的设计者们使用了一个技巧,即使用常数表达式代替立即数的概念。
; s( }, |# i3 T6 t3 F常数表达式表示一个常数,且该常数对应8位的位图,即可以由一个8位的常数通过循环移位偶数位得到。 4 O# W- D8 l1 X+ c# g
) o+ g9 ~! ?4 `0 G1 T: g
6 @: J7 J1 d" J* N3 K6 r
比如0x3fc可以由8位常数0xff循环左移2位或循环右移30位得到,是常数表达式。再比如0x1fe,虽然可以有0xff循环左移1位或循环右移31位得到,但是移动的位数不是偶数,因此不是常数表达式。* x/ v( G; l0 H- {; j$ ~% Z, j" u
根据常数表达式的定义,常数表达式只需要12bit的存储空间。
3 i: G' c- E) A9 u# Z" z/ |6 U0 U 5 Z$ N$ j+ T3 a" v3 {4 H1 [
3 M; H1 i( f# j/ p8 N$ p5 I5 Z12bit空间中,低8字节存储8位位图,高4字节存储循环右移的次数。4字节只能表示24=16种移动次数,但是由于常数表达式定义中,移位限定为偶数次,因此这4个字节刚好能表示0、2、4、6、8…32位16个数字! Y! t& Z6 p. M# s1 t
比如0x3fc的二进制表示为0xfff,即使用8位数字0xff循环右移0xf*2=30次得到。& d4 j) b& y5 p+ P8 u7 O" I
明确了常数表达式的含义后,可以通过简单的编程识别一个数字是否是常数表达式。
, U3 t: Q# k/ j w/*
# }, n/ h. c3 h" M- w+ g循环左移两位
" G9 }3 M5 H% T4 t- U3 R& @; b*// a7 S7 ?# _+ D5 w3 O
void roundLeftShiftTwoBit(unsigned int& num)
7 k# q: _, J! o3 \; G# n{
+ v4 j# B; I& o* U6 v4 S( funsigned int oveRFlow=num & 0xc0;//取左移即将溢出的两位
! W. v# X7 z4 \num=(num<<2) (overFlow>>30);//将溢出部分追加到尾部
L5 U h% O' z' j; A" s}' z3 L9 s3 z2 n4 \
/*
/ v/ S1 K& h2 Q& L0 U p判断num是否是常数表达式,8位数字循环右移偶数位得到4 R' {3 j+ @8 x9 S) P1 I
*/! }- t% |0 S" m
bool constExpr(unsigned int num)
6 K5 x4 P$ S. _; c& ^+ j3 e/ Q{
& o/ ^! }2 [- |% d% Q, {for(int i=0;i<16;i++){* d0 s+ P9 `1 X$ e5 d
if(num<=0xff)9 C4 L9 U4 G4 ^8 i5 {; i, J
return true;//有效位图% C/ F; ]! b' ?0 n, G
else
3 c0 L9 x. I4 wroundLeftShiftTwoBit(num);//循环左移2位! C& X4 t8 q3 t. ~1 L. G& c
}
' P& }6 V p2 Q# Z. V; p* t" Y% {}
" u- t. c' F) a4 f- b/ H# K ProundLeftShiftTwoBit函数将一个无符号32位整数循环左移2位,constExpr反复将一个无符号32位整数循环左移2位,直到发现该数字在0到0xff之间为止,否则表示该数字不是常数表达式。+ T4 N! }8 `1 x# Y3 ? Y
通过常数表达式,Arm指令便避免了“立即数”仅仅局限于0~4095的数字范围的问题了。但是常数表达式仍不能表示全部的32位整数,比如0x1fe。对于非常数表达式的数字,只能通过拆分来解决。
2 T2 F: v4 v g2 b) v+ ~1 u7 N比如Arm指令mov r0,#0x1fe不是合法的指令,因为0x1fe不是常数表达式。那么只能将0x1fe拆分,表示为两条指令,比如:
! F9 c- ?6 \. I8 U5 Nmov r0,#0xfe
+ Y5 B, O! P+ x4 _& Aadd r0,r0,#0x100/ ]& } u: Z/ }& K( `5 j
除了拆分数字的方法,Arm的LDR宏使用临时变量的方式实现非常数表达式的数据传送,即:
% r0 |; `0 D. B+ N" ELDR r0,=0x1fe1 e' f5 M6 ]& C
ldr宏指令不是Arm的真正指令,它由如下两条指令实现:
) Y* K! o8 M" i6 N& x& j.tmp .word 0x1fe! C/ P3 n7 W& O& ?* u3 A1 u
ldr r0,.tmp p4 F7 y! M2 x
ldr r0,[r0]( n2 o' N$ |0 n
Arm的ldr指令用于将内存的数据加载到寄存器。这里数字0x1fe被存储在ldr指令附近的位置.tmp处,ldr r0,.tmp被编译为ldr r0,[pc+offset]的形式,其中offset为符号.tmp相对ldr指令的偏移。这样ldr r0,.tmp获取了符号.tmp的地址,然后再次使用ldr r0,[r0]将.tmp地址处的数据取出,即0x1fe。
6 D' I* L/ v* g! }, |通过以上的描述,我们明确了Arm指令中常数表达式的真正含义,并基于此编写了一个验证常数表达式的函数。最后,我们解析了如何在Arm中实现非常数表达式数据的传送,并讨论了它的实现。希望对你理解Arm的常数表达式有所帮助。
) x1 R$ f# o. w. v& C |
|