【发布时间】:2020-09-15 04:32:41
【问题描述】:
这个汇编代码合理吗?
cmp op1, op2
pushf
call _func
popf
jz .L0
我想知道 GCC as 在什么情况下可以生成这样的代码 cmp 后跟 call 而不是条件跳转
【问题讨论】:
-
你见过gcc生成这样的代码吗?如果是这样,来自什么源代码?从某种意义上说,它是“合理的”,它会起作用,但我不能说我在编译器输出中看到过这样的构造。
这个汇编代码合理吗?
cmp op1, op2
pushf
call _func
popf
jz .L0
我想知道 GCC as 在什么情况下可以生成这样的代码 cmp 后跟 call 而不是条件跳转
【问题讨论】:
不,GCC 永远不会这样做,如果它想在调用后立即实现一个布尔值,它总是会使用 setz bl 或其他东西(在调用保留寄存器中)。或者只是确保cmp 的两个参数在调用后都可用,然后进行或重做比较。
其中任何一个都会便宜得多; popf 非常慢,因为它可以像 AC 一样修改 FLAGS,并且(在内核模式下)像 IF 这样的 FLAGS。它需要取决于 CPU 模式的条件行为。它被微编码尽可能多的微指令。 (检查指令表https://agner.org/optimize/)。与 Skylake-X 上的 7 类似,每 21 个周期的吞吐量为 1 (https://uops.info)。
如果人类程序员真的想保存/恢复包括 ZF 在内的多个 FLAGS,他们会使用 lahf / mov ebx, eax 或其他东西。我认为 GCC 也不会这样做。
GCC 从不在调用中使用 push/pop 来保存/恢复某些内容。它在函数顶部保留空间并保存它想要使用的任何调用保留寄存器。要在调用之前将某些内容溢出到内存中,它将使用mov,而不是推送。
GCC 仅在调用之前使用 push 来传递堆栈参数。但是我们可以排除您的示例(并假设call _func 不使用堆栈参数),因为函数拥有它们的堆栈参数。因此,如果 pushf 传递了一个 arg,popf 将弹出 _func 可能踩到的潜在垃圾。
【讨论】:
test / jnz 与 jnz 的成本大致相同,除了代码大小,感谢宏融合。
pushf 和popf 是相当慢的指令。编译器绝不会使用这些来保留条件标志,否则它可以这样做。
cmp 本身移动到call 之后。这避免了对pushf/popf 的需要。所以是的,@ syacer 是正确的,稍后再做cmp 是最好的选择。