CS:APP 示例使用带有两个操作数的 idivq?

CS:APP example uses idivq with two operands?

我正在通过 "computer systems a programmer's perspective"(第 3 版)一书阅读有关 x86-64(和一般汇编)的内容。根据网络上的其他来源,作者指出 idivq 只接受一个操作数——正如 this 一个声称的那样。但是,作者在后面的一些章节中给出了一个带有指令 idivq , %rcx.

的例子

两个操作数?我起初以为这是一个错误,但从那以后在书中经常发生这种情况。

此外,应根据寄存器 %rdx(高位 64 位)和 %rax(低位 64 位)中的数量给出股息 - 因此如果在体系结构那么第二个操作数似乎不可能是指定的红利。


这是一个练习的例子(懒得把它全部写下来 - 所以图片是最好的方式)。它声称 GCC 在编译一个简短的 C 函数时发出 idivq , %rcx

我认为你的书有误。

idivq只有一个操作数。如果我尝试 assemble 这个片段:

idivq , %rcx

我收到这个错误:

test.s: Assembler messages:
test.s:1: Error: operand type mismatch for `idiv'

这个有效:

idivq %rcx

但你可能已经知道了。

它也可能是一个宏(不太可能,但有可能。)。

也许您应该联系本书的作者,以便他们可以在勘误表中添加一个条目。

那是个错误。 只有 imul 有立即数和 2 寄存器形式。

mul,div,或者idiv仍然只存在于8086引入的单操作数形式,使用RDX:RAX作为输出的隐式双宽度操作数(以及 division 的输入)。

或EDX:EAX、DX:AX或AH:AL,当然取决于操作数的大小。请查阅 ISA 参考资料,例如 Intel 手册,而不是本书! https://www.felixcloutier.com/x86/idiv

另见 and

x86-64 的唯一硬件 division 指令是 idivdiv。删除了 64 位模式 aam,它通过立即数执行 8 位 div 转换。 ( and 有一个在 16 位模式下使用 aam 的例子)。

当然对于division by constants idiv and div (and aam) 是非常低效的。对 2 的幂使用移位,否则使用乘法逆运算,除非您优化的是代码大小而不是性能。


CS:APP 3e Global Edition 显然在实践问题中有多个像这样的严重 x86-64 指令集错误,声称 GCC 发出了不可能的指令。不仅仅是错别字或细微的错误,还有误导性的废话,这对于熟悉 x86-64 指令集的人来说显然是错误的。这不仅仅是一个语法错误,它试图使用不可编码的指令(除了扩展为多条指令的宏之外,没有任何语法可以表达它们。使用宏将 idivq 定义为伪指令会很奇怪)。

例如I correctly guessed missing part of a function, but gcc generated assembly code doesn't match the answer 是另一个,它表明 (%rbx, %rdi, %rsi)(%rsi, %rsi, 9) 是有效的寻址模式!比例因子实际上是一个 2 位移位计数,所以这些都是垃圾,并且表明作者严重缺乏关于他们正在教授的 ISA 的知识,而不是打字错误。

他们的代码不会 assemble 使用任何 AT&T 语法 assembler.

另外 是另一个例子,他们有一个无意义的 addq %eax 而不是 inc %rdx,并且在 mov 存储中有一个不匹配的操作数大小。


他们似乎只是在编造东西并声称它是由 GCC 发出的。 IDK 如果他们从真正的 GCC 输出开始并将其编辑成他们认为更好的示例,或者实际从头开始手写而不进行测试。

GCC 的实际输出会使用魔术常数(定点乘法逆)乘以 divide 乘以 9(即使在 -O0,但这显然不是调试模式代码。他们可以使用 -Os).

大概他们不想谈论 Why does GCC use multiplication by a strange number in implementing integer division? 并用他们编造的指令替换了那段代码。从上下文中,您可能可以找出他们期望输出的位置;也许他们的意思是 rcx /= 9.


这些错误来自全球版中的第 3 方练习题

来自发布者的网站 (https://csapp.cs.cmu.edu/3e/errata.html)

Note on the Global Edition: Unfortunately, the publisher arranged for the generation of a different set of practice and homework problems in the global edition. The person doing this didn't do a very good job, and so these problems and their solutions have many errors. We have not created an errata for this edition.

所以CS:APP3e大概是一本不错的教材,只要拿到北美版的,还是无视练习/作业题。这解释了教科书的声誉与 wide 使用之间的巨大脱节与严重和明显的(对于熟悉 x86-64 asm 的人来说)错误,像这样的错误超越了草率到不知道-语言领域。


如何设计假设的 idiv reg, regidiv $imm, reg

Also, the dividend should be given from the quantity in registers %rdx (high-order 64 bits) and %rax (low-order 64 bits) - so if this is defined in the architecture then it does not seem possible that the second operand could be a specified dividend.

如果 Intel 或 AMD dividiv 引入了一种新的方便形式,他们会设计它使用单宽度 dividend 因为编译器总是这样使用它。

大多数语言都像 C,隐式地将 + - * / 的两个操作数提升为相同的类型,并产生该宽度的结果。当然,如果已知输入很窄,则可以对其进行优化。 (例如,使用一个 imul r32 来实现 a * (int64_t)b)。

但是 dividiv 会在商溢出时出错,因此在编译 int32_t q = (int64_t)a / (int32_t)b 时使用单个 32 位 idiv 是不安全的。

编译器 总是 在 DIV 或 cdqcqo 之前 DIV 使用 xor edx,edx 实际上做 n / n => n 位 division。

使用 dividend 的真正全角 division 不仅仅是零扩展或符号扩展只能通过内部函数或 asm 手动完成(因为gcc/clang 和其他编译器不知道什么时候优化是安全的),或者在 gcc 辅助函数中,例如64 位/64 位 division in 32 位代码。 (或 64 位代码中的 128 位 division)。

因此,最有帮助的是 div/idiv,它也避免了设置 RDX 的额外指令,并最大限度地减少了隐式寄存器操作数的数量。 (就像 imul r32, r/m32 and imul r32, r/m32, imm 做的那样:使非 widening 乘法的常见情况更方便,没有隐式寄存器。这是英特尔语法,就像手册一样,目标优先)

最简单的方法是执行 dst /= src 的 2 操作数指令。或者用商和余数替换两个操作数。对像 BMI1 andn 这样的 3 个操作数使用 VEX 编码,你可能有
idivx remainder_dst, dividend, divisor。第二个操作数也是商的输出。或者您可以将余数写入 RDX,并为商提供非破坏性目的地。

或者更有可能针对只需要商的简单情况进行优化,idivx quot, dividend, divisor而不是将余数存储在任何地方。当您需要商时,您始终可以使用常规 idiv

BMI2 mulx 使用隐式 rdx 输入操作数,因为它的目的是允许多个 dep 链的 add-with-carry 进行扩展精度乘法。所以它仍然必须产生 2 个输出。但是这种假设的 idiv 的新形式将存在以节省 idiv 正常使用的代码大小和 uops 而 aren't widening .所以386imul reg, reg/mem是比较点,不是BMI2mulx.

IDK 如果引入直接形式的 idivx 也有意义;您只会出于代码大小的原因使用它。乘法逆比 div 常量运算更有效,因此此类指令在现实世界中的用例非常少。

有趣的是,gas 似乎允许以下内容:

mov , %rax
mov [=10=], %rdx
mov , %rcx
idivq %rcx, %rax
ret

这仍在执行引擎盖下的一个操作数除法,但它看起来像两个操作数形式。只要第一个操作数是一个寄存器而第二个操作数具体是 %rax,就可以工作。但是,通常 idivq 似乎需要一个操作数形式。