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

发布于 2024-09-02 16:06:59 字数 1144 浏览 5 评论 0原文

使用 clang -O2 (或使用 在线演示)编译下一个代码片段后:

#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])));
}

我得到下一个片段flip 中的 llvm 汇编:

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 ,没有尾部优化(即使有适当的 llc 标志)

有人可以解释为什么 clang 生成这样的代码吗?

同样,我不明白为什么 llvm 有 tail call 如果它可以简单地检查下一个 ret 将使用上一个 call 的结果,然后再执行适当的优化或生成尾部调用指令的本机等效项?

After compilation next snippet of code with clang -O2 (or with 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])));
}

I'm getting next snippet of llvm assembly in 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

I thought that tail call means dropping current stack (i.e. return will be to the upper frame, so next instruction should be ret %5), but according to this code it will do mul for it. And in native assembly there is simple call without tail optimisation (even with appropriate flag for llc)

Can sombody explain why clang generates such code?

As well I can't understand why llvm have tail call if it can simply check that next ret will use result of prev call and later do appropriate optimisation or generate native equivalent of tail-call instruction?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

调妓 2024-09-09 16:06:59

查看 'call' 说明 //llvm.org/docs/LangRef.html" rel="nofollow noreferrer">LLVM 汇编语言参考手册。它说:

可选的“tail”标记表示被调用函数不访问调用者中的任何分配器或可变参数。请注意,即使调用没有发生在 ret 指令之前,也可能会被标记为“tail”。

Clang 中的 LLVM 优化之一可能会分析被调用者是否访问调用者中的任何分配器或可变参数。如果没有,则传递会将调用标记为尾部调用,并让 LLVM 的另一部分弄清楚如何处理“尾部”标记。也许该函数现在不能是真正的尾部调用,但经过进一步的转换后它可能是。我猜这样做是为了使传递的顺序不那么重要。

Take a look at the 'call' instruction in the LLVM Assembly Language Reference Manual. It says:

The optional "tail" marker indicates that the callee function does not access any allocas or varargs in the caller. Note that calls may be marked "tail" even if they do not occur before a ret instruction.

It's likely that one of the LLVM optimization passes in Clang analyzes whether or not the callee accesses any allocas or varargs in the caller. If it doesn't, the pass marks the call as a tail call and lets another part of the LLVM figure out what to do with the "tail" marker. Maybe the function can't be a real tail call right now, but after further transformations it could be. I'm guessing it's done this way to make the ordering of the passes less important.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文