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
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.
I wonder, why nobody told about the venerable -annot
option, that will dump annotations for all calls. Although using assembly is the most sure method, not everyone is good at reading the assembly. But with annot it is so easy, that you can even automate it. For example, given that your code is in test.ml
file, we can automatically check that all calls are in a tail position with the following one-liner:
ocamlc -annot test.ml && if grep -A1 call test.annot | grep stack; then echo "non tailrecursive"; exit 1; fi
The ocaml -annot test.ml
will compile a file an create test.annot
file, that will contain an annotation for each expression. The grep -A1 call test.annot
will extract all call annotations, and look at their contents. The grep stack
will return true, if there is at least one stack call.
There is actually even a emacs complement, that you can find in the ocaml repository, that will extract this information from the annot
file. For example, there is a caml-types-show-call
function, that will show a kind of call of a function, specified at the point. However, this function currently has a bug (it looks like it is no longer supported), to fix it you need to apply the following patch to it:
--- a/emacs/caml-types.el
+++ b/emacs/caml-types.el
@@ -221,7 +221,7 @@ See `caml-types-location-re' for annotation file format."
(right (caml-types-get-pos target-buf (elt node 1)))
(kind (cdr (assoc "call" (elt node 2)))))
(move-overlay caml-types-expr-ovl left right target-buf)
- (caml-types-feedback kind)))))
+ (caml-types-feedback "call: %s" kind)))))
(if (and (= arg 4)
(not (window-live-p (get-buffer-window caml-types-buffer))))
(display-buffer caml-types-buffer))
来源:https://stackoverflow.com/questions/23186717/verify-that-an-ocaml-function-is-tail-recursive