Global fallback for constants
[hiphop-php.git] / hphp / hack / src / typing / typing_redecl_service.ml
blob1b042eefef50dee97fbbdcaaf3d39f74e9a44c2a
1 (**
2 * Copyright (c) 2014, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the "hack" directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
9 *)
13 (*****************************************************************************)
14 (* On the fly type-declaration are called when the user modified a file
15 * we are not at initilalization time anymore. Therefore, we have a bit more
16 * work to do. We need calculate what must be re-checked.
18 (*****************************************************************************)
19 open Utils
21 (*****************************************************************************)
22 (* The neutral element of declaration (cf procs/multiWorker.mli) *)
23 (*****************************************************************************)
24 let otf_neutral = [], SSet.empty
25 let compute_deps_neutral = ISet.empty, ISet.empty
27 (*****************************************************************************)
28 (* This is the place where we are going to put everything necessary for
29 * the redeclaration. We could "pass" the values directly to the workers,
30 * but it gives too much work to the master and slows things downn.
31 * So what we do instead is:
32 * Set the value of "data_storage", which means that one of the data nodes
33 * has this value.
34 * Let each worker pool the data from "data_storage".
35 * I tried replicating the data to speed things up but it had no effect.
37 (*****************************************************************************)
39 type fast = FileInfo.names SMap.t
40 type classes = SSet.t SMap.t
42 module OnTheFlyStore = GlobalStorage.Make(struct
43 type t = Naming.env * classes * fast
44 end)
46 (*****************************************************************************)
47 (* Re-declaring the types in a file *)
48 (*****************************************************************************)
50 let on_the_fly_decl_file nenv all_classes fast (errors, failed) fn =
51 try
52 (* We start "recording" dependencies.
53 * Whenever we are type-checking or declaring types, the checker
54 * records all the dependencies in a global (cf Typing_deps).
55 * At any given time, all the workers must be aware of all the
56 * dependencies (cf Typing_deps.udpate_dependencies).
57 * The problem is, sending the entire graph every time is too
58 * expensive. So what we do instead, is that we "record" the changes.
59 * If a new dependency shows up, it will end-up in Typing_deps.record_acc.
60 * Sending only the difference is a drastic improvement in perf.
62 Typing_decl.make_env nenv all_classes fn;
63 errors, failed
64 with Utils.Error l ->
65 (* It is important to add the file that is the cause of the failure.
66 * What can happen is that during a declaration phase, we realize
67 * that a parent class is outdated. When this happens, we redeclare
68 * the class, even if it is in a different file. Therefore, the file
69 * where the error occurs might be different from the file we
70 * are declaring right now.
72 let file_with_error = Pos.filename (fst (List.hd l)) in
73 assert (file_with_error <> "");
74 let failed = SSet.add file_with_error failed in
75 let failed = SSet.add fn failed in
76 l :: errors, failed
77 | e -> failwith ("Things went horribly wrong: "^Printexc.to_string e)
79 (*****************************************************************************)
80 (* Given a set of classes, compare the old and the new type and deduce
81 * what must be rechecked accordingly.
83 (*****************************************************************************)
85 let compute_classes_deps old_classes new_classes acc classes =
86 let to_redecl, to_recheck = acc in
87 let trace = ref ISet.empty in
88 let rdd, rdc =
89 Typing_compare.get_classes_deps old_classes new_classes trace classes
91 let to_redecl = ISet.union rdd to_redecl in
92 let to_recheck = ISet.union rdc to_recheck in
93 to_redecl, to_recheck
95 (*****************************************************************************)
96 (* Given a set of functions, compare the old and the new type and deduce
97 * what must be rechecked accordingly.
99 (*****************************************************************************)
101 let compute_funs_deps old_funs (to_redecl, to_recheck) funs =
102 let rdd, rdc = Typing_compare.get_funs_deps old_funs funs in
103 let to_redecl = ISet.union rdd to_redecl in
104 let to_recheck = ISet.union rdc to_recheck in
105 to_redecl, to_recheck
107 (*****************************************************************************)
108 (* Given a set of typedefs, compare the old and the new type and deduce
109 * what must be rechecked accordingly.
111 (*****************************************************************************)
113 let compute_types_deps old_types (to_redecl, to_recheck) types =
114 let rdc = Typing_compare.get_types_deps old_types types in
115 let to_redecl = ISet.union rdc to_redecl in
116 let to_recheck = ISet.union rdc to_recheck in
117 to_redecl, to_recheck
119 (*****************************************************************************)
120 (* Given a set of global constants, compare the old and the new type and
121 * deduce what must be rechecked accordingly.
123 (*****************************************************************************)
125 let compute_gconsts_deps old_gconsts (to_redecl, to_recheck) gconsts =
126 let rdd, rdc = Typing_compare.get_gconsts_deps old_gconsts gconsts in
127 let to_redecl = ISet.union rdd to_redecl in
128 let to_recheck = ISet.union rdc to_recheck in
129 to_redecl, to_recheck
131 (*****************************************************************************)
132 (* Sometimes, we know that the API of class hasn't changed. The only thing
133 * that changed are the positions (the user added a new line somewhere).
134 * When that happens, it is too expensive to go off and redeclare everything
135 * that inherits from this class.
136 * Some classes have thousands of sub-classes, working on such of file,
137 * without this function, would be nightmarish. Every new line, would trigger
138 * a huge re-declaration phase.
139 * The solution is simple. If we know the API hasn't changed (only the
140 * positions). We create a substitution (from Pos to Pos), replacing the
141 * old_positions with the new one. We then apply this substitution to all the
142 * classes that needed to be redeclared directly on the name nodes.
144 (*****************************************************************************)
147 let update_positions classes to_update =
148 (* First compute the substitution *)
149 let position_subst, is_empty =
150 let old_classes = Typing_env.Classes.get_old_batch classes in
151 let new_classes = Typing_env.Classes.find_batch classes in
152 Typing_compare.get_classes_psubst old_classes new_classes classes
154 if is_empty
155 then ()
156 else begin
157 (* Now apply the sustitution in parallel on all the data nodes *)
158 Typing_env.Classes.apply_batch to_update begin fun class_ ->
159 Typing_compare.SubstPos.class_type position_subst class_
160 end;
161 end;
165 (*****************************************************************************)
166 (* Redeclares a list of files
167 * And then computes the files that must be redeclared/rechecked by looking
168 * at what changed in the signatures of the classes/functions.
170 (*****************************************************************************)
172 let redeclare_files nenv all_classes fast filel =
173 List.fold_left
174 (on_the_fly_decl_file nenv all_classes fast)
175 ([], SSet.empty)
176 filel
178 let otf_decl_files nenv all_classes fast filel =
179 SharedMem.invalidate_caches();
180 (* Redeclaring the files *)
181 let errors, failed = redeclare_files nenv all_classes fast filel in
182 errors, failed
184 let compute_deps ~update_pos nenv fast filel =
185 let infol = List.map (fun fn -> SMap.find_unsafe fn fast) filel in
186 let names = List.fold_left FileInfo.merge_names FileInfo.empty_names infol in
187 let { FileInfo.n_classes; n_funs; n_types; n_consts } = names in
188 let acc = ISet.empty, ISet.empty in
189 (* Fetching everything at once is faster *)
190 let old_funs = Typing_env.Funs.get_old_batch n_funs in
191 let acc = compute_funs_deps old_funs acc n_funs in
193 let old_types = Typing_env.Typedefs.get_old_batch n_types in
194 let acc = compute_types_deps old_types acc n_types in
196 let old_consts = Typing_env.GConsts.get_old_batch n_consts in
197 let acc = compute_gconsts_deps old_consts acc n_consts in
199 let old_classes = Typing_env.Classes.get_old_batch n_classes in
200 let new_classes = Typing_env.Classes.get_batch n_classes in
201 let compare_classes = compute_classes_deps old_classes new_classes in
202 let (to_redecl, to_recheck) = compare_classes acc n_classes in
203 (* TODO: DEACTIVATING THE CODE FOR NOW BECAUSE OF A BUG
204 if update_pos
205 then update_positions classes (SSet.diff to_redecl to_recheck);
206 let to_redecl = SSet.inter to_redecl to_recheck in
208 to_redecl, to_recheck
210 (*****************************************************************************)
211 (* Load the environment and then redeclare *)
212 (*****************************************************************************)
214 let load_and_otf_decl_files acc filel =
216 let nenv, all_classes, fast = OnTheFlyStore.load() in
217 otf_decl_files nenv all_classes fast filel
218 with e ->
219 Printf.printf "Error: %s\n" (Printexc.to_string e);
220 flush stdout;
221 raise e
223 let load_and_compute_deps ~update_pos acc filel =
225 let nenv, _, fast = OnTheFlyStore.load() in
226 compute_deps ~update_pos nenv fast filel
227 with e ->
228 Printf.printf "Error: %s\n" (Printexc.to_string e);
229 flush stdout;
230 raise e
232 (*****************************************************************************)
233 (* Merges the results comming back from the different workers *)
234 (*****************************************************************************)
236 let merge_on_the_fly (errorl1, failed1) (errorl2, failed2) =
237 errorl1 @ errorl2, SSet.union failed1 failed2
239 let merge_compute_deps (to_redecl1, to_recheck1) (to_redecl2, to_recheck2) =
240 ISet.union to_redecl1 to_redecl2, ISet.union to_recheck1 to_recheck2
242 (*****************************************************************************)
243 (* The parallel worker *)
244 (*****************************************************************************)
246 let parallel_otf_decl ~update_pos workers nenv all_classes fast fnl =
247 OnTheFlyStore.store (nenv, all_classes, fast);
248 let errors, failed =
249 MultiWorker.call
250 workers
251 ~job:load_and_otf_decl_files
252 ~neutral:otf_neutral
253 ~merge:merge_on_the_fly
254 ~next:(Bucket.make fnl)
256 let to_redecl, to_recheck =
257 MultiWorker.call
258 workers
259 ~job:(load_and_compute_deps ~update_pos)
260 ~neutral:compute_deps_neutral
261 ~merge:merge_compute_deps
262 ~next:(Bucket.make fnl)
264 OnTheFlyStore.clear();
265 errors, failed, to_redecl, to_recheck
267 (*****************************************************************************)
268 (* Code invalidating the heap *)
269 (*****************************************************************************)
271 let invalidate_heap { FileInfo.n_funs; n_classes; n_types; n_consts } =
272 Typing_env.Funs.oldify_batch n_funs;
273 Typing_env.Classes.oldify_batch n_classes;
274 Typing_env.Typedefs.oldify_batch n_types;
275 Typing_env.GConsts.oldify_batch n_consts;
276 Naming_heap.FunHeap.remove_batch n_funs;
277 Naming_heap.ClassStatus.remove_batch n_classes;
278 Naming_heap.ClassHeap.remove_batch n_classes;
279 Naming_heap.TypedefStatus.remove_batch n_types;
280 Naming_heap.TypedefHeap.remove_batch n_types;
281 Naming_heap.ConstHeap.remove_batch n_consts;
282 SharedMem.collect();
285 let remove_old_defs { FileInfo.n_funs; n_classes; n_types; n_consts } =
286 Typing_env.Funs.remove_old_batch n_funs;
287 Typing_env.Classes.remove_old_batch n_classes;
288 Typing_env.Typedefs.remove_old_batch n_types;
289 Typing_env.GConsts.remove_old_batch n_consts;
290 SharedMem.collect();
293 let get_defs fast =
294 SMap.fold begin fun _ names1 names2 ->
295 FileInfo.merge_names names1 names2
296 end fast FileInfo.empty_names
298 (*****************************************************************************)
299 (* The main entry points *)
300 (*****************************************************************************)
302 let init workers fast =
303 let fnl = SMap.fold (fun x _ y -> x :: y) fast [] in
304 let all_classes = Typing_decl_service.get_classes fast in
305 all_classes, fnl
307 let redo_type_decl ~update_pos workers nenv fast =
308 let all_classes, fnl = init workers fast in
309 let defs = get_defs fast in
310 invalidate_heap defs;
311 (* If there aren't enough files, let's do this ourselves ... it's faster! *)
312 let result =
313 if List.length fnl < 10
314 then
315 let errors, failed = otf_decl_files nenv all_classes fast fnl in
316 let to_redecl, to_recheck = compute_deps ~update_pos nenv fast fnl in
317 errors, failed, to_redecl, to_recheck
318 else parallel_otf_decl ~update_pos workers nenv all_classes fast fnl
320 remove_old_defs defs;
321 result