2 * Copyright (c) 2017, Facebook, Inc.
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.
11 open Instruction_sequence
13 open Emit_memoize_helpers
16 (* Precomputed information required for generation of memoized methods *)
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
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
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
55 then "$" ^
String.lowercase_ascii
(Hhbc_id.Class.to_raw_string class_id
)
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
;
68 if info
.memoize_is_trait
71 instr_string
(Hhbc_id.Class.to_raw_string info
.memoize_class_id
);
75 let get_cls_method info param_count method_id
=
77 Hhbc_id.Method.add_suffix
method_id memoize_suffix
in
78 if info
.memoize_is_trait
80 instr_fpushclsmethodsd param_count
SpecialClsRef.Self
method_id
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 =
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
95 info
.memoize_instance_method_count
= 1
96 && not non_null_return
in
99 then guarded_shared_single_memoize_cache info
.memoize_class_prefix
100 else shared_single_memoize_cache info
.memoize_class_prefix
in
102 guarded_shared_single_memoize_cache_guard info
.memoize_class_prefix
in
105 optional
needs_guard [
111 instr_querym_cget_pt
0 ssmc;
113 instr_istypec
Hhbc_ast.OpNull
;
118 optional
needs_guard [
124 instr_querym_cget_pt
0 ssmcg;
134 instr_fpushobjmethodd_nullthrows
0 renamed_name;
138 instr_setm_pt
0 ssmc;
139 optional
needs_guard [
143 instr_fpushobjmethodd_nullthrows
0 renamed_name;
152 instr_setm_pt
0 ssmcg;
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
=
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
177 instr_setl
first_local;
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
195 Emit_body.emit_method_prolog ~
pos ~params ~should_emit_init_this
:false;
198 param_code_sets params
first_parameter_local;
200 instr_dim_warn_pt
(shared_multi_memoize_cache info
.memoize_class_prefix
);
201 instr_memoget
0 first_local local_count
;
210 instr_fpushobjmethodd_nullthrows
param_count renamed_name;
211 param_code_gets params
;
212 instr_fcall
param_count;
215 instr_dim_define_pt
(shared_multi_memoize_cache info
.memoize_class_prefix
);
216 instr_memoset
0 first_local local_count
;
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
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
238 optional
needs_guard [
247 instr_istypec
Hhbc_ast.OpNull
;
253 optional
needs_guard [
259 ^ guarded_single_memoize_cache_guard info
.memoize_class_prefix
);
272 get_cls_method info
0 method_id;
276 optional
needs_guard [
279 get_cls_method info
0 method_id;
288 ^ guarded_single_memoize_cache_guard info
.memoize_class_prefix
);
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
311 Emit_body.emit_method_prolog ~
pos ~params
:params ~should_emit_init_this
:false;
312 param_code_sets params
param_count;
315 ^ multi_memoize_cache info
.memoize_class_prefix
);
318 instr_memoget
1 first_local param_count;
328 ^ multi_memoize_cache info
.memoize_class_prefix
);
330 get_cls_method info
param_count method_id;
331 param_code_gets params
;
332 instr_fcall
param_count;
335 instr_memoset
1 first_local param_count;
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
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
=
355 true (* is_memoize_wrapper *)
358 [] (* static_inits *)
361 let emit ~
pos ~non_null_return env info index return_type_info params is_static
method_id =
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
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
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
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
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
404 if original_name
= Naming_special_names.Members.__construct
405 || original_name
= Naming_special_names.Members.__destruct
407 else ast_method
.Ast.m_ret
in
408 let method_id = Hhbc_id.Method.from_ast_name original_name
in
410 [Ast_scope.ScopeItem.Method ast_method
;
411 Ast_scope.ScopeItem.Class ast_class
] in
412 let namespace = ast_class
.Ast.c_namespace
in
414 emit_memoize_wrapper_body env info index ast_method
415 ~
scope ~
namespace ast_method
.Ast.m_params
ret in
424 false (*method_no_injection*)
425 false (*method_inout_wrapper*)
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
442 make_memoize_wrapper_method env info count ast_class ast_method
in
444 if Hhas_method.is_static hhas_method then count
else count
+1 in
445 newcount, hhas_method::acc
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
469 if needs_guard ast_method
473 true false false false false true
474 (guarded_shared_single_memoize_cache info
.memoize_class_prefix
)
475 null_init None
notype None
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]
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
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
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
502 Hhbc_id.Prop.from_ast_name
503 (String.lowercase_ascii
@@ original_method_name) in
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]
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
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
526 let emit_properties info ast_methods
=
527 let rec aux l has_instance_cache acc
=
530 List.concat
@@ (List.rev acc
)
531 | x
::xs
when is_memoize x
->
533 then aux xs has_instance_cache
((make_static_property info x
) :: acc
)
536 if has_instance_cache
538 else ((make_instance_property info x xs
) :: acc) in
540 | _::xs
-> aux xs has_instance_cache
acc in
541 aux ast_methods
false []