Rust: 教 rustc 做尾调用

创建于 2011-01-27  ·  18评论  ·  资料来源: rust-lang/rust

Rustc 还不知道如何进行尾调用('be' 而不是 'ret')。 教它怎么做应该不会太难。

A-LLVM

最有用的评论

如果可能,我们计划最终实施有保证的 TCO。 我们甚至为它保留了一个关键词,“成为”。 请检查 RFC 存储库。

2016 年 8 月 3 日 19:46 -0400,Antoine PLASKOWSKI [email protected]写道:

我是生锈的新闻,我很伤心。 我尝试了一个尾递归函数,并且我堆栈溢出的速度非常快。 最糟糕的是,当我在发布时编译行为改变。 他只是不调用我的函数。 一位朋友对我说,LLVM 优化为 undefine 值(LLVM 知道这是一个无限的尾递归?!?)。

fn rec(i: i32) { rec(i + 1) } fn main() { println!("{}", rec(0)); }

我同意 Boiethios 的观点,这是一种具有功能特性的语言,它没有实现尾调用丢失的东西。

我用 C 和 C++ 编写,它们不保证尾调用,但实际上它们处理得很好。 这不会阻止我学习 rust,现在我知道 rust 不能处理它,我会在没有它的情况下编写代码,所以这不是一个大问题。

但我认为这对于现代语言来说是一个非常好的特性。


