Compiler construction 验证OCaml函数是否为尾部递归函数
如何判断OCaml是否将特定函数识别为尾部递归?特别是,我想知道OCaml编译器是否能够识别Compiler construction 验证OCaml函数是否为尾部递归函数,compiler-construction,ocaml,tail-recursion,short-circuiting,Compiler Construction,Ocaml,Tail Recursion,Short Circuiting,如何判断OCaml是否将特定函数识别为尾部递归?特别是,我想知道OCaml编译器是否能够识别 感谢Jeffrey下面的回答,我尝试了这个简单的函数 let rec check_all l = match l with | [] -> true | hd :: tl -> hd && check_all tl 事实上,它确实优化了: camlTest__check_all_1008: .cfi_startpro
感谢Jeffrey下面的回答,我尝试了这个简单的函数
let rec check_all l =
match l with
| [] -> true
| hd :: tl ->
hd && check_all tl
事实上,它确实优化了:
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
关于OCaml内部,许多其他人比我更明智,但对于简单的函数,很容易在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
编译器已将递归调用更改为循环(即,函数是尾部递归的)
以下是您通过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
递归由实际的递归调用(不是尾部递归)处理
正如我所说,如果您比我更了解OCaml中间形式,可能有更好的方法来解决这个问题。从OCaml 4.03开始,尽管文件中有输入错误,您可以在函数应用程序中使用
@tailcall
,如果不是这样,编译器将发出警告
(f[@tailcall])x y
如果f x y
不是尾部调用,则发出警告
例如:
$ 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
$cat tailrec.ml
让rec f a x=if x我想知道,为什么没有人告诉我古老的-annot
选项,它将为所有调用转储注释。尽管使用汇编是最可靠的方法,但并不是每个人都擅长阅读汇编。但有了annot,它是如此简单,以至于你甚至可以将其自动化。例如,假设您的代码位于test.ml
文件中,我们可以使用以下一行代码自动检查所有调用是否处于尾部位置:
ocamlc -annot test.ml && if grep -A1 call test.annot | grep stack; then echo "non tailrecursive"; exit 1; fi
ocaml-annot test.ml
将编译一个文件,创建一个test.annot
文件,该文件将包含每个表达式的注释。grep-A1 call test.annot
将提取所有调用注释,并查看其内容。如果至少有一个堆栈调用,grep堆栈将返回true
实际上,您甚至可以在存储库中找到一个emacs补充,它将从annot
文件中提取此信息。例如,有一个caml-types-show-call
函数,它将显示在该点指定的函数的一种调用。但是,此函数当前有一个bug(看起来不再受支持),要修复它,您需要对其应用以下修补程序:
--- 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))
回答得好(+1)。准确地说,我们不应该谈论尾部调用和尾部调用优化吗?真正的问题是OCaml是否优化尾部调用,而不是它是否识别尾部调用(不管这意味着什么)。随着事情变得越来越复杂,信息也会在-annot
选项的输出中提供给编译器,一个显示类型注释的工具可以用尾部调用信息装饰文件。ocamlopt[.opt]
的-dlinear
选项在修改后的源代码输出中也会有注释,带有尾部调用。顺便问一下:您是如何获得这么好的汇编输出的?请参见下面Jeffrey的回答:ocamlopt-c-SPerhaps您应该提交PR?可能吧,但我不想让OCaml开发人员负担过重,因为PR太小(有很多待处理的PRs),更好的办法是将这些代码提取到一个单独的回购协议中。
--- 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))