-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathanalyzer.ml
2629 lines (2467 loc) · 84.6 KB
/
analyzer.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
(*
The Haxe Compiler
Copyright (C) 2005-2016 Haxe Foundation
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*)
open Ast
open Type
open Common
let s_expr_pretty e = s_expr_pretty "" (s_type (print_context())) e
let rec is_true_expr e1 = match e1.eexpr with
| TConst(TBool true) -> true
| TParenthesis e1 -> is_true_expr e1
| _ -> false
let rec is_const_expression e = match e.eexpr with
| TConst _ ->
true
| TParenthesis e1
| TMeta(_,e1) ->
is_const_expression e1
| _ ->
false
let map_values ?(allow_control_flow=true) f e =
let branching = ref false in
let efinal = ref None in
let f e =
if !branching then
f e
else begin
efinal := Some e;
mk (TConst TNull) e.etype e.epos
end
in
let rec loop complex e = match e.eexpr with
| TIf(e1,e2,Some e3) ->
branching := true;
let e2 = loop true e2 in
let e3 = loop true e3 in
{e with eexpr = TIf(e1,e2,Some e3)}
| TSwitch(e1,cases,edef) ->
branching := true;
let cases = List.map (fun (el,e) -> el,loop true e) cases in
let edef = Option.map (loop true) edef in
{e with eexpr = TSwitch(e1,cases,edef)}
| TBlock [e1] ->
loop complex e1
| TBlock el ->
begin match List.rev el with
| e1 :: el ->
let e1 = loop true e1 in
let e = {e with eexpr = TBlock (List.rev (e1 :: el))} in
{e with eexpr = TMeta((Meta.MergeBlock,[],e.epos),e)}
| [] ->
f e
end
| TTry(e1,catches) ->
branching := true;
let e1 = loop true e1 in
let catches = List.map (fun (v,e) -> v,loop true e) catches in
{e with eexpr = TTry(e1,catches)}
| TMeta(m,e1) ->
{e with eexpr = TMeta(m,loop complex e1)}
| TParenthesis e1 ->
{e with eexpr = TParenthesis (loop complex e1)}
| TBreak | TContinue | TThrow _ | TReturn _ ->
if not allow_control_flow then raise Exit;
e
| _ ->
if not complex then raise Exit;
f e
in
let e = loop false e in
e,!efinal
let can_throw e =
let rec loop e = match e.eexpr with
| TConst _ | TLocal _ | TTypeExpr _ | TFunction _ | TBlock _ -> ()
| TCall _ | TNew _ | TThrow _ | TCast(_,Some _) -> raise Exit
| TField _ -> raise Exit (* sigh *)
| _ -> Type.iter loop e
in
try
loop e; false
with Exit ->
true
let rec can_be_inlined e = match e.eexpr with
| TConst _ -> true
| TParenthesis e1 | TMeta(_,e1) -> can_be_inlined e1
| _ -> false
let rec can_be_used_as_value com e =
let rec loop e = match e.eexpr with
| TBlock [e] -> loop e
| TBlock _ | TSwitch _ | TTry _ -> raise Exit
| TCall({eexpr = TConst (TString "phi")},_) -> raise Exit
(* | TCall _ | TNew _ when (match com.platform with Cpp | Php -> true | _ -> false) -> raise Exit *)
| TReturn _ | TThrow _ | TBreak | TContinue -> raise Exit
| TFunction _ -> ()
| _ -> Type.iter loop e
in
try
loop e;
true
with Exit ->
false
let rec skip e = match e.eexpr with
| TParenthesis e1 | TMeta(_,e1) | TBlock [e1] -> skip e1
| _ -> e
let wrap_meta s e =
mk (TMeta((Meta.Custom s,[],e.epos),e)) e.etype e.epos
let rec expr_eq e1 e2 = match e1.eexpr,e2.eexpr with
| TConst ct1,TConst ct2 -> ct1 = ct2
| _ -> false
let is_unbound v =
Meta.has Meta.Unbound v.v_meta
let is_really_unbound v =
v.v_name <> "`trace" && is_unbound v
let is_ref_type = function
| TType({t_path = ["cs"],("Ref" | "Out")},_) -> true
| _ -> false
let dynarray_map f d =
DynArray.iteri (fun i e -> DynArray.unsafe_set d i (f e)) d
let dynarray_mapi f d =
DynArray.iteri (fun i e -> DynArray.unsafe_set d i (f i e)) d
module Config = struct
type t = {
optimize : bool;
const_propagation : bool;
copy_propagation : bool;
code_motion : bool;
local_dce : bool;
dot_debug : bool;
}
let flag_no_check = "no_check"
let flag_no_const_propagation = "no_const_propagation"
let flag_const_propagation = "const_propagation"
let flag_no_copy_propagation = "no_copy_propagation"
let flag_copy_propagation = "copy_propagation"
let flag_code_motion = "code_motion"
let flag_no_code_motion = "no_code_motion"
let flag_no_local_dce = "no_local_dce"
let flag_local_dce = "local_dce"
let flag_ignore = "ignore"
let flag_no_simplification = "no_simplification"
let flag_dot_debug = "dot_debug"
let has_analyzer_option meta s =
try
let rec loop ml = match ml with
| (Meta.Analyzer,el,_) :: ml ->
if List.exists (fun (e,p) ->
match e with
| EConst(Ident s2) when s = s2 -> true
| _ -> false
) el then
true
else
loop ml
| _ :: ml ->
loop ml
| [] ->
false
in
loop meta
with Not_found ->
false
let is_ignored meta =
try
let rec loop ml = match ml with
| (Meta.Analyzer,el,_) :: ml ->
if List.exists (fun (e,p) ->
match e with
| EConst(Ident s2) when flag_ignore = s2 -> true
| _ -> false
) el then
true
else
loop ml
| (Meta.HasUntyped,_,_) :: _ ->
true
| _ :: ml ->
loop ml
| [] ->
false
in
loop meta
with Not_found ->
false
let get_base_config com optimize =
{
optimize = optimize;
const_propagation = not (Common.raw_defined com "analyzer-no-const-propagation");
copy_propagation = not (Common.raw_defined com "analyzer-no-copy-propagation");
code_motion = Common.raw_defined com "analyzer-code-motion";
local_dce = not (Common.raw_defined com "analyzer-no-local-dce");
dot_debug = false;
}
let update_config_from_meta config meta =
List.fold_left (fun config meta -> match meta with
| (Meta.Analyzer,el,_) ->
List.fold_left (fun config e -> match fst e with
| EConst (Ident s) when s = flag_no_const_propagation -> { config with const_propagation = false}
| EConst (Ident s) when s = flag_const_propagation -> { config with const_propagation = true}
| EConst (Ident s) when s = flag_no_copy_propagation -> { config with copy_propagation = false}
| EConst (Ident s) when s = flag_copy_propagation -> { config with copy_propagation = true}
| EConst (Ident s) when s = flag_no_code_motion -> { config with code_motion = false}
| EConst (Ident s) when s = flag_code_motion -> { config with code_motion = true}
| EConst (Ident s) when s = flag_no_local_dce -> { config with local_dce = false}
| EConst (Ident s) when s = flag_local_dce -> { config with local_dce = true}
| EConst (Ident s) when s = flag_dot_debug -> {config with dot_debug = true}
| _ -> config
) config el
| _ ->
config
) config meta
let get_class_config com c =
let config = get_base_config com true in
update_config_from_meta config c.cl_meta
let get_field_config com c cf =
let config = get_class_config com c in
update_config_from_meta config cf.cf_meta
end
(*
This module rewrites some expressions to reduce the amount of special cases for subsequent analysis. After analysis
it restores some of these expressions back to their original form.
The following expressions are removed from the AST after `apply` has run:
- OpBoolAnd and OpBoolOr binary operations are rewritten to TIf
- OpAssignOp on a variable is rewritten to OpAssign
- Prefix increment/decrement operations are rewritten to OpAssign
- Postfix increment/decrement operations are rewritten to a TBlock with OpAssign and OpAdd/OpSub
- `do {} while(true)` is rewritten to `while(true) {}`
- TWhile expressions are rewritten to `while (true)` with appropriate conditional TBreak
- TFor is rewritten to TWhile
*)
module TexprFilter = struct
let apply com e =
let rec loop e = match e.eexpr with
| TBinop(OpBoolAnd | OpBoolOr as op,e1,e2) ->
let e_then = e2 in
let e_if,e_else = if op = OpBoolOr then
mk (TUnop(Not,Prefix,e1)) com.basic.tbool e.epos,mk (TConst (TBool(true))) com.basic.tbool e.epos
else
e1,mk (TConst (TBool(false))) com.basic.tbool e.epos
in
loop (mk (TIf(e_if,e_then,Some e_else)) e.etype e.epos)
| TBinop(OpAssignOp op,({eexpr = TLocal _} as e1),e2) ->
let e = {e with eexpr = TBinop(op,e1,e2)} in
loop {e with eexpr = TBinop(OpAssign,e1,e)}
| TUnop((Increment | Decrement as op),flag,({eexpr = TLocal _} as e1)) ->
let e_one = mk (TConst (TInt (Int32.of_int 1))) com.basic.tint e1.epos in
let e = {e with eexpr = TBinop(OpAssignOp (if op = Increment then OpAdd else OpSub),e1,e_one)} in
let e = if flag = Prefix then
e
else
mk (TBlock [
{e with eexpr = TBinop(OpAssignOp (if op = Increment then OpAdd else OpSub),e1,e_one)};
{e with eexpr = TBinop((if op = Increment then OpSub else OpAdd),e1,e_one)};
]) e.etype e.epos
in
loop e
| TWhile(e1,e2,DoWhile) when is_true_expr e1 ->
loop {e with eexpr = TWhile(e1,e2,NormalWhile)}
| TWhile(e1,e2,flag) when not (is_true_expr e1) ->
let p = e.epos in
let e_break = mk TBreak t_dynamic p in
let e_not = mk (TUnop(Not,Prefix,Codegen.mk_parent e1)) e1.etype e1.epos in
let e_if eo = mk (TIf(e_not,e_break,eo)) com.basic.tvoid p in
let rec map_continue e = match e.eexpr with
| TContinue ->
(e_if (Some e))
| TWhile _ | TFor _ ->
e
| _ ->
Type.map_expr map_continue e
in
let e2 = if flag = NormalWhile then e2 else map_continue e2 in
let e_if = e_if None in
let e_block = if flag = NormalWhile then Type.concat e_if e2 else Type.concat e2 e_if in
let e_true = mk (TConst (TBool true)) com.basic.tbool p in
let e = mk (TWhile(Codegen.mk_parent e_true,e_block,NormalWhile)) e.etype p in
loop e
| TFor(v,e1,e2) ->
let v' = alloc_var "tmp" e1.etype in
let ev' = mk (TLocal v') e1.etype e1.epos in
let ehasnext = mk (TField(ev',quick_field e1.etype "hasNext")) (tfun [] com.basic.tbool) e1.epos in
let ehasnext = mk (TCall(ehasnext,[])) com.basic.tbool ehasnext.epos in
let enext = mk (TField(ev',quick_field e1.etype "next")) (tfun [] v.v_type) e1.epos in
let enext = mk (TCall(enext,[])) v.v_type e1.epos in
let eassign = mk (TVar(v,Some enext)) com.basic.tvoid e.epos in
let ebody = Type.concat eassign e2 in
let e = mk (TBlock [
mk (TVar (v',Some e1)) com.basic.tvoid e1.epos;
mk (TWhile((mk (TParenthesis ehasnext) ehasnext.etype ehasnext.epos),ebody,NormalWhile)) com.basic.tvoid e1.epos;
]) com.basic.tvoid e.epos in
loop e
| _ ->
Type.map_expr loop e
in
loop e
let unapply com config e =
let rec block_element acc el = match el with
| {eexpr = TBinop((OpAssign | OpAssignOp _),_,_) | TUnop((Increment | Decrement),_,_)} as e1 :: el ->
block_element (e1 :: acc) el
| {eexpr = TLocal _} as e1 :: el when not config.Config.local_dce ->
block_element (e1 :: acc) el
(* no-side-effect *)
| {eexpr = TEnumParameter _ | TFunction _ | TConst _ | TTypeExpr _ | TLocal _} :: el ->
block_element acc el
(* no-side-effect composites *)
| {eexpr = TParenthesis e1 | TMeta(_,e1) | TCast(e1,None) | TField(e1,_) | TUnop(_,_,e1)} :: el ->
block_element acc (e1 :: el)
| {eexpr = TArray(e1,e2) | TBinop(_,e1,e2)} :: el ->
block_element acc (e1 :: e2 :: el)
| {eexpr = TArrayDecl el1 | TCall({eexpr = TField(_,FEnum _)},el1)} :: el2 -> (* TODO: check e1 of FEnum *)
block_element acc (el1 @ el2)
| {eexpr = TObjectDecl fl} :: el ->
block_element acc ((List.map snd fl) @ el)
| {eexpr = TIf(e1,{eexpr = TBlock []},(Some {eexpr = TBlock []} | None))} :: el ->
block_element acc (e1 :: el)
| {eexpr = TBlock [e1]} :: el ->
block_element acc (e1 :: el)
| {eexpr = TBlock []} :: el ->
block_element acc el
| e1 :: el ->
block_element (e1 :: acc) el
| [] ->
acc
in
let changed = ref false in
let var_uses = ref IntMap.empty in
let get_num_uses v =
try IntMap.find v.v_id !var_uses with Not_found -> 0
in
let change_num_uses v delta =
var_uses := IntMap.add v.v_id ((try IntMap.find v.v_id !var_uses with Not_found -> 0) + delta) !var_uses;
in
let rec loop e = match e.eexpr with
| TLocal v ->
change_num_uses v 1;
| TBinop(OpAssign,{eexpr = TLocal _},e2) ->
loop e2
| _ ->
Type.iter loop e
in
loop e;
let rec fuse acc el = match el with
| ({eexpr = TVar(v1,None)} as e1) :: {eexpr = TBinop(OpAssign,{eexpr = TLocal v2},e2)} :: el when v1 == v2 ->
changed := true;
let e1 = {e1 with eexpr = TVar(v1,Some e2)} in
change_num_uses v1 (-1);
fuse (e1 :: acc) el
| ({eexpr = TVar(v1,None)} as e1) :: ({eexpr = TIf(eif,_,Some _)} as e2) :: el when can_be_used_as_value com e2 && (match com.platform with Php | C -> false | Cpp when not (Common.defined com Define.Cppia) -> false | _ -> true) ->
begin try
let i = ref 0 in
let check_assign e = match e.eexpr with
| TBinop(OpAssign,{eexpr = TLocal v2},e2) when v1 == v2 -> incr i; e2
| _ -> raise Exit
in
let e,_ = map_values ~allow_control_flow:false check_assign e2 in
let e = match follow e.etype with
| TAbstract({a_path=[],"Void"},_) -> {e with etype = v1.v_type}
| _ -> e
in
let e1 = {e1 with eexpr = TVar(v1,Some e)} in
changed := true;
change_num_uses v1 (- !i);
fuse (e1 :: acc) el
with Exit ->
fuse (e1 :: acc) (e2 :: el)
end
| ({eexpr = TVar(v1,Some e1)} as ev) :: e2 :: el when Meta.has Meta.CompilerGenerated v1.v_meta && get_num_uses v1 <= 1 && can_be_used_as_value com e1 ->
let found = ref false in
let affected = ref false in
let rec check_non_var_side_effect e2 = match e2.eexpr with
| TBinop((OpAssign | OpAssignOp _),{eexpr = TLocal _},e2) ->
check_non_var_side_effect e2
| TUnop((Increment | Decrement),_,{eexpr = TLocal _}) ->
()
| TBinop((OpAssign | OpAssignOp _),_,_) | TUnop((Increment | Decrement),_,_) ->
raise Exit
| TCall _ | TNew _ ->
raise Exit
| _ ->
Type.iter check_non_var_side_effect e2
in
let check_interference e2 =
let rec check e1 e2 = match e1.eexpr with
| TLocal v1 ->
let rec check2 e2 = match e2.eexpr with
| TUnop((Increment | Decrement),_,{eexpr = TLocal v2}) | TBinop((OpAssign | OpAssignOp _),{eexpr = TLocal v2},_) when v1 == v2 ->
raise Exit
| _ ->
Type.iter check2 e2
in
check2 e2
| TField _ when Optimizer.is_affected_type e1.etype ->
check_non_var_side_effect e2;
| TCall _ | TNew _ | TBinop((OpAssign | OpAssignOp _),_,_) | TUnop((Increment | Decrement),_,_) ->
check_non_var_side_effect e2
| _ ->
()
in
try
check e1 e2;
check e2 e1;
with Exit ->
begin match com.platform with
| Cpp when not (Common.defined com Define.Cppia) -> raise Exit
| Php -> raise Exit (* They don't define evaluation order, so let's exit *)
| _ -> affected := true;
end
in
let rec replace e =
let e = match e.eexpr with
| TWhile _ | TFunction _ ->
e
| TLocal v2 when v1 == v2 && not !affected ->
found := true;
e1
| TBinop((OpAssign | OpAssignOp _ as op),({eexpr = TArray(e1,e2)} as ea),e3) ->
let e1 = replace e1 in
let e2 = replace e2 in
let ea = {ea with eexpr = TArray(e1,e2)} in
let e3 = replace e3 in
{e with eexpr = TBinop(op,ea,e3)}
| TBinop((OpAssign | OpAssignOp _ as op),e1,e2) ->
let e2 = replace e2 in
let e1 = replace e1 in
{e with eexpr = TBinop(op,e1,e2)}
| TCall({eexpr = TLocal v},_) when is_really_unbound v ->
e
| _ ->
Type.map_expr replace e
in
check_interference e;
e
in
begin try
let e = replace e2 in
if not !found then raise Exit;
changed := true;
change_num_uses v1 (-1);
fuse (e :: acc) el
with Exit ->
fuse (ev :: acc) (e2 :: el)
end
| {eexpr = TUnop((Increment | Decrement as op,Prefix,({eexpr = TLocal v} as ev)))} as e1 :: e2 :: el ->
begin try
let e2,f = match e2.eexpr with
| TReturn (Some e2) -> e2,(fun e -> {e2 with eexpr = TReturn (Some e)})
| TBinop(OpAssign,e21,e22) -> e22,(fun e -> {e2 with eexpr = TBinop(OpAssign,e21,e)})
| TVar(v,Some e2) -> e2,(fun e -> {e2 with eexpr = TVar(v,Some e)})
| _ -> raise Exit
in
let ops_match op1 op2 = match op1,op2 with
| Increment,OpSub
| Decrement,OpAdd ->
true
| _ ->
false
in
begin match e2.eexpr with
| TBinop(op2,{eexpr = TLocal v2},{eexpr = TConst (TInt i32)}) when v == v2 && Int32.to_int i32 = 1 && ops_match op op2 ->
changed := true;
change_num_uses v2 (-1);
let e = (f {e1 with eexpr = TUnop(op,Postfix,ev)}) in
fuse (e :: acc) el
| _ ->
raise Exit
end
with Exit ->
fuse (e1 :: acc) (e2 :: el)
end
| e1 :: el ->
fuse (e1 :: acc) el
| [] ->
acc
in
let rec loop e = match e.eexpr with
| TBlock el ->
let el = List.map loop el in
(* fuse flips element order, but block_element doesn't care and flips it back *)
let el = fuse [] el in
let el = block_element [] el in
let rec fuse_loop el =
changed := false;
let el = fuse [] el in
let el = block_element [] el in
if !changed then fuse_loop el else el
in
let el = fuse_loop el in
{e with eexpr = TBlock el}
| TCall({eexpr = TLocal v},_) when is_really_unbound v ->
e
| _ ->
Type.map_expr loop e
in
let e = loop e in
let if_or_op e e1 e2 e3 = match (skip e1).eexpr,(skip e3).eexpr with
| TUnop(Not,Prefix,e1),TConst (TBool true) -> {e with eexpr = TBinop(OpBoolOr,e1,e2)}
| _,TConst (TBool false) -> {e with eexpr = TBinop(OpBoolAnd,e1,e2)}
| _,TBlock [] -> {e with eexpr = TIf(e1,e2,None)}
| _ -> match (skip e2).eexpr with
| TBlock [] ->
let e1' = mk (TUnop(Not,Prefix,e1)) e1.etype e1.epos in
let e1' = Optimizer.optimize_unop e1' Not Prefix e1 in
{e with eexpr = TIf(e1',e3,None)}
| _ ->
{e with eexpr = TIf(e1,e2,Some e3)}
in
let rec loop e = match e.eexpr with
| TIf(e1,e2,Some e3) ->
let e1 = loop e1 in
let e2 = loop e2 in
let e3 = loop e3 in
if_or_op e e1 e2 e3;
| TBlock el ->
let el = List.map (fun e ->
let e = loop e in
match e.eexpr with
| TIf _ -> {e with etype = com.basic.tvoid}
| _ -> e
) el in
{e with eexpr = TBlock el}
| TWhile(e1,e2,NormalWhile) ->
let e1 = loop e1 in
let e2 = loop e2 in
begin match e2.eexpr with
| TBlock ({eexpr = TIf(e1,({eexpr = TBlock[{eexpr = TBreak}]} as eb),None)} :: el2) ->
let e1 = skip e1 in
let e1 = match e1.eexpr with TUnop(_,_,e1) -> e1 | _ -> {e1 with eexpr = TUnop(Not,Prefix,e1)} in
{e with eexpr = TWhile(e1,{eb with eexpr = TBlock el2},NormalWhile)}
| TBlock el ->
let rec loop2 el = match el with
| {eexpr = TBreak | TContinue | TReturn _ | TThrow _} as e :: el ->
[e]
| e :: el ->
e :: (loop2 el)
| [] ->
[]
in
let el = loop2 el in
{e with eexpr = TWhile(e1,{e2 with eexpr = TBlock el},NormalWhile)}
| _ ->
{e with eexpr = TWhile(e1,e2,NormalWhile)}
end
| _ ->
Type.map_expr loop e
in
loop e
end
(*
A BasicBlock represents a node in the control flow. It has expression elements similar to TBlock in the AST,
but also holds additional information related to control flow and variables.
Basic blocks are created whenever it is relevant for control flow. They differ from TBlock in that only their
final element can be a control flow expression (the terminator). As a consequence, a given TBlock is split up
into several basic blocks when control flow expressions are encountered.
*)
module BasicBlock = struct
type block_kind =
| BKRoot (* The unique root block of the graph *)
| BKNormal (* A normal block *)
| BKFunctionBegin (* Entry block of a function *)
| BKFunctionEnd (* Exit block of a function *)
| BKSub (* A sub block *)
| BKConditional (* A "then", "else" or "case" block *)
| BKLoopHead (* Header block of a loop *)
| BKException (* Relay block for exceptions *)
| BKUnreachable (* The unique unreachable block *)
type cfg_edge_Flag =
| FlagExecutable (* Used by constant propagation to handle live edges *)
| FlagDce (* Used by DCE to keep track of handled edges *)
| FlagCodeMotion (* Used by code motion to track handled edges *)
| FlagCopyPropagation (* Used by copy propagation to track handled eges *)
type cfg_edge_kind =
| CFGGoto (* An unconditional branch *)
| CFGFunction (* Link to a function *)
| CFGMaybeThrow (* The block may or may not throw an exception *)
| CFGCondBranch of texpr (* A conditional branch *)
| CFGCondElse (* A conditional alternative (else,default) *)
and cfg_edge = {
cfg_from : t; (* The source block *)
cfg_to : t; (* The target block *)
cfg_kind : cfg_edge_kind; (* The edge kind *)
mutable cfg_flags : cfg_edge_Flag list; (* Edge flags *)
}
and syntax_edge =
| SEIfThen of t * t (* `if` with "then" and "next" *)
| SEIfThenElse of t * t * t * Type.t (* `if` with "then", "else" and "next" *)
| SESwitch of (texpr list * t) list * t option * t (* `switch` with cases, "default" and "next" *)
| SETry of t * (tvar * t) list * t (* `try` with catches and "next" *)
| SEWhile of t * t (* `while` with "body" and "next" *)
| SESubBlock of t * t (* "sub" with "next" *)
| SEMerge of t (* Merge to same block *)
| SEEnd (* End of syntax *)
| SENone (* No syntax exit *)
and t = {
bb_id : int; (* The unique ID of the block *)
bb_type : Type.t; (* The block type *)
bb_pos : pos; (* The block position *)
bb_kind : block_kind; (* The block kind *)
mutable bb_closed : bool; (* Whether or not the block has been closed *)
(* elements *)
mutable bb_el : texpr DynArray.t; (* The block expressions *)
mutable bb_phi : texpr DynArray.t; (* SSA-phi expressions *)
(* relations *)
mutable bb_outgoing : cfg_edge list; (* Outgoing edges *)
mutable bb_incoming : cfg_edge list; (* Incoming edges *)
mutable bb_dominator : t; (* The block's dominator *)
mutable bb_dominated : t list; (* The dominated blocks *)
mutable bb_df : t list; (* The dominance frontier *)
mutable bb_syntax_edge : syntax_edge; (* The syntactic edge *)
mutable bb_loop_groups : int list; (* The loop groups this block belongs to *)
mutable bb_scopes : int list; (* The scopes this block belongs to *)
(* variables *)
mutable bb_var_writes : tvar list; (* List of assigned variables *)
}
let has_flag edge flag =
List.mem flag edge.cfg_flags
let _create id kind scopes t p =
let rec bb = {
bb_kind = kind;
bb_id = id;
bb_type = t;
bb_pos = p;
bb_closed = false;
bb_el = DynArray.create();
bb_phi = DynArray.create();
bb_outgoing = [];
bb_incoming = [];
bb_dominator = bb;
bb_dominated = [];
bb_df = [];
bb_syntax_edge = SENone;
bb_loop_groups = [];
bb_var_writes = [];
bb_scopes = scopes;
} in
bb
end
(*
A Graph contains all relevant information for a given method. It is built from the field expression
and then refined in subsequent modules such as Ssa.
*)
module Graph = struct
open BasicBlock
type texpr_lookup = BasicBlock.t * bool * int
type tfunc_info = BasicBlock.t * Type.t * pos * tfunc
type var_write = BasicBlock.t list
type var_info = {
vi_var : tvar; (* The variable itself *)
vi_extra : tvar_extra; (* The original v_extra *)
mutable vi_origin : tvar; (* The origin variable of this variable *)
mutable vi_writes : var_write; (* A list of blocks that assign to this variable *)
mutable vi_value : texpr_lookup option; (* The value of this variable, if known *)
mutable vi_ssa_edges : texpr_lookup list; (* The expressions this variable influences *)
mutable vi_reaching_def : tvar option; (* The current reaching definition variable of this variable *)
}
type t = {
mutable g_root : BasicBlock.t; (* The unique root block *)
mutable g_exit : BasicBlock.t; (* The unique exit block *)
mutable g_unreachable : BasicBlock.t; (* The unique unreachable block *)
mutable g_functions : tfunc_info IntMap.t; (* A map of functions, indexed by their block IDs *)
mutable g_nodes : BasicBlock.t IntMap.t; (* A map of all blocks *)
mutable g_cfg_edges : cfg_edge list; (* A list of all CFG edges *)
mutable g_var_infos : var_info DynArray.t; (* A map of variable information *)
mutable g_loops : BasicBlock.t IntMap.t; (* A map containing loop information *)
}
let create_var_info g v =
let vi = {
vi_var = v;
vi_extra = v.v_extra;
vi_origin = v;
vi_writes = [];
vi_value = None;
vi_ssa_edges = [];
vi_reaching_def = None;
} in
DynArray.add g.g_var_infos vi;
let i = DynArray.length g.g_var_infos - 1 in
v.v_extra <- Some([],Some (mk (TConst (TInt (Int32.of_int i))) t_dynamic null_pos))
let get_var_info g v = match v.v_extra with
| Some(_,Some {eexpr = TConst (TInt i32)}) -> DynArray.get g.g_var_infos (Int32.to_int i32)
| _ -> assert false
(* edges *)
let set_syntax_edge g bb se =
bb.bb_syntax_edge <- se
let get_syntax_edge g bb =
bb.bb_syntax_edge
let add_cfg_edge g bb_from bb_to kind =
if bb_from.bb_id > 0 then begin
let edge = { cfg_from = bb_from; cfg_to = bb_to; cfg_kind = kind; cfg_flags = [] } in
g.g_cfg_edges <- edge :: g.g_cfg_edges;
bb_from.bb_outgoing <- edge :: bb_from.bb_outgoing;
bb_to.bb_incoming <- edge :: bb_to.bb_incoming;
end
let add_ssa_edge g v bb is_phi i =
let vi = get_var_info g v in
vi.vi_ssa_edges <- (bb,is_phi,i) :: vi.vi_ssa_edges
(* nodes *)
let add_function g tf t p bb =
g.g_functions <- IntMap.add bb.bb_id (bb,t,p,tf) g.g_functions
let alloc_id =
let r = ref 1 in
(fun () ->
incr r;
!r
)
let create_node g kind scopes bb_dom t p =
let bb = BasicBlock._create (alloc_id()) kind scopes t p in
bb.bb_dominator <- bb_dom;
bb_dom.bb_dominated <- bb :: bb_dom.bb_dominated;
g.g_nodes <- IntMap.add bb.bb_id bb g.g_nodes;
bb
let close_node g bb =
if bb.bb_id > 0 then begin
assert(not bb.bb_closed);
bb.bb_closed <- true
end
let iter_dom_tree g f =
let rec loop bb =
f bb;
List.iter loop bb.bb_dominated
in
loop g.g_root
(* expressions *)
let add_texpr g bb e =
DynArray.add bb.bb_el e
let get_texpr g bb is_phi i =
DynArray.get (if is_phi then bb.bb_phi else bb.bb_el) i
(* variables *)
let declare_var g v =
create_var_info g v
let add_var_def g bb v =
if bb.bb_id > 0 then begin
bb.bb_var_writes <- v :: bb.bb_var_writes;
let vi = get_var_info g v in
vi.vi_writes <- bb :: vi.vi_writes;
end
let set_var_value g v bb is_phi i =
(get_var_info g v).vi_value <- Some (bb,is_phi,i)
let get_var_value g v =
let value = (get_var_info g v).vi_value in
let bb,is_phi,i = match value with
| None -> raise Not_found
| Some l -> l
in
match (get_texpr g bb is_phi i).eexpr with
| TVar(_,Some e) | TBinop(OpAssign,_,e) -> e
| _ -> assert false
let add_var_origin g v v_origin =
(get_var_info g v).vi_origin <- v_origin
let get_var_origin g v =
(get_var_info g v).vi_origin
(* graph *)
let create t p =
let bb_root = BasicBlock._create 1 BKRoot [] t p; in
let bb_unreachable = BasicBlock._create 0 BKUnreachable [] t_dynamic null_pos in
{
g_root = bb_root;
g_exit = bb_unreachable;
g_unreachable = bb_unreachable;
g_functions = IntMap.empty;
g_nodes = IntMap.add bb_root.bb_id bb_root IntMap.empty;
g_cfg_edges = [];
g_var_infos = DynArray.create();
g_loops = IntMap.empty;
}
let calculate_df g =
List.iter (fun edge ->
let rec loop bb =
if bb != g.g_unreachable && bb != edge.cfg_to && bb != edge.cfg_to.bb_dominator then begin
bb.bb_df <- edge.cfg_to :: bb.bb_df;
if bb.bb_dominator != bb then loop bb.bb_dominator
end
in
loop edge.cfg_from
) g.g_cfg_edges
let finalize g bb_exit =
g.g_exit <- bb_exit;
calculate_df g;
end
type analyzer_context = {
com : Common.context;
config : Config.t;
graph : Graph.t;
mutable entry : BasicBlock.t;
mutable has_unbound : bool;
mutable loop_counter : int;
mutable loop_stack : int list;
mutable scopes : int list;
mutable scope_depth : int;
}
(*
Transforms an expression to a graph, and a graph back to an expression. This module relies on TexprFilter being
run first.
The created graph is intact and can immediately be transformed back to an expression, or used for analysis first.
*)
module TexprTransformer = struct
open BasicBlock
open Graph
let rec func ctx bb tf t p =
let g = ctx.graph in
let create_node kind bb t p =
let bb = Graph.create_node g kind ctx.scopes bb t p in
bb.bb_loop_groups <- ctx.loop_stack;
bb
in
let bb_root = create_node BKFunctionBegin bb tf.tf_expr.etype tf.tf_expr.epos in
let bb_exit = create_node BKFunctionEnd bb_root tf.tf_expr.etype tf.tf_expr.epos in
List.iter (fun (v,_) ->
declare_var g v;
add_var_def g bb_root v
) tf.tf_args;
add_function g tf t p bb_root;
add_cfg_edge g bb bb_root CFGFunction;
let make_block_meta b =
let e = mk (TConst (TInt (Int32.of_int b.bb_id))) ctx.com.basic.tint b.bb_pos in
wrap_meta ":block" e
in
let bb_breaks = ref [] in
let bb_continue = ref None in
let b_try_stack = ref [] in
let begin_loop bb_loop_pre bb_continue' =
let old = !bb_breaks,!bb_continue in
bb_breaks := [];
bb_continue := Some bb_continue';
let id = ctx.loop_counter in
g.g_loops <- IntMap.add id bb_loop_pre g.g_loops;
ctx.loop_stack <- id :: ctx.loop_stack;
bb_continue'.bb_loop_groups <- id :: bb_continue'.bb_loop_groups;
ctx.loop_counter <- id + 1;
(fun () ->
let breaks = !bb_breaks in
bb_breaks := fst old;
bb_continue := snd old;
ctx.loop_stack <- List.tl ctx.loop_stack;
breaks;
)
in
let begin_try b =
b_try_stack := b :: !b_try_stack;
(fun () ->
b_try_stack := List.tl !b_try_stack
)
in
let increase_scope () =
ctx.scope_depth <- ctx.scope_depth + 1;
ctx.scopes <- ctx.scope_depth :: ctx.scopes;
(fun () ->
ctx.scopes <- List.tl ctx.scopes;
)
in
let add_terminator bb e =
add_texpr g bb e;
close_node g bb;
g.g_unreachable
in
let r = Str.regexp "^\\([A-Za-z0-9_]\\)+$" in
let check_unbound_call v el = match v.v_name,el with
| "__js__",[{eexpr = TConst (TString s)}] when Str.string_match r s 0 -> ()
| _ -> ctx.has_unbound <- true
in
let rec value bb e = match e.eexpr with
| TLocal v ->
bb,e
| TBinop(OpAssign,({eexpr = TLocal v} as e1),e2) ->
block_element bb e,e1
| TBlock [e1] ->
value bb e1
| TBlock _ | TIf _ | TSwitch _ | TTry _ ->
bind_to_temp bb false e
| TCall({eexpr = TLocal v},el) when is_really_unbound v ->
check_unbound_call v el;
bb,e
| TCall(e1,el) ->
call bb e e1 el
| TBinop((OpAssign | OpAssignOp _) as op,e1,e2) ->
let bb,e2 = value bb e2 in
let bb,e1 = value bb e1 in
bb,{e with eexpr = TBinop(op,e1,e2)}
| TBinop(op,e1,e2) ->
let bb,e1,e2 = match ordered_value_list bb [e1;e2] with
| bb,[e1;e2] -> bb,e1,e2
| _ -> assert false
in
bb,{e with eexpr = TBinop(op,e1,e2)}
| TUnop(op,flag,e1) ->
let bb,e1 = value bb e1 in
bb,{e with eexpr = TUnop(op,flag,e1)}
| TArrayDecl el ->
let bb,el = ordered_value_list bb el in
bb,{e with eexpr = TArrayDecl el}
| TObjectDecl fl ->
let el = List.map snd fl in
let bb,el = ordered_value_list bb el in
bb,{e with eexpr = TObjectDecl (List.map2 (fun (s,_) e -> s,e) fl el)}
| TField({eexpr = TTypeExpr _},fa) ->
bb,e
| TField(e1,fa) ->
let bb,e1 = value bb e1 in
bb,{e with eexpr = TField(e1,fa)}
| TArray(e1,e2) ->
let bb,e1,e2 = match ordered_value_list bb [e1;e2] with
| bb,[e1;e2] -> bb,e1,e2
| _ -> assert false
in
bb,{e with eexpr = TArray(e1,e2)}
| TMeta(m,e1) ->
let bb,e1 = value bb e1 in
bb,{e with eexpr = TMeta(m,e1)}
| TParenthesis e1 ->
let bb,e1 = value bb e1 in
bb,{e with eexpr = TParenthesis e1}
| TCast(e1,mto) ->
let bb,e1 = value bb e1 in
bb,{e with eexpr = TCast(e1,mto)}
| TNew(c,tl,el) ->
let bb,el = ordered_value_list bb el in
bb,{e with eexpr = TNew(c,tl,el)}