-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Confusing interaction between @tailcall and inlining #9510
Copy link
Copy link
Closed
Labels
Description
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 (); ()
endthe 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 ()
endthe code is accepted by ocamlopt (which does not inline in this case), but rejected by ocamlc and Flambda (which do inline).
CC: @mshinwell, @lthls.
Reactions are currently unavailable