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 指令是 idiv
和 div
。删除了 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, reg
或 idiv $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 为 div
或 idiv
引入了一种新的方便形式,他们会设计它使用单宽度 dividend 因为编译器总是这样使用它。
大多数语言都像 C,隐式地将 + - * / 的两个操作数提升为相同的类型,并产生该宽度的结果。当然,如果已知输入很窄,则可以对其进行优化。 (例如,使用一个 imul r32
来实现 a * (int64_t)b
)。
但是 div
和 idiv
会在商溢出时出错,因此在编译 int32_t q = (int64_t)a / (int32_t)b
时使用单个 32 位 idiv
是不安全的。
编译器 总是 在 DIV 或 cdq
或 cqo
之前 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 似乎需要一个操作数形式。
我正在通过 "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
另见
x86-64 的唯一硬件 division 指令是 idiv
和 div
。删除了 64 位模式 aam
,它通过立即数执行 8 位 div 转换。 (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, reg
或 idiv $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 为 div
或 idiv
引入了一种新的方便形式,他们会设计它使用单宽度 dividend 因为编译器总是这样使用它。
大多数语言都像 C,隐式地将 + - * / 的两个操作数提升为相同的类型,并产生该宽度的结果。当然,如果已知输入很窄,则可以对其进行优化。 (例如,使用一个 imul r32
来实现 a * (int64_t)b
)。
但是 div
和 idiv
会在商溢出时出错,因此在编译 int32_t q = (int64_t)a / (int32_t)b
时使用单个 32 位 idiv
是不安全的。
编译器 总是 在 DIV 或 cdq
或 cqo
之前 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 似乎需要一个操作数形式。