代码之家  ›  专栏  ›  技术社区  ›  ony

clang 1.1和1.0生成的尾调用(llvm 2.7和2.6)

  •  1
  • ony  · 技术社区  · 14 年前

    编译后,使用clang-o2(或 online demo ):

    #include <stdio.h>
    #include <stdlib.h>
    
    int flop(int x);
    int flip(int x) {
      if (x == 0) return 1;
      return (x+1)*flop(x-1);
    }
    int flop(int x) {
      if (x == 0) return 1;
      return (x+0)*flip(x-1);
    }
    
    int main(int argc, char **argv) {
      printf("%d\n", flip(atoi(argv[1])));
    }
    

    我要把LLVM程序集的下一个片段 flip :

    bb1.i:                                            ; preds = %bb1
      %4 = add nsw i32 %x, -2                         ; <i32> [#uses=1]
      %5 = tail call i32 @flip(i32 %4) nounwind       ; <i32> [#uses=1]
      %6 = mul nsw i32 %5, %2                         ; <i32> [#uses=1]
      br label %flop.exit
    

    我想 tail call 表示丢弃当前堆栈(即返回到上一帧,因此下一条指令应为 ret %5 ,但是根据这个代码 mul 为了它。在本机程序集中 call 不进行尾部优化(即使有适当的有限责任公司标志)

    Sombody能解释为什么clang会生成这样的代码吗?

    我也不明白为什么LLVM 尾部调用 如果它能简单地检查下一个 ret 将使用上一个的结果 呼叫 稍后,是否进行适当的优化或生成本地等价的尾调用指令?

    1 回复  |  直到 14 年前
        1
  •  3
  •   Evan Shaw    14 年前

    看看 'call' instruction LLVM Assembly Language Reference Manual . 它说:

    可选的“tail”标记表示被调用函数不访问调用程序中的任何allocas或varargs。注意,调用可能被标记为“tail”,即使它们不是在ret指令之前发生的。

    clang中传递的一个llvm优化可能会分析被调用方是否访问调用方中的任何allocas或varargs。如果没有,那么pass会将调用标记为尾调用,并让LLVM的另一部分了解如何处理“尾”标记。也许函数现在不能是真正的尾调用,但是在进一步的转换之后,它可能是。我猜这样做是为了减少传球顺序的重要性。