为什么任何现代 x86 掩码移位计数到 CL 中的 5 个低位

Why any modern x86 masks shift count to the 5 low bits in CL

我正在研究 x86 ASM 中的左右移位操作,比如 shl eax, cl

来自 IA-32 英特尔架构软件开发人员手册 3

All IA-32 processors (starting with the Intel 286 processor) do mask the shift count to 5 bits, resulting in a maximum count of 31. This masking is done in all operating modes (including the virtual-8086 mode) to reduce the maximum execution time of the instructions.

我正在尝试理解这种逻辑背后的原因。也许它以这种方式工作是因为在硬件级别上很难使用 1 个周期实现寄存器中所有 32(或 64)位的移位?

任何详细的解释都会有很大帮助!

我不认为将32 位寄存器移位32 比移位31 位更难。 从数学的角度来看,saturate shift count 而不是 mask 更合适。我们必须记住 SHR EAX,32 什么都不做,必须使用其他指令来清除 EAX 的内容。

也许 Intel 开发人员希望对 rotateshift 操作使用相同的内部机制。例如 ROR EAX,35 等同于 ROR EAX,3,因此 SHR EAX,35 等同于 SHR EAX,3

编辑以更正 re:80386 的陈述,(令我惊讶的是)它确实有一个桶形移位器。


很高兴听到 286 被描述为 "modern" :-)

8086 运行一个SHL AX, CL在8个时钟+每个位移4个时钟。所以如果 CL = 255 这是一个非常慢的指令!

所以 286 帮了大家一个忙,通过屏蔽到 0..31 来限制计数。将指令限制为最多 5 + 31 个时钟。这对于 16 位寄存器是一个有趣的折衷。

[我找到了《80186/80188 80C186/80C188 硬件参考手册》(订货号270788-001),里面说这个创新最先出现。 SHL et al 运行 5+n 个时钟(用于寄存器操作),与 286 相同。FWIW,186 还添加了 PUSHA/POPA、PUSH immed.、INS/OUTS、BOUND , ENTER/LEAVE, INUL 即刻。和 SHL/ROL 等等。不知道为什么186好像不是人。]

对于 386,他们保持相同的掩码,但这也适用于 32 位寄存器移位。我找到了一份“80386 程序员参考手册”(订货号 230985-001),它给出了所有寄存器移位的时钟计数为 3。 "Intel 80386 Hardware Reference Manual"(订单号 231732-002)第 2.4 节 "Execution Unit" 表示执行单元包括:

• The Data Unit contains the ALU, a file of eight 32-bit general-purpose registers, and a 64-bit barrel shifter (which performs multiple bit shifts in one clock).

所以,我不知道他们为什么不将 32 位移位屏蔽为 0..63。至此我只能提出历史错误论。

我同意,对于任何计数 >= 参数大小,没有 return 为零的 (GPR) 移位是一种耻辱。这将需要硬件检查超出底部 6/5 和 return 零的任何位集。作为妥协,也许只是 Bit6/Bit5.

[我没试过,但我怀疑使用 PSLLQ 等是一项艰苦的工作——将计数和值改组为 xmm,然后再将结果改组回来——与测试轮班计数并以某种 b运行ch 自由方式屏蔽轮班结果。]

无论如何...该行为的原因似乎已成历史。

用于电子产品;如果轮班次数是恒定的,你可以什么都不做(这就像将 "input bit 0" 的电线连接到 "output bit 1" 的电线等)。

您可以将一个变量移位计数分解为多个 "shift with constant count" 操作,最后得到的结果大致如下:

if( (count & 1) != 0) { v = v << 1; }
if( (count & 2) != 0) { v = v << 2; }
if( (count & 4) != 0) { v = v << 4; }
if( (count & 8) != 0) { v = v << 8; }
if( (count & 16) != 0) { v = v << 16; }

当然这些条件也变得没什么了(更像是,"bit 0 of count is enable/disable flag for circuit that does constant shift by 1")。问题是每个 "shift by constant" 都依赖于前一个 "shift by constant" 的值,所以在 "step N" 完成之前你不能开始 "step N+1"。步骤之间的同步需要时间,因此更多的步骤(支持更大的计数)会使速度变慢。大于寄存器中位数的计数很少见;而且您真的不想让常见案例变慢以支持罕见案例。

尽管英特尔当前的手册怎么说,屏蔽轮班计数是 186 中的新功能。例如,这个 CPU-detection code on reverse-engineering.SE uses that fact to distinguish 8086/88 from 80186/88. Perhaps Intel isn't counting 186 because it wasn't 100% IBM-PC compatible 是为嵌入式系统设计的吗?或者英特尔当前的手册是错误的;不是第一次了。


在 x86 从简单的微编码 8086 到 186、286 和 386 的演变过程中,这是一个几乎任意的设计决定,但我们可以看到一些动机。 386 有桶形移位器(恒定时间移位),186 和 286 没有。 IDK 如果 ISA 设计决策是在硬件设计决策之前或之后确定的。

ARM 选择不同,饱和 移位计数而不是包装它们。 ARM 移位寄存器宽度或更多确实 将值归零。

