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

所以我之前已经阅读了很多次 ,技术上.NET 确实支持尾调用优化(TCO),因为它有操作码,只有C#不生成它。

我不确定为什么TCO需要操作码或它会做什么。 据我所知,能够进行TCO的要求是递归调用的结果不与当前函数范围中的任何变量组合。 如果你没有那个,那么我没有看到操作码如何阻止你必须保持堆栈框架打开。 如果你有,那么编译器是否总能轻易地将其编译为迭代的东西?

那么操作码有什么意义呢? 显然有些东西我不见了。 在完全可以使用TCO的情况下,不能总是在编译器级别处理,而不是在操作码级别处理? 什么是不能的例子?

按照你已经提供的链接,这是我觉得的部分,非常接近你的问题。


资源

CLR和尾调用

当您处理由CLR管理的语言时, 有两种编译器在起作用 。 编译器从您的语言源代码到IL(C#开发人员将其称为csc.exe),然后是从IL到本机代码的编译器(在运行时调用的JIT 32/64位编译器)或NGEN时间)。 source-> IL和IL-> native编译器都理解尾调用优化。 但IL->本机编译器 – 我将其简称为JIT – 对最终是否将使用尾调用优化有最终决定权。 source-> IL编译器可以帮助生成有助于进行尾调用的IL,包括使用“尾部”。 IL前缀(稍后会详细介绍)。 通过这种方式, source-> IL编译器可以构造它生成的IL,以说服JIT进行尾调用。 但是JIT总是可以选择做任何想做的事情。

JIT何时进行尾调用?

我问起了我在JIT工作的大厅里的邻居Fei Chen和Grant Richins,在什么条件下各种JIT将采用尾调优化。 完整的答案相当详细。 快速总结是JIT尝试尽可能地使用尾调用优化,但是有很多原因导致无法使用尾调用优化。 尾调用是不可选的一些原因:

  • 呼叫后呼叫者不会立即返回(duh :-))
  • 调用者和被调用者之间的堆栈参数是不兼容的,这种方式需要在调用者执行之前在调用者的帧中移动东西
  • 来电者和被叫者返回不同的类型
  • 我们内联调用(内联比尾调用更好,并为更多优化打开了大门)
  • 安全受到阻碍
  • 调试器/分析器关闭了JIT优化

在你的问题的上下文中最有趣的部分,在我看来,在许多场景中,它是超级清晰的,是上面提到的安全性的例子 ……

在许多情况下,.NET中的安全性取决于堆栈是否准确… 在运行时 ..这就是为什么,如上所述, CIL编译器的源和(运行时)CIL到本机JIT共享负担。编译器,最后的说法与后者有关。

猜测:在x86汇编程序这样的简单语言中,你可以“手动”管理堆栈,你不需要操作码 – 你可以恰当地设置调用堆栈。

但是在像.NET CIL这样的更高级别的东西中,堆栈是为您部分管理的,并且调用函数的整个行为是单个操作码(例如调用)。 因此,您需要一个不同的操作码来实现TCO – 一个“将控制流传递给此函数,但不创建新的堆栈帧”。