为什么尾调用优化需要一个操作码?

13

所以我以前读过很多次,从技术上讲,.NET确实支持尾调用优化(TCO),因为它有相应的操作码,只是C#没有生成它。

我不确定为什么需要一个操作码来实现TCO或者它会做什么。据我所知,能够进行TCO的要求是递归调用的结果不与当前函数作用域中的任何变量结合。如果你没有这个要求,那么我不明白一个操作码如何使你无需保留堆栈帧。如果您具备该要求,那么编译器是否总是可以将其轻松编译成迭代形式?

那么操作码的作用是什么呢?显然我缺少了一些知识。在可能进行TCO的情况下,编译器不总是可以在编译时处理,而不是操作码级别上处理吗?有哪些例外呢?


2
我的无知猜测是,这个操作码更像是高级编译器给JITter的一个提示,从而使后者(其快速编译是其关键特征之一)免于分析IL以查看尾调用优化在任何给定情况下是否可行,从而节省时间消耗的任务。 - 500 - Internal Server Error
2个回答

8

根据您提供的链接,以下是我认为与您的问题非常相关的部分:


来源

CLR和尾调用

当您处理由CLR管理的语言时,有两种编译器在运行。一种是从您的语言源代码到IL的编译器(C#开发人员称其为csc.exe),然后是从IL到本机代码的编译器(在运行时或NGEN时间调用的JIT 32/64位编译器)。源-> IL和IL->本机编译器都了解尾调用优化。但是IL->本机编译器(我将其简称为JIT)最终决定是否使用尾调用优化。源-> IL编译器可以帮助生成有利于进行尾调用的IL,包括使用“tail”IL前缀(稍后详细介绍)。以此方式,源-> IL编译器可以构造它生成的IL,以说服JIT进行尾调用。但是JIT始终可以自行决定。

JIT何时进行尾调用?

我问了Fei Chen和Grant Richins,他们是我大厅隔壁的邻居,碰巧在JIT上工作,在什么条件下各种JIT将使用尾调用优化。完整答案相当详细。简要总结是,JIT尝试在任何时候都使用尾调用优化,但是有许多原因导致无法使用尾调用优化。一些不支持尾调用的原因:

  • 调用者在调用后不立即返回(显然:-))
  • 调用者和被调用者之间的堆栈参数不兼容,需要在调用者帧中移动东西,然后被调用者才能执行
  • 调用者和被调用者返回不同类型
  • 我们代替调用(内联比尾调用好得多,并为许多其他优化打开了大门)
  • 安全性阻碍了尾调用
  • 调试器/分析器关闭了JIT优化

与您的问题相关的最有趣部分,在许多情况下,是上述安全示例...

在很多情况下,.NET中的安全性取决于堆栈在运行时的准确性...这就是为什么,如上所述,负担由源到CIL编译器和(运行时)CIL到本机JIT编译器共同承担,最终决定权在后者手中。


但是为什么需要尾部操作码呢?JIT 可以始终尝试 TCO。 - usr

1

猜测:在像x86汇编语言这样的简单语言中,您可以手动管理堆栈,因此不需要操作码-您只需适当设置调用堆栈即可。

但是在像.NET CIL这样的较高级别的语言中,堆栈部分由系统自动管理,并且调用函数的整个操作是单一操作码(例如call)。因此,您需要另一个操作码来实现TCO-一个可以“将控制流传递到该函数,但不创建新堆栈帧”的操作码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接