为什么 %cl 是 sal-operation 接受作为参数的唯一寄存器?
Why is %cl the only register the sal-operation will accept as a parameter?
今天,在处理 x86 汇编项目时,我尝试使用 salq
操作将 %rax
寄存器的值左移 %rbx
的值,但无法成功让它工作。当然,使用立即值移位效果很好,但我不知道为什么我不能使用寄存器。我什至尝试使用 %bl
,考虑到我在编译时收到的错误是操作数大小错误,我认为这是问题所在的大小 - 仍然没有用。
经过反复试验,一位朋友将我链接到一个页面,解释说 %cl
是唯一可用于 sal
的寄存器。你瞧——现在我的代码可以工作了。我的好奇心是 为什么 ,就这一切的技术方面而言,%cl
是我们唯一可以使用的,其他三个中的 none 应该 相同的暂存器?在网上找不到任何相关信息,所以希望这里有人对 x86-Assembly 投入足够的资金来解释这一点。 :)
这个限制来自于 8086,所以我们必须回到那里去尝试猜测基本原理。
一个可能的提示来自编码。您可能知道,大多数 8086 ALU 指令由一个单字节操作码和一个指定操作数的 Mod-Reg-R/M 字节、一个寄存器和一个可以是寄存器或内存的 R/M 组成。寄存器操作数在此字节的 3 位 Reg 字段中指定,R/M 操作数由其余 5 位指定。
在移位的情况下,我们实际上有 7 条不同的指令共享一个操作码。操作码 D2 用于所有 8 位变量移位和循环指令:
rol r/m8, cl
ror r/m8, cl
rcl r/m8, cl
rcr r/m8, cl
shl r/m8, cl
shr r/m8, cl
sar r/m8, cl
由于移位计数的 cl
操作数是硬编码的,因此寄存器操作数不需要 Mod-Reg-R/M 字节的 Reg 字段,而是可以用来区分这些指令。这在
等指令列表中注明
D2 /2 rcl r/m8, cl
换句话说,rcl r/m8, cl
使用操作码 D2 和 3 位 Reg 字段中的值 2。
(shl r/m8, cl
其实有两种编码,D2 /4和D2 /6。大概bit 1用来表示逻辑或算术移位,因为shr
和sar
是D2 /5 和 D2 /7。所以在某种意义上 D2 /4 可能是 shl
而 D2 /6 是 sal
,但它们实际上是相同的操作。)
16位变量shift和rotates是一样的,只是使用opcode D3.
因此,通过对移位计数寄存器进行硬编码,指令集设计人员必须对 7 条指令进行编码,同时只用完一个可用的操作码。如果他们允许使用任意寄存器,他们将需要 7 个不同的操作码用于 8 位移位和循环指令,以及 7 个用于 16 位版本。 opcode map is pretty crowded. I suppose 60-6F were available at the time,但他们可能想保留它们以备后用。否则他们将不得不在其他地方放弃指令,或者采用更复杂的编码方案,这意味着在解码器上花费更多的晶体管。
还有一个问题是他们为什么特别选择 cl
,而不是说 bl
。他们确实有将 cx
用作“计数”寄存器的一般理念,而硬编码 cx
的其他指令倾向于将其用作某种计数:loop
和 rep
例如。他们可能认为这会让程序员更容易记住。
正如 fuz 所指出的那样,后来的 BMI2 extension did add shlx/shrx/sarx
可以从任意寄存器中获取它们的移位计数,但它们必须被塞进指令的一个奇数角 space 并且具有一个更复杂的编码——需要一个 VEX 前缀和总共 5 个字节或更多的字节来编码。
今天,在处理 x86 汇编项目时,我尝试使用 salq
操作将 %rax
寄存器的值左移 %rbx
的值,但无法成功让它工作。当然,使用立即值移位效果很好,但我不知道为什么我不能使用寄存器。我什至尝试使用 %bl
,考虑到我在编译时收到的错误是操作数大小错误,我认为这是问题所在的大小 - 仍然没有用。
经过反复试验,一位朋友将我链接到一个页面,解释说 %cl
是唯一可用于 sal
的寄存器。你瞧——现在我的代码可以工作了。我的好奇心是 为什么 ,就这一切的技术方面而言,%cl
是我们唯一可以使用的,其他三个中的 none 应该 相同的暂存器?在网上找不到任何相关信息,所以希望这里有人对 x86-Assembly 投入足够的资金来解释这一点。 :)
这个限制来自于 8086,所以我们必须回到那里去尝试猜测基本原理。
一个可能的提示来自编码。您可能知道,大多数 8086 ALU 指令由一个单字节操作码和一个指定操作数的 Mod-Reg-R/M 字节、一个寄存器和一个可以是寄存器或内存的 R/M 组成。寄存器操作数在此字节的 3 位 Reg 字段中指定,R/M 操作数由其余 5 位指定。
在移位的情况下,我们实际上有 7 条不同的指令共享一个操作码。操作码 D2 用于所有 8 位变量移位和循环指令:
rol r/m8, cl
ror r/m8, cl
rcl r/m8, cl
rcr r/m8, cl
shl r/m8, cl
shr r/m8, cl
sar r/m8, cl
由于移位计数的 cl
操作数是硬编码的,因此寄存器操作数不需要 Mod-Reg-R/M 字节的 Reg 字段,而是可以用来区分这些指令。这在
D2 /2 rcl r/m8, cl
换句话说,rcl r/m8, cl
使用操作码 D2 和 3 位 Reg 字段中的值 2。
(shl r/m8, cl
其实有两种编码,D2 /4和D2 /6。大概bit 1用来表示逻辑或算术移位,因为shr
和sar
是D2 /5 和 D2 /7。所以在某种意义上 D2 /4 可能是 shl
而 D2 /6 是 sal
,但它们实际上是相同的操作。)
16位变量shift和rotates是一样的,只是使用opcode D3.
因此,通过对移位计数寄存器进行硬编码,指令集设计人员必须对 7 条指令进行编码,同时只用完一个可用的操作码。如果他们允许使用任意寄存器,他们将需要 7 个不同的操作码用于 8 位移位和循环指令,以及 7 个用于 16 位版本。 opcode map is pretty crowded. I suppose 60-6F were available at the time,但他们可能想保留它们以备后用。否则他们将不得不在其他地方放弃指令,或者采用更复杂的编码方案,这意味着在解码器上花费更多的晶体管。
还有一个问题是他们为什么特别选择 cl
,而不是说 bl
。他们确实有将 cx
用作“计数”寄存器的一般理念,而硬编码 cx
的其他指令倾向于将其用作某种计数:loop
和 rep
例如。他们可能认为这会让程序员更容易记住。
正如 fuz 所指出的那样,后来的 BMI2 extension did add shlx/shrx/sarx
可以从任意寄存器中获取它们的移位计数,但它们必须被塞进指令的一个奇数角 space 并且具有一个更复杂的编码——需要一个 VEX 前缀和总共 5 个字节或更多的字节来编码。