Skip to content

Commit 49aa87c

Browse files
authored
Introduce warning 68 to warn about hidden allocation due to pattern match of mutable field in curried functions (#9751)
Introduce new warning 68
1 parent d9a3ad4 commit 49aa87c

11 files changed

Lines changed: 134 additions & 41 deletions

File tree

Changes

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ Working version
270270
(Nicolás Ojeda Bär, review by Gabriel Scherer, Florian Angeletti and Leo
271271
White)
272272

273+
- #9751: Add warning 68. Pattern-matching depending on mutable state
274+
prevents the remaining arguments from being uncurried.
275+
(Hugo Heuzard, review by Leo White)
276+
273277
### Internal/compiler-libs changes:
274278

275279
- #9216: add Lambda.duplicate which refreshes bound identifiers

boot/ocamlc

3.7 KB
Binary file not shown.

boot/ocamllex

0 Bytes
Binary file not shown.

debugger/time_travel.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ let new_checkpoint_list checkpoint_count accepted rejected =
181181
let (k, l) =
182182
list_truncate2 (checkpoint_count - List.length accepted) rejected
183183
in
184-
(List.merge (fun {c_time = t1} {c_time = t2} -> compare t2 t1) accepted k,
184+
(List.merge (fun t1 t2 -> compare t2.c_time t1.c_time) accepted k,
185185
l)
186186

187187
(* Clean the checkpoint list. *)

lambda/translcore.ml

Lines changed: 65 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -733,25 +733,53 @@ and transl_apply ~scopes
733733
sargs)
734734
: Lambda.lambda)
735735

