那是一个错误。只有
imul有立即数和2个寄存器的形式。
乘法、除法或整数除法仍然只存在于8086引入的单操作数形式中,使用RDX:RAX作为隐式双宽度输出操作数(以及除法的输入)。
当然,根据操作数大小,也可以使用EDX:EAX、DX:AX或AH:AL。请参考像英特尔手册这样的ISA参考,而不是本书!
https://www.felixcloutier.com/x86/idiv
还可以参见
何时以及为什么要使用符号扩展并在mul/div中使用cdq?和
为什么在使用DIV指令之前EDX应该为0?
x86-64唯一的硬件除法指令是idiv
和div
。64位模式下移除了aam
,该指令通过立即数进行8位除法(在Dividing in Assembler x86和Displaying Time in Assembly中有使用aam
的16位模式示例)。
当然,对于常量除法,idiv
和div
(以及aam
)非常低效。除非你优化的是代码大小而不是性能,否则请使用移位来进行2的幂次方计算,或者使用乘法逆元。
CS:APP 3e Global Edition显然在实践问题中存在多个严重的x86-64指令集错误,声称GCC会发出不可能的指令。这不仅仅是拼写错误或微妙的错误,而是具有误导性的胡言乱语,对熟悉x86-64指令集的人非常明显地是错误的。这不仅仅是语法错误,而是试图使用不能编码的指令(除了扩展为多个指令的宏之外,没有语法可以表达它们。将idivq定义为使用宏的伪指令将非常奇怪)。
例如,
我正确猜到了一个函数的缺失部分,但gcc生成的汇编代码与答案不符是另一个例子,其中它建议
(%rbx, %rdi, %rsi)
和
(%rsi, %rsi, 9)
是有效的寻址模式!比例因子实际上是2位移位计数器,因此这些都是无用的,并且是作者对他们所教授的ISA的认识严重缺乏的迹象,而不是拼写错误。
他们的代码无法与任何AT&T语法汇编器配合使用。
另一个例子是
这个x86-64 addq指令是什么意思,只有一个操作数?(来自CSAPP书3版),其中他们有一个荒谬的
addq %eax
而不是
inc %rdx
,并且在
mov
存储中有一个不匹配的操作数大小。
他们似乎只是编造了一些东西,并声称它是由GCC发出的。我不知道他们是否从真实的GCC输出开始,然后将其编辑成他们认为更好的示例,或者是从头开始手写而没有经过测试。
即使在-O0模式下,GCC的实际输出也会使用魔术常数(固定点乘法逆)进行乘法以除以9(但这显然不是调试模式代码)。他们本可以使用 -Os 模式。
想必他们不想谈论
为什么GCC在实现整数除法时使用奇怪的数字进行乘法运算?并用自己虚构的指令替换了那个代码块。从上下文中,您可能可以确定他们期望输出结果的位置;也许他们的意思是
rcx /= 9
。
这些错误来自于全球版的第三方练习问题
来自出版商网站 (https://csapp.cs.cmu.edu/3e/errata.html)
关于全球版的说明: 不幸的是,出版商安排在全球版中生成了一组不同的练习和作业问题。 负责人做得不太好,因此这些问题及其解决方案存在许多错误。我们没有为这个版本创建勘误表。
所以CS:APP 3e可能是一本不错的教材,只要你获取北美版,或忽略练习/作业问题。这就解释了教科书的声誉和广泛使用与严重和明显的错误(对于熟悉x86-64汇编语言的人来说)之间的巨大脱节,这些错误超出了粗心大意的范畴,进入了不懂语言的领域。
如何设计一个假设的idiv reg, reg
或idiv $imm, reg
此外,被除数应该来自寄存器%rdx(高64位)和%rax(低64位)中的数量 - 因此,如果在架构中定义了这一点,那么似乎不可能将第二个操作数指定为被除数。
如果英特尔或AMD引入了新的方便形式的div
或idiv
,他们会设计成使用单宽度被除数,因为编译器总是这样使用。
大多数语言都像C一样,隐式提升+ - * /的两个操作数到相同类型,并产生该宽度的结果。当然,如果已知输入是窄的,则可以进行优化。 (例如,使用一个imul r32
实现a *(int64_t)b
)。
但是,如果商溢出,div
和idiv
会出错,因此在编译int32_t q =(int64_t)a /(int32_t)b
时,不能使用单个32位idiv
。
编译器始终在DIV之前使用xor edx,edx
或在IDIV之前使用cdq
或cqo
来实际进行n / n => n位除法。
对于不仅是零或符号扩展的被除数的实际全宽度除法只能通过使用intrinsic或汇编手动完成(因为gcc / clang和其他编译器不知道何时优化是安全的),或在gcc助手函数中进行,例如在32位代码中执行64位/ 64位除法(或在64位代码中执行128位除法)。
因此,最有帮助的是一个避免使用额外指令来设置RDX的div / idiv,同时最小化隐含寄存器操作数的数量。就像imul r32,r/m32
和imul r32,r/m32,imm
一样:以无隐式寄存器的方式使非扩展乘法的常见情况更加便捷。 这是Intel语法,与手册相同,目标首先。
最简单的方法是使用一个两个操作数的指令进行dst /= src
。 或者可能用商和余数替换两个操作数。 使用适用于3个操作数的VEX编码,例如BMI1 andn
,您可以使用以下方式:
idivx remainder_dst,dividend,divisor
。 第二个操作数也是商的输出。或者您可以将余数写入RDX,并为商使用非破坏性目标。
或者更有可能针对仅需要商的简单情况进行优化,
idivx quot, dividend, divisor
并且不在任何地方存储余数。当您需要商时,您始终可以使用常规的
idiv
。
BMI2 mulx
使用隐式
rdx
输入操作数,因为它的目的是允许多个加法链与进位相加以进行扩展精度乘法。因此,它仍然必须产生2个输出。但是,这种假设的新形式的
idiv
将存在于节省代码大小和uops的正常使用
idiv
的周围,而这些使用并不是扩大的。因此,386
imul reg,reg / mem
是比较的重点,而不是BMI2
mulx
。
我不知道引入
idivx
的立即形式是否有意义;出于代码大小的原因,您只会使用它。乘法逆元是除以常数的更有效方法,因此几乎没有实际用例可以使用这样的指令。
imul
具有多操作数和立即形式,而mul
、div
或idiv
没有。 - Peter Cordes(%rbx, %rdi, %rsi)
和(%rsi, %rsi, 9)
是有效的寻址模式!(实际上比例因子是2位移位计数,所以这些是完全错误的,是作者严重缺乏理解的标志,而不是打字错误。) - Peter Cordes