Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do ML family compilers do any sophisticated optimization for tail calls?

I (believe) the following function definition is tail-recursive:

fun is_sorted [] = true
  | is_sorted [x] = true
  | is_sorted (x::(y::xs)) =
    if x > y
       then false
       else is_sorted (y::xs)

Trivially, it's equivalent to the following declaration

fun is_sorted [] = true
  | is_sorted [x] = true
  | is_sorted (x::(y::xs)) = 
    (x <= y) andalso (is_sorted (y::xs))

Yet in this version the last step is to apply the 'andalso', so its not tail-recursive. Or it would seem so, except that since (at least Standard) ML (of NJ) uses short-circuit evaluation, the andalso is in fact /not/ the last step. So would this function have tail-call optimization? Or there any other interesting instances where an ML function that does not obviously use tail-recursion in fact gets optimized?

like image 939
Zach Halle Avatar asked Jul 17 '14 13:07

Zach Halle


People also ask

Does GCC do tail call optimization?

Regarding functions call optimization, gcc can do tail-call elimination to save the cost of allocating a new stack frame, and tail recursion elimination to turn a recursive function to non-recursive iterative one.

Which languages have tail call optimization?

This kind of function call is called a tail call, and languages like Haskell, Scala, and Scheme can avoid keeping around unnecessary stack frames in such calls. This is called tail call optimization (TCO) or tail call elimitation.

Which languages optimize tail recursion?

Tail recursion is important to some high-level languages, especially functional and logic languages and members of the Lisp family. In these languages, tail recursion is the most commonly used way (and sometimes the only way available) of implementing iteration.

How does tail call optimization work?

Tail call optimization happens when the compiler transforms a call immediately followed by a ret into a single jmp . This transformation saves one instruction, and more importantly it eliminates the implicit push/pop from the stack done by call and ret .


2 Answers

Note that

A andalso B

is equivalent to

if A then B else false

The SML language definition even defines it that way. Consequently, B is in tail position. No fancy optimisation necessary.

like image 88
Andreas Rossberg Avatar answered Sep 21 '22 03:09

Andreas Rossberg


If I translate your second function to OCaml I get this:

let rec is_sorted : int list -> bool = function
| [] -> true
| [_] -> true
| x :: y :: xs -> x < y && is_sorted xs

This is compiled as a tail-recursive function by ocamlopt. The essence of the generated code (x86_64) is this:

        .globl      _camlAndalso__is_sorted_1008
_camlAndalso__is_sorted_1008:
        .cfi_startproc
.L103:
        cmpq        $1, %rax
        je  .L100
        movq        8(%rax), %rbx
        cmpq        $1, %rbx
        je  .L101
        movq        (%rbx), %rdi
        movq        (%rax), %rax
        cmpq        %rdi, %rax
        jge .L102
        movq        8(%rbx), %rax
        jmp .L103
        .align      2
.L102:
        movq        $1, %rax
        ret
        .align      2
.L101:
        movq        $3, %rax
        ret
        .align      2
.L100:
        movq        $3, %rax
        ret
        .cfi_endproc

As you can see there are no recursive calls in this code, just a loop.

(But other posters are right that OCaml doesn't do all that much in the way of sophisticated analysis. This particular result seems pretty straightforward.)

like image 37
Jeffrey Scofield Avatar answered Sep 22 '22 03:09

Jeffrey Scofield