File: structure.ml

package info (click to toggle)
js-of-ocaml 6.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 37,932 kB
  • sloc: ml: 135,957; javascript: 58,364; ansic: 437; makefile: 422; sh: 12; perl: 4
file content (351 lines) | stat: -rw-r--r-- 11,208 bytes parent folder | download | duplicates (2)
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
open Stdlib
open Code

let get_edges g src = try Addr.Hashtbl.find g src with Not_found -> Addr.Set.empty

let add_edge g src dst = Addr.Hashtbl.replace g src (Addr.Set.add dst (get_edges g src))

let reverse_tree t =
  let g = Addr.Hashtbl.create 16 in
  Addr.Hashtbl.iter (fun child parent -> add_edge g parent child) t;
  g

let reverse_graph g =
  let g' = Addr.Hashtbl.create 16 in
  Addr.Hashtbl.iter
    (fun child parents -> Addr.Set.iter (fun parent -> add_edge g' parent child) parents)
    g;
  g'

type graph = Addr.Set.t Addr.Hashtbl.t

type t =
  { succs : Addr.Set.t Addr.Hashtbl.t
  ; preds : Addr.Set.t Addr.Hashtbl.t
  ; reverse_post_order : Addr.t list
  ; block_order : int Addr.Hashtbl.t
  }

let get_nodes g =
  List.fold_left
    ~init:Addr.Set.empty
    ~f:(fun s pc -> Addr.Set.add pc s)
    g.reverse_post_order

let block_order g pc = Addr.Hashtbl.find g.block_order pc

let is_backward g pc pc' =
  Addr.Hashtbl.find g.block_order pc >= Addr.Hashtbl.find g.block_order pc'

let is_forward g pc pc' =
  Addr.Hashtbl.find g.block_order pc < Addr.Hashtbl.find g.block_order pc'

(* pc has at least two forward edges moving into it *)
let is_merge_node' block_order preds pc =
  let s = try Addr.Hashtbl.find preds pc with Not_found -> Addr.Set.empty in
  let o = Addr.Hashtbl.find block_order pc in
  try
    ignore
      (Addr.Set.fold
         (fun pc' found_first ->
           if Addr.Hashtbl.find block_order pc' < o
           then
             if found_first
             then (* Exit early to avoid quadratic behavior *) raise Exit
             else true
           else found_first)
         s
         false);
    false
  with Exit -> true

let empty_body body =
  List.for_all
    ~f:(fun i ->
      match i with
      | Event _ -> true
      | _ -> false)
    body

let rec leave_try_body block_order preds blocks pc =
  if is_merge_node' block_order preds pc
  then false
  else
    match Addr.Map.find pc blocks with
    | { body; branch = Return _ | Stop; _ } when empty_body body -> false
    | { body; branch = Branch (pc', _); _ } when empty_body body ->
        leave_try_body block_order preds blocks pc'
    | _ -> true

let build_graph blocks pc =
  let succs = Addr.Hashtbl.create 16 in
  let l = ref [] in
  let visited = Addr.Hashtbl.create 16 in
  let poptraps = ref [] in
  let rec traverse ~englobing_exn_handlers pc =
    if not (Addr.Hashtbl.mem visited pc)
    then (
      Addr.Hashtbl.add visited pc ();
      let successors = Code.fold_children blocks pc Addr.Set.add Addr.Set.empty in
      Addr.Hashtbl.add succs pc successors;
      let block = Addr.Map.find pc blocks in
      Addr.Set.iter
        (fun pc' ->
          let englobing_exn_handlers =
            match block.branch with
            | Pushtrap ((body_pc, _), _, _) when pc' = body_pc ->
                pc :: englobing_exn_handlers
            | Poptrap (leave_pc, _) -> (
                match englobing_exn_handlers with
                | [] -> assert false
                | enter_pc :: rem ->
                    poptraps := (enter_pc, leave_pc) :: !poptraps;
                    rem)
            | _ -> englobing_exn_handlers
          in
          traverse ~englobing_exn_handlers pc')
        successors;
      l := pc :: !l)
  in
  traverse ~englobing_exn_handlers:[] pc;
  let block_order = Addr.Hashtbl.create 16 in
  List.iteri !l ~f:(fun i pc -> Addr.Hashtbl.add block_order pc i);
  let preds = reverse_graph succs in
  List.iter !poptraps ~f:(fun (enter_pc, leave_pc) ->
      if leave_try_body block_order preds blocks leave_pc
      then (
        (* Add an edge to limit the [try] body *)
        Addr.Hashtbl.replace
          succs
          enter_pc
          (Addr.Set.add leave_pc (Addr.Hashtbl.find succs enter_pc));
        Addr.Hashtbl.replace
          preds
          leave_pc
          (Addr.Set.add enter_pc (Addr.Hashtbl.find preds leave_pc))));
  { succs; preds; reverse_post_order = !l; block_order }

let reversed_dominator_tree g =
  (* A Simple, Fast Dominance Algorithm
     Keith D. Cooper, Timothy J. Harvey, and Ken Kennedy *)
  let dom = Addr.Hashtbl.create 16 in
  let rec inter pc pc' =
    (* Compute closest common ancestor *)
    if pc = pc'
    then pc
    else if is_forward g pc pc'
    then inter pc (Addr.Hashtbl.find dom pc')
    else inter (Addr.Hashtbl.find dom pc) pc'
  in
  List.iter g.reverse_post_order ~f:(fun pc ->
      let l = Addr.Hashtbl.find g.succs pc in
      Addr.Set.iter
        (fun pc' ->
          if is_forward g pc pc'
          then
            let d = try inter pc (Addr.Hashtbl.find dom pc') with Not_found -> pc in
            Addr.Hashtbl.replace dom pc' d)
        l);
  (* Check we have reached a fixed point (reducible graph) *)
  List.iter g.reverse_post_order ~f:(fun pc ->
      let l = Addr.Hashtbl.find g.succs pc in
      Addr.Set.iter
        (fun pc' ->
          if is_forward g pc pc'
          then
            let d = Addr.Hashtbl.find dom pc' in
            assert (inter pc d = d))
        l);

  dom

let dominator_tree g =
  let idom = reversed_dominator_tree g in
  reverse_tree idom

(* pc has at least two forward edges moving into it *)
let is_merge_node g pc = is_merge_node' g.block_order g.preds pc

let is_loop_header g pc =
  let s = try Addr.Hashtbl.find g.preds pc with Not_found -> Addr.Set.empty in
  let o = Addr.Hashtbl.find g.block_order pc in
  Addr.Set.exists (fun pc' -> Addr.Hashtbl.find g.block_order pc' >= o) s

let sort_in_post_order t l =
  List.sort ~cmp:(fun a b -> compare (block_order t b) (block_order t a)) l

let blocks_in_reverse_post_order g = g.reverse_post_order

(* pc dominates pc' *)
let rec dominates g idom pc pc' =
  pc = pc' || (is_forward g pc pc' && dominates g idom pc (Addr.Hashtbl.find idom pc'))

(*
let dominance_frontier g idom =
  let frontiers = Addr.Hashtbl.create 16 in
  Addr.Hashtbl.iter
    (fun pc preds ->
      if Addr.Set.cardinal preds > 1
      then
        let dom = Addr.Hashtbl.find idom pc in
        let rec loop runner =
          if runner <> dom
          then (
            add_edge frontiers runner pc;
            loop (Addr.Hashtbl.find idom runner))
        in
        Addr.Set.iter loop preds)
    g.preds;
  frontiers
*)

(* Compute a map from each block to the set of loops it belongs to *)
let mark_loops g =
  let in_loop = Addr.Hashtbl.create 16 in
  Addr.Hashtbl.iter
    (fun pc preds ->
      let rec mark_loop pc' =
        if not (Addr.Set.mem pc (get_edges in_loop pc'))
        then (
          add_edge in_loop pc' pc;
          if pc' <> pc then Addr.Set.iter mark_loop (Addr.Hashtbl.find g.preds pc'))
      in
      Addr.Set.iter (fun pc' -> if is_backward g pc' pc then mark_loop pc') preds)
    g.preds;
  in_loop

let rec measure blocks g ~idom ~root pc limit =
  if not (dominates g idom root pc)
  then limit
  else if is_loop_header g pc
  then -1
  else
    let b = Addr.Map.find pc blocks in
    let limit =
      List.fold_left b.body ~init:limit ~f:(fun acc x ->
          match x with
          (* A closure is never small *)
          | Let (_, Closure _) -> -1
          | Event _ -> acc
          | _ -> acc - 1)
    in
    if limit < 0
    then limit
    else
      Addr.Set.fold
        (fun pc limit ->
          if limit < 0 then limit else measure blocks g ~idom ~root pc limit)
        (get_edges g.succs pc)
        limit

let is_small blocks g ~idom ~root pc = measure blocks g ~idom ~root pc 20 >= 0

let shrink_loops blocks ({ succs; preds; reverse_post_order; _ } as g) =
  let add_edge pred succ =
    Addr.Hashtbl.replace succs pred (Addr.Set.add succ (Addr.Hashtbl.find succs pred));
    Addr.Hashtbl.replace preds succ (Addr.Set.add pred (Addr.Hashtbl.find preds succ))
  in
  let in_loop = mark_loops g in
  let idom = reversed_dominator_tree g in
  let dom = reverse_tree idom in
  let root = List.hd reverse_post_order in
  let rec traverse ignored pc =
    let succs = get_edges dom pc in
    let loops = get_edges in_loop pc in
    let block = Addr.Map.find pc blocks in
    Addr.Set.iter
      (fun pc' ->
        (* Whatever is in the scope of an exception handler should not be
           moved outside *)
        let ignored =
          match block.branch with
          | Pushtrap ((body_pc, _), _, _) when pc' = body_pc ->
              Addr.Set.union ignored loops
          | _ -> ignored
        in
        let loops' = get_edges in_loop pc' in
        let left_loops = Addr.Set.diff (Addr.Set.diff loops loops') ignored in
        (* If we leave a loop, we add an edge from predecessors of
           the loop header to the current block, so that it is
           considered outside of the loop. *)
        Addr.Set.iter
          (fun pc0 ->
            if not (is_small blocks g ~idom ~root:pc0 pc')
            then
              Addr.Set.iter
                (fun pc -> if is_forward g pc pc0 then add_edge pc pc')
                (get_edges g.preds pc0))
          left_loops;
        traverse ignored pc')
      succs
  in
  traverse Addr.Set.empty root

let build_graph blocks pc =
  let g = build_graph blocks pc in
  shrink_loops blocks g;
  g

(* Ensure that all loops have a predecessor block. Function
   shrink_loops assumes this. *)
let norm p =
  let free_pc = ref p.free_pc in
  let visited = BitSet.create' p.free_pc in
  let rec mark_used ~function_start pc =
    if not (BitSet.mem visited pc)
    then (
      if not function_start then BitSet.set visited pc;
      let block = Addr.Map.find pc p.blocks in
      List.iter
        ~f:(fun i ->
          match i with
          | Let (_, Closure (_, (pc', _), _)) -> mark_used ~function_start:true pc'
          | _ -> ())
        block.body;
      fold_children p.blocks pc (fun pc' () -> mark_used ~function_start:false pc') ())
  in
  mark_used ~function_start:true p.start;
  let closure_need_update = function
    | Let (_, Closure (_, (pc, _), _)) -> BitSet.mem visited pc
    | _ -> false
  in
  let rewrite_cont cont blocks =
    let npc = !free_pc in
    incr free_pc;
    let body =
      let b = Addr.Map.find (fst cont) blocks in
      match b.body with
      | (Event _ as e) :: _ -> [ e ]
      | _ -> []
    in
    let blocks = Addr.Map.add npc { body; params = []; branch = Branch cont } blocks in
    (npc, []), blocks
  in
  let blocks =
    Addr.Map.fold
      (fun pc block blocks ->
        if List.exists block.body ~f:closure_need_update
        then
          let blocks = ref blocks in
          let body =
            List.map block.body ~f:(function
              | Let (x, Closure (params, cont, loc)) as i when closure_need_update i ->
                  let cont', blocks' = rewrite_cont cont !blocks in
                  blocks := blocks';
                  Let (x, Closure (params, cont', loc))
              | i -> i)
          in
          Addr.Map.add pc { block with body } !blocks
        else blocks)
      p.blocks
      p.blocks
  in
  if BitSet.mem visited p.start
  then (
    let npc = !free_pc in
    incr free_pc;
    let blocks =
      Addr.Map.add npc { body = []; params = []; branch = Branch (p.start, []) } blocks
    in
    { blocks; free_pc = !free_pc; start = npc })
  else { blocks; free_pc = !free_pc; start = p.start }