繁体   English   中英

为什么x86汇编中的64位被分红?

[英]Why is the dividend 64 bits in x86 assembly?

为什么idiv x86汇编指令将EDX:EAX (64位)除以给定寄存器,而其他数学运算(包括乘法)只对单个输入和输出寄存器进行操作?

乘法:

mov eax, 3
imul eax, 5

师:

mov edx, 0
mov eax, 15
mov ebx, 5
idiv ebx

我知道EDX用于存储余数,但为什么没有针对此行为的单独指令? 这对我来说似乎不一致。

指令集提供了有效实现任意宽度整数运算所必需的指令。 对于加法和减法,除了固定宽度结果之外,您需要知道的是操作是否导致进位(用于加法)或借用(用于减法)。 这就是进位标志的原因。 对于乘法,您需要能够将两个单词相乘并获得双字结果。 这就是为什么imuledx:eax产生结果的原因。 对于除法,您需要能够划分双倍宽度数并获得商和余数。

要了解为什么需要这些特定操作,请参阅Knuth的“计算机编程艺术”第2卷 ,其中详细介绍了实现任意宽度算法的算法。

至于为什么x86指令集中没有更多不同形式的乘法和除法指令,乘法和除法不是2的幂,比其他指令要少得多,因此英特尔可能不想使用可用于更频繁使用的指令的操作码。 通用程序中的大多数乘法和除法都是2的幂; 对于这些,你可以使用bitshifts或lea指令。

还有一个“双倍宽度”乘法(单操作数mulimul )。

如果你问“为什么没有只给出商数的双操作数idiv ”,那么我真的不知道(我有一个理论,但我不是为英特尔工作)而且我希望它存在太..

如果你想用一个不是2的幂的模数进行模乘,你可以很好地工作,你可以做一个mul并直接用div跟进它,一切都已经在正确的位置。 这是结果,而不是理由,因为我们不得不问英特尔..但这是一个理论。 早在8086年代,只有双倍宽度乘法(这是一种缓慢的迭代乘法,早期退出与你在软件中所做的相同)。 后来在80286中,他们增加了一些更灵活的乘法,但他们从来没有对分区做同样的事情。 也许它并不那么紧迫 - 毕竟,划分是相对罕见的,而你经常需要乘以小常数,例如索引结构数组。

对于加法和减法,溢出是由进位标志处理的单个位。 如果要取两个任意N位操作数并将它们相乘,则需要2 * N位来存储结果,非常简单,自己尝试0xFF * 0xFF = 0xFE01。 如果您只使用N位大小的寄存器,则乘法指令将非常有限。 除法得到N位,除法乘以2 * N位。 如果您打扰N位* N位= 2 * N位,那么您还应该实现2 * N位/ N位= N位。 这就是为什么它存在,不幸的是虽然硬件不仅仅是语言,语言本应该知道并完成这个,如果我乘以两个字节,编译器应该抱怨精度,如果我的结果变量小于16位。 同时,任何使用加,减,乘或除运算的程序员也应该知道溢出,并使用这些语言使用两倍于操作数宽度的变量,这样它们就不会溢出......

这里有两个问题。 首先,存在双宽输入或输出的问题,你忽略了完全加宽乘法的单操作数MUL / IMUL形式,包括结果的高一半:N * N => 2N位,做EDX:EAX = EAX * src 请参阅其他答案,了解这有用的原因。

BMI2甚至引入了更灵活的全乘法指令MULX ,它具有三个显式操作数(两个输出和一个输入)和一个隐式操作数(第二个源= EDX)。


其次,给出一个使用立即操作数的示例,该操作数也不适用于DIV / IDIV,并且没有人提到过。

有一个模糊的指令实际上是一个立即div,执行8位/ imm8 => 8位商/余数,而不是16/8 => 8.它被称为AAM ,并且在64位模式下不可用。 汇编器默认除以10(对于BCD的预期用例),但它与任何imm8的操作码相同。 以下是如何使用DIV或AAM将0-99整数转换为两个ASCII数字 ,同时指出了AAM和DIV r/m8之间的许多细微差别。

英特尔可以随时添加即时版本的IDIV,但从未这样做过。 我的猜测是DIV / IDIV足够慢(并且非常罕见), mov reg, imm32的额外开销可以忽略不计,并且在这样的指令上花费操作码空间(和解码器晶体管)从未被认为是值得的。


更重要的是, 通过编译时常量进行的实际硬件划分通常仅对代码大小有用,而不是对性能有用。 自90年代以来,模块化乘法逆是众所周知的(由编译器编写者) 由于编译器甚至不使用常量除法,因此英特尔极不可能在这种技术成熟后设计的CPU中添加指令。 例如,clang编译unsigned int div10(unsigned int a) { return a/10; } unsigned int div10(unsigned int a) { return a/10; }

    mov     ecx, edi         # just to zero-extend to 64-bit
    mov     eax, 3435973837  # a sign-extended imm32 can't represent this constant, I guess.  clang uses imul r,r,imm for other cases.
    imul    rax, rcx         # 64-bit multiply instead of 32x32 => 64 in two separate regs
    shr     rax, 35          # extract part of the high-half result.
    ret

对于带符号的除法,需要更多的指令,有时一些加/减对于不那么简单的除数的结果。 一下Godbolt的一些例子 即便如此,这比硬件除法指令要快, 后者非常慢,例如Haswell上DIV r64 22-29周期延迟,吞吐量DIV r64


如果他们要在更多指令上花费操作码(和解码器晶体管/电源), 那么具有单宽度分红的双寄存器形式的IDIV可能对编译器有用

我不太了解硬件分频器是如何在内部实现的,所以IDK如果只有N / N => N位除法而不是通常的2N / N => N就可以节省。在编译器输出中,差不多所有部门都是在CDQ或xor edx,edx 在许多x86微体系结构中,除法是可变延迟的,因此如果在被除数实际上只有N位时有任何加速,那么硬件可能已经找到了。 但是, Skylake DIV / IDIV r32的延迟是26c (但64位除数要慢很多,而且延迟时间也很长)。

大概是DIV r32, r32指令仍会产生2个输出(商和余数),我想在两个输入寄存器中? 因此,您经常需要额外的MOV指令来保存输入。 或者可能需要立即选择商或余数进入一个目的地,或使用两个单独的操作码/商/余数?

此时,他们可以添加一个VEX编码版本,有点像MULX ,有三个显式操作数。 但是,MULX的预期用例允许扩展精度乘法与扩展精度附加进位交错,因此DIVX r64(quotient), r64(remainder), r/m64(divisor) (带有隐式被除数)在RDX?)会有很大的不同(对扩展精度不太有用)。 他们可能仍然将隐含股息设为RDX:RAX。 或者也许他们甚至不称它为DIVX,因为那已经是视频编解码器/公司的商标

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM