In the following example,
module A : sig end = struct
let _ = fun () ->
let f () =
let rec aux k = if true then k () else (aux [@tailcall]) k in
(aux [@tailcall]) (fun () -> ())
in
f (); ()
end
the second [@tailcall] annotation causes the program to be rejected:
File "example.ml", line 5, characters 6-38:
5 | (aux [@tailcall]) (fun () -> ())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Warning 51: expected tailcall
This happens due to a "used-once" inlining of f:
ᐅ ocamlopt.opt -dlambda example.ml
(let
(A/80 =
(seq
(function param/86
(seq
(let (param/88 = 0a)
(letrec
(aux/82
(function k/83
(if 1a (apply k/83 0a) (apply aux/82 k/83 @tailcall))))
(apply aux/82 (function param/84 0a) @tailcall)))
0a))
(makeblock 0)))
(seq (setfield_ptr(root-init) 0 (global IO!) A/80) 0a))
If f is applied twice, or defined with [@local never], the program is accepted.
This behaviour leads to inconsistencies according to the specific compiler being used. With a modified example,
module A : sig end = struct
let f () =
let rec aux k = if true then k () else (aux [@tailcall]) k in
(aux [@tailcall]) (fun () -> ())
let () = f ()
end
the code is accepted by ocamlopt (which does not inline in this case), but rejected by ocamlc and Flambda (which do inline).
CC: @mshinwell, @lthls.
In the following example,
the second
[@tailcall]annotation causes the program to be rejected:This happens due to a "used-once" inlining of
f:ᐅ ocamlopt.opt -dlambda example.ml (let (A/80 = (seq (function param/86 (seq (let (param/88 = 0a) (letrec (aux/82 (function k/83 (if 1a (apply k/83 0a) (apply aux/82 k/83 @tailcall)))) (apply aux/82 (function param/84 0a) @tailcall))) 0a)) (makeblock 0))) (seq (setfield_ptr(root-init) 0 (global IO!) A/80) 0a))If
fis applied twice, or defined with[@local never], the program is accepted.This behaviour leads to inconsistencies according to the specific compiler being used. With a modified example,
the code is accepted by
ocamlopt(which does not inline in this case), but rejected byocamlcand Flambda (which do inline).CC: @mshinwell, @lthls.