EDA365电子论坛网

标题: ARM的常数表达式 [打印本页]

作者: zxcvbvbnmn    时间: 2021-6-4 10:33
标题: ARM的常数表达式
如果说Intel指令中的立即数,相信大家都很熟悉。类似的,Arm指令中的“立即数”就是常数表达式。之所以称为常数表达式,而不称为立即数是有原因的。
+ Q+ M# J, v' l' L0 t& D& hIntel指令属于CISC指令集,指令是不定长的,因此可以将任意32位立即数编码到指令内。
4 Q6 v. u9 ~# E# C: h2 e
. h# l: `4 B: V) {% |# M

8 J6 w6 A0 F+ F6 ?2 L0 }. T  o
  d" J9 |% _7 @/ i5 F- h, @
Arm指令属于RISC指令集,指令是定长的32字节。众所周知,指令中操作码是必须的字段,如果把32位立即数直接编码到指令内部,操作码就无“容身之地”了……4 @9 v- }. W' t! ?) e2 I
2 q! ^- K+ [. d, x" l1 J& H
  V0 W- d! c, e
/ T# \2 y1 i1 {1 d
因此,Arm指令中“立即数”的位数必小于32位。那么如何在Arm指令中正常表示立即数呢?我们看看Arm的通用指令格式。
% ~( C8 P1 q9 Y8 s7 c
, V% Q! @$ h" ?# g4 h+ ?9 e" `& d( X% k
0 }8 l3 g3 u; t3 Q* Y  x" F
; ?/ k$ u7 n2 w2 V0 d
Arm指令中,操作码(opcode)、目的操作数(Rd)、源操作数1(Rn)是必须的字段。条件码(cond)、符号位标记(s)源操作数2(oprand2)是可选的。其中Rd和Rn必须是寄存器,因此Arm的“立即数”只能存储在oprand2。
: q- ?! `# j1 f. ]# L5 y7 S( `2 b
$ {2 r( t9 d+ _) @8 {/ V  b5 R  M' r/ m; J/ p
在Arm的指令编码内,使用“立即数”的指令为“立即数”提供了12bit的存储空间,也就是说Arm的“立即数”只能表示212=4096个数字。这显然无法表示所有的32位立即数,如果使用这12bit表示0~4095的数字,那么从4096~(232-1)的数组都不能表示了。考虑到这种“后果”,Arm指令集的设计者们使用了一个技巧,即使用常数表达式代替立即数的概念。* o# i. a% |! X
常数表达式表示一个常数,且该常数对应8位的位图,即可以由一个8位的常数通过循环移位偶数位得到。
/ m' K1 ]1 f8 @! l8 r3 l' e
  }3 }, g9 s' s, l' E  ?. p

# d0 L$ r) p7 a5 ^6 A! k2 d
比如0x3fc可以由8位常数0xff循环左移2位或循环右移30位得到,是常数表达式。再比如0x1fe,虽然可以有0xff循环左移1位或循环右移31位得到,但是移动的位数不是偶数,因此不是常数表达式。
6 D1 u( \0 r. _5 B根据常数表达式的定义,常数表达式只需要12bit的存储空间。+ l( \4 I; l1 U" B
/ }; p( P4 g( _! c. `, Y: Y9 y
: |! u: P2 c3 V
12bit空间中,低8字节存储8位位图,高4字节存储循环右移的次数。4字节只能表示24=16种移动次数,但是由于常数表达式定义中,移位限定为偶数次,因此这4个字节刚好能表示0、2、4、6、8…32位16个数字!
& {- Z% P, I' H9 J/ ^% m8 X2 J$ H比如0x3fc的二进制表示为0xfff,即使用8位数字0xff循环右移0xf*2=30次得到。& ]6 w* X4 ?8 v8 F
明确了常数表达式的含义后,可以通过简单的编程识别一个数字是否是常数表达式。0 j7 p; V' r! N- C  F$ W. p
/*
7 F% R- k1 H; \循环左移两位6 [5 V4 v1 f2 P2 s9 j- q1 D
*/
, E* G7 @, k5 l" W1 ~void roundLeftShiftTwoBit(unsigned int& num)3 W% w8 X9 z  J: x6 r
{
* Q0 c) f4 Q! T$ o7 _4 |1 k) H- eunsigned int overFlow=num & 0xc0;//取左移即将溢出的两位
- v! @; [8 q  g" M& tnum=(num<<2) (overFlow>>30);//将溢出部分追加到尾部' H1 X5 l, c1 _4 c1 o
}
: e% W: ?, z- _5 f8 k/*: d7 @- b; G' |8 U3 E6 R' N6 S( d
判断num是否是常数表达式,8位数字循环右移偶数位得到
! v) J' [$ |9 Y0 g* e*/
- _! e- L8 O6 T0 M7 W4 A6 \% u5 X2 @bool constExpr(unsigned int num)
0 R2 d1 v$ _+ y; k% z' _{
) E$ T' D2 w) p8 Z+ Ofor(int i=0;i<16;i++){
, D8 c0 d" o/ j4 q# nif(num<=0xff)
4 a! Z" _+ o! R" ~& ?4 k% i3 N8 F9 ^return true;//有效位图/ t* V# X1 P4 W, y4 s
else' I$ ~( M( ~/ }" k5 P0 Z" }
roundLeftShiftTwoBit(num);//循环左移2位/ I( d: j* O( l- I6 f
}
1 N2 X6 ?8 [) p' d: V! K# g}

5 v: N* Y* E+ D  @roundLeftShiftTwoBit函数将一个无符号32位整数循环左移2位,constExpr反复将一个无符号32位整数循环左移2位,直到发现该数字在0到0xff之间为止,否则表示该数字不是常数表达式。0 P6 E: ^' k. A6 d
通过常数表达式,Arm指令便避免了“立即数”仅仅局限于0~4095的数字范围的问题了。但是常数表达式仍不能表示全部的32位整数,比如0x1fe。对于非常数表达式的数字,只能通过拆分来解决。
8 ?) g+ a+ R# S' ?9 k& _- {+ h6 g比如Arm指令mov r0,#0x1fe不是合法的指令,因为0x1fe不是常数表达式。那么只能将0x1fe拆分,表示为两条指令,比如:
  W4 W  O% U2 @7 k7 T3 ]mov r0,#0xfe/ T; m% z4 F- ]2 T
add r0,r0,#0x100

- u0 i, `6 y; S/ \除了拆分数字的方法,Arm的LDR宏使用临时变量的方式实现非常数表达式的数据传送,即:
0 ^0 R4 J+ I9 M, G# p9 d( ULDR r0,=0x1fe+ o% D4 @( V' M! A! h9 n9 E
ldr宏指令不是Arm的真正指令,它由如下两条指令实现:4 V# f3 H, U& O" ^! u
.tmp .word 0x1fe
8 B# p% |" h: {' _& V/ d1 g- oldr r0,.tmp% B5 a5 Q- J  W9 i
ldr r0,[r0]
. I1 D, m6 {8 ?: t! y
Arm的ldr指令用于将内存的数据加载到寄存器。这里数字0x1fe被存储在ldr指令附近的位置.tmp处,ldr r0,.tmp被编译为ldr r0,[pc+offset]的形式,其中offset为符号.tmp相对ldr指令的偏移。这样ldr r0,.tmp获取了符号.tmp的地址,然后再次使用ldr r0,[r0]将.tmp地址处的数据取出,即0x1fe。
' y5 N# k' C9 Y- U6 V$ F通过以上的描述,我们明确了Arm指令中常数表达式的真正含义,并基于此编写了一个验证常数表达式的函数。最后,我们解析了如何在Arm中实现非常数表达式数据的传送,并讨论了它的实现。希望对你理解Arm的常数表达式有所帮助。3 ~& j! Y6 B: u, g- V8 x

作者: modengxian111    时间: 2021-6-4 13:20
ARM指令中的“立即数”就是常数表达式
作者: nevadaooo    时间: 2021-6-4 15:30
涨知识了




欢迎光临 EDA365电子论坛网 (https://bbs.eda365.com/) Powered by Discuz! X3.2