3

I wrote a small compiler in Racket for a scheme-like language. Now I want to implement TCO for my compiler.

My idea is: I need to detect tail calls before transform them into intermedia representation. But from this page, it seems like TCO is usually done at assembly level by changing call to jmp. I am kinda stuck at here.

Any suggestions would be appreciated.

EDIT: My target is x86 assembly code. The IR I used is three address code.

And here are the 12 passes for my compiler, the flatten pass is where i transform source code to 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)
 ))
齐天大圣
  • 1,169
  • 5
  • 15
  • 36
  • Why do you need to this this before you go into your IR, as opposed to doing it in ANF (or CPS)? – Leif Andersen Sep 30 '16 at 15:56
  • [ANF](https://en.wikipedia.org/wiki/Administrative_Normal_Form), [CPS](https://en.wikipedia.org/wiki/Continuation-passing_style), [IR](https://en.wikipedia.org/wiki/Intermediate_representation). – Will Ness Sep 30 '16 at 16:54
  • Ah yes, thanks for including those links. – Leif Andersen Sep 30 '16 at 17:13
  • @WillNess Hi, What if I already have a IR? – 齐天大圣 Sep 30 '16 at 21:54
  • @LeifAndersen Because that is when I can properly detect tail calls, isn't it? after flattening my source code into IR i don't know how would i be able to do it – 齐天大圣 Sep 30 '16 at 21:55
  • 1
    This really depends on your IR. Also, its not uncommon for languages to you many different IRs, check out http://nanopass.org for example. – Leif Andersen Sep 30 '16 at 22:33
  • @Jackddddd Are you by chance following Siek's book "Essentials of Compilation"? https://github.com/jsiek/Essentials-of-Compilation – soegaard Oct 02 '16 at 13:59
  • @soegaard Yes. That is the tutorial I've been following – 齐天大圣 Oct 02 '16 at 14:00
  • I can't find anything in that book on tail calls. Chez Scheme is a complex compiler, but you can see the intermediate languages here: https://github.com/cisco/ChezScheme/blob/master/s/np-languages.ss#L300 You can see that the expression in tail position are marked in the pass from language L10.5 to language L11 (see line 679). – soegaard Oct 02 '16 at 14:28

3 Answers3

4

The answer depends on your compilation target.

If you are compiling to assembler (or machine code) then you can handle tail calls in the code generator (see for example "An Incremental Approach to Compiler Construction" by Abdulaziz Ghuloum.

If the target language is C-like (i.e. calls build context) then you have several options depending on how avanced you want your compiler to be. Others have mentioned ANF and CPS as intermediate forms. It is also possible to introduce a trampoline. See for "Scheme Implementation Techniques" by Felix Winkelman for a list of strategies.

If your target language supports tail recursion consider using a strategy where a Scheme call is translated into a call in the target language.

Anyways: If you are interested in compiling Scheme, then do not hesitate to get hold of a copy of LiSP: Lisp in Small Pieces by Christian Queinnec.

soegaard
  • 30,661
  • 4
  • 57
  • 106
  • What if my target is to x86 assembly? and i already used three address code as my IR. Can i still have ANF or CPS?(sorry if this sound dumb but I don't know where to start) – 齐天大圣 Sep 30 '16 at 21:45
  • In that case take a look at "An Incremental Approach to Compiler Construction". Also get a copy of Intels manual on x86 opcodes and study the section on function calls (If I remember correctly there is a reference in Aziz's paper.) – soegaard Sep 30 '16 at 22:04
2

To me, the place to start the process of implementing tail call optimization would be detecting it via an explicit language construct, similar to the approach taken by Clojure's recur operator because that's the simplest thing that might possibly work. This would result in one procedure that recognizes tail calls and another procedure that implements tail calls.

Further development to automatically recognize tail calls becomes a modification of the first procedure. Further development to improve the optimization becomes a modification of the second procedure. Development of each can happen independently [or not at all].

ben rudgers
  • 3,647
  • 2
  • 20
  • 32
1

You can detect tail calls quite early, ideally straight after lambda lifting (you did not say it explicitly, but likely your convert-to-closures pass is doing it).

Then, calls marked as tail can be lowered at a later stage, but it is not as simple as just doing a jump - you have to clean up your stack frame first (if you're using stack for passing function arguments). Easier if you're using registers only to pass arguments.

SK-logic
  • 9,605
  • 1
  • 23
  • 35