而 x86 SIMD 移位,如 pslld xmm0, 32 or pslld xmm1, xmm0 saturate the count; you can shift out all the bits of each element with MMX/SSE/AVX shifts, or on a per-element basis with AVX2 vpsllvd/q which might be good if you're calculating a per-element shift count with c-192, c-128, c-64, c or something. OTOH AVX512VBMI2 VPSHRDVw/d/q SIMD 双移位确实会将计数屏蔽为操作数大小 -1,因此不可能让某些元素一直移位超过边界并只留下位来自目标元素中的 src2。正如下面针对 386 标量 shrd 所讨论的,这将需要更宽的桶形移位器,或一些特殊的高计数外壳。


186 / 286 有 O(n) shifts/rotates(没有桶形移位器)所以掩码限制了最坏情况下的移位性能。

8086:SHL AX, CL 每个位移位需要 8 个时钟 + 4 个时钟。 CL=255 的最坏情况是 1028 个周期。 286:5 + n,最坏情况 5+31 = 36 个周期。

286 shift-count masking 也可以限制多任务系统的最坏情况中断延迟,如果 shifts 不能中止指令中途并且没有任何更慢的指令。 (286 引入了它的受保护 mode 版本,所以英特尔可能正在考虑多用户设置,恶意非特权用户试图拒绝系统服务。)或者动机可能是真实代码意外地(?)使用大班次计数。此外,如果移位未完全微编码,则无需在专用移位硬件中使计数输入的宽度超过 5 位。建立一个更宽的计数器只是为了花费更长的时间是没有用的。

更新:屏蔽计数在 186 中是新的,排除了多用户公平性,但仍然可以通过允许大量移位计数零寄存器的软件避免最坏情况的 IRQ 延迟。

16 位寄存器的 186 / 286 行为需要与现有软件的 8086 保持足够的向后兼容性。这可能就是屏蔽为 5 位计数的原因( % 32),而不是 % 16。 (不对 8 位操作数大小使用 % 16% 8 也可能使移位计数器硬件更简单,而不是根据操作数大小将高位复用为 0。)

向后兼容是 x86 的主要卖点之一。大概没有广泛使用的(在 8086 上)软件依赖于大于 32 的移位计数仍然将寄存器归零,否则英特尔可能通过检查所有高位为零并与仅使用低 4 的移位器的结果混合来使计数饱和位。

但请注意,旋转使用相同的计数掩码,因此检测到高计数的假设硬件必须避免将旋转的结果归零,并且仍然必须使 FLAGS 正确地移动 32,并且旋转-通过进位。

16 位 186 屏蔽到 % 32 的另一个可能重要的原因是循环进位 (rcl / rcr),这在 8086 上可以用计数有意义16.(计数 mod 9 或 17 是等效的。)32 位 rcl 不能旋转 32,但是;仍然屏蔽 % 32。但这不是向后兼容的问题;旋转 16 到 31 可能是,如果任何代码首先使用 RCL / RCR 超过 1。 (绝对是比较晦涩的说明之一。)

所以 186 的 cl % 32 设计可能足够兼容,并实现了所需的硬件简化/换档周期上限。

186 显然 intended for embedded use and had some integrated devices with addresses that conflicted with IBM-PC, so perhaps Intel felt like they could experiment with this change in 186 to see if it caused problems. Since it didn't(?), they kept it for 286? This is a totally made up guess based on a couple random facts extracted from comments from other people. I wasn't using PCs until Linux on a P-MMX Pentium and am only idly curious about this history, not a retrocomputing enthusiast. Speaking of which, you https://retrocomputing.stackexchange.com/ 可能是询问此 186 设计决策的好地方。

为什么 386 没有加宽计数掩码以实现更宽的移位?

为什么不让 386 仍然能够用 shl eax, 32 移出所有位?

目前还没有使用 386 需要向后兼容的 32 位寄存器的现有软件。 32 位 mode(以及 16 位 mode 中的 32 位操作数大小)是 386 的新功能。因此 386 可以选择任何东西进行 32 位移位。 (但 8 位和 16 位移位的工作方式与 186/286 完全相同,以确保兼容性。)

我不知道英特尔是否认为屏蔽班次计数作为一项功能非常有用。屏蔽到与 16 位移位相同的 % 32 可能是他们最容易实现的,并且可用于 32 位移位。

根据 some random SO comments

386 使用桶形移位器 进行了 O(1) 次移位。支持更大的移位数需要更宽的桶形移位器。

386 还介绍了 shld / shrd 双精度移位 从另一个寄存器移入位,而不是 0 或符号位的副本.如果能够将所有位移出并使用 shld eax, edx, 37 作为具有错误依赖性的复制和移位,那就太好了。但是 shl/rd 的支持计数 >= 32 需要更宽的桶形移位器,而不仅仅是 "zero the output on high bits set" 检查。对于每个输出位,当前设计对该位有 32 个可能的来源。允许更宽的计数会将每个结果位的可能来源增加到 64 个。正如@Brendan 所示,您可以执行一个多步骤过程,而不是为每个位构建一个 32:1 多路复用器,但是您会有更多的门延迟。

SHLD / SHRD 将他们的数量与其他轮班区别对待是不一致的,除 % 32 之外的任何东西都会使其更难构建。

我不确定这个论点是否成立:shld ax, dx, 25 理论上会做一些事情,但是 Intel's current manual says 如果计数大于操作数的大小,结果是未定义。(我没有测试实际的硬件以查看会发生什么。)如果允许其他班次进行更广泛的计数,英特尔可以简单地对 386 中的 32 位 shld/shrd 说同样的话。


随机想法:通过进位循环很慢,并且在 modern CPU 上进行了微编码以获取计数!= 1。IDK 如果那会不会是另一个并发症。