【问题标题】:Forcing tail call elimination in C with inline asm使用内联 asm 在 C 中强制消除尾调用
【发布时间】:2012-09-30 15:13:02
【问题描述】:

我一直在设计一种小型编程语言,我想要一个相对简单的后端选项。由于所有通常的原因(例如“我无法遵循好的建议”和“我不知道汇编”),C 是一个相对不错的选择,更重要的是,我的语言中并没有太多不能用 C 轻松表达(更多的是关于类型检查器,没有 GC 或重型运行时来适应) - 除了一件事:优化尾调用。

这不是一个交易破坏者:我真的不需要它,因为该语言具有循环结构,但它也应该是功能性的,所以如果可能的话,TCO 似乎是一个有价值的东西。 Trampoline 或lazy-thunk 解决方案会起作用,但似乎(根据文献,我还没有没有分析过,而且无论如何也没有任何使用信息)对性能有显着影响。不希望实现阻止用户以有效的方式表达问题。

This question 让我认为应该可以创建一个相对简单的特定于平台的“tailcall”块,以替换“return”并强制执行我想要的行为。但是,由于对 x86 不够熟悉,我不知道如何控制 C 的自动堆栈操作,我应该将跳转的目标放在哪里等等。(在我看来,它在函数启动后扩展了堆栈。 .. 所以我需要在尾调用之前缩小堆栈,这有点不必要,或者在堆栈操作之后以某种方式在目标主体的开始处进行跳转,这听起来几乎是不可能的编辑: 用于静态未知目标 ..?)

所以:

  • 可以这样做吗?
  • 可以安全完成吗?
  • 这是否明智?

我应该重复一遍,我知道可以在 C(即我的高级语言,而不是 C)中实现 工作 的尾调用消除,我只是想添加特定于编译器和平台的优化,以帮助它或多或少与“真实事物”无法区分。而且我主要想使用 C,因为我对它非常有信心,而我从未使用过 LLVM,并且只有最基本的 x86 阅读知识,所以我希望跳过这一步并让输出正常工作。我知道我将来一定要好好学习这两样东西,但是……

(即我问这个是因为这个想法卡在我的脑海里,它让我觉得可能有更好的方法来做它(tm),而不是因为我需要解决方案获得一个工作后端。)

【问题讨论】:

  • 这让我想起了Continuation Passing Style,特别是Chicken,一个Scheme to C 编译器。你可能想研究这些。
  • 我不明白你为什么需要内联汇编。您可以发出在函数开头带有标签的 C 代码,然后将尾部调用替换为重新绑定函数参数并转到标签。
  • TCE 只是将递归尾调用更改为while(1) { body } 语句,同时在“再次执行”之前跟踪参数。编辑:基本上是@Ferruccio 所说的;p
  • 当然,我已经为递归这样做了,但我的意思是尾调用静态未知函数指针的一般情况。除非我完全误解了你,否则goto/while 解决方案对此无济于事。
  • 更好地检查 GCC 通过一些优化生成的代码,它非常擅长消除尾递归(甚至是一些非尾递归)。

标签: c gcc assembly x86


【解决方案1】:

这个可以吗?

是的。

可以安全完成吗?

是的。

这是否明智?

没有。 gcc 已经支持TCO for you 并且还支持computed goto,所以绝对没有理由这样做。

【讨论】:

  • 您链接的 TCO 问题与此无关。它讨论了通过转储汇编器输出来确定 gcc 是否对特定函数执行了 TCO。
  • @Kiyura - 它说明 GCC 执行 TCO;你不需要通过汇编来获得它在 GCC 上。
  • 但这是一种优化,本质上无法保证。即使是这样,它也不适用于这种情况,因为被调用的函数在编译时不一定是已知的。
  • @Kiyura - 他在帖子中究竟在哪里说“函数不一定在编译时已知”?
  • @Leushenko:TCO 是否可以应用于给定函数的关键问题是该函数是否真的需要堆栈帧。这意味着,一旦在堆栈上传递的参数与提供给包装器本身的原始参数不同,call 不会 被消除。在 32 位 x86 中,如果包装函数的参数数量与包装函数的参数数量不同,那么您总是会遇到这种情况(因为参数是在堆栈上传递的)。在 64 位 x86_64 中,如果包装函数的 args 超出参数寄存器的容量,则会发生这种情况。 并非所有尾部返回函数都符合 TCO 条件。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-18
  • 1970-01-01
  • 2019-07-10
  • 2016-09-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多