Add codegen for inout arguments
[hiphop-php.git] / hphp / hack / src / hhbc / emit_memoize_method.ml
blob9cad587ff370b0ad2bdb0c9334620bedba6a6f5a
1 (**
2 * Copyright (c) 2017, 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 *)
11 open Instruction_sequence
12 open Hh_core
13 open Emit_memoize_helpers
14 open Hhbc_ast
16 (* Precomputed information required for generation of memoized methods *)
17 type memoize_info = {
18 (* Prefix on method and property names *)
19 memoize_class_prefix : string;
21 (* Number of instance methods with <<__Memoize>> *)
22 memoize_instance_method_count : int;
24 (* True if the enclosing class is a trait *)
25 memoize_is_trait : bool;
27 (* Enclosing class ID *)
28 memoize_class_id : Hhbc_id.Class.t;
31 let is_memoize ast_method =
32 Emit_attribute.ast_any_is_memoize ast_method.Ast.m_user_attributes
34 let make_info ast_class class_id ast_methods =
35 let check_method ast_method =
36 if is_memoize ast_method
37 then
38 let pos = fst ast_method.Ast.m_name in
39 Emit_memoize_helpers.check_memoize_possible pos
40 ~ret_by_ref: ast_method.Ast.m_ret_by_ref
41 ~params: ast_method.Ast.m_params
42 ~is_method:true;
43 if ast_class.Ast.c_kind = Ast.Cinterface
44 then Emit_fatal.raise_fatal_runtime pos
45 "<<__Memoize>> cannot be used in interfaces"
46 else if List.mem ast_method.Ast.m_kind Ast.Abstract
47 then Emit_fatal.raise_fatal_parse pos
48 ("Abstract method " ^ Hhbc_id.Class.to_raw_string class_id ^ "::" ^
49 snd ast_method.Ast.m_name ^ " cannot be memoized")
51 List.iter ast_methods check_method;
52 let is_trait = ast_class.Ast.c_kind = Ast.Ctrait in
53 let class_prefix =
54 if is_trait
55 then "$" ^ String.lowercase_ascii (Hhbc_id.Class.to_raw_string class_id)
56 else "" in
57 let instance_count =
58 List.count ast_methods (fun ast_method -> is_memoize ast_method &&
59 not (List.mem ast_method.Ast.m_kind Ast.Static)) in
61 memoize_is_trait = is_trait;
62 memoize_class_prefix = class_prefix;
63 memoize_instance_method_count = instance_count;
64 memoize_class_id = class_id;
67 let get_self info =
68 if info.memoize_is_trait
69 then instr_self
70 else gather [
71 instr_string (Hhbc_id.Class.to_raw_string info.memoize_class_id);
72 instr_clsrefgetc;
75 let get_cls_method info param_count method_id =
76 let method_id =
77 Hhbc_id.Method.add_suffix method_id memoize_suffix in
78 if info.memoize_is_trait
79 then
80 instr_fpushclsmethodsd param_count SpecialClsRef.Self method_id
81 else
82 instr_fpushclsmethodd param_count method_id info.memoize_class_id
84 let make_memoize_instance_method_no_params_code
85 ~non_null_return info method_id =
86 let renamed_name =
87 Hhbc_id.Method.add_suffix method_id memoize_suffix in
88 let label_0 = Label.Regular 0 in
89 let label_1 = Label.Regular 1 in
90 let label_2 = Label.Regular 2 in
91 let label_3 = Label.Regular 3 in
92 let label_4 = Label.Regular 4 in
93 let label_5 = Label.Regular 5 in
94 let needs_guard =
95 info.memoize_instance_method_count = 1
96 && not non_null_return in
97 let ssmc =
98 if needs_guard
99 then guarded_shared_single_memoize_cache info.memoize_class_prefix
100 else shared_single_memoize_cache info.memoize_class_prefix in
101 let ssmcg =
102 guarded_shared_single_memoize_cache_guard info.memoize_class_prefix in
103 gather [
104 instr_checkthis;
105 optional needs_guard [
106 instr_null;
107 instr_ismemotype;
108 instr_jmpnz label_0;
110 instr_baseh;
111 instr_querym_cget_pt 0 ssmc;
112 instr_dup;
113 instr_istypec Hhbc_ast.OpNull;
114 instr_jmpnz label_1;
115 instr_retc;
116 instr_label label_1;
117 instr_popc;
118 optional needs_guard [
119 instr_label label_0;
120 instr_null;
121 instr_maybememotype;
122 instr_jmpz label_2;
123 instr_baseh;
124 instr_querym_cget_pt 0 ssmcg;
125 instr_jmpz label_2;
126 instr_null;
127 instr_retc;
128 instr_label label_2;
129 instr_null;
130 instr_ismemotype;
131 instr_jmpnz label_3;
133 instr_this;
134 instr_fpushobjmethodd_nullthrows 0 renamed_name;
135 instr_fcall 0;
136 instr_unboxr;
137 instr_baseh;
138 instr_setm_pt 0 ssmc;
139 optional needs_guard [
140 instr_jmp label_4;
141 instr_label label_3;
142 instr_this;
143 instr_fpushobjmethodd_nullthrows 0 renamed_name;
144 instr_fcall 0;
145 instr_unboxr;
146 instr_label label_4;
147 instr_null;
148 instr_maybememotype;
149 instr_jmpz label_5;
150 instr_true;
151 instr_baseh;
152 instr_setm_pt 0 ssmcg;
153 instr_popc;
154 instr_label label_5;
156 instr_retc ]
158 (* md is the already-renamed memoize method that must be wrapped *)
159 let make_memoize_instance_method_with_params_code ~pos
160 env info method_id params index =
161 let renamed_name =
162 Hhbc_id.Method.add_suffix method_id memoize_suffix in
163 let param_count = List.length params in
164 let label = Label.Regular 0 in
165 let first_local = Local.Unnamed param_count in
166 (* All memoized methods in the same class share a cache. We distinguish the
167 methods from each other by adding a unique integer indexing the method itself
168 to the set of indices for the cache.
169 The total number of unnamed locals is one for the optional index, and
170 one for each formal parameter.
172 let index_block, local_count =
173 if info.memoize_instance_method_count > 1
174 then
175 gather [
176 instr_int index;
177 instr_setl first_local;
178 instr_popc ],
179 param_count + 1
180 else
181 empty,
182 param_count
184 let begin_label, default_value_setters =
185 (* Default value setters belong in the
186 * wrapper method not in the original method *)
187 Emit_param.emit_param_default_value_setter env params
189 (* The index of the first local that represents a formal is the number of
190 parameters, plus one for the optional index. This is equal to the count
191 of locals, so we'll just use that. *)
192 let first_parameter_local = local_count in
193 gather [
194 begin_label;
195 Emit_body.emit_method_prolog ~pos ~params ~should_emit_init_this:false;
196 instr_checkthis;
197 index_block;
198 param_code_sets params first_parameter_local;
199 instr_baseh;
200 instr_dim_warn_pt (shared_multi_memoize_cache info.memoize_class_prefix);
201 instr_memoget 0 first_local local_count;
202 instr_isuninit;
203 instr_jmpnz label;
204 instr_cgetcunop;
205 instr_retc;
206 instr_label label;
207 instr_ugetcunop;
208 instr_popu;
209 instr_this;
210 instr_fpushobjmethodd_nullthrows param_count renamed_name;
211 param_code_gets params;
212 instr_fcall param_count;
213 instr_unboxr;
214 instr_baseh;
215 instr_dim_define_pt (shared_multi_memoize_cache info.memoize_class_prefix);
216 instr_memoset 0 first_local local_count;
217 instr_retc;
218 default_value_setters ]
220 let make_memoize_static_method_no_params_code ~non_null_return info method_id =
221 let label_0 = Label.Regular 0 in
222 let label_1 = Label.Regular 1 in
223 let label_2 = Label.Regular 2 in
224 let label_3 = Label.Regular 3 in
225 let label_4 = Label.Regular 4 in
226 let label_5 = Label.Regular 5 in
227 let original_name_lc = String.lowercase_ascii
228 (Hhbc_id.Method.to_raw_string method_id) in
229 let needs_guard = not non_null_return in
230 let smc =
231 if needs_guard
232 then original_name_lc
233 ^ guarded_single_memoize_cache info.memoize_class_prefix
234 else original_name_lc
235 ^ single_memoize_cache info.memoize_class_prefix
237 gather [
238 optional needs_guard [
239 instr_null;
240 instr_ismemotype;
241 instr_jmpnz label_0;
243 instr_string smc;
244 get_self info;
245 instr_cgets;
246 instr_dup;
247 instr_istypec Hhbc_ast.OpNull;
248 instr_jmpnz label_1;
249 instr_retc;
250 instr_label label_1;
251 instr_popc;
252 instr_label label_0;
253 optional needs_guard [
254 instr_null;
255 instr_maybememotype;
256 instr_jmpz label_2;
257 instr_string
258 (original_name_lc
259 ^ guarded_single_memoize_cache_guard info.memoize_class_prefix);
260 get_self info;
261 instr_cgets;
262 instr_jmpz label_2;
263 instr_null;
264 instr_retc;
265 instr_label label_2;
266 instr_null;
267 instr_ismemotype;
268 instr_jmpnz label_3;
270 instr_string smc;
271 get_self info;
272 get_cls_method info 0 method_id;
273 instr_fcall 0;
274 instr_unboxr;
275 instr_sets;
276 optional needs_guard [
277 instr_jmp label_4;
278 instr_label label_3;
279 get_cls_method info 0 method_id;
280 instr_fcall 0;
281 instr_unboxr;
282 instr_label label_4;
283 instr_null;
284 instr_maybememotype;
285 instr_jmpz label_5;
286 instr_string
287 (original_name_lc
288 ^ guarded_single_memoize_cache_guard info.memoize_class_prefix);
289 get_self info;
290 instr_true;
291 instr_sets;
292 instr_popc;
293 instr_label label_5;
295 instr_retc ]
297 let make_memoize_static_method_with_params_code ~pos
298 env info method_id params =
299 let param_count = List.length params in
300 let label = Label.Regular 0 in
301 let first_local = Local.Unnamed param_count in
302 let original_name_lc = String.lowercase_ascii
303 (Hhbc_id.Method.to_raw_string method_id) in
304 let begin_label, default_value_setters =
305 (* Default value setters belong in the
306 * wrapper method not in the original method *)
307 Emit_param.emit_param_default_value_setter env params
309 gather [
310 begin_label;
311 Emit_body.emit_method_prolog ~pos ~params:params ~should_emit_init_this:false;
312 param_code_sets params param_count;
313 instr_string
314 (original_name_lc
315 ^ multi_memoize_cache info.memoize_class_prefix);
316 get_self info;
317 instr_basesc 0;
318 instr_memoget 1 first_local param_count;
319 instr_isuninit;
320 instr_jmpnz label;
321 instr_cgetcunop;
322 instr_retc;
323 instr_label label;
324 instr_ugetcunop;
325 instr_popu;
326 instr_string
327 (original_name_lc
328 ^ multi_memoize_cache info.memoize_class_prefix);
329 get_self info;
330 get_cls_method info param_count method_id;
331 param_code_gets params;
332 instr_fcall param_count;
333 instr_unboxr;
334 instr_basesc 1;
335 instr_memoset 1 first_local param_count;
336 instr_retc;
337 default_value_setters ]
339 let make_memoize_static_method_code ~pos ~non_null_return env info method_id params =
340 if List.is_empty params then
341 make_memoize_static_method_no_params_code ~non_null_return info method_id
342 else
343 make_memoize_static_method_with_params_code ~pos env info method_id params
345 let make_memoize_instance_method_code ~pos ~non_null_return env info index method_id params =
346 if List.is_empty params && info.memoize_instance_method_count <= 1
347 then make_memoize_instance_method_no_params_code ~non_null_return info method_id
348 else make_memoize_instance_method_with_params_code ~pos env info method_id params index
350 (* Construct the wrapper function *)
351 let make_wrapper return_type params instrs =
352 Emit_body.make_body
353 instrs
354 [] (* decl_vars *)
355 true (* is_memoize_wrapper *)
356 params
357 (Some return_type)
358 [] (* static_inits *)
359 None (* doc *)
361 let emit ~pos ~non_null_return env info index return_type_info params is_static method_id =
362 let instrs =
363 if is_static
364 then make_memoize_static_method_code ~pos ~non_null_return env info method_id params
365 else make_memoize_instance_method_code ~pos ~non_null_return env info index method_id params
367 make_wrapper return_type_info params instrs
369 let emit_memoize_wrapper_body env memoize_info index ast_method
370 ~scope ~namespace params ret =
371 let is_static =List.mem ast_method.Ast.m_kind Ast.Static in
372 let tparams =
373 Hh_core.List.map (Ast_scope.Scope.get_tparams scope) (fun (_, (_, s), _) -> s) in
374 let return_type_info =
375 Emit_body.emit_return_type_info ~scope ~skipawaitable:false ~namespace ret in
376 let non_null_return = cannot_return_null ast_method.Ast.m_fun_kind ast_method.Ast.m_ret in
377 let params =
378 Emit_param.from_asts ~namespace ~tparams:tparams ~generate_defaults:true ~scope params in
379 let pos = ast_method.Ast.m_span in
380 let (_,original_name) = ast_method.Ast.m_name in
381 let method_id =
382 Hhbc_id.Method.from_ast_name original_name in
383 (*let method_id = Hhbc_id.Method.add_suffix method_id Generate_memoized.memoize_suffix in*)
384 emit ~pos ~non_null_return env memoize_info index return_type_info params is_static method_id
386 let make_memoize_wrapper_method env info index ast_class ast_method =
387 (* This is cut-and-paste from emit_method above, with special casing for
388 * wrappers *)
389 let method_is_abstract =
390 List.mem ast_method.Ast.m_kind Ast.Abstract in
391 let method_is_final = List.mem ast_method.Ast.m_kind Ast.Final in
392 let method_is_static = List.mem ast_method.Ast.m_kind Ast.Static in
393 let method_attributes =
394 Emit_attribute.from_asts (Emit_env.get_namespace env) ast_method.Ast.m_user_attributes in
395 let method_is_private =
396 List.mem ast_method.Ast.m_kind Ast.Private in
397 let method_is_protected =
398 List.mem ast_method.Ast.m_kind Ast.Protected in
399 let method_is_public =
400 List.mem ast_method.Ast.m_kind Ast.Public ||
401 (not method_is_private && not method_is_protected) in
402 let (_, original_name) = ast_method.Ast.m_name in
403 let ret =
404 if original_name = Naming_special_names.Members.__construct
405 || original_name = Naming_special_names.Members.__destruct
406 then None
407 else ast_method.Ast.m_ret in
408 let method_id = Hhbc_id.Method.from_ast_name original_name in
409 let scope =
410 [Ast_scope.ScopeItem.Method ast_method;
411 Ast_scope.ScopeItem.Class ast_class] in
412 let namespace = ast_class.Ast.c_namespace in
413 let method_body =
414 emit_memoize_wrapper_body env info index ast_method
415 ~scope ~namespace ast_method.Ast.m_params ret in
416 Hhas_method.make
417 method_attributes
418 method_is_protected
419 method_is_public
420 method_is_private
421 method_is_static
422 method_is_final
423 method_is_abstract
424 false (*method_no_injection*)
425 false (*method_inout_wrapper*)
426 method_id
427 method_body
428 (Hhas_pos.pos_to_span ast_method.Ast.m_span)
429 false (*method_is_async*)
430 false (*method_is_generator*)
431 false (*method_is_pair_generator*)
432 false (*method_is_closure_body*)
434 let emit_wrapper_methods env info ast_class ast_methods =
435 (* Wrapper methods may not have iterators *)
436 Iterator.reset_iterator ();
437 let _, hhas_methods =
438 List.fold_left ast_methods ~init:(0, []) ~f:(fun (count,acc) ast_method ->
439 if Emit_attribute.ast_any_is_memoize ast_method.Ast.m_user_attributes
440 then
441 let hhas_method =
442 make_memoize_wrapper_method env info count ast_class ast_method in
443 let newcount =
444 if Hhas_method.is_static hhas_method then count else count+1 in
445 newcount, hhas_method::acc
446 else count, acc) in
447 hhas_methods
449 let empty_dict_init = Some (Typed_value.Dict [])
450 let false_init = Some (Typed_value.Bool false)
451 let null_init = Some (Typed_value.Null)
453 let notype = Hhas_type_info.make (Some "") (Hhas_type_constraint.make None [])
455 let is_memoize ast_method =
456 Emit_attribute.ast_any_is_memoize ast_method.Ast.m_user_attributes
458 let is_static ast_method =
459 List.mem ast_method.Ast.m_kind Ast.Static
461 let needs_guard ast_method =
462 not (cannot_return_null ast_method.Ast.m_fun_kind ast_method.Ast.m_ret)
464 let make_instance_property info ast_method rest =
465 let is_single_instance_method =
466 not (List.exists rest ~f:(fun m -> is_memoize m && not (is_static m))) in
467 if is_single_instance_method && List.is_empty ast_method.Ast.m_params
468 then
469 if needs_guard ast_method
470 then
471 let property2 =
472 Hhas_property.make
473 true false false false false true
474 (guarded_shared_single_memoize_cache info.memoize_class_prefix)
475 null_init None notype None
477 let property1 =
478 Hhas_property.make true false false false false true
479 (guarded_shared_single_memoize_cache_guard info.memoize_class_prefix)
480 false_init None notype None
482 [property1; property2]
483 else
484 let property =
485 Hhas_property.make true false false false false true
486 (shared_single_memoize_cache info.memoize_class_prefix)
487 null_init None notype None
489 [property]
490 else
491 let property =
492 Hhas_property.make true false false false false true
493 (shared_multi_memoize_cache info.memoize_class_prefix)
494 empty_dict_init None notype None
496 [property]
498 let make_static_property info ast_method =
499 let no_params = List.is_empty ast_method.Ast.m_params in
500 let original_method_name = snd ast_method.Ast.m_name in
501 let prop_name =
502 Hhbc_id.Prop.from_ast_name
503 (String.lowercase_ascii @@ original_method_name) in
504 if no_params then
505 if needs_guard ast_method then
506 let property2 = Hhas_property.make true false false true false true
507 (Hhbc_id.Prop.add_suffix
508 prop_name (guarded_single_memoize_cache info.memoize_class_prefix))
509 null_init None notype None in
510 let property1 = Hhas_property.make true false false true false true
511 (Hhbc_id.Prop.add_suffix
512 prop_name (guarded_single_memoize_cache_guard info.memoize_class_prefix))
513 false_init None notype None in
514 [property1; property2]
515 else
516 let property = Hhas_property.make true false false true false true
517 (Hhbc_id.Prop.add_suffix prop_name (single_memoize_cache info.memoize_class_prefix))
518 null_init None notype None in
519 [property]
520 else
521 let property = Hhas_property.make true false false true false true
522 (Hhbc_id.Prop.add_suffix prop_name (multi_memoize_cache info.memoize_class_prefix))
523 empty_dict_init None notype None in
524 [property]
526 let emit_properties info ast_methods =
527 let rec aux l has_instance_cache acc =
528 match l with
529 | [] ->
530 List.concat @@ (List.rev acc)
531 | x::xs when is_memoize x ->
532 if is_static x
533 then aux xs has_instance_cache ((make_static_property info x) :: acc)
534 else
535 let acc =
536 if has_instance_cache
537 then acc
538 else ((make_instance_property info x xs) :: acc) in
539 aux xs true acc
540 | _::xs -> aux xs has_instance_cache acc in
541 aux ast_methods false []