-
Notifications
You must be signed in to change notification settings - Fork 125
/
imp_Inlining.ml
685 lines (646 loc) · 29 KB
/
imp_Inlining.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
(*
Copyright © 2011 MLstate
This file is part of OPA.
OPA is free software: you can redistribute it and/or modify it under the
terms of the GNU Affero General Public License, version 3, as published by
the Free Software Foundation.
OPA 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 Affero General Public License for
more details.
You should have received a copy of the GNU Affero General Public License
along with OPA. If not, see <http://www.gnu.org/licenses/>.
*)
module J = JsAst
module List = Base.List
module Format = Base.Format
module String = Base.String
(* Inlining works roughly as described in
http://research.microsoft.com/en-us/um/people/simonpj/Papers/inlining/
Note that it works only on generated code, and not on javascript in general
It cannot deal with code that is too imperative. However the generated code is not
completely functional either because the compilation of tail calls introduces some
assignments.
Due to this imperativeness, inlining is more complicated that what is described
in the paper.
*)
type occur_kind =
| NeverUsed (* in that case, the value of the binding is necessarily read
* if the var never appears and the value of its bindings is not
* read, the variable is not in the map *)
| Once of JsIdentSet.t * bool (* the var appears once after the bindings in the set
* and the value of its binding is not used
* the boolean is true if you must execute the use
* after having executed the defition
* for example, it is false in [x = f(); if bool then x],
* since f() may be a side effect, you cannot inline x
*)
| Multiple (* multiple occurrences after possibly any binding *)
(* BEWARE:
* (a=1)+a counts as two occurrences of a
* when (a=1, a) counts as one occurrence of a
* because (a=1)+a really means (a=1,a)+a
*)
let occurrence_analysis params code =
let acc = JsIdentMap.empty in (* maps identifiers to their occur kind *)
let env = JsIdentSet.empty in (* the set of parameters that have been assigned at the current point in the program *)
let safe_vars = JsIdentSet.empty in (* the set of variables that are always used when defined (if they are used)
* used to compute the value of the boolean in the Once case of occur_kind
* this env is reset when going inside a switch or an if
* It seems to show that we really lack some control flow analysis here *)
let rec aux_s tra_s tra_e _need_value (env,acc,safe_vars) stm =
match stm with
| J.Js_switch (_,e,esl,o) ->
let env, acc, safe_vars = aux_e tra_e tra_s true (env,acc,safe_vars) e in
let acc = List.fold_left
(fun acc (e,s) ->
let _env, acc, _ = aux_e tra_e tra_s true (env,acc,JsIdentSet.empty) e in
(* we can dump this env because we know that no binding occurs in the expression *)
let _, acc, _ = aux_s tra_s tra_e true (env,acc,JsIdentSet.empty) s in
acc
) acc esl in
let _, acc, _ =
match o with
| None -> env, acc, safe_vars
| Some s -> aux_s tra_s tra_e true (env,acc,JsIdentSet.empty) s in
env, acc, safe_vars
| J.Js_if (_,e,s1,o) ->
(* the case None for o is not generated by the backend but can happen
* because Imp_Cleanup generates it on cases like if () then {...} else { /* fall through */ }
* in that case, we do the same same as if the code hadn't been cleaned up *)
let env, acc, safe_vars = aux_e tra_e tra_s true (env,acc,safe_vars) e in
(* dumping the env that comes out *)
let _, acc, _ = aux_s tra_s tra_e true (env,acc, JsIdentSet.empty) s1 in
let acc =
match o with
| None -> acc
| Some s2 ->
let _, acc, _ = aux_s tra_s tra_e true (env,acc, JsIdentSet.empty) s2 in
acc in
env, acc, safe_vars
| J.Js_var (_,i,Some e) ->
assert (not (JsIdentSet.mem i params));
aux_assign tra_e tra_s false (env,acc,safe_vars) i e
| J.Js_expr (_,e) ->
aux_e tra_e tra_s false (env,acc,safe_vars) e
| J.Js_function _
| J.Js_throw _
| J.Js_trycatch _
| J.Js_with _ ->
OManager.i_error "@[<v2>Imp_inlining:@ @[<v2>unexpected construct@ %a@] in@ %a@]@\n"
JsPrint.pp#code [stm] JsPrint.pp#code code
| J.Js_var (_,_,None)
| J.Js_return _
| J.Js_continue _
| J.Js_break _
| J.Js_comment _
| J.Js_label _
| J.Js_block _
| J.Js_while _
| J.Js_dowhile _
| J.Js_for _
| J.Js_forin _ ->
tra_s true (env,acc,safe_vars) stm
and aux_assign tra_e tra_s need_value (env,acc,safe_vars) i e =
let env, acc, safe_vars = aux_e tra_e tra_s true (env,acc,safe_vars) e in
(* beware: do not count i, this is a def, not a use *)
(* beware not to put parameters in the map, we cannot inline them *)
if JsIdentSet.mem i params then
JsIdentSet.add i env, acc, safe_vars
else
env, (if need_value then JsIdentMap.add i NeverUsed acc else acc), (JsIdentSet.add i safe_vars)
and aux_e tra_e tra_s need_value (env,acc,safe_vars) expr =
match expr with
| J.Je_cond (_,e1,e2,e3) ->
(* we don't care about [env] in if then else in expression, because no tail calls appears in them *)
let env, acc, safe_vars = aux_e tra_e tra_s true (env, acc, safe_vars) e1 in
let env, acc, _ = aux_e tra_e tra_s true (env, acc, JsIdentSet.empty) e2 in
let env, acc, _ = aux_e tra_e tra_s true (env, acc, JsIdentSet.empty) e3 in
env, acc, safe_vars
| J.Je_comma (_,el,e) ->
let env, acc, safe_vars = List.fold_left (fun env_acc e -> aux_e tra_e tra_s false env_acc e) (env,acc,safe_vars) el in
let env, acc, safe_vars = aux_e tra_e tra_s need_value (env, acc, safe_vars) e in
env, acc, safe_vars
| J.Je_binop (_,J.Jb_assign,J.Je_ident (_,i),e) ->
aux_assign tra_e tra_s need_value (env,acc,safe_vars) i e
| J.Je_unop (_,op,_) when J.is_assignment_unop op -> assert false (* FIXME *)
| J.Je_binop (_,op,_,_) when J.is_assignment_binop op -> assert false
| J.Je_ident (_,i) ->
if JsIdentSet.mem i params then
(* same remark as in aux_assign *)
env, acc, safe_vars
else
let acc =
try
(match JsIdentMap.find i acc with
(* could actually compute the set of identifiers after which there are
* inline points in the multiple case too *)
| NeverUsed -> JsIdentMap.add i Multiple acc
| Once _ -> JsIdentMap.add i Multiple acc
| Multiple -> acc)
with Not_found ->
JsIdentMap.add i (Once (env, JsIdentSet.mem i safe_vars)) acc in
env, acc, safe_vars
| _ -> tra_e true (env,acc,safe_vars) expr in
let fold_stm (env,acc,safe_vars) stm =
JsWalk.TStatement.traverse_fold_context_down aux_s aux_e true (env,acc,safe_vars) stm in
let _env, acc, _ =
List.fold_left fold_stm (env,acc,safe_vars) code in
(*Printf.printf ">>>\n%!";
JsIdentMap.iter
(fun i k ->
Printf.printf "%s: " (JsIdent.to_string i);
(match k with
| Once (s,b) -> Printf.printf "Once safe:%b" b; JsIdentSet.iter (fun s -> Printf.printf " %s" (JsIdent.to_string s)) s
| Multiple -> Printf.printf "Multiple"
| NeverUsed -> Printf.printf "NeverUsed");
Printf.printf "\n%!"
) acc;*)
acc
let contains_vars params e =
JsWalk.Expr.exists
(function
| J.Je_ident (_,i) -> JsIdentSet.mem i params
| _ -> false)
e
let rec object_depth = function
| J.Je_object (_, fields) -> 1 + (List.fold_left (fun m (_,e) -> max (object_depth e) m ) 0 fields)
| _ -> 0
let local_inlining_maximal_object_depth = 5
let local_inlining_policy = function
| J.Je_ident _
| J.Je_num _
| J.Je_bool _
| J.Je_null _
| J.Je_undefined _ (* beware could be redefined *)
| J.Je_this _ (* beware, do not inline that inside a local function! *)
-> `always
(* we don't want to merge objects that have been carefully splitted in many pieces on purpose *)
| J.Je_object _ as obj when object_depth obj > local_inlining_maximal_object_depth ->
`never
(* beware not to inline side effects, even once
* you can reorder them by doing so *)
| _e ->
(* we must check later whether there are side effects or not
* because we can potentially inline an expression that does side effect
* into one that didn't *)
`once
type inline_kind =
| Safe of J.expr (* you can inline this binding *)
| Unsafe of J.expr (* you must check at the inline point if there was
* an assignment that would make inlining invalid *)
let simplify occur_env params code =
let env = JsIdentSet.empty in (* same as in occurrence_analysis *)
let acc = JsIdentMap.empty in (* maps identifiers to be inlined to their inline_kind *)
let weak_acc = JsIdentMap.empty in (* the set of identifiers to be inlined if no side effect happens
* between the def and the use *)
let set_to_clean_up = ref JsIdentSet.empty in (* the binding of these identifiers and its expression should be removed *)
let rec aux_s =
fun tra_s tra_e (env,acc,weak_acc) stm ->
match stm with
(* FIXME: factorize this fake control flow computation of whatever it is
* with the one in the occurrence analyser *)
| J.Js_if (label,e,s1,o) ->
let (env, acc,weak_acc), e = aux_e tra_e tra_s (env,acc,weak_acc) e in
let (_env, acc,_), s1 = aux_s tra_s tra_e (env, acc, weak_acc) s1 in
let acc, o =
match o with
| None -> acc, None
| Some s2 ->
let (_env, acc,_), s2 = aux_s tra_s tra_e (env, acc, weak_acc) s2 in
acc, Some s2 in
(env, acc,weak_acc), J.Js_if (label,e,s1,o)
| J.Js_switch (label,e,esl,o) ->
let (env,acc,weak_acc), e = aux_e tra_e tra_s (env,acc,weak_acc) e in
let acc, (esl:(J.expr * J.statement) list) =
List.fold_left_map
(fun acc (e,s) ->
let (_env, acc, _), e = aux_e tra_e tra_s (env,acc,weak_acc) e in
let (_env, acc, _), s = aux_s tra_s tra_e (env,acc,weak_acc) s in
acc, (e, s)
) acc (esl:(J.expr * J.statement) list) in
let acc, o =
match o with
| None -> acc, None
| Some s ->
let (_env, acc, _), s = aux_s tra_s tra_e (env, acc, weak_acc) s in
acc, Some s in
(env, acc, weak_acc), J.Js_switch (label,e,esl,o)
| J.Js_var (label,i,o) -> (
assert (not (JsIdentSet.mem i params));
try
let kind = JsIdentMap.find i occur_env in
if kind = NeverUsed then
(env, acc, weak_acc), J.Js_block (label,[])
else (
match o with
| None -> (env, acc, weak_acc), stm (* we don't know yet if this variable is needed *)
| Some e ->
let (env,acc,weak_acc), decision = aux_binding tra_e tra_s (env,acc,weak_acc) kind i e in
match decision with
| `keep_binding e -> (env,acc,weak_acc), J.Js_var (label, i, Some e)
| `delete_binding -> (env,acc,weak_acc), J.Js_block (label,[])
)
with Not_found ->
(* local variable not in the map -> never used -> delete it *)
match o with
| None -> (env,acc,weak_acc), J.Js_block (label,[])
| Some e ->
let (env,acc,weak_acc), e = aux_e tra_e tra_s (env,acc,weak_acc) e in
(env,acc,weak_acc), J.Js_expr (label,e)
)
| J.Js_function _ -> assert false
| _ -> tra_s (env,acc,weak_acc) stm
and aux_binding =
fun tra_e tra_s (env,acc,weak_acc) kind i e ->
(* inline in the body *)
let (env, acc, weak_acc), e = aux_e tra_e tra_s (env,acc,weak_acc) e in
let policy = local_inlining_policy e in
match policy, kind with
| _, NeverUsed -> assert false
| `never, _
| `once, Multiple -> (env, acc, weak_acc), `keep_binding e
| (`once | `always), Once (set,safe) ->
let has_side_effects = Imp_Common.does_side_effects e in
if contains_vars set e || not safe && has_side_effects then
(* cannot inline, because we are in the situation of:
* [y = x, x = 2, _ = y] where we cannot inline y
* because x doesn't mean the same at the definition of y
* and at the use of y
* or [x = f(); if bool then x] because inlining may cause
* the side effect of x not to be executed *)
(env, acc, weak_acc), `keep_binding e
else
if has_side_effects then
(* we put the binding in the environment but we don't
* remove the binding because perhaps we won't able to
* inline after all
* if we do, then acc will be updated to make sure the
* binding is removed after all *)
let weak_acc = JsIdentMap.add i e weak_acc in
(env, acc, weak_acc), `keep_binding e
else
(* inline is safe, go for it and delete the binding *)
let acc = JsIdentMap.add i (Safe e) acc in
(env, acc, weak_acc), `delete_binding
| `always, Multiple ->
(* we put the binding in the environment but we don't
* remove the binding as we don't know if we will be able
* to remove all the uses *)
let acc = JsIdentMap.add i (Unsafe e) acc in
(env, acc, weak_acc), `keep_binding e
and aux_e =
fun tra_e tra_s (env,acc,weak_acc) expr ->
let (env, acc, weak_acc), expr =
match expr with
| J.Je_binop (label1,J.Jb_assign,J.Je_ident (label2,i),e) ->
if JsIdentSet.mem i params then
(* we know the identifier won't be rewritten anyway, so
* we can traverse without caution *)
tra_e (env,acc,weak_acc) expr
else (
try
(* beware not to rewrite the identifier *)
let kind = JsIdentMap.find i occur_env in
if kind = NeverUsed then
(* keep the expression but remove the assigment *)
aux_e tra_e tra_s (env,acc,weak_acc) e
else
let (env,acc,weak_acc), decision = aux_binding tra_e tra_s (env,acc,weak_acc) kind i e in
match decision with
| `keep_binding e -> (env,acc,weak_acc), J.Je_binop (label1,J.Jb_assign,J.Je_ident (label2,i),e)
| `delete_binding -> (env,acc,weak_acc), JsCons.Expr.string "deadcode1"
with Not_found ->
(* the identifier is not in the map means it is unused
* and the binding is unused *)
if Imp_Common.does_side_effects e then
aux_e tra_e tra_s (env, acc, weak_acc) e
else
(env, acc, weak_acc), JsCons.Expr.string "deadcode2"
)
| J.Je_ident (_,i) -> (
try
match JsIdentMap.find i acc with
| Safe e ->
(* don't go down in the expression, it was already
* rewritten before being put in the environment
* plus we know we never inline anything containing
* assigments to parameters so the env doesn't need to
* be updated by looking at the expression *)
(env,acc,weak_acc), e
| Unsafe e ->
if contains_vars env e then
(env,acc,weak_acc), expr (* cannot inline *)
else
(* don't go down either, same reason as above *)
(env,acc,weak_acc), e
with Not_found ->
try
(* same as in the case Safe in acc
* the only difference is that the weak_map
* is reset from times to times
* since we just inlined something that contains a side effect
* we must empty the weak acc *)
let e = JsIdentMap.find i weak_acc in
set_to_clean_up := JsIdentSet.add i !set_to_clean_up;
(env, acc, JsIdentMap.empty), e
with Not_found ->
(* parameter or global variable *)
(env,acc,weak_acc), expr
)
| J.Je_function _ -> assert false
| _ -> tra_e (env,acc,weak_acc) expr in
match expr with
(* put assignments also, delete, etc, same kind of stuff as in does_side_effects? ?? *)
| J.Je_call (_,_,_,false) -> (env, acc, JsIdentMap.empty), expr
| _ -> (env, acc, weak_acc), expr in
let foldmap_stm env_acc stm =
JsWalk.TStatement.traverse_foldmap aux_s aux_e env_acc stm in
let (_env,_acc,_), code = List.fold_left_map foldmap_stm (env,acc,weak_acc) code in
(* clean up *)
let set_to_clean_up = !set_to_clean_up in
let code =
List.map (fun stm ->
JsWalk.TStatement.map
(fun stm ->
match stm with
| J.Js_var (_,i,Some _) when JsIdentSet.mem i set_to_clean_up ->
JsCons.Statement.block []
| _ -> stm)
(fun expr ->
match expr with
| J.Je_binop (_,J.Jb_assign,J.Je_ident (_,i),_) when JsIdentSet.mem i set_to_clean_up ->
JsCons.Expr.string "deadcode3"
| _ -> expr)
stm
) code in
(* simplified code *)
code
let local_inline_stm stm =
let rewrite_body params body =
let params_set = JsIdentSet.from_list params in
let code = ref body in
(* FIXME: don't need to iterate 4 times all the times
* we should stop as soon as the rewriting didn't do anything *)
for _i = 1 to 4 do
let occur_env = occurrence_analysis params_set !code in
code := simplify occur_env params_set !code;
done;
!code in
JsWalk.TStatement.traverse_map
(fun tra _tra_e stm ->
match stm with
| J.Js_function (label,name,env,[J.Js_return (label2,Some (J.Je_function (label3,None,params,body)))]) ->
let body = rewrite_body (env @ params) body in
J.Js_function (label,name,env,[J.Js_return (label2,Some (J.Je_function (label3,None,params,body)))])
| J.Js_function (label,name,params,body) ->
let body = rewrite_body params body in
J.Js_function (label,name,params,body)
| _ -> tra stm
)
(fun tra _tra_s expr ->
match expr with
| J.Je_function (label,name,params,body) ->
let body = rewrite_body params body in
J.Je_function (label,name,params,body)
| _ -> tra expr
)
stm
let local_inline code =
List.map local_inline_stm code
let global_inlining_policy_for_var e =
(* since we can't know whether a global variable is used several times
* we assume global vars are always used several times
* FIXME: actually, we could when the variable is not exported of the compilation unit
* but this information is lost (for now) very early in the compilation *)
match e with
| J.Je_ident _
| J.Je_num _
| J.Je_bool _
| J.Je_null _
| J.Je_undefined _
(* beware could be redefined, assuming it isn't *)
(* beware: do not inline 'this' *)
-> true
| _ -> false
let global_inlining_policy_for_function _name params body =
(* FIXME: same here, when a function is used once, it can be inlined no matter what *)
(* we inline but we do not want to make the code bigger, and it is difficult to know
* beforehand if the inlined code will be simplified or not
* so for now, we are conservative when choosing or not to inline *)
(* BEWARE: should make sure not to put recursive functions in here
* FIXME: should be able to inline functions as:
* function(x) {
* var a;
* return x
* }
* function (x) {
* x.f()
* return void;
* }
*)
let simple_expr ?(param_only=false) = function
(* param only is some kind of attemps to avoid a blowup? *)
| J.Je_ident (_,i) when not param_only || List.mem i params -> true
| J.Je_num _
| J.Je_bool _
| J.Je_null _
| J.Je_string (_, "", _) (* FIXME: which strings are we allowed to inline
here, and in the local inlining ?*)
| J.Je_undefined _ -> true
| _ -> false in
match body with
| [J.Js_return (_,Some e)] -> (
match e with
| J.Je_unop (_,_,e1) when simple_expr e1 -> Some e
(* FIXME: do not inline operators that do assignments (or side effects like delete?) *)
| J.Je_binop (_,_,e1,e2) when simple_expr e1 && simple_expr e2 -> Some e
| J.Je_dot (_,e1,_) when simple_expr e1 -> Some e
| J.Je_call (_, e1, l, _) when simple_expr e1 && List.for_all (simple_expr ~param_only:true) l ->
Some e
| _ -> if simple_expr e then Some e else None
)
| _ -> None
(* alpha converting [vars] in [body], while returning the new names of [vars]
* (and the new body of course)*)
let refresh vars body =
let freshs = List.map (fun _ -> Imp_Env.next_param "inline") vars in
let map =
List.fold_left2
(fun map var fresh -> JsIdentMap.add var fresh map)
JsIdentMap.empty vars freshs in
let body =
JsWalk.OnlyExpr.map
(fun e ->
match e with
| J.Je_ident (label,i) -> (
try J.Je_ident (label, JsIdentMap.find i map)
with Not_found -> e
)
| J.Je_function _ -> assert false
| _ -> e
) body in
freshs, body
type env = {
functions : [`var of J.expr | `fun_ of (JsIdent.t list * J.expr) ] JsIdentMap.t;
(* maps from some global identifiers (the only that we saw fit for inlining)
* to their body *)
closures : JsIdent.t JsIdentMap.t;
(* used to map empty closures to the function they represent
* most probably useless now *)
}
(* utility to save [env] for separated compilation *)
module S =
struct
type t = env
let pass = "pass_JavascriptCompilation_imp_Inlining"
let pp_element f = function
| `var e ->
Format.fprintf f "`var %a" (JsPrint.pp#expr ~leading:true) e
| `fun_ (params,e) ->
Format.fprintf f "`fun %a -> %a"
(Format.pp_list "," (fun f i -> Format.pp_print_string f (JsIdent.to_string i))) params
(JsPrint.pp#expr ~leading:true) e
let pp_functions f m =
JsIdentMap.iter
(fun k v ->
Format.fprintf f "@[<2>%s:@ %a@]@\n" (JsIdent.to_string k) pp_element v
) m
let pp_closures f m =
JsIdentMap.iter
(fun k v ->
Format.fprintf f "@[<2>%s: %s@]@\n" (JsIdent.to_string k) (JsIdent.to_string v)
) m
let pp f env =
Format.fprintf f "@[{@\n @[<2>functions: %a@];@\n @[<2>closures: %a@]@\n}@]"
pp_functions env.functions pp_closures env.closures
end
module R =
struct
include ObjectFiles.Make(S)
let refresh_expr = JsWalk.Refresh.expr
let refresh_element = function
| `var e -> `var (refresh_expr e)
| `fun_ (params,e) -> `fun_ (params,refresh_expr e)
let load env =
fold ~deep:true (* FIXME: shouldn't be true, but the environment
* saved should have been rewritten by the inlining
* actually, if you depend on a plugin, then it won't
* be loaded if one of your deep dependency depends
* on it i think, so it also forces you go deep
*)
(fun {functions=functions1; closures=closures1} {functions=old_functions; closures=old_closures} ->
let functions1 =
JsIdentMap.fold
(fun k v env ->
let v = refresh_element v in
(* we can possibly have collisions in the map, if
* you depend on several independant packages that
* load the same plugins *)
JsIdentMap.add k v env
) old_functions functions1 in
let closures1 = JsIdentMap.merge (fun a _ -> a) closures1 old_closures in
{functions=functions1; closures=closures1}
) env
let save ~env ~loaded_env ~initial_env =
let functions_to_be_saved = JsIdentMap.diff2 env.functions loaded_env.functions initial_env.functions in
let closures_to_be_saved = JsIdentMap.diff2 env.closures loaded_env.closures initial_env.closures in
let env_to_be_saved = {functions = functions_to_be_saved; closures = closures_to_be_saved} in
save env_to_be_saved
end
let empty_env = { functions = JsIdentMap.empty; closures = JsIdentMap.empty }
let env_of_map closure_map =
let closure_map = IdentMap.fold (fun k v acc -> JsIdentMap.add (JsCons.Ident.ident k) (JsCons.Ident.ident v) acc) closure_map JsIdentMap.empty in
{ functions = JsIdentMap.empty; closures = closure_map }
(* analysis of a toplevel statement, it fills the environment *)
let global_inline_analyse_stm (env:env) stm =
JsWalk.OnlyStatement.fold
(fun env -> function
| J.Js_var (_,name, Some (J.Je_function (_, None, params, body)))
| J.Js_function (_,name,params,body) -> (
match global_inlining_policy_for_function name params body with
| None -> env
| Some e -> {env with functions = JsIdentMap.add name (`fun_ (params,e)) env.functions}
)
| J.Js_var (_,v,Some e) ->
if global_inlining_policy_for_var e then
{env with functions = JsIdentMap.add v (`var e) env.functions}
else
env
| _ -> env
) env stm
let global_inline_analyse_code env code =
List.fold_left global_inline_analyse_stm env code
(* rewriting of a toplevel statement, given an inlining environment *)
let global_inline_rewrite_stm (env:env) (stm:JsAst.statement) : JsAst.statement =
let make_var_decl local_vars =
List.map (fun i -> JsCons.Statement.var i ?expr:None) local_vars in
let rewrite_expr_aux =
(fun self tra self_stm _tra_stm toplevel local_vars e ->
match e with
| J.Je_ident (_,i) -> (
try
match JsIdentMap.find i env.functions with
| `var e -> self toplevel local_vars e
| `fun_ _ -> tra toplevel local_vars e
with Not_found -> tra toplevel local_vars e
)
| J.Je_call (label,J.Je_ident (_,J.ExprIdent (Ident.FakeSource s)),J.Je_ident (label2,clos) :: args,pure)
when String.is_contained "clos_arg" s (* FIXME: export a function in qmlClosure that does this check instead
* (this is safe, but fragile) *)
&& JsIdentMap.mem clos env.closures ->
let e = J.Je_call (label, J.Je_ident (label2, JsIdentMap.find clos env.closures), args, pure) in
self toplevel local_vars e
| J.Je_call (_,J.Je_ident (_,i), args,_) -> (
try
let rec aux i =
match JsIdentMap.find i env.functions with
| `var (J.Je_ident (_,j)) -> aux j
| `fun_ (params,body) when List.length params = List.length args ->
(* not inlining when there are arity problems, but it could be done
* easily (right List.map2 raises an exception) *)
let params, body = refresh params body in
let assignments = List.map2 (fun l p -> JsCons.Expr.assign_ident l p) params args in
let e = JsCons.Expr.comma assignments body in
let local_vars = params @ local_vars in
self toplevel local_vars e
| `var _
| `fun_ _ ->
tra toplevel local_vars e in
aux i
with Not_found ->
tra toplevel local_vars e
)
| J.Je_function (label,name,params,body) ->
let new_local_vars, body = List.fold_left_map (self_stm false) [] body in
let body = make_var_decl new_local_vars @ body in
local_vars, J.Je_function (label, name, params, body)
| _ -> tra toplevel local_vars e
) in
let rewrite_stm_aux =
(fun self tra self_expr _tra_expr toplevel local_vars stm ->
match stm with
| J.Js_function (label,name,params,body) ->
let new_local_vars, body = List.fold_left_map (self false) [] body in
let body = make_var_decl new_local_vars @ body in
local_vars, J.Js_function (label, name, params, body)
| J.Js_var (label,i,Some e) when toplevel ->
let new_local_vars, e = self_expr false [] e in
let e = JsCons.Expr.maybe_scope new_local_vars e in
local_vars, J.Js_var (label, i, Some e)
| J.Js_return _
| J.Js_switch _
| J.Js_if _
| J.Js_throw _
| J.Js_trycatch _
| J.Js_for _
| J.Js_forin _
| J.Js_dowhile _
| J.Js_while _
| J.Js_with _ when toplevel -> assert false (* no expression at toplevel are treated *)
| _ -> tra toplevel local_vars stm) in
let local_vars = [] in
let local_vars, stm =
JsWalk.TStatement.self_traverse_foldmap_context_down rewrite_stm_aux rewrite_expr_aux true local_vars stm in
assert (local_vars = []);
stm