forked from ocaml-flambda/flambda-backend
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathzero_alloc_checker.ml
2863 lines (2412 loc) · 100 KB
/
zero_alloc_checker.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(**********************************************************************************
* MIT License *
* *
* *
* Copyright (c) 2022-2022 Jane Street Group LLC *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy *
* of this software and associated documentation files (the "Software"), to deal *
* in the Software without restriction, including without limitation the rights *
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell *
* copies of the Software, and to permit persons to whom the Software is *
* furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in all *
* copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, *
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE *
* SOFTWARE. *
* *
**********************************************************************************)
[@@@ocaml.warning "+a-30-40-41-42"]
let debug = false
module String = Misc.Stdlib.String
module Witness = struct
type kind =
| Alloc of
{ bytes : int;
dbginfo : Debuginfo.alloc_dbginfo
}
| Indirect_call
| Indirect_tailcall
| Direct_call of { callee : string }
| Direct_tailcall of { callee : string }
| Extcall of { callee : string }
| Arch_specific
| Probe of
{ name : string;
handler_code_sym : string
}
| Widen
type t =
{ dbg : Debuginfo.t;
kind : kind
}
let create dbg kind = { dbg; kind }
let compare { dbg = dbg1; kind = kind1 } { dbg = dbg2; kind = kind2 } =
(* compare by [dbg] first to print the errors in the order they appear in
the source file. *)
let c = Debuginfo.compare dbg1 dbg2 in
if c <> 0 then c else Stdlib.compare kind1 kind2
let print_kind ppf kind =
let open Format in
match kind with
| Alloc { bytes; dbginfo = _ } -> fprintf ppf "allocation of %d bytes" bytes
| Indirect_call -> fprintf ppf "indirect call"
| Indirect_tailcall -> fprintf ppf "indirect tailcall"
| Direct_call { callee } -> fprintf ppf "direct call %s" callee
| Direct_tailcall { callee : string } ->
fprintf ppf "direct tailcall %s" callee
| Extcall { callee } -> fprintf ppf "external call to %s" callee
| Arch_specific -> fprintf ppf "arch specific operation"
| Probe { name; handler_code_sym } ->
fprintf ppf "probe \"%s\" handler %s" name handler_code_sym
| Widen -> fprintf ppf "widen"
let print ppf { kind; dbg } =
Format.fprintf ppf "%a {%a}@," print_kind kind Debuginfo.print_compact dbg
end
let take_first_n t n ~to_seq ~of_seq ~cardinal =
let len = cardinal t in
if len <= n then t else t |> to_seq |> Seq.take n |> of_seq
module Witnesses : sig
type t
val empty : t
val widen : t
val is_empty : t -> bool
val iter : t -> f:(Witness.t -> unit) -> unit
val join : t -> t -> t
val meet : t -> t -> t
val lessequal : t -> t -> bool
val create : Witness.kind -> Debuginfo.t -> t
val print : Format.formatter -> t -> unit
val elements : t -> Witness.t list
val compare : t -> t -> int
val size : t -> int
val cutoff : t -> n:int -> t
type components =
{ nor : t;
exn : t;
div : t
}
val simplify : components -> components
end = struct
include Set.Make (Witness)
let print ppf t = Format.pp_print_seq Witness.print ppf (to_seq t)
let size t = cardinal t
let cutoff t ~n = take_first_n t n ~to_seq ~of_seq ~cardinal
let join t1 t2 =
let res = union t1 t2 in
match !Flambda_backend_flags.zero_alloc_checker_details_cutoff with
| Keep_all -> res
| No_details ->
if not (is_empty res)
then Misc.fatal_errorf "expected no witnesses got %a" print res;
res
| At_most n ->
(* CR-someday gyorsh: current implementation is naive: first compute the
union and then remove some elements. This can be optimized but should
preserve the property that we keep the smallest [n] witnesses from both
sets. This property makes user error messages more stable and
independent of iteration order. *)
cutoff res ~n
let meet = inter
let lessequal = subset
let create kind dbg = singleton (Witness.create dbg kind)
let widen = singleton (Witness.create Debuginfo.none Witness.Widen)
let iter t ~f = iter f t
type components =
{ nor : t;
exn : t;
div : t
}
let simplify { nor; exn; div } =
{ div =
(* don't print diverge witnesses unless they are the only ones. *)
(if is_empty nor && is_empty exn then div else empty);
nor;
(* only print the exn witnesses that are not also nor witnesses. *)
exn = diff exn nor
}
end
module Tag = struct
type t =
| N
| E
| D
let compare : t -> t -> int = fun t1 t2 -> Stdlib.compare t1 t2
let print = function N -> "nor" | E -> "exn" | D -> "div"
end
module Var : sig
type t
val create : string -> Tag.t -> t
val name : t -> string
val tag : t -> Tag.t
val print : Format.formatter -> t -> unit
val compare : t -> t -> int
val equal : t -> t -> bool
module Map : Map.S with type key = t
module Set : Set.S with type elt = t
end = struct
module T = struct
type t =
{ name : string;
tag : Tag.t
}
let compare { tag = tag1; name = name1 } { tag = tag2; name = name2 } =
let c = String.compare name1 name2 in
if c = 0 then Tag.compare tag1 tag2 else c
end
include T
module Map = Map.Make (T)
module Set = Set.Make (T)
let equal t1 t2 = compare t1 t2 = 0
let name t = t.name
let tag t = t.tag
let create name tag = { name; tag }
let print ppf { name; tag } =
Format.fprintf ppf "%s.%s@ " name (Tag.print tag)
end
(** Abstract value for each component of the domain. *)
module V : sig
type t
(** order of the abstract domain *)
val lessequal : t -> t -> bool
val join : t -> t -> t
val meet : t -> t -> t
val transform : t -> t -> t
val replace_witnesses : Witnesses.t -> t -> t
val diff_witnesses : expected:t -> actual:t -> Witnesses.t
val get_witnesses : t -> Witnesses.t
val print : witnesses:bool -> Format.formatter -> t -> unit
val unresolved : Witnesses.t -> Var.t -> t
val is_resolved : t -> bool
val get_unresolved_names : t -> String.Set.t
val apply : t -> env:(Var.t -> t option) -> t
val bot : t
val safe : t
val top : Witnesses.t -> t
(** [compare] is structural on terms (for the use of [V.t] as a key in sets and maps),
whereas [lessequal] is the order of the abstract domain (for fixed point checks). *)
val compare : t -> t -> int
val match_with :
bot:'a ->
safe:'a ->
top:(Witnesses.t -> 'a) ->
unresolved:(unit -> 'a) ->
t ->
'a
end = struct
let pp_w ~witnesses ppf w =
if witnesses then Format.fprintf ppf "@, (%a)" Witnesses.print w else ()
let pp_var ~witnesses ppf var w =
Format.fprintf ppf "(%a%a)@," Var.print var (pp_w ~witnesses) w
let pp_top ~witnesses ppf w = Format.fprintf ppf "(top%a)" (pp_w ~witnesses) w
(** Variables with witnesses *)
module Vars : sig
type t
val empty : t
val compare : t -> t -> int
(** [same_vars] compares variables ignoring witnesses *)
val same_vars : t -> t -> bool
val get_vars : t -> Var.Set.t
val get_unresolved_name : t -> String.Set.t
val join : t -> t -> t
val update : t -> Var.t -> Witnesses.t -> t
val singleton : Var.t -> Witnesses.t -> t
val has_witnesses : t -> bool
val replace_witnesses : t -> Witnesses.t -> t
val print : witnesses:bool -> Format.formatter -> t -> unit
val fold :
f:(Var.t -> Witnesses.t -> 'acc -> 'acc) -> init:'acc -> t -> 'acc
val exists : (Var.t -> Witnesses.t -> bool) -> t -> bool
val cutoff : t -> int -> t
val cutoff_witnesses : t -> int -> t
end = struct
type t = Witnesses.t Var.Map.t
let empty = Var.Map.empty
let fold ~f ~init t = Var.Map.fold f t init
let singleton var w = Var.Map.singleton var w
let compare t1 t2 = Var.Map.compare Witnesses.compare t1 t2
let same_vars = Var.Map.equal (fun _ _ -> (* ignore witnesses *) true)
let get_vars t =
(* CR-someday gyorsh: If this is called often, it may be better to store
the result of [get_vars t] in [t] instead of recomputing it. *)
t |> Var.Map.to_seq |> Seq.map fst |> Var.Set.of_seq
let get_unresolved_name t =
t |> Var.Map.to_seq |> Seq.map fst |> Seq.map Var.name
|> String.Set.of_seq
let exists p t = Var.Map.exists p t
let has_witnesses vars = exists (fun _ w -> not (Witnesses.is_empty w)) vars
let join t1 t2 =
Var.Map.union (fun _var w1 w2 -> Some (Witnesses.join w1 w2)) t1 t2
let update t var witnesses =
Var.Map.update var
(function
| None -> Some witnesses | Some w -> Some (Witnesses.join w witnesses))
t
let replace_witnesses t w = Var.Map.map (fun _ -> w) t
let print ~witnesses ppf t =
Var.Map.iter (fun var w -> pp_var ~witnesses ppf var w) t
let cutoff_witnesses t n = Var.Map.map (Witnesses.cutoff ~n) t
let cutoff t n =
let t = cutoff_witnesses t n in
take_first_n t n ~to_seq:Var.Map.to_seq ~of_seq:Var.Map.of_seq
~cardinal:Var.Map.cardinal
end
(** Normal form of Transform.
[Transform] represents an abstract transformer of a primitive
such as a function call. *)
module Transform : sig
type t
val var_with_top : Var.t -> var_witnesses:Witnesses.t -> Witnesses.t -> t
val add_top : t -> Witnesses.t -> t
val add_var : t -> Var.t -> Witnesses.t -> t
val vars : var1:Var.t -> w1:Witnesses.t -> var2:Var.t -> w2:Witnesses.t -> t
val print : witnesses:bool -> Format.formatter -> t -> unit
val compare : t -> t -> int
val equal : t -> t -> bool
val has_witnesses : t -> bool
val replace_witnesses : t -> Witnesses.t -> t
val flatten : t -> t -> t
val get_top : t -> Witnesses.t option
val get_vars : t -> Vars.t
(** [same_vars] compares variables ignoring witnesses *)
val same_vars : t -> t -> bool
(** does not change the number of variables, only their witnesses. *)
val cutoff_witnesses : t -> n:int -> t
end = struct
type t =
| Args of Vars.t
| Args_with_top of
{ w : Witnesses.t;
vars : Vars.t
}
(* The binary "transform" is commutative and associative, so a nested
"transform" can be flattened in the normal form. The arguments are
represented as a set of variables and optionally top. if top is absent,
[vars] must contain at least two elements. if top is present, [vars] must
have at least one element. This is enforced by the available
constructors.
We never need to represent other constants because these cases can be
simplified to either a constant or a variable. *)
let compare t1 t2 =
match t1, t2 with
| Args a1, Args a2 -> Vars.compare a1 a2
| Args _, Args_with_top _ -> -1
| Args_with_top _, Args _ -> 1
| ( Args_with_top { w = w1; vars = vars1 },
Args_with_top { w = w2; vars = vars2 } ) ->
let c = Vars.compare vars1 vars2 in
if c <> 0 then c else Witnesses.compare w1 w2
let cutoff_witnesses t ~n =
match t with
| Args vars -> Args (Vars.cutoff_witnesses vars n)
| Args_with_top { w; vars } ->
Args_with_top
{ w = Witnesses.cutoff w ~n; vars = Vars.cutoff_witnesses vars n }
let equal t1 t2 = compare t1 t2 = 0
let var_with_top var ~var_witnesses w =
let vars = Vars.singleton var var_witnesses in
Args_with_top { w; vars }
let vars ~var1 ~w1 ~var2 ~w2 =
assert (not (Var.equal var1 var2));
let vars = Vars.(update (singleton var1 w1) var2 w2) in
Args vars
let add_top t w =
match t with
| Args vars -> Args_with_top { w; vars }
| Args_with_top { w = w'; vars } ->
Args_with_top { w = Witnesses.join w w'; vars }
let add_var t var witnesses =
(* CR gyorsh: future optimization is to return [t] when vars is phys equal
to (update vars).*)
match t with
| Args vars -> Args (Vars.update vars var witnesses)
| Args_with_top { w; vars } ->
Args_with_top { w; vars = Vars.update vars var witnesses }
let flatten tr1 tr2 =
match tr1, tr2 with
| ( Args_with_top { w = w1; vars = vars1 },
Args_with_top { w = w2; vars = vars2 } ) ->
Args_with_top { w = Witnesses.join w1 w2; vars = Vars.join vars1 vars2 }
| Args_with_top { w; vars }, Args vars'
| Args vars', Args_with_top { w; vars } ->
Args_with_top { w; vars = Vars.join vars vars' }
| Args vars1, Args vars2 -> Args (Vars.join vars1 vars2)
let get_top t =
match t with Args_with_top { w; vars = _ } -> Some w | Args _ -> None
let get_vars t =
match t with Args_with_top { w = _; vars } | Args vars -> vars
let same_vars t1 t2 = Vars.same_vars (get_vars t1) (get_vars t2)
let has_witnesses t =
match t with
| Args_with_top { w; vars } ->
Vars.has_witnesses vars || not (Witnesses.is_empty w)
| Args vars -> Vars.has_witnesses vars
let replace_witnesses t w =
match t with
| Args vars -> Args (Vars.replace_witnesses vars w)
| Args_with_top { w = _; vars } ->
Args_with_top { w; vars = Vars.replace_witnesses vars w }
let print ~witnesses ppf t =
match t with
| Args vars ->
Format.fprintf ppf "(transform:@.%a)@." (Vars.print ~witnesses) vars
| Args_with_top { w; vars } ->
Format.fprintf ppf "(transform:@.%a@.%a)@." (pp_top ~witnesses) w
(Vars.print ~witnesses) vars
end
module Transforms : sig
type t
val join : t -> t -> t
val empty : t
val add : Transform.t -> t -> t
val compare : t -> t -> int
val cutoff : t -> int -> t
val iter : (Transform.t -> unit) -> t -> unit
val map : (Transform.t -> Transform.t) -> t -> t
val fold : (Transform.t -> 'a -> 'a) -> t -> 'a -> 'a
val exists : (Transform.t -> bool) -> t -> bool
val get_unresolved_names : t -> String.Set.t
exception Widen of Witnesses.t
end = struct
(* Join of two Transform with the same set of vars: merged both sets of Vars
into one Transform in normal form, without loss of precision or
witnesses, even if one Transform has "Top" and the other does not.
Naive implementation: map with key of type [Var.Set.t] to data of type
[Transform.t] *)
module M = Map.Make (Var.Set)
type t = Transform.t M.t
exception Widen of Witnesses.t
let maybe_widen t =
match !Flambda_backend_flags.zero_alloc_checker_join with
| Keep_all -> t
| Widen n ->
if M.cardinal t > n
then
if M.exists (fun _var tr -> Transform.has_witnesses tr) t
then raise (Widen Witnesses.widen)
else raise (Widen Witnesses.empty)
else t
| Error n ->
if M.cardinal t > n
then
Misc.fatal_errorf
"Join with %d paths is too big, use \
-disable-precise-zero-alloc-checker"
(M.cardinal t)
else t
let empty = M.empty
let get_key tr = tr |> Transform.get_vars |> Vars.get_vars
let get_unresolved_names t =
(* collect the keys *)
t |> M.to_seq |> Seq.map fst |> Seq.map Var.Set.to_seq |> Seq.concat
|> Seq.map Var.name |> String.Set.of_seq
let add tr t =
let res = M.add (get_key tr) tr t in
maybe_widen res
let compare t1 t2 = M.compare Transform.compare t1 t2
let iter f t = M.iter (fun _key tr -> f tr) t
let fold f t init = M.fold (fun _key tr acc -> f tr acc) t init
let exists f t = M.exists (fun _key tr -> f tr) t
let join t1 t2 =
let res =
M.union (fun _var tr1 tr2 -> Some (Transform.flatten tr1 tr2)) t1 t2
in
maybe_widen res
let map f t = M.fold (fun _key tr acc -> add (f tr) acc) t M.empty
let cutoff t n =
let t = map (Transform.cutoff_witnesses ~n) t in
take_first_n t n ~to_seq:M.to_seq ~of_seq:M.of_seq ~cardinal:M.cardinal
end
(* CR-someday gyorsh: treatment of vars and top is duplicated between Args and
Transform, is there a nice way to factor it out?
For instance, Join.t could be defined as a record { args : Args.t; ... }
with the ellipsis encoding top/safe. It may simplify a couple of functions
in the Join module, and perhaps lead to a type like { args : Args.t; rest :
'a; } that could then be used in other modules such as Transform. *)
(** helper for Join *)
module Args : sig
type t
val add_tr : t -> Transform.t -> t
val add_var : t -> Var.t -> Witnesses.t -> t
val empty : t
val join : t -> t -> t
val get : t -> Vars.t * Transforms.t
(** [transform t tr] replace each element x of [t] with "transform x tr" *)
val transform : t -> Transform.t -> t
(** [transform_var t var w]
replace each element x of [t] with "transfrom x var". *)
val transform_var : t -> Var.t -> Witnesses.t -> t
(** [transform_top t w] replace each element x of [t] with
"transform t (Top w)" *)
val transform_top : t -> Witnesses.t -> t
val transform_join : t -> t -> t
val has_witnesses : t -> bool
val replace_witnesses : t -> Witnesses.t -> t
val cutoff : t -> int -> t
val compare : t -> t -> int
val print : witnesses:bool -> Format.formatter -> t -> unit
end = struct
type t =
{ vars : Vars.t;
trs : Transforms.t
}
let empty = { vars = Vars.empty; trs = Transforms.empty }
let get { vars; trs } = vars, trs
let print ~witnesses ppf { vars; trs } =
let pp_trs ppf trs =
Transforms.iter (Transform.print ~witnesses ppf) trs
in
Format.fprintf ppf "vars=(%a)@.transforms=(%a)@," (Vars.print ~witnesses)
vars pp_trs trs
let add_var t var witnesses =
(* Optimization to avoid allocation when the content hasn't changed. *)
let vars = Vars.update t.vars var witnesses in
if vars == t.vars then t else { t with vars }
let add_tr t tr =
let trs = Transforms.add tr t.trs in
if trs == t.trs then t else { t with trs }
let join ({ vars = v1; trs = trs1 } as t) ({ vars = v2; trs = trs2 } as t')
=
if debug
then
Format.fprintf Format.std_formatter "join@.%a@. %a@."
(print ~witnesses:true) t (print ~witnesses:true) t';
let vars = Vars.join v1 v2 in
let trs = Transforms.join trs1 trs2 in
{ vars; trs }
let transform { vars; trs } tr =
let from_vars =
(* add each x from [vars] to [tr] *)
Vars.fold
~f:(fun var w acc -> Transforms.add (Transform.add_var tr var w) acc)
vars ~init:Transforms.empty
in
let from_trs = Transforms.map (fun tr' -> Transform.flatten tr tr') trs in
{ vars = Vars.empty; trs = Transforms.join from_vars from_trs }
let transform_top { vars; trs } w =
let from_vars =
Vars.fold
~f:(fun var var_witnesses acc ->
Transforms.add (Transform.var_with_top var ~var_witnesses w) acc)
vars ~init:Transforms.empty
in
let from_trs = Transforms.map (fun tr -> Transform.add_top tr w) trs in
{ vars = Vars.empty; trs = Transforms.join from_vars from_trs }
let transform_var { vars; trs } var witnesses =
let acc =
Vars.fold
~f:(fun var1 w1 args ->
if Var.equal var1 var
then add_var args var witnesses
else
let tr = Transform.vars ~var1 ~w1 ~var2:var ~w2:witnesses in
add_tr args tr)
vars ~init:empty
in
let from_trs =
Transforms.map (fun tr -> Transform.add_var tr var witnesses) trs
in
{ acc with trs = Transforms.join from_trs acc.trs }
let transform_join t ({ vars; trs } as t') =
if debug
then
Format.fprintf Format.std_formatter "transform_join@.%a@. %a@."
(print ~witnesses:true) t (print ~witnesses:true) t';
let acc =
Vars.fold vars ~init:empty ~f:(fun var witnesses acc ->
join acc (transform_var t var witnesses))
in
Transforms.fold (fun tr acc -> join acc (transform t tr)) trs acc
let has_witnesses { vars; trs } =
Vars.has_witnesses vars || Transforms.exists Transform.has_witnesses trs
let replace_witnesses { vars; trs } w =
{ vars = Vars.replace_witnesses vars w;
trs = Transforms.map (fun tr -> Transform.replace_witnesses tr w) trs
}
let cutoff { vars; trs } n =
{ vars = Vars.cutoff vars n; trs = Transforms.cutoff trs n }
let compare { vars = vars1; trs = trs1 } { vars = vars2; trs = trs2 } =
let c = Vars.compare vars1 vars2 in
if c <> 0 then c else Transforms.compare trs1 trs2
end
(** normal form of join *)
module Join : sig
type t
val tr_with_safe : Transform.t -> t
val tr_with_top : Transform.t -> Witnesses.t -> t
val var_with_top : Var.t -> var_witnesses:Witnesses.t -> Witnesses.t -> t
val var_with_safe : Var.t -> Witnesses.t -> t
val var_with_tr : Var.t -> Witnesses.t -> Transform.t -> t
val vars : var1:Var.t -> w1:Witnesses.t -> var2:Var.t -> w2:Witnesses.t -> t
val trs : Transform.t -> Transform.t -> t
val add_top : t -> Witnesses.t -> t
val add_safe : t -> t
val add_var : t -> Var.t -> Witnesses.t -> t
val add_tr : t -> Transform.t -> t
val flatten : t -> t -> unit -> t
val distribute_transform_over_join : t -> Transform.t -> unit -> t
val distribute_transform_var_over_join :
t -> Var.t -> Witnesses.t -> unit -> t
val distribute_transform_top_over_join : t -> Witnesses.t -> unit -> t
val distribute_transform_over_joins : t -> t -> unit -> t
val get_top : t -> Witnesses.t option
val has_safe : t -> bool
val get : t -> Vars.t * Transforms.t
val print : witnesses:bool -> Format.formatter -> t -> unit
val has_witnesses : t -> bool
val replace_witnesses : t -> Witnesses.t -> t
val compare : t -> t -> int
val get_unresolved_names : t -> String.Set.t
end = struct
type t =
| Args_with_safe of Args.t
| Args_with_top of
{ w : Witnesses.t;
args : Args.t
}
| Args of Args.t
(* Tracks "top" to preserve witnesses. For example simplifying
* "join (Top w) (Var v w')" to "Top w"
* loses the witness w' when w' is non-empty and v resolved to Top at the end.
* Simplifying to "Top (join w w')" is wrong if v is resolved to Safe or Bot. *)
(* Only Top or Safe are allowed not both. At least two elements must be
present in the join: if one of the constants is present then at least one
of vars or transforms must be non-empty. If no constants, then vars or
transforms have at least two elements between them. The following
constructor ensure it. *)
(* [cutoff] is a heuristic to limit the size of the representation when
tracking witnesses. Adding [@zero_alloc] assert on a function has a
limited overhead on compilation time and memory.
The representation may be resolved to more than [n] witnesses (e.g.,
different variables from Args.vars and Args.trs) or less than [n]
witnesses (when some of the variables are not resolved to Top and
therefore don't contribute witnesses).
Cutoff for "join with top" does not affect the precision of generated
summaries, only the number of witnesses reported.
Termination of fixpoint: [Args] is sorted, [Arg.cutoff] keeps the least
[n], and [Arg.join] is used for fixpoint computation before applying the
cutoff. *)
let args_with_top w args =
if not (Args.has_witnesses args)
then
Misc.fatal_errorf "Join Top without witnesses in args:%a"
(Args.print ~witnesses:false)
args;
match !Flambda_backend_flags.zero_alloc_checker_details_cutoff with
| Keep_all -> Args_with_top { w; args }
| No_details ->
Misc.fatal_errorf "unexpected: (Join (Top %a) %a) " Witnesses.print w
(Args.print ~witnesses:true)
args
| At_most n ->
(* Normal form after cutoff: args contains at least one element even
after cutoff because [n] is guaranteed to be positive. *)
let len = Witnesses.size w in
if len > n
then
Misc.fatal_errorf "expected at most %d witnesses, got %d: %a" n len
Witnesses.print w;
Args_with_top { w; args = Args.cutoff args n }
let tr_with_safe tr = Args_with_safe Args.(add_tr empty tr)
let tr_with_top tr w = args_with_top w Args.(add_tr empty tr)
let var_with_top var ~var_witnesses w =
args_with_top w Args.(add_var empty var var_witnesses)
let var_with_safe var w = Args_with_safe Args.(add_var empty var w)
let trs tr1 tr2 =
assert (not (Transform.equal tr1 tr2));
Args Args.(add_tr (add_tr empty tr1) tr2)
let vars ~var1 ~w1 ~var2 ~w2 =
assert (not (Var.equal var1 var2));
Args Args.(add_var (add_var empty var2 w2) var1 w1)
let var_with_tr var w tr = Args Args.(add_var (add_tr empty tr) var w)
let add_safe t =
match t with
| Args_with_top _ -> t
| Args_with_safe _ -> t
| Args args -> Args_with_safe args
let add_top t witnesses =
match t with
| Args_with_top { w; args } ->
args_with_top (Witnesses.join w witnesses) args
| Args_with_safe args | Args args -> args_with_top witnesses args
let add_var t var witnesses =
match t with
| Args_with_safe args -> Args_with_safe (Args.add_var args var witnesses)
| Args args -> Args (Args.add_var args var witnesses)
| Args_with_top { w; args } ->
args_with_top w (Args.add_var args var witnesses)
let flatten t1 t2 () =
match t1, t2 with
| Args a1, Args a2 -> Args (Args.join a1 a2)
| Args_with_safe a1, Args_with_safe a2 -> Args_with_safe (Args.join a1 a2)
| Args_with_top { w; args = a1 }, (Args a2 | Args_with_safe a2)
| (Args a2 | Args_with_safe a2), Args_with_top { w; args = a1 } ->
args_with_top w (Args.join a1 a2)
| Args_with_top { w = w1; args = a1 }, Args_with_top { w = w2; args = a2 }
->
args_with_top (Witnesses.join w1 w2) (Args.join a1 a2)
| Args args1, Args_with_safe args2 | Args_with_safe args1, Args args2 ->
Args_with_safe (Args.join args1 args2)
let distribute_transform_over_join t tr () =
match t with
| Args_with_safe args ->
let args = Args.(add_tr (transform args tr) tr) in
Args args
| Args_with_top { w; args } ->
let tr' = Transform.add_top tr w in
let args = Args.(add_tr (transform args tr) tr') in
Args args
| Args args -> Args (Args.transform args tr)
let distribute_transform_var_over_join t var witnesses () =
match t with
| Args_with_safe args ->
let args =
Args.add_var (Args.transform_var args var witnesses) var witnesses
in
Args args
| Args_with_top { w; args } ->
let tr' = Transform.var_with_top var ~var_witnesses:witnesses w in
let args = Args.(add_tr (transform_var args var witnesses) tr') in
Args args
| Args args -> Args (Args.transform_var args var witnesses)
let distribute_transform_top_over_join t w () =
match t with
| Args_with_safe args ->
let args = Args.transform_top args w in
args_with_top w args
| Args_with_top { w = w'; args } ->
let args = Args.(transform_top args w) in
args_with_top (Witnesses.join w' w) args
| Args args -> Args (Args.transform_top args w)
let distribute_transform_over_joins t1 t2 () =
match t1, t2 with
| Args a1, Args a2 -> Args (Args.transform_join a1 a2)
| Args_with_safe a1, Args_with_safe a2 ->
let new_args = Args.transform_join a1 a2 in
Args_with_safe (Args.join a1 (Args.join a2 new_args))
| Args_with_safe a1, Args a2 | Args a2, Args_with_safe a1 ->
let new_args = Args.transform_join a1 a2 in
Args (Args.join a2 new_args)
| Args_with_top { w = w1; args = a1 }, Args_with_top { w = w2; args = a2 }
->
if debug
then
Format.printf "distribute_transform_over_joins:@.%a@.%a@."
(Args.print ~witnesses:true)
a1
(Args.print ~witnesses:true)
a2;
let new_args = Args.transform_join a1 a2 in
let args_top =
Args.join (Args.transform_top a1 w2) (Args.transform_top a2 w1)
in
args_with_top (Witnesses.join w1 w2) (Args.join new_args args_top)
| Args_with_top { w; args = a1 }, Args a2
| Args a2, Args_with_top { w; args = a1 } ->
let new_args = Args.transform_join a1 a2 in
let args_top = Args.transform_top a2 w in
Args (Args.join new_args args_top)
| Args_with_top { w; args = a1 }, Args_with_safe a2
| Args_with_safe a2, Args_with_top { w; args = a1 } ->
let new_args = Args.transform_join a1 a2 in
let args_top = Args.transform_top a2 w in
let args = Args.join new_args args_top in
args_with_top w args
let add_tr t tr =
match t with
| Args_with_safe args -> Args_with_safe (Args.add_tr args tr)
| Args args -> Args (Args.add_tr args tr)
| Args_with_top { w; args } -> args_with_top w (Args.add_tr args tr)
let get_top t =
match t with
| Args_with_top { w; args = _ } -> Some w
| Args _ | Args_with_safe _ -> None
let has_safe t =
match t with
| Args_with_safe _ -> true
| Args _ | Args_with_top _ -> false
let get t =
match t with
| Args_with_top { w = _; args } | Args args | Args_with_safe args ->
Args.get args
let has_witnesses t =
match t with
| Args_with_safe args | Args args -> Args.has_witnesses args
| Args_with_top { w; args } ->
(not (Witnesses.is_empty w)) || Args.has_witnesses args
let replace_witnesses t witnesses =
match t with
| Args_with_safe args ->
Args_with_safe (Args.replace_witnesses args witnesses)
| Args args -> Args (Args.replace_witnesses args witnesses)
| Args_with_top { w = _; args } ->
args_with_top witnesses (Args.replace_witnesses args witnesses)
let compare t1 t2 =
match t1, t2 with