【问题标题】:GCC compile to assembly: cmp followed by call instead of conditional jumpGCC 编译为程序集:cmp 后跟调用而不是条件跳转
【发布时间】:2020-09-15 04:32:41
【问题描述】:

这个汇编代码合理吗?

cmp op1, op2
pushf
call _func
popf
jz .L0

我想知道 GCC as 在什么情况下可以生成这样的代码 cmp 后跟 call 而不是条件跳转

【问题讨论】:

  • 你见过gcc生成这样的代码吗?如果是这样,来自什么源代码?从某种意义上说,它是“合理的”,它会起作用,但我不能说我在编译器输出中看到过这样的构造。

标签: assembly gcc x86


【解决方案1】:

不,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 可能踩到的潜在垃圾。

【讨论】:

  • 如果它想在调用后实现一个布尔值,它总是会使用 setz bl 或其他东西(在调用保留的寄存器中)。 然后 GCC 将不得不做测试 %bl, %bl 进行跳跃,对吗?这仍然会做额外的工作,而不仅仅是将 cmp op1, op2 放在 call 之后。
  • @syacer:是的,如果 op1 和 op2 易于保存,那么稍后再进行比较会更好。如果它们是从全局加载的寄存器值(函数可能会修改),则 GCC 需要发明临时变量来保存它们。根据寄存器压力,将副本保存在调用保留寄存器中可能会更糟。即使有备用的 2 个寄存器 gcc 可以保存/恢复,GCC 仍可能选择最直接的方式并只保存布尔结果。请注意,test / jnzjnz 的成本大致相同,除了代码大小,感谢宏融合。
  • @syacer 你必须记住的一件事是pushfpopf 是相当慢的指令。编译器绝不会使用这些来保留条件标志,否则它可以这样做。
  • @fuz:您回复的评论是说另一种方法是将cmp 本身移动到call 之后。这避免了对pushf/popf 的需要。所以是的,@ syacer 是正确的,稍后再做cmp 是最好的选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-13
相关资源
最近更新 更多