Skip to content

Commit b71489f

Browse files
authored
Ensure that functions are evaluated after their arguments (#10728)
1 parent 18c4d16 commit b71489f

File tree

4 files changed

+78
-25
lines changed

4 files changed

+78
-25
lines changed

Changes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ Working version
5151
- #10681: Enforce boolean conditions for the native backend
5252
(Vincent Laviron, review by Gabriel Scherer)
5353

54+
- #10728: Ensure that functions are evaluated after their arguments
55+
(Stephen Dolan, review by Mark Shinwell)
56+
5457
### Standard library:
5558

5659
* #10622: Annotate `Uchar.t` with immediate attribute

middle_end/closure/closure.ml

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

221221
(* Check if a clambda term is ``pure'',
222-
that is without side-effects *and* not containing function definitions *)
222+
that is without side-effects *and* not containing function definitions
223+
(Pure terms may still read mutable state) *)
223224

224225
let rec is_pure = function
225226
Uvar _ -> true
@@ -729,17 +730,19 @@ type env = {
729730
*)
730731

731732
(* Approximates "no effects and no coeffects" *)
732-
let is_substituable ~mutable_vars = function
733+
let rec is_substituable ~mutable_vars = function
733734
| Uvar v -> not (V.Set.mem v mutable_vars)
734735
| Uconst _ -> true
736+
| Uoffset(arg, _) -> is_substituable ~mutable_vars arg
735737
| _ -> false
736738

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

742-
let bind_params { backend; mutable_vars; _ } loc fpc params args body =
744+
let bind_params { backend; mutable_vars; _ } loc fdesc params args funct body =
745+
let fpc = fdesc.fun_float_const_prop in
743746
let rec aux subst pl al body =
744747
match (pl, al) with
745748
([], []) -> substitute (Debuginfo.from_location loc) (backend, fpc)
@@ -772,7 +775,16 @@ let bind_params { backend; mutable_vars; _ } loc fpc params args body =
772775
in
773776
(* Reverse parameters and arguments to preserve right-to-left
774777
evaluation order (PR#2910). *)
775-
aux V.Map.empty (List.rev params) (List.rev args) body
778+
let params, args = List.rev params, List.rev args in
779+
let params, args, body =
780+
(* Ensure funct is evaluated after args *)
781+
match params with
782+
| my_closure :: params when not fdesc.fun_closed ->
783+
(params @ [my_closure]), (args @ [funct]), body
784+
| _ ->
785+
params, args, (if is_pure funct then body else Usequence (funct, body))
786+
in
787+
aux V.Map.empty params args body
776788

777789
let warning_if_forced_inline ~loc ~attribute warning =
778790
if attribute = Always_inline then
@@ -782,27 +794,39 @@ let warning_if_forced_inline ~loc ~attribute warning =
782794
(* Generate a direct application *)
783795

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

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

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)