注:仅限x86-16bit环境讨论
我个人觉得,就是编码太麻烦,opcode不太够用,指令太长。
汇编指令由前缀、opcode、操作对象组成,具体的来说,如下图:
对于一般的MOV来说,操作对象有两个,可能是:寄存器(reg)、立即数(imm)、内存(mem)
16位环境下,寄存器的编码规则如下:
000 AX AL 001 CX CL 010 DX DL 011 BX BL 100 SP AH 101 BP CH 110 SI DH 111 DI BH
另外有一个bit专门判断选择的是8bit还是16bit寄存器,编码表上并无段寄存器存在。
为什么当时不把段寄存器也放进去?
原因是放进去的话,指令就不好编码了。
因为MOV操作的寄存器有两个,其中的一个还可能是内存,还可能是寄存器组合,这样的话,2*3bit用于描述寄存器,剩下2个bit用于描述是内存还是别的东西,这样算下来,一个字节勉强够用。
而如果把段寄存器加上,就需要4个bit编码一个寄存器,仅仅描述两个寄存器就需要一个字节,加上其它的修饰,MOV指令长度就需要整体增加一个字节,这对于过去的计算机来说,开销无疑是非常巨大的,过去计算机内存都是以KB计算的,每个MOV指令增加一个字节,在设计者看来是无法接受的。
早期的汇编指令设计原则之一就是尽量少占存储空间,因为当时的存储设备实在是太贵了。
下图中就是具体MOV指令的编码(不含段寄存器)情况:
图中w代表MOV操作数的大小(是8bit还是16bit)。
同时,Intel的指令是慢慢发展起来的,并且保证了指令的二进制兼容,Intel早期为了提高指令的速度,还对一部分常用的指令进行了优化,上图中红框里的三个指令就是针对常用指令进行的优化。优化的目的是缩短指令长度(MOV reg,imm缩短1字节,MOV AX,[mem]缩短1字节)。
如果把段寄存器都加上,MOV指令最短长度就需要3字节,并且1011 w reg这种编码就不够用了(因为加上段寄存器的话reg就需要4bit)。
而且,如果MOV里加了,INC/DEC要不要加?XCHG要不要加?如果都加上,INC/DEC用单字节也不好编码了,XCHG用单字节也不好编码了。
实际中,操作段寄存器的MOV指令,用了另外的opcode编码:
那么为什么这里不把立即数加上呢?
因为这里头,如果加上立即数的话,需要再占用一个opcode的值。而MOV指令本身已经使用了28个opcode的数值了,单字节的opcode一共只有256个,考虑到未来的扩展需要,肯定不能把256个值都用了,所以单字节的opcode能省就省,考虑到修改段寄存器并不是很常见的操作,所以就省了这个opcode
其实到了32位时代,如果彻底放弃兼容性,重新设计汇编编码的话,完全可以把立即数->段寄存器的指令加上,但因为Intel的文化里,兼容性是非常重要的,所以Intel不放弃兼容性,指令只要这样一代代传下来。
Intel当年为了让指令尽可能的段,用了很多单字节指令:
前缀类指令:9个
XCHG:8个
标志位相关:12个
INC/DEC:16个
PUSH/POP相关:27个
IN/OUT:8个
再加上跳转类的用掉的将近40个opcode,各种算数运算用掉的30多个,基本上单字节的编码已经用的差不多了。
所以,为了让指令更紧凑,只能把常用的寄存器的常用功能编码,不常用的只能以后再说。
部分单字节指令的编码构成(reg代表通用寄存器,sreg2代表段寄存器):