我应该在哪个阶段为我的编译器实现尾调用优化

时间:2016-09-30 14:47:15

标签: compiler-construction scheme racket tail-call-optimization

我在Racket中为类似方案的语言编写了一个小编译器。现在我想为我的编译器实现TCO。

我的想法是:我需要在将它们转换为intermedia表示之前检测尾调用。但是从page开始,通常会将call更改为jmp,从而在汇编级别完成TCO。我有点被困在这里。

任何建议将不胜感激。

编辑:我的目标是x86汇编代码。我使用的IR是三个地址码。

以下是我的编译器的12次传递,flatten传递是我将源代码转换为IR

的地方
(define test-passes
(list
 `("uniquify"                ,(uniquify '())                                   ,interp-scheme)
 `("reveal-functions"        ,(reveal-functions '())                           ,interp-F)
 `("convert-to-closures"     ,convert-to-closure                               ,interp-F)
 `("expose allocation"       ,expose-allocation                                ,interp-F)
 `("flatten"                 ,(flatten #f)                                     ,interp-C)
 `("instruction selection"   ,select-instructions                              ,interp-x86)
 `("liveness analysis"       ,(uncover-live (void))                            ,interp-x86)
 `("build interference"      ,(build-interference (void) (void) (void) (void)) ,interp-x86)
 `("allocate register"       ,allocate-registers                               ,interp-x86) 
 `("lower-conditionals"      ,lower-conditionals                               ,interp-x86)
 `("patch-instructions"      ,patch-instructions                               ,interp-x86)
 `("x86"                     ,print-x86                                        #f)
 ))

3 个答案:

答案 0 :(得分:4)

答案取决于你的编译目标。

如果要编译汇编程序(或机器代码),则可以在代码生成器中处理尾调用(例如,参见Abdulaziz Ghuloum的“编译器构造的增量方法”。

如果目标语言是C语言(即调用构建上下文),那么根据您希望编译器的程度如何,您有几个选项。其他人提到ANF和CPS作为中间形式。也可以引入蹦床。请参阅Felix Winkelman的“计划实施技术”,了解战略清单。

如果您的目标语言支持尾递归,请考虑使用一种策略,将Scheme调用转换为目标语言的调用。

无论如何:如果你有兴趣编译Scheme,那么请不要犹豫,拿到一份LiSP:Christian Queinnec的小件Lisp。

答案 1 :(得分:2)

对我而言,启动实现尾调用优化的进程的地方将通过显式语言构造检测它,类似于Clojure's recur operator采用的方法,因为这是最简单的事情这可能会奏效。这将导致一个过程识别尾调用,另一个过程实现尾调用。

自动识别尾调用的进一步开发成为第一个过程的修改。进一步开发以改进优化成为第二过程的修改。每个人的发展可以独立发生[或根本不发生]。

答案 2 :(得分:1)

你可以很早就检测到尾调用,理想情况是在lambda解除后直接调用(你没有明确说明,但很可能你的转换为封闭传递正在进行)。

然后,标记为tail的调用可以在稍后阶段降低,但它不像只是跳转那么简单 - 你必须首先清理你的堆栈帧(如果你正在使用堆栈来传递函数参数)。如果您仅使用寄存器传递参数,则更容易。