为什么尾调用优化需要操作码?
所以我之前已经阅读了很多次 ,技术上.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 – 一个“将控制流传递给此函数,但不创建新的堆栈帧”。
- DateTimeStyles.RoundtripKind枚举是什么意思?
- 在C#.NET 2中获取像explorer这样的可执行文件的程序版本
- 如何将类(从通用“基础”类派生)转换为该通用“基础”类
- Wix – 如何从安装目录安装后运行exe文件?
- 添加行时,DataGridView抛出“InvalidOperationException:Operation无效…”
- 使用Table Per Hierarchyinheritance在LINQ to Entities查询中转换为派生类型
- for循环的优化
- System.ComponentModel.Win32Exception:操作成功完成
- 如何使用MonthCalender在文本框中插入日期?