Source file ref_unboxing.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
(* Js_of_ocaml compiler
 * http://www.ocsigen.org/js_of_ocaml/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, with linking exception;
 * either version 2.1 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *)

open! Stdlib
open Code

(*
ocamlc does not perform reference unboxing when emitting debugging
information. Inlining can also enable additional reference unboxing.

We currently do not unbox references which are used within the scope
of an exception handler. This should often not result in significant
performance improvements, and is tricky to get right. Indeed, we would
need to introduce variables for these references right before the
[Pushtrap], and then add [Assign] instructions to keep their contents
up to date whenever a reference is updated.
*)

let debug = Debug.find "unbox-refs"

let times = Debug.find "times"

let stats = Debug.find "stats"

let rewrite_body unboxed_refs body ref_contents subst =
  let ref_contents, subst, l =
    List.fold_left
      ~f:(fun (ref_contents, subst, acc) i ->
        match i with
        | Let (x, Block (0, [| y |], (NotArray | Unknown), Maybe_mutable))
          when Var.Set.mem x unboxed_refs -> Var.Map.add x y ref_contents, subst, acc
        | Let (y, Field (x, 0, Non_float)) when Var.Map.mem x ref_contents ->
            ref_contents, Var.Map.add y (Var.Map.find x ref_contents) subst, acc
        | Offset_ref (x, n) when Var.Map.mem x ref_contents ->
            let y = Var.fork x in
            ( Var.Map.add x y ref_contents
            , subst
            , Let
                ( y
                , Prim
                    ( Extern "%int_add"
                    , [ Pv (Var.Map.find x ref_contents)
                      ; Pc (Int (Targetint.of_int_exn n))
                      ] ) )
              :: acc )
        | Set_field (x, 0, Non_float, y) when Var.Map.mem x ref_contents ->
            Var.Map.add x y ref_contents, subst, acc
        | Event _ -> (
            ( ref_contents
            , subst
            , match acc with
              | Event _ :: prev ->
                  (* Avoid consecutive events (keep just the last one) *)
                  i :: prev
              | _ -> i :: acc ))
        | _ -> ref_contents, subst, i :: acc)
      body
      ~init:(ref_contents, subst, [])
  in
  ref_contents, subst, List.rev l

