为什么编译器会复制一些指令?

时间:2017-12-28 20:38:38

标签: c++ compiler-construction clang

有时编译器会生成具有可以安全删除的奇怪指令重复的代码。请考虑以下代码:

int gcd(unsigned x, unsigned y) {
  return x == 0 ? y : gcd(y % x, x);
}

以下是汇编代码(已生成development web server (dev_appserver.py)并启用了优化):

gcd(unsigned int, unsigned int): # @gcd(unsigned int, unsigned int)
  mov eax, esi
  mov edx, edi
  test edx, edx
  je .LBB0_1
.LBB0_2: # =>This Inner Loop Header: Depth=1
  mov ecx, edx
  xor edx, edx
  div ecx
  test edx, edx
  mov eax, ecx
  jne .LBB0_2
  mov eax, ecx
  ret
.LBB0_1:
  ret

在以下代码段中:

  mov eax, ecx
  jne .LBB0_2
  mov eax, ecx

如果没有发生跳转,eax会被重新分配,原因不明确。

另一个例子是函数末尾的两个ret:一个也可以完美地工作。

编译器是否完全不够智能,或者有理由不删除重复项?

2 个答案:

答案 0 :(得分:40)

编译器可以执行对人们来说不明显的优化,删除指令并不总能让事情变得更快。

少量搜索表明,当RET紧跟在条件分支之后,各种AMD处理器都存在分支预测问题。通过填充基本上是无操作的插槽,可以避免性能问题。

更新

示例参考," AMD64处理器软件优化指南的第6.2节" (见http://support.amd.com/TechDocs/25112.PDF)说:

具体而言,请避免以下两种情况:

  • 任何类型的分支(有条件或无条件),其单字节近似返回RET指令作为其目标。参见“示例”。

  • 直接在单字节近返RET指令之前的代码中出现的条件分支。

它还详细说明了为什么跳转目标应该具有对齐,这也可能解释函数末尾的重复RET。

答案 1 :(得分:5)

任何编译器都会有一堆转换用于寄存器重命名,展开,提升等。将它们的输出结合起来会导致次优的情况,例如您所显示的情况。 Marc Glisse提供了很好的建议:它值得一个错误报告。您正在描述窥视孔优化器放弃指令的机会

  • 不会影响寄存器的状态&内存,或
  • 不会影响对功能的后置条件至关重要的状态,对其公共API无关紧要。

听起来像symbolic execution技术的机会。如果约束求解器没有找到给定MOV的分支点,那么它实际上可能是NOP。