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
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.
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.
Dynamic polymorphism is usually combined with recursive data types, but Ocaml modules are not recursive by default.
Because foldl is tail recursive, languages that perform tail call elimination would rewrite it to something that resembles the following.
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 iff 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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With