736-
and transl_function0
737-
~scopes loc return untuplify_fn max_arity
736+
and transl_curried_function
737+
~scopes loc return
738+
repr partial (param:Ident.t) cases =
739+
let max_arity = Lambda.max_arity () in
740+
let rec loop ~scopes loc return ~arity partial (param:Ident.t) cases =
741+
match cases with
742+
[{c_lhs=pat; c_guard=None;
743+
c_rhs={exp_desc =
744+
Texp_function
745+
{ arg_label = _; param = param'; cases = cases';
746+
partial = partial'; }; exp_env; exp_type;exp_loc}}]
747+
when arity < max_arity ->
748+
if Parmatch.inactive ~partial pat
749+
then
750+
let kind = value_kind pat.pat_env pat.pat_type in
751+
let return_kind = function_return_value_kind exp_env exp_type in
752+
let ((_, params, return), body) =
753+
loop ~scopes exp_loc return_kind ~arity:(arity + 1)
754+
partial' param' cases'
755+
in
756+
((Curried, (param, kind) :: params, return),
757+
Matching.for_function ~scopes loc None (Lvar param)
758+
[pat, body] partial)
759+
else begin
760+
begin match partial with
761+
| Total ->
762+
Location.prerr_warning pat.pat_loc
763+
Match_on_mutable_state_prevent_uncurry
764+
| Partial -> ()
765+
end;
766+
transl_tupled_function ~scopes ~arity
767+
loc return repr partial param cases
768+
end
769+
| cases ->
770+
transl_tupled_function ~scopes ~arity
771+
loc return repr partial param cases
772+
in
773+
loop ~scopes loc return ~arity:1 partial param cases
774+
775+
and transl_tupled_function
776+
~scopes ~arity loc return
738777
repr partial (param:Ident.t) cases =
739778
match cases with
740-
[{c_lhs=pat; c_guard=None;
741-
c_rhs={exp_desc = Texp_function { arg_label = _; param = param'; cases;
742-
partial = partial'; }; exp_env; exp_type} as exp}]
743-
when max_arity > 1 && Parmatch.inactive ~partial pat ->
744-
let kind = value_kind pat.pat_env pat.pat_type in
745-
let return_kind = function_return_value_kind exp_env exp_type in
746-
let ((_, params, return), body) =
747-
transl_function0 ~scopes exp.exp_loc return_kind false (max_arity - 1)
748-
repr partial' param' cases
749-
in
750-
((Curried, (param, kind) :: params, return),
751-
Matching.for_function ~scopes loc None (Lvar param)
752-
[pat, body] partial)
753779
| {c_lhs={pat_desc = Tpat_tuple pl}} :: _
754-
when untuplify_fn && List.length pl <= max_arity ->
780+
when !Clflags.native_code
781+
&& arity = 1
782+
&& List.length pl <= (Lambda.max_arity ()) ->
755783
begin try
756784
let size = List.length pl in
757785
let pats_expr_list =
@@ -783,37 +811,38 @@ and transl_function0
783811
((Tupled, tparams, return),
784812
Matching.for_tupled_function ~scopes loc params
785813
(transl_tupled_cases ~scopes pats_expr_list) partial)
786-
with Matching.Cannot_flatten ->
787-
((Curried, [param, Pgenval], return),
788-
Matching.for_function ~scopes loc repr (Lvar param)
789-
(transl_cases ~scopes cases) partial)
814+
with Matching.Cannot_flatten ->
815+
transl_function0 ~scopes loc return repr partial param cases
790816
end
791-
| {c_lhs=pat} :: other_cases ->
792-
let kind =
817+
| _ -> transl_function0 ~scopes loc return repr partial param cases
818+
819+
and transl_function0
820+
~scopes loc return
821+
repr partial (param:Ident.t) cases =
822+
let kind =
823+
match cases with
824+
| [] ->
825+
(* With Camlp4, a pattern matching might be empty *)
826+
Pgenval
827+
| {c_lhs=pat} :: other_cases ->
793828
(* All the patterns might not share the same types. We must take the
794829
union of the patterns types *)
795830
List.fold_left (fun k {c_lhs=pat} ->
796-
Typeopt.value_kind_union k
797-
(value_kind pat.pat_env pat.pat_type))
831+
Typeopt.value_kind_union k
832+
(value_kind pat.pat_env pat.pat_type))
798833
(value_kind pat.pat_env pat.pat_type) other_cases
799-
in
800-
((Curried, [param, kind], return),
801-
Matching.for_function ~scopes loc repr (Lvar param)
802-
(transl_cases ~scopes cases) partial)
803-
| [] ->
804-
(* With Camlp4, a pattern matching might be empty *)
805-
((Curried, [param, Pgenval], return),
806-
Matching.for_function ~scopes loc repr (Lvar param)
807-
(transl_cases ~scopes cases) partial)
834+
in
835+
((Curried, [param, kind], return),
836+
Matching.for_function ~scopes loc repr (Lvar param)
837+
(transl_cases ~scopes cases) partial)
808838

809839
and transl_function ~scopes e param cases partial =
810840
let ((kind, params, return), body) =
811841
event_function ~scopes e
812842
(function repr ->
813843
let pl = push_defaults e.exp_loc [] cases partial in
814844
let return_kind = function_return_value_kind e.exp_env e.exp_type in
815-
transl_function0 ~scopes e.exp_loc return_kind
816-
!Clflags.native_code (Lambda.max_arity())
845+
transl_curried_function ~scopes e.exp_loc return_kind
817846
repr partial param pl)
818847
in
819848
let attr = default_function_attribute in
@@ -1107,8 +1136,7 @@ and transl_letop ~scopes loc env let_ ands param case partial =
11071136
let (kind, params, return), body =
11081137
event_function ~scopes case.c_rhs
11091138
(function repr ->
1110-
transl_function0 ~scopes case.c_rhs.exp_loc return_kind
1111-
!Clflags.native_code (Lambda.max_arity())
1139+
transl_curried_function ~scopes case.c_rhs.exp_loc return_kind
11121140
repr partial param [case])
11131141
in
11141142
let attr = default_function_attribute in

man/ocamlc.m

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,10 @@ compilation in any way (even if it is fatal). If a warning is enabled,
960960
67
961961
\ \ Unused functor parameter.
962962

963+
68
964+
\ \ Pattern-matching depending on mutable state prevents the remaining
965+
arguments from being uncurried.
966+
963967
The letters stand for the following sets of warnings. Any letter not
964968
mentioned here corresponds to the empty set.
965969

@@ -1013,7 +1017,7 @@ compilation in any way (even if it is fatal). If a warning is enabled,
10131017

10141018
.IP
10151019
The default setting is
1016-
.BR \-w\ +a\-4\-6\-7\-9\-27\-29\-30\-32..42\-44\-45\-48\-50\-60\-66 .
1020+
.BR \-w\ +a\-4\-6\-7\-9\-27\-29\-30\-32..42\-44\-45\-48\-50\-60\-66\-67\-68 .
10171021
Note that warnings
10181022
.BR 5 \ and \ 10
10191023
are not always triggered, depending on the internals of the type checker.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
File "w68.ml", line 34, characters 33-43:
2+
34 | let dont_warn_with_partial_match None x = x
3+
^^^^^^^^^^
4+
Warning 8 [partial-match]: this pattern-matching is not exhaustive.
5+
Here is an example of a case that is not matched:
6+
Some _
7+
File "w68.ml", line 14, characters 10-13:
8+
14 | let alloc {a} b = a + b
9+
^^^
10+
Warning 68 [match-on-mutable-state-prevent-uncurry]: This pattern depends on mutable state.
11+
It prevents the remaining arguments from being uncurried, which will cause additional closure allocations.

testsuite/tests/warnings/w68.ml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
(* TEST
2+
3+
flags = "-w A"
4+
5+
* setup-ocamlopt.byte-build-env
6+
** ocamlopt.byte
7+
*** check-ocamlopt.byte-output
8+
**** run
9+
***** check-program-output
10+
*)
11+
12+
type a = { mutable a : int }
13+
14+
let alloc {a} b = a + b
15+
16+
let noalloc b {a} = b + a
17+
18+
let measure name f =
19+
let a = {a = 1} in
20+
let b = 2 in
21+
let before = Gc.minor_words () in
22+
let (_ : int) = f ~a ~b in
23+
let after = Gc.minor_words () in
24+
let alloc = int_of_float (after -. before) in
25+
match alloc with
26+
| 0 -> Printf.printf "%S doesn't allocate\n" name
27+
| _ -> Printf.printf "%S allocates\n" name
28+
29+
let () =
30+
measure "noalloc" (fun ~a ~b -> noalloc b a);
31+
measure "alloc" (fun ~a ~b -> alloc a b)
32+
33+
34+
let dont_warn_with_partial_match None x = x
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"noalloc" doesn't allocate
2+
"alloc" allocates

utils/warnings.ml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ type t =
9292
| Redefining_unit of string (* 65 *)
9393
| Unused_open_bang of string (* 66 *)
9494
| Unused_functor_parameter of string (* 67 *)
95+
| Match_on_mutable_state_prevent_uncurry (* 68 *)
9596
;;
9697

9798
(* If you remove a warning, leave a hole in the numbering. NEVER change
@@ -169,9 +170,10 @@ let number = function
169170
| Redefining_unit _ -> 65
170171
| Unused_open_bang _ -> 66
171172
| Unused_functor_parameter _ -> 67
173+
| Match_on_mutable_state_prevent_uncurry -> 68
172174
;;
173175

174-
let last_warning_number = 67
176+
let last_warning_number = 68
175177
;;
176178

177179
(* Third component of each tuple is the list of names for each warning. The
@@ -327,6 +329,9 @@ let descriptions =
327329
["unused-open-bang"];
328330
67, "Unused functor parameter.",
329331
["unused-functor-parameter"];
332+
68, "Pattern-matching depending on mutable state prevents the remaining \
333+
arguments from being uncurried.",
334+
["match-on-mutable-state-prevent-uncurry"];
330335
]
331336
;;
332337

@@ -567,7 +572,7 @@ let parse_options errflag s =
567572
current := {(!current) with error; active}
568573

569574
(* If you change these, don't forget to change them in man/ocamlc.m *)
570-
let defaults_w = "+a-4-6-7-9-27-29-30-32..42-44-45-48-50-60-66-67";;
575+
let defaults_w = "+a-4-6-7-9-27-29-30-32..42-44-45-48-50-60-66-67-68";;
571576
let defaults_warn_error = "-a+31";;
572577

573578
let () = parse_options false defaults_w;;
@@ -805,6 +810,10 @@ let message = function
805810
which shadows the existing one.\n\
806811
Hint: Did you mean 'type %s = unit'?" name
807812
| Unused_functor_parameter s -> "unused functor parameter " ^ s ^ "."
813+
| Match_on_mutable_state_prevent_uncurry ->
814+
"This pattern depends on mutable state.\n\
815+
It prevents the remaining arguments from being uncurried, which will \
816+
cause additional closure allocations."
808817
;;
809818

810819
let nerrors = ref 0;;

0 commit comments

Comments
 (0)