let rewrite_cont relevant_vars ref_contents (pc', args) =
  let refs, _ = Int.Hashtbl.find relevant_vars pc' in
  let vars = Var.Map.filter (fun x _ -> Var.Set.mem x refs) ref_contents in
  pc', List.map ~f:snd (Var.Map.bindings vars) @ args

let rewrite_branch relevant_vars ref_contents branch =
  match branch with
  | Return _ | Raise _ | Stop -> branch
  | Branch cont -> Branch (rewrite_cont relevant_vars ref_contents cont)
  | Cond (x, cont, cont') ->
      Cond
        ( x
        , rewrite_cont relevant_vars ref_contents cont
        , rewrite_cont relevant_vars ref_contents cont' )
  | Switch (x, a) ->
      Switch (x, Array.map ~f:(fun cont -> rewrite_cont relevant_vars ref_contents cont) a)
  | Pushtrap (cont, x, cont') ->
      Pushtrap
        ( rewrite_cont relevant_vars ref_contents cont
        , x
        , rewrite_cont relevant_vars ref_contents cont' )
  | Poptrap cont -> Poptrap (rewrite_cont relevant_vars ref_contents cont)

let rewrite_function p ~unboxed_refs pc subst =
  let g = Structure.(dominator_tree (build_graph p.blocks pc)) in
  let relevant_vars =
    let relevant_vars = Int.Hashtbl.create 16 in
    let rec traverse_tree g pc refs =
      let block = Addr.Map.find pc p.blocks in
      let refs' =
        List.fold_left
          ~f:(fun s i ->
            match i with
            | Let (x, Block (0, [| _ |], (NotArray | Unknown), Maybe_mutable))
              when Var.Hashtbl.mem unboxed_refs x -> Var.Set.add x s
            | _ -> s)
          ~init:refs
          block.body
      in
      Int.Hashtbl.add relevant_vars pc (refs, refs');
      Addr.Set.iter (fun pc' -> traverse_tree g pc' refs') (Structure.get_edges g pc)
    in
    traverse_tree g pc Var.Set.empty;
    relevant_vars
  in
  let rec traverse_tree' g pc blocks subst =
    let block = Addr.Map.find pc p.blocks in
    let refs, refs' = Int.Hashtbl.find relevant_vars pc in
    let ref_contents =
      Var.Set.fold (fun x m -> Var.Map.add x (Var.fork x) m) refs Var.Map.empty
    in
    let params = List.map ~f:snd (Var.Map.bindings ref_contents) @ block.params in
    let ref_contents, subst, body = rewrite_body refs' block.body ref_contents subst in
    let branch = rewrite_branch relevant_vars ref_contents block.branch in
    let blocks = Addr.Map.add pc { params; body; branch } blocks in
    Addr.Set.fold
      (fun pc' (blocks, subst) -> traverse_tree' g pc' blocks subst)
      (Structure.get_edges g pc)
      (blocks, subst)
  in
  let blocks, subst = traverse_tree' g pc p.blocks subst in
  { p with blocks }, subst

let f p =
  let t = Timer.make () in
  let candidates = Var.Hashtbl.create 128 in
  let updated = Var.Hashtbl.create 128 in
  let visited = BitSet.create' p.free_pc in
  let discard x = Var.Hashtbl.remove candidates x in
  let check_field_access depth x =
    match Var.Hashtbl.find candidates x with
    | exception Not_found -> false
    | depth' ->
        if depth' = depth
        then true
        else (
          Var.Hashtbl.remove candidates x;
          false)
  in
  (* A reference can be defined within the scope of an exception
     handler and used within the scope of another exception handler.
     So exception handlers should have strictly increasing depths
     within a function. [max_depth] is the largest depth used so far
     inside a function. This way, we know which depth to use when
     entering the scope of an exception handler. We use [depth_stack]
     to restore the previous depth when leaving the scope of an
     exception handler. *)
  let rec traverse depth_stack max_depth depth start_pc pc =
    if not (BitSet.mem visited pc)
    then (
      BitSet.set visited pc;
      let block = Addr.Map.find pc p.blocks in
      List.iter
        ~f:(fun i ->
          match i with
          | Let (x, Block (0, [| _ |], (NotArray | Unknown), Maybe_mutable)) ->
              Freevars.iter_instr_free_vars discard i;
              Var.Hashtbl.replace candidates x depth
          | Let (_, Closure (_, (pc', _), _)) ->
              traverse [] (max_depth + 1) (max_depth + 1) pc' pc'
          | Let (_, Field (x, 0, Non_float)) -> ignore (check_field_access depth x)
          | Offset_ref (x, _) ->
              if check_field_access depth x then Var.Hashtbl.replace updated x start_pc
          | Set_field (x, _, Non_float, y) ->
              discard y;
              if check_field_access depth x then Var.Hashtbl.replace updated x start_pc
          | _ -> Freevars.iter_instr_free_vars discard i)
        block.body;
      Freevars.iter_last_free_var discard block.branch;
      match block.branch with
      | Pushtrap ((pc', _), _, (pc'', _)) ->
          traverse (depth :: depth_stack) (max_depth + 1) (max_depth + 1) start_pc pc';
          traverse depth_stack max_depth depth start_pc pc''
      | Poptrap (pc', _) ->
          traverse (List.tl depth_stack) max_depth (List.hd depth_stack) start_pc pc'
      | _ ->
          Code.fold_children
            p.blocks
            pc
            (fun pc' () -> traverse depth_stack max_depth depth start_pc pc')
            ())
  in
  traverse [] 0 0 p.start p.start;
  if debug ()
  then
    Print.program
      Format.err_formatter
      (fun _ i ->
        match i with
        | Instr (Let (x, _))
          when Var.Hashtbl.mem candidates x && Var.Hashtbl.mem updated x -> "REF"
        | _ -> "")
      p;
  Var.Hashtbl.filter_map_inplace
    (fun x _depth -> try Some (Var.Hashtbl.find updated x) with Not_found -> None)
    candidates;
  let functions =
    Var.Hashtbl.fold (fun _ pc s -> Addr.Set.add pc s) candidates Addr.Set.empty
  in
  let p, subst =
    Addr.Set.fold
      (fun pc (p, subst) -> rewrite_function p ~unboxed_refs:candidates pc subst)
      functions
      (p, Var.Map.empty)
  in
  let p =
    if Var.Map.is_empty subst
    then p
    else Subst.Excluding_Binders.program (Subst.from_map subst) p
  in
  if times () then Format.eprintf "  reference unboxing: %a@." Timer.print t;
  if stats ()
  then Format.eprintf "Stats - reference unboxing: %d@." (Var.Hashtbl.length candidates);
  p