|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
如果说Intel指令中的立即数,相信大家都很熟悉。类似的,ARM指令中的“立即数”就是常数表达式。之所以称为常数表达式,而不称为立即数是有原因的。% y t: _4 T0 H7 P6 Q& ~
Intel指令属于CISC指令集,指令是不定长的,因此可以将任意32位立即数编码到指令内。2 p0 y8 o; K* a# \# R0 C
8 }& D0 A: F& c& K
) p0 ?9 L7 c E, K" ^9 t. b8 P7 n1 q- ], i/ z
Arm指令属于RISC指令集,指令是定长的32字节。众所周知,指令中操作码是必须的字段,如果把32位立即数直接编码到指令内部,操作码就无“容身之地”了……
8 l k8 R+ }1 e8 K4 c0 v3 d7 N+ R L2 n
* I7 P6 ^0 G$ ~2 g$ g
$ J; o+ `: w2 t P# N6 a+ A因此,Arm指令中“立即数”的位数必小于32位。那么如何在Arm指令中正常表示立即数呢?我们看看Arm的通用指令格式。
( Z/ N' x* u6 k" H3 J( F/ U
9 a' K# C* k# P* K O& V5 Z& F8 ?# M# k) W8 `3 Z7 d) v
3 ?5 T9 b: k* D0 c1 C' x& V5 \Arm指令中,操作码(opcode)、目的操作数(Rd)、源操作数1(Rn)是必须的字段。条件码(cond)、符号位标记(s)源操作数2(oprand2)是可选的。其中Rd和Rn必须是寄存器,因此Arm的“立即数”只能存储在oprand2。8 P) ]% v' [- U" o/ m! q' e
: O2 `7 ?, ^# {- I# O2 f. f0 J% |8 t: V: ?5 |
在Arm的指令编码内,使用“立即数”的指令为“立即数”提供了12bit的存储空间,也就是说Arm的“立即数”只能表示212=4096个数字。这显然无法表示所有的32位立即数,如果使用这12bit表示0~4095的数字,那么从4096~(232-1)的数组都不能表示了。考虑到这种“后果”,Arm指令集的设计者们使用了一个技巧,即使用常数表达式代替立即数的概念。
' |0 Z% O. F, k+ h常数表达式表示一个常数,且该常数对应8位的位图,即可以由一个8位的常数通过循环移位偶数位得到。 1 f/ c: j# L' U
% A3 m/ X$ ^, `/ V. R
0 A% t! h: ~ v. E4 x3 ?# z7 O7 L比如0x3fc可以由8位常数0xff循环左移2位或循环右移30位得到,是常数表达式。再比如0x1fe,虽然可以有0xff循环左移1位或循环右移31位得到,但是移动的位数不是偶数,因此不是常数表达式。
3 w- }" r, J! m3 {$ `' `根据常数表达式的定义,常数表达式只需要12bit的存储空间。
* @0 q# K# ^8 z) ]4 J4 B
2 M; U' b1 l [- W8 P& h
/ M# j T5 ~* J0 ^( c8 G6 c5 K12bit空间中,低8字节存储8位位图,高4字节存储循环右移的次数。4字节只能表示24=16种移动次数,但是由于常数表达式定义中,移位限定为偶数次,因此这4个字节刚好能表示0、2、4、6、8…32位16个数字! x4 J6 D" G3 T, i& U. |
比如0x3fc的二进制表示为0xfff,即使用8位数字0xff循环右移0xf*2=30次得到。8 Q* M7 k* ^6 _8 m u8 c4 H6 x
明确了常数表达式的含义后,可以通过简单的编程识别一个数字是否是常数表达式。
( r+ c0 |( f+ ?. |7 a: z v/*
( i2 X, F( A; p/ ?* i) O: }: {- ~循环左移两位
, L# a+ y+ E7 r7 f- R*/
& E* J. Q. l2 \5 Z7 T2 k4 g) H0 vvoid roundLeftShiftTwoBit(unsigned int& num)0 Y& E1 E D' y9 Z" t+ U; Y
{% X4 K% b. z: Y
unsigned int oveRFlow=num & 0xc0;//取左移即将溢出的两位
U4 c$ I* r: O9 x0 G* E7 W+ qnum=(num<<2) (overFlow>>30);//将溢出部分追加到尾部* ?9 E9 E5 j: a
}- B8 P+ ~6 T+ i8 j" z
/*. Y+ Y, Z# d$ V: l. U
判断num是否是常数表达式,8位数字循环右移偶数位得到
5 k1 Z) Y! h2 Z( |*/
# Y- T. Z5 w! U6 S1 a( Abool constExpr(unsigned int num)+ z6 j4 j% L% O
{0 G* P, y7 n; c4 J+ h9 K
for(int i=0;i<16;i++){+ m# O. R! F0 [( m% d" W
if(num<=0xff)
/ d( a' N" p8 M% F/ p+ `5 o, z( xreturn true;//有效位图/ q) H" L& g2 T
else
7 k. `8 C. t& p* |5 O$ V) @roundLeftShiftTwoBit(num);//循环左移2位
7 Y' x9 V, S1 O; x}0 u- a1 c+ L5 X, s7 ^- o- p% Z
} V# Y* b0 W7 ~0 m7 W" O. p& X
roundLeftShiftTwoBit函数将一个无符号32位整数循环左移2位,constExpr反复将一个无符号32位整数循环左移2位,直到发现该数字在0到0xff之间为止,否则表示该数字不是常数表达式。( z9 S$ a* t* M2 J/ o
通过常数表达式,Arm指令便避免了“立即数”仅仅局限于0~4095的数字范围的问题了。但是常数表达式仍不能表示全部的32位整数,比如0x1fe。对于非常数表达式的数字,只能通过拆分来解决。$ B$ K& b- r* e7 Y; j
比如Arm指令mov r0,#0x1fe不是合法的指令,因为0x1fe不是常数表达式。那么只能将0x1fe拆分,表示为两条指令,比如:1 m j: k' r' O
mov r0,#0xfe
& G s* N9 x1 y, a/ w$ Aadd r0,r0,#0x100+ e, v5 v6 C5 X0 Z$ W& T3 U# ]4 l
除了拆分数字的方法,Arm的LDR宏使用临时变量的方式实现非常数表达式的数据传送,即:1 h. W; q# u) ^- Q" ]
LDR r0,=0x1fe
1 T# ]( z. {* w0 i# Wldr宏指令不是Arm的真正指令,它由如下两条指令实现:
) J& t! `2 c0 n/ j0 C.tmp .word 0x1fe
# @% \* s) {5 }- ^6 eldr r0,.tmp
' ~# F, S W0 T( R" Gldr r0,[r0]
' _( n2 g. f- T: Q0 t# G. g: q9 E$ dArm的ldr指令用于将内存的数据加载到寄存器。这里数字0x1fe被存储在ldr指令附近的位置.tmp处,ldr r0,.tmp被编译为ldr r0,[pc+offset]的形式,其中offset为符号.tmp相对ldr指令的偏移。这样ldr r0,.tmp获取了符号.tmp的地址,然后再次使用ldr r0,[r0]将.tmp地址处的数据取出,即0x1fe。% K+ C( Y) K/ @6 F9 n
通过以上的描述,我们明确了Arm指令中常数表达式的真正含义,并基于此编写了一个验证常数表达式的函数。最后,我们解析了如何在Arm中实现非常数表达式数据的传送,并讨论了它的实现。希望对你理解Arm的常数表达式有所帮助。
9 l; K) T+ D% ~+ @( l |
|