天天看点

信息编码的代价:为什么ARM的立即数和intel的乘法都不好用?

作者:底层技术栈

#头条创作挑战赛#

ARM和intel,是两大主流的CPU架构。

ARM是RISC精简指令集的代表,intel是CISC复杂指令集的代表。

ARM的指令长度固定,每条指令固定为4字节。

intel的指令长度不固定,最少1个字节,最多可以不超过16字节。

长度不固定,使用起来就比较灵活,但相应的编码格式就比较复杂。

长度固定,使用起来就比较死板,但相应的编码格式就比较简单。

ARM和intel在汇编层面的区别,都是因为指令长度是否固定。

1,ARM的让人头大的立即数,

intel的mov指令是可以携带任意大小的立即数的,所以 int a = 0x12345678; 这样的代码只需要一条汇编就可以实现。

但是ARM的指令总长度才4个字节,显然它携带不了长达4个字节的数字。

所以,立即数的加载成了ARM中的一个问题。

信息编码的代价:为什么ARM的立即数和intel的乘法都不好用?

稍微大一点的立即数,在ARM中都没法用一条汇编实现。

上图的C代码,在ARM中会生成2条汇编:

信息编码的代价:为什么ARM的立即数和intel的乘法都不好用?

先加载最低16位的0x5678,然后再加载高16位的0x1234:

一条简单的C代码 int a = 0x12345678,居然要分两次才能实现。

因为CPU的指令条数一般100-200条之间,所以指令码就要占1个字节。

另外,CPU的寄存器个数也有16-32个,所以寄存器的编号也要占5个二进制位。

然后,4个字节的ARM指令也就还剩下2个字节可以携带数字(其他信息再占2-3位)。

所以,ARM加载一个4字节的数字需要2条汇编,加载8字节的数字需要4条汇编。

信息编码的代价:为什么ARM的立即数和intel的乘法都不好用?

ARM的mov指令的格式,其中5-20位用于携带立即数字

intel的指令因为长度不固定,就可以携带任意大小的数字:把数字添加在指令码的末尾就行。

信息编码的代价:为什么ARM的立即数和intel的乘法都不好用?

intel的汇编

同样的C代码,在intel上编译之后只需要1条汇编,从上图可以看到按小端序排列的0x12345678:

b8是mov的机器码,然后低位在低字节,高位在高字节,所以数字的字节顺序是反着的。

指令长度不固定,对于反汇编工具来说写起来就相对麻烦一点,因为只能一个字节一个字节的解析。

但对于编译器的作者来说,写起来就比较舒服:因为不用考虑什么样的数字可以携带,什么样的数字没法携带。

在ARM上,没法在指令里携带的数字(常量),还得给常量分配寄存器,真是太难用了[捂脸]

信息编码的代价:为什么ARM的立即数和intel的乘法都不好用?

这个代码在ARM上需要给0x12345678分配寄存器

一般来说,编译器框架只会给变量分配寄存器,常量是默认不分配寄存器的。

但是,ARM的指令没法携带大于16位(有效数字)的常量,所以对数字的运算非常麻烦!

mov指令可以携带的数字算多的(16位),加法指令可以携带的数字更少(12位),

也就是说,大于4095的数字都没法直接使用。

信息编码的代价:为什么ARM的立即数和intel的乘法都不好用?

ARM的加法指令,只能携带12位的有效数字

我这两天开始给scf编译器添加ARM64的机器码生成,遇到的最大问题就是立即数问题。

2,intel的乘法,

英特尔的乘法也是个扯淡的设计。

它同时使用EAX和EDX存放乘法的结果,让编译器的寄存器分配特别的复杂!

如果EAX里存在别的变量怎么办?

如果EDX里存在别的变量怎么办?

都得先给它腾出来[捂脸]

如果其中一个乘数是常量数字(立即数)怎么办?

也得先把它放到寄存器里去,一般使用EAX或EDX其中之一。

int a = 0x12345678;

int b = 0x87654321;

int c = a * b;

本来c就是a乘以b的结果的最低32位,直接这么设计指令就行:mul c, a, b.

高32位溢出了,没法保存到变量c里,直接丢弃就行。

想保存高32位,可以把a, b, c都扩充到64位的乘法:

int64_t c = (int64_t)a * (int64_t)b;

这两个类型转换,就会在这两个数字的前面添加32位的0或1(扩充到64位),然后它们的乘法结果就可以全部保存下来了。

但是英特尔为了让乘法的指令长度短一些,居然要求先把其中一个乘数放到EAX里,而结果又同时使用EAX:EDX!

真是个难用的设计。

3,比乘法更难用的是,intel的除法[捂脸]

乘法的两个数字是对称的,结果与先后次序无关,但是除法的两个数字是不对称的:

被除数需要先放到EAX:EDX,然后再除以除数。

如果除数是一个常量数字,英特尔的除法又没法直接携带数字,还得给这个除数找一个空闲的寄存器!

而EAX和EDX这时又同时被被除数占据了,没法用来临时存放除数。

4,信息编码的代价,

固定了指令长度之后,指令的解码就比较简单,但怎么携带大的数字就成了问题。

不固定指令长度,那就要让指令使用一些特别的默认寄存器:这样代码的长度是短了,但编译器写起来就更麻烦了。

总之,二进制的位数与它表示的信息量是相关的:只能各种折中,难以面面俱到。

ARM体系结构与编程 第2版 ¥59 购买

继续阅读