Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verify that an OCaml function is tail-recursive

How can I tell if OCaml recognizes a particular function as tail-recursive? In particular, I want to find out if the OCaml compiler recognizes Short-circuited operators and tail recursion


Thanks to Jeffrey's answer below, I tried this with the simple function

let rec check_all l =
    match l with
    | [] -> true
    | hd :: tl ->
        hd && check_all tl

and indeed, it does optimize to:

camlTest__check_all_1008:
        .cfi_startproc
.L102:
        cmpl    $1, %eax
        je      .L100
        movl    (%eax), %ebx
        cmpl    $1, %ebx
        je      .L101
        movl    4(%eax), %eax
        jmp     .L102
        .align  16
.L101:
        movl    $1, %eax
        ret
like image 546
dspyz Avatar asked Apr 20 '14 19:04

dspyz


People also ask

How do you know if a function is tail recursive?

A function is tail-recursive if it ends by returning the value of the recursive call. Keeping the caller's frame on stack is a waste of memory because there's nothing left to do once the recursive call returns its value.

Is tail recursive OCaml?

The tail recursive function uses an accumulator, a, to store the value of the result of the previous call. This allows OCaml to perform tail call optimization which results in the the stack not overflowing.

Is OCaml recursive?

Dynamic polymorphism is usually combined with recursive data types, but Ocaml modules are not recursive by default.

Is Foldl tail recursive?

Because foldl is tail recursive, languages that perform tail call elimination would rewrite it to something that resembles the following.


2 Answers

Starting from OCaml 4.03, and despite the typo in the Changes file, you can use @tailcall in a function application and the compiler will warn if it is not the case.

(f [@tailcall]) x y warns if f x y is not a tail-call

Example:

$ cat tailrec.ml
let rec f a x = if x <= 1 then a else (f [@tailcall]) (a * x) (x - 1)

let rec g x = if x <= 1 then 1 else x * (g [@tailcall]) (x - 1)

$ ocaml tailrec.ml

File "tailrec.ml", line 3, characters 40-63:
Warning 51: expected tailcall
like image 194
anol Avatar answered Sep 19 '22 00:09

anol


Many others are wiser than I am about OCaml internals, but for simple functions it's pretty easy to see tail recursion in the generated assembly code of ocamlopt:

$ cat tailrec.ml
let rec f a x = if x <= 1 then a else f (a * x) (x - 1)

let rec g x = if x <= 1 then 1 else x * g (x - 1)
$ ocamlopt -c -S tailrec.ml

If you ignore a lot of extra output you see this for f:

_camlTailrec__f_1008:
        .cfi_startproc
.L101:
        cmpq    $3, %rbx
        jg      .L100
        ret
        .align  2
.L100:
        movq    %rbx, %rdi
        addq    $-2, %rdi
        sarq    $1, %rbx
        decq    %rax
        imulq   %rbx, %rax
        incq    %rax
        movq    %rdi, %rbx
        jmp     .L101
        .cfi_endproc

The compiler has changed the recursive call into a loop (i.e., the function is tail recursive).

Here's what you get for g:

        .cfi_startproc
        subq    $8, %rsp
        .cfi_adjust_cfa_offset  8
.L103:
        cmpq    $3, %rax
        jg      .L102
        movq    $3, %rax
        addq    $8, %rsp
        .cfi_adjust_cfa_offset  -8
        ret
        .cfi_adjust_cfa_offset  8
        .align  2
.L102:
        movq    %rax, 0(%rsp)
        addq    $-2, %rax
        call    _camlTailrec__g_1011
.L104:
        movq    %rax, %rbx
        sarq    $1, %rbx
        movq    0(%rsp), %rax
        decq    %rax
        imulq   %rbx, %rax
        incq    %rax
        addq    $8, %rsp
        .cfi_adjust_cfa_offset  -8
        ret
        .cfi_adjust_cfa_offset  8
        .cfi_endproc

The recursion is handled by an actual recursive call (not tail recursive).

As I say, there may be better ways to figure this out if you understand the OCaml intermediate forms better than I do.

like image 43
Jeffrey Scofield Avatar answered Sep 20 '22 00:09

Jeffrey Scofield