我在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)
))
答案 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的调用可以在稍后阶段降低,但它不像只是跳转那么简单 - 你必须首先清理你的堆栈帧(如果你正在使用堆栈来传递函数参数)。如果您仅使用寄存器传递参数,则更容易。