OCaml编译器是否会处理布尔运算符以使递归尾递归?

时间:2014-12-16 12:27:08

标签: ocaml

让我们看看以下函数is_prime

let is_prime n =
  let rec div_check i = i * i > n || (n mod i <> 0 && div_check (i+1))
  in
  n >= 2 && div_check 2

因此,如果n mod i <> 0 false ,则会停止。

但如果n mod i <> 0 true ,那么它会以递归方式继续。

我的问题是,如果它继续,OCaml是否优化了编译器,因此只能直接返回div_check(i+1),即尾递归?或者它仍会保留true &&部分并等待div_check返回?

ps 该函数取自http://www.cs.cornell.edu/Courses/cs3110/2014sp/lectures/2/lec02.html

1 个答案:

答案 0 :(得分:4)

答案是肯定的。我已经简化了你的例子,使编译器输出更容易理解:

let rec is_prime n =
  let rec div_check i =
    i * i > n || (i + i < n && div_check (i+1)) in
  div_check n

鉴于此输入,编译器将发出以下程序集(我添加了一些注释):

camlIs_prime__div_check_1010:
.L102:
    movq    16(%rbx), %rdi
    movq    %rax, %rsi
    sarq    $1, %rsi
    movq    %rax, %rdx
    decq    %rdx
    imulq   %rsi, %rdx           ; i * i
    incq    %rdx
    cmpq    %rdi, %rdx           ; if i * i <= n then return
    jle .L101
    movq    $3, %rax
    ret
.L101:
    movq    16(%rbx), %rdi
    leaq    -1(%rax, %rax), %rsi ; i + i
    cmpq    %rdi, %rsi           ; i + i ? n
    jge .L100                    ; if i + i >= n then return
    addq    $2, %rax             ; else i := i + 1
    jmp .L102                    ;      div_check i
.L100:
    movq    $1, %rax
    ret

但要谨慎,这只适用于vanilla OCaml的短路运营商。 即使是let (&&) = (&&) break jmp .L102call camlIs_prime__div_check_...这样无害的变更,也会被div_check取代。

此外,这仅在呼叫位于短路运营商右侧时才有效。左表达式是marked非尾递归。我已对其进行了测试,实际上,将&&放在-annot运算符的左侧会发出非尾递归代码。

如果您不确定调用是否为tail,您可以随时向编译器添加*.annot标志,并查看相应的call (tail)文件,{{1}注释。在梅林有一些工具支持,但我还没有弄清楚如何正确使用它。请记住,最终的判断仍然是集会输出。