Skip to content

Commit f1e2e97

Browse files
committed
Ensure that functions are evaluated after their arguments (cherry picked from commit b71489f)
1 parent 56703cd commit f1e2e97

File tree

3 files changed

+75
-25
lines changed

3 files changed

+75
-25
lines changed

middle_end/closure/closure.ml

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,8 @@ let is_pure_prim p =
223223
| Arbitrary_effects, _ -> false
224224

225225
(* Check if a clambda term is ``pure'',
226-
that is without side-effects *and* not containing function definitions *)
226+
that is without side-effects *and* not containing function definitions
227+
(Pure terms may still read mutable state) *)
227228

228229
let rec is_pure = function
229230
Uvar _ -> true
@@ -731,17 +732,19 @@ type env = {
731732
*)
732733

733734
(* Approximates "no effects and no coeffects" *)
734-
let is_substituable ~mutable_vars = function
735+
let rec is_substituable ~mutable_vars = function
735736
| Uvar v -> not (V.Set.mem v mutable_vars)
736737
| Uconst _ -> true
738+
| Uoffset(arg, _) -> is_substituable ~mutable_vars arg
737739
| _ -> false
738740

739741
(* Approximates "only generative effects" *)
740742
let is_erasable = function
741743
| Uclosure _ -> true
742744
| u -> is_pure u
743745

744-
let bind_params { backend; mutable_vars; _ } loc fpc params args body =
746+
let bind_params { backend; mutable_vars; _ } loc fdesc params args funct body =
747+
let fpc = fdesc.fun_float_const_prop in
745748
let rec aux subst pl al body =
746749
match (pl, al) with
747750
([], []) -> substitute (Debuginfo.from_location loc) (backend, fpc)
@@ -770,7 +773,16 @@ let bind_params { backend; mutable_vars; _ } loc fpc params args body =
770773
in
771774
(* Reverse parameters and arguments to preserve right-to-left
772775
evaluation order (PR#2910). *)
773-
aux V.Map.empty (List.rev params) (List.rev args) body
776+
let params, args = List.rev params, List.rev args in
777+
let params, args, body =
778+
(* Ensure funct is evaluated after args *)
779+
match params with
780+
| my_closure :: params when not fdesc.fun_closed ->
781+
(params @ [my_closure]), (args @ [funct]), body
782+
| _ ->
783+
params, args, (if is_pure funct then body else Usequence (funct, body))
784+
in
785+
aux V.Map.empty params args body
774786

775787
(* Check if a lambda term is ``pure'',
776788
that is without side-effects *and* not containing function definitions *)
@@ -783,27 +795,39 @@ let warning_if_forced_inline ~loc ~attribute warning =
783795
(* Generate a direct application *)
784796

785797
let direct_apply env fundesc ufunct uargs ~loc ~attribute =
786-
let app_args =
787-
if fundesc.fun_closed then uargs else uargs @ [ufunct] in
788-
let app =
789-
match fundesc.fun_inline, attribute with
790-
| _, Never_inline | None, _ ->
791-
let dbg = Debuginfo.from_location loc in
792-
warning_if_forced_inline ~loc ~attribute
793-
"Function information unavailable";
794-
Udirect_apply(fundesc.fun_label, app_args, dbg)
795-
| Some(params, body), _ ->
796-
bind_params env loc fundesc.fun_float_const_prop params app_args
797-
body
798-
in
799-
(* If ufunct can contain side-effects or function definitions,
800-
we must make sure that it is evaluated exactly once.
801-
If the function is not closed, we evaluate ufunct as part of the
802-
arguments.
803-
If the function is closed, we force the evaluation of ufunct first. *)
804-
if not fundesc.fun_closed || is_pure ufunct
805-
then app
806-
else Usequence(ufunct, app)
798+
match fundesc.fun_inline, attribute with
799+
| _, Never_inline
800+
| None, _ ->
801+
let dbg = Debuginfo.from_location loc in
802+
warning_if_forced_inline ~loc ~attribute
803+
"Function information unavailable";
804+
if fundesc.fun_closed && is_pure ufunct then
805+
Udirect_apply(fundesc.fun_label, uargs, dbg)
806+
else if not fundesc.fun_closed &&
807+
is_substituable ~mutable_vars:env.mutable_vars ufunct then
808+
Udirect_apply(fundesc.fun_label, uargs @ [ufunct], dbg)
809+
else begin
810+
let args = List.map (fun arg ->
811+
if is_substituable ~mutable_vars:env.mutable_vars arg then
812+
None, arg
813+
else
814+
let id = V.create_local "arg" in
815+
Some (VP.create id, arg), Uvar id) uargs in
816+
let app_args = List.map snd args in
817+
List.fold_left (fun app (binding,_) ->
818+
match binding with
819+
| None -> app
820+
| Some (v, e) -> Ulet(Immutable, Pgenval, v, e, app))
821+
(if fundesc.fun_closed then
822+
Usequence (ufunct, Udirect_apply (fundesc.fun_label, app_args, dbg))
823+
else
824+
let clos = V.create_local "clos" in
825+
Ulet(Immutable, Pgenval, VP.create clos, ufunct,
826+
Udirect_apply(fundesc.fun_label, app_args @ [Uvar clos], dbg)))
827+
args
828+
end
829+
| Some(params, body), _ ->
830+
bind_params env loc fundesc params uargs ufunct body
807831

808832
(* Add [Value_integer] info to the approximation of an application *)
809833

testsuite/tests/basic/eval_order_8.ml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
(* TEST *)
2+
3+
(* closed, inlined *)
4+
let[@inline always] f () () = print_endline "4"
5+
let () = (let () = print_string "3" in f) (print_string "2") (print_string "1")
6+
7+
(* closed, not inlined *)
8+
let[@inline never] f () () = print_endline "4"
9+
let () = (let () = print_string "3" in f) (print_string "2") (print_string "1")
10+
11+
(* closure, inlined *)
12+
let[@inline never] g x =
13+
(let () = print_string "3" in fun () () -> print_endline x)
14+
(print_string "2") (print_string "1")
15+
let () = g "4"
16+
17+
(* closure, not inlined *)
18+
let[@inline never] g x =
19+
(let () = print_string "3" in
20+
let[@inline never] f () () = print_endline x in f)
21+
(print_string "2") (print_string "1")
22+
let () = g "4"
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
1234
2+
1234
3+
1234
4+
1234

0 commit comments

Comments
 (0)