您收到此消息是因为您订阅了此线程。
直接回复这封邮件,在 GitHub 上查看(https://github.com/rust-lang/rust/issues/217#issuecomment-237409642),或者静音线程(https://github.com/notifications/unsubscribe -auth/AABsipoedHrbnKDekmzCr-dl8M6g-Gojks5qcShKgaJpZM4AC-q_)。

所有18条评论

根据 Graydon,我们需要在实现之前对调用约定做更多的思考。 LLVM 需要 fastcc 约定来实现尾调用,但它并不能完全满足我们的要求:

graydon: brson: 从 llvm-land 的评论来看,我们可能有麻烦了。 我们也许可以弥补 fastcc 今天的所作所为,但它可以在明天改变主意。
graydon:我以为 fastcc == x86_fastcall,但我错了。
graydon:这些是不同的调用约定。 fastcc 是“本周 llvm 的任何感觉”
graydon:我们需要切换到 x86_fastcall,然后从长远来看,编写我们自己的调用约定。

由于 LLVM 处理尾调用的方式存在一些复杂性,这目前实现了一半。 Rustc 解析 'be' 表达式并将它们转换为 call+ret 对,这足以让我们在简单的、不是很深的情况下“工作”。 可能足以引导。

显然只有一个 CC (fastcc) 支持有保证的尾调用,为了适应它,我们需要在几个地方更改我们的 ABI 假设(当您启用 -tailcallopt 标志时,它变成被调用者恢复)。 因此,即使我们根据需要用“tail”注释我们的 call+ret 对,我们实际上还不能告诉 LLVM 开始执行该优化。

事实证明,自托管不需要比这里显示的更多的实现。 奔向下一个里程碑。

有人觉得我们真的会再这样做了吗?

似乎不会发生。

LLVM 和尾调用的情况如何? 如果它们可以在没有一些侵入性的东西(比如声明被调用者被尾调用)的情况下可靠地工作,我们可以定义一组有限的东西,这些东西可以传递给尾调用(调用者自己的参数,标量),并在发生某些事情时出错否则通过。 不过,这种尾调用在实践中可能不是很有用。

它们仅与在非尾调用情况下次优的被调用者 ABI 一起使用。 所以从某种意义上说,是的,它们要求被调用者被声明为尾部调用。

我们可以通过分析一个 crate 来实现这一点,当我们发现一个被尾调用的函数时,或者在友好的尾调用 ABI 下单独编译它,或者编译在入口时切换 ABI 的包装器,或者其他什么。 或者,我们可以在任何地方切换 _every_ 函数以使用尾调用友好的 ABI。 我不确定我们目前是否正在这样做。 我们有一段时间,但我们可能已经停下来了。 它是 LLVM “fastcall” ABI,我认为我们不再使用它了。

无论如何,这有点乱。

我们现在有兄弟调用优化。 我们是否打算尝试做更多的事情?

不会发生,除非在#2216 解决的范围内。 #2216 的解决方案应该足够广泛,以支持通用状态机编码。

这继续在谈话中出现,我们再次保留be 。 重新开放。

我认为 Graydon 对邮件列表的评论:

https://mail.mozilla.org/pipermail/rust-dev/2013-April/003557.html

在这棺材上钉了一颗钉子。 在此转载:


在 2013 年 10 月 4 日上午 5:43,Artella Coding 写道:

嗨,rust 会做尾调用优化吗? 我问的原因是
以下尾调用递归实现导致堆栈
溢出。 谢谢。

不,而且很可能不会。 我们对此有一个长期存在的错误:

https://github.com/mozilla/rust/issues/217

以及一个 wiki 页面和几个邮件列表线程:

https://github.com/mozilla/rust/wiki/Bikeshed-tailcall
https://mail.mozilla.org/pipermail/rust-dev/2011-August/000689.html
https://mail.mozilla.org/pipermail/rust-dev/2012-January/001280.html
...

这一切的总结是:

  • 我们都知道尾调用是一种良性的语言特性。
    不需要进一步阐述他们的优点。 我们中许多人
    有 lisp 和 ML 背景,非常喜欢它们。 他们的
    缺席是心痛和悲伤,不是轻而易举的。
  • 尾巴用确定性破坏来“玩坏了”。 包含
    〜框的确定性下降。 并不是说他们不是
    可组合的,但组合它们的选项是 UI-awkward,
    性能惩罚,语义复杂或以上所有。
  • 尾调用也与 C 工具中的假设“玩得很糟糕”,包括
    平台 ABI 和动态链接。
  • 尾调用需要一个会影响性能的调用约定
    相对于 C 约定。
  • 我们发现尾递归的大多数情况都可以很好地转换为
    循环和大多数非递归尾调用的情况对状态进行编码
    可以很好地转换为环绕的循环的机器
    枚举。 这些都_相当_不像
    使用尾调用的变体,但它们确实有效并且“一样快”*,
    以及 C 和 C++ 程序员的惯用语(他们是我们的
    主要受众)。

很抱歉说出这一切,心情很沉重,但我们
尝试并没有找到一种方法来进行与它们相关的权衡
总结出一个包含在 rust 中的论点。

-格雷登

  • 在速度方面,一个循环中的状态机是间接调度的,所以
    可能会比由尾调用编码的状态机运行得慢
    州对州; 另一方面,如果获得这种性能的方式
    “后退”是在任何地方打开尾随,我们将交易一个孤立的
    跨所有程序次优性能税的次优性能案例。 我们
    不觉得这种交易可以接受。

值得注意的是,Haskell 通常需要一个静态参数转换来进行内联、融合等。 http://stackoverflow.com/a/9660027/667457

Rust 可以通过说在另一个函数中定义的函数应该可以忽略尾调用优化来支持静态参数转换。 或者,如果嵌套在另一个函数中的函数之间的尾调用失败了兄弟优化,也可以简单地发出警告。 不确定目前的情况。

遗憾的是,尾递归将无法实现。 然后我有一个问题:如果缺少函数式的基本特征,为什么要创建一种看起来像函数式语言语法的语言语法?

我是生锈的新闻,我很伤心。 我尝试了一个尾递归函数,并且我堆栈溢出的速度非常快。 最糟糕的是,当我在发布时编译行为改变。 他只是不调用我的函数。 一位朋友对我说,LLVM 优化为未定义的值(LLVM 知道这是无限的尾递归?!?)。

fn rec(i: i32) {
  rec(i + 1)
}

fn main() {
  println!("{}", rec(0));
}

我同意 Boiethios 的观点,这是一种具有功能特性的语言,它没有实现尾调用错过了一些东西。

我用 C 和 C++ 编写,它们不保证尾调用,但实际上它们处理得很好。 这不会阻止我学习 rust,现在我知道 rust 无法处理它。 我会在没有代码的情况下进行编码,所以这不是一个大问题。

但我认为这对于现代语言来说是一个非常好的特性。

请注意

fn rec(i: i32) {
  println!("Hello");
  rec(i + 1)
}

货物释放模式下也出现堆栈溢出

如果可能,我们计划最终实施有保证的 TCO。 我们甚至为它保留了一个关键词,“成为”。 请检查 RFC 存储库。

2016 年 8 月 3 日 19:46 -0400,Antoine PLASKOWSKI [email protected]写道:

我是生锈的新闻,我很伤心。 我尝试了一个尾递归函数,并且我堆栈溢出的速度非常快。 最糟糕的是,当我在发布时编译行为改变。 他只是不调用我的函数。 一位朋友对我说,LLVM 优化为 undefine 值(LLVM 知道这是一个无限的尾递归?!?)。

fn rec(i: i32) { rec(i + 1) } fn main() { println!("{}", rec(0)); }

我同意 Boiethios 的观点,这是一种具有功能特性的语言,它没有实现尾调用丢失的东西。

我用 C 和 C++ 编写,它们不保证尾调用,但实际上它们处理得很好。 这不会阻止我学习 rust,现在我知道 rust 不能处理它,我会在没有它的情况下编写代码,所以这不是一个大问题。

但我认为这对于现代语言来说是一个非常好的特性。


您收到此消息是因为您订阅了此线程。
直接回复这封邮件,在 GitHub 上查看(https://github.com/rust-lang/rust/issues/217#issuecomment-237409642),或者静音线程(https://github.com/notifications/unsubscribe -auth/AABsipoedHrbnKDekmzCr-dl8M6g-Gojks5qcShKgaJpZM4AC-q_)。

只是一个问题:假设可以进行调用图分析并将尾调用转换为循环,至少在单个 crate 中,但它在编译时计算成本很高,并且编码相当棘手。 有没有讨论过这种可能性? 现在这仍然是一个选项,而不考虑调用约定的选择。

lbstanza 的方法是要求对函数进行注释以使它们符合 TCO(defn+ 而不是 defn)。 在 rust 中,这将在很大程度上减轻 timthelion 建议的额外编译时间开销,只要您没有在任何地方都使用尾调用,哈哈。

此页面是否有帮助?
0 / 5 - 0 等级