| 
 | 1 | +open! Stdlib  | 
 | 2 | +open Code  | 
 | 3 | + | 
 | 4 | +(*  | 
 | 5 | +ocamlc does not perform reference unboxing when emitting debugging  | 
 | 6 | +information. Inlining can also enable additional reference unboxing.  | 
 | 7 | +
  | 
 | 8 | +We currently does not unbox references which are used within the scope  | 
 | 9 | +of an exception handler. This should often not result in significant  | 
 | 10 | +performance improvements, and is tricky to get right. Indeed, we would  | 
 | 11 | +need to introduce variables for these references right before the  | 
 | 12 | +[Pushtrap], and then add [Assign] instructions to keep their contents  | 
 | 13 | +up to date whenever a reference is updated.  | 
 | 14 | +*)  | 
 | 15 | + | 
 | 16 | +let debug = Debug.find "unbox-refs"  | 
 | 17 | + | 
 | 18 | +let times = Debug.find "times"  | 
 | 19 | + | 
 | 20 | +let stats = Debug.find "stats"  | 
 | 21 | + | 
 | 22 | +let rewrite refs block m =  | 
 | 23 | +  let m, l =  | 
 | 24 | +    List.fold_left  | 
 | 25 | +      ~f:(fun (m, acc) i ->  | 
 | 26 | +        match i with  | 
 | 27 | +        | Let (x, Block (0, [| y |], (NotArray | Unknown), Maybe_mutable))  | 
 | 28 | +          when Var.Set.mem x refs -> Var.Map.add x y m, acc  | 
 | 29 | +        | Let (y, Field (x, 0, Non_float)) when Var.Map.mem x m ->  | 
 | 30 | +            (* Optimized away by Phisimpl *)  | 
 | 31 | +            m, Let (y, Prim (Extern "%identity", [ Pv (Var.Map.find x m) ])) :: acc  | 
 | 32 | +        | Offset_ref (x, n) when Var.Map.mem x m ->  | 
 | 33 | +            let y = Var.fresh () in  | 
 | 34 | +            ( Var.Map.add x y m  | 
 | 35 | +            , Let  | 
 | 36 | +                ( y  | 
 | 37 | +                , Prim  | 
 | 38 | +                    ( Extern "%int_add"  | 
 | 39 | +                    , [ Pv (Var.Map.find x m); Pc (Int (Targetint.of_int_exn n)) ] ) )  | 
 | 40 | +              :: acc )  | 
 | 41 | +        | Set_field (x, 0, Non_float, y) when Var.Map.mem x m -> Var.Map.add x y m, acc  | 
 | 42 | +        | Event _ -> (  | 
 | 43 | +            ( m  | 
 | 44 | +            , match acc with  | 
 | 45 | +              | Event _ :: prev ->  | 
 | 46 | +                  (* Avoid consecutive events (keep just the last one) *)  | 
 | 47 | +                  i :: prev  | 
 | 48 | +              | _ -> i :: acc ))  | 
 | 49 | +        | _ -> m, i :: acc)  | 
 | 50 | +      block.body  | 
 | 51 | +      ~init:(m, [])  | 
 | 52 | +  in  | 
 | 53 | +  m, List.rev l  | 
 | 54 | + | 
 | 55 | +let rewrite_cont relevant_vars vars (pc', args) =  | 
 | 56 | +  let refs, _ = Int.Hashtbl.find relevant_vars pc' in  | 
 | 57 | +  let vars = Var.Map.filter (fun x _ -> Var.Set.mem x refs) vars in  | 
 | 58 | +  pc', List.map ~f:snd (Var.Map.bindings vars) @ args  | 
 | 59 | + | 
 | 60 | +let rewrite_function p variables pc =  | 
 | 61 | +  let relevant_vars = Int.Hashtbl.create 16 in  | 
 | 62 | +  let g = Structure.(dominator_tree (build_graph p.blocks pc)) in  | 
 | 63 | +  let rec traverse_tree g pc refs =  | 
 | 64 | +    let block = Addr.Map.find pc p.blocks in  | 
 | 65 | +    let refs' =  | 
 | 66 | +      List.fold_left  | 
 | 67 | +        ~f:(fun s i ->  | 
 | 68 | +          match i with  | 
 | 69 | +          | Let (x, Block (0, [| _ |], (NotArray | Unknown), Maybe_mutable))  | 
 | 70 | +            when Var.Hashtbl.mem variables x -> Var.Set.add x s  | 
 | 71 | +          | _ -> s)  | 
 | 72 | +        ~init:refs  | 
 | 73 | +        block.body  | 
 | 74 | +    in  | 
 | 75 | +    Int.Hashtbl.add relevant_vars pc (refs, refs');  | 
 | 76 | +    Addr.Set.iter (fun pc' -> traverse_tree g pc' refs') (Structure.get_edges g pc)  | 
 | 77 | +  in  | 
 | 78 | +  traverse_tree g pc Var.Set.empty;  | 
 | 79 | +  let rec traverse_tree' g pc blocks =  | 
 | 80 | +    let block = Addr.Map.find pc p.blocks in  | 
 | 81 | +    let refs, refs' = Int.Hashtbl.find relevant_vars pc in  | 
 | 82 | +    let vars =  | 
 | 83 | +      Var.Set.fold (fun x m -> Var.Map.add x (Var.fork x) m) refs Var.Map.empty  | 
 | 84 | +    in  | 
 | 85 | +    let params = List.map ~f:snd (Var.Map.bindings vars) @ block.params in  | 
 | 86 | +    let vars, body = rewrite refs' block vars in  | 
 | 87 | +    let branch =  | 
 | 88 | +      match block.branch with  | 
 | 89 | +      | Return _ | Raise _ | Stop -> block.branch  | 
 | 90 | +      | Branch cont -> Branch (rewrite_cont relevant_vars vars cont)  | 
 | 91 | +      | Cond (x, cont, cont') ->  | 
 | 92 | +          Cond  | 
 | 93 | +            ( x  | 
 | 94 | +            , rewrite_cont relevant_vars vars cont  | 
 | 95 | +            , rewrite_cont relevant_vars vars cont' )  | 
 | 96 | +      | Switch (x, a) ->  | 
 | 97 | +          Switch (x, Array.map ~f:(fun cont -> rewrite_cont relevant_vars vars cont) a)  | 
 | 98 | +      | Pushtrap (cont, x, cont') ->  | 
 | 99 | +          Pushtrap  | 
 | 100 | +            ( rewrite_cont relevant_vars vars cont  | 
 | 101 | +            , x  | 
 | 102 | +            , rewrite_cont relevant_vars vars cont' )  | 
 | 103 | +      | Poptrap cont -> Poptrap (rewrite_cont relevant_vars vars cont)  | 
 | 104 | +    in  | 
 | 105 | +    let blocks = Addr.Map.add pc { params; body; branch } blocks in  | 
 | 106 | +    Addr.Set.fold  | 
 | 107 | +      (fun pc' blocks -> traverse_tree' g pc' blocks)  | 
 | 108 | +      (Structure.get_edges g pc)  | 
 | 109 | +      blocks  | 
 | 110 | +  in  | 
 | 111 | +  let blocks = traverse_tree' g pc p.blocks in  | 
 | 112 | +  { p with blocks }  | 
 | 113 | + | 
 | 114 | +let f p =  | 
 | 115 | +  let t = Timer.make () in  | 
 | 116 | +  let candidates = Var.Hashtbl.create 128 in  | 
 | 117 | +  let updated = Var.Hashtbl.create 128 in  | 
 | 118 | +  let visited = BitSet.create' p.free_pc in  | 
 | 119 | +  let discard x = Var.Hashtbl.remove candidates x in  | 
 | 120 | +  let check_field_access depth x =  | 
 | 121 | +    match Var.Hashtbl.find candidates x with  | 
 | 122 | +    | exception Not_found -> false  | 
 | 123 | +    | depth' ->  | 
 | 124 | +        if depth' = depth  | 
 | 125 | +        then true  | 
 | 126 | +        else (  | 
 | 127 | +          Var.Hashtbl.remove candidates x;  | 
 | 128 | +          false)  | 
 | 129 | +  in  | 
 | 130 | +  (* A reference can be defined within the scope of an exception  | 
 | 131 | +     handler and used within the scope of another exception handler.  | 
 | 132 | +     So exception handlers should have strictly increasing depths  | 
 | 133 | +     within a function. [max_depth] is the largest depth used so far  | 
 | 134 | +     inside a function. This way, we know which depth to use when  | 
 | 135 | +     entering the scope of an exception handler. We use [depth_stack]  | 
 | 136 | +     to restore the previous depth when leaving the scope of an  | 
 | 137 | +     exception handler. *)  | 
 | 138 | +  let rec traverse depth_stack max_depth depth start_pc pc =  | 
 | 139 | +    if not (BitSet.mem visited pc)  | 
 | 140 | +    then (  | 
 | 141 | +      BitSet.set visited pc;  | 
 | 142 | +      let block = Addr.Map.find pc p.blocks in  | 
 | 143 | +      List.iter  | 
 | 144 | +        ~f:(fun i ->  | 
 | 145 | +          match i with  | 
 | 146 | +          | Let (x, Block (0, [| _ |], (NotArray | Unknown), Maybe_mutable)) ->  | 
 | 147 | +              Freevars.iter_instr_free_vars discard i;  | 
 | 148 | +              Var.Hashtbl.replace candidates x depth  | 
 | 149 | +          | Let (_, Closure (_, (pc', _), _)) ->  | 
 | 150 | +              traverse [] (max_depth + 1) (max_depth + 1) pc' pc'  | 
 | 151 | +          | Let (_, Field (x, 0, Non_float)) -> ignore (check_field_access depth x)  | 
 | 152 | +          | Offset_ref (x, _) ->  | 
 | 153 | +              if check_field_access depth x then Var.Hashtbl.replace updated x start_pc  | 
 | 154 | +          | Set_field (x, _, Non_float, y) ->  | 
 | 155 | +              discard y;  | 
 | 156 | +              if check_field_access depth x then Var.Hashtbl.replace updated x start_pc  | 
 | 157 | +          | _ -> Freevars.iter_instr_free_vars discard i)  | 
 | 158 | +        block.body;  | 
 | 159 | +      Freevars.iter_last_free_var discard block.branch;  | 
 | 160 | +      match block.branch with  | 
 | 161 | +      | Pushtrap ((pc', _), _, (pc'', _)) ->  | 
 | 162 | +          traverse (depth :: depth_stack) (max_depth + 1) (max_depth + 1) start_pc pc';  | 
 | 163 | +          traverse depth_stack max_depth depth start_pc pc''  | 
 | 164 | +      | Poptrap (pc', _) ->  | 
 | 165 | +          traverse (List.tl depth_stack) max_depth (List.hd depth_stack) start_pc pc'  | 
 | 166 | +      | _ ->  | 
 | 167 | +          Code.fold_children  | 
 | 168 | +            p.blocks  | 
 | 169 | +            pc  | 
 | 170 | +            (fun pc' () -> traverse depth_stack max_depth depth start_pc pc')  | 
 | 171 | +            ())  | 
 | 172 | +  in  | 
 | 173 | +  traverse [] 0 0 p.start p.start;  | 
 | 174 | +  if debug ()  | 
 | 175 | +  then  | 
 | 176 | +    Print.program  | 
 | 177 | +      Format.err_formatter  | 
 | 178 | +      (fun _ i ->  | 
 | 179 | +        match i with  | 
 | 180 | +        | Instr (Let (x, _))  | 
 | 181 | +          when Var.Hashtbl.mem candidates x && Var.Hashtbl.mem updated x -> "REF"  | 
 | 182 | +        | _ -> "")  | 
 | 183 | +      p;  | 
 | 184 | +  Var.Hashtbl.filter_map_inplace  | 
 | 185 | +    (fun x _depth -> try Some (Var.Hashtbl.find updated x) with Not_found -> None)  | 
 | 186 | +    candidates;  | 
 | 187 | +  let functions =  | 
 | 188 | +    Var.Hashtbl.fold (fun _ pc s -> Addr.Set.add pc s) candidates Addr.Set.empty  | 
 | 189 | +  in  | 
 | 190 | +  let p = Addr.Set.fold (fun pc p -> rewrite_function p candidates pc) functions p in  | 
 | 191 | +  if times () then Format.eprintf "  reference unboxing: %a@." Timer.print t;  | 
 | 192 | +  if stats ()  | 
 | 193 | +  then Format.eprintf "Stats - reference unboxing: %d@." (Var.Hashtbl.length candidates);  | 
 | 194 | +  p  | 
0 commit comments