2 * Copyright (c) 2017, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
11 open Instruction_sequence
12 module SU
= Hhbc_string_utils
14 let has_type_constraint ti
=
16 | Some ti
when (Hhas_type_info.has_type_constraint ti
) -> true
19 let emit_method_prolog ~pos ~params ~should_emit_init_this
=
21 (if should_emit_init_this
22 then instr
(IMisc
(InitThisLoc
(Local.Named
"$this")))
25 List.filter_map params
(fun p
->
26 if Hhas_param.is_variadic p
28 let param_type_info = Hhas_param.type_info p
in
29 let param_name = Hhas_param.name p
in
30 if has_type_constraint param_type_info
31 then Some
(instr
(IMisc
(VerifyParamType
(Param_named
param_name))))
33 if List.is_empty
instr_list
35 else gather
(Emit_pos.emit_pos pos
:: instr_list)
38 let tparams_to_strings tparams
=
39 List.map tparams
(fun (_
, (_
, s
), _
, _
) -> s
)
41 let rec emit_def env def
=
43 | Ast.Stmt s
-> Emit_statement.emit_stmt env s
45 let cns_name = snd c
.Ast.cst_name
in
46 if c
.Ast.cst_kind
= Ast.Cst_define
&&
47 not
(Namespace_env.is_global_namespace c
.Ast.cst_namespace
)
49 (* replace 'define' in namespace with call to 'define' function *)
51 Emit_env.with_namespace c
.Ast.cst_namespace
env in
54 let p_name = fst c
.Ast.cst_name
in
56 p_name, Ast.String
(SU.strip_global_ns
cns_name);
58 p0, Ast.Expr
(p0, Ast.Call
((p0, Ast.Id
(p0, "define")), [], args, [])) in
59 Emit_statement.emit_stmt
env define_call
62 if c
.Ast.cst_kind
= Ast.Cst_define
64 (* names of constants declared using 'define function' are always
65 prefixed with '\\', see 'def' function in 'namespaces.ml' *)
66 Hhbc_id.Const.from_raw_string
(SU.strip_global_ns
cns_name)
67 else Hhbc_id.Const.from_ast_name
cns_name in
69 Emit_expression.emit_expr ~need_ref
:false env c
.Ast.cst_value
;
70 Emit_pos.emit_pos_then c
.Ast.cst_span
71 @@ instr
(IIncludeEvalDefine
(DefCns
cns_id));
74 (* We assume that SetNamespaceEnv does namespace setting *)
75 | Ast.Namespace
(_
, defs
) ->
80 and check_namespace_update
env ns
=
82 let prev_ns = Emit_env.get_namespace
env in
83 let check_map2 prev curr
=
84 (* Check whether any definition updates an earlier definition *)
85 SMap.iter
(fun key
value ->
86 match SMap.get key prev
with
87 | None
-> () (* new element, it is fine *)
89 if v
= value then () (* same element, it is fine *)
90 else Emit_fatal.raise_fatal_parse
p @@ Printf.sprintf
91 "Cannot use namespace %s as %s because the name is already in use"
92 (SU.strip_global_ns
value) key
95 check_map2 prev_ns.Namespace_env.ns_ns_uses ns
.Namespace_env.ns_ns_uses
;
96 check_map2 prev_ns.Namespace_env.ns_class_uses ns
.Namespace_env.ns_class_uses
;
97 check_map2 prev_ns.Namespace_env.ns_fun_uses ns
.Namespace_env.ns_fun_uses
;
98 check_map2 prev_ns.Namespace_env.ns_const_uses ns
.Namespace_env.ns_const_uses
100 and emit_defs
env defs
=
101 let rec aux env defs
=
103 | Ast.SetNamespaceEnv ns
:: defs
->
104 check_namespace_update
env ns
;
105 let env = Emit_env.with_namespace ns
env in
107 | [] -> Emit_statement.emit_dropthrough_return
env
108 (* emit last statement in the list as final statement *)
110 (* emit statement as final if it is one before the last and last statement is
111 empty markup (which will be no-op) *)
112 | [Ast.Stmt s
; Ast.Stmt
(_
, Ast.Markup
((_
, ""), None
))] ->
113 Emit_statement.emit_final_statement
env s
115 gather
[emit_def env d
; Emit_statement.emit_dropthrough_return
env]
117 let i1 = emit_def env d
in
118 let i2 = aux env defs
in
121 let rec emit_markup env defs
=
123 | Ast.Stmt
(_
, Ast.Markup
((_
, s
), echo_expr_opt
))::defs
->
125 Emit_statement.emit_markup env s echo_expr_opt ~check_for_hashbang
:true
127 let i2 = aux env defs
in
129 | [_
] | [] -> aux env defs
131 let i1 = emit_def env d
in
132 if is_empty
i1 then emit_markup env defs
133 else gather
[i1; aux env defs
]
137 let check_redefinition v1 v2 text
=
139 then Emit_fatal.raise_fatal_runtime
Pos.none
("Multiple " ^ text ^
" directives")
142 let check_num_iters n1 n2
= check_redefinition n1 n2
".numiters"
143 let check_num_cls_ref_slots n1 n2
= check_redefinition n1 n2
".numclsrefslots"
146 |> List.fold_left ~init
:Unique_list_string.empty ~f
:Unique_list_string.add
147 |> Unique_list_string.items
149 let make_body_ function_directives_opt body_instrs decl_vars
150 is_memoize_wrapper is_memoize_wrapper_lsb
151 params return_type_info static_inits doc_comment
153 let body_instrs = rewrite_user_labels
body_instrs in
154 let body_instrs = rewrite_class_refs
body_instrs in
155 let params, body_instrs =
156 if Hhbc_options.relabel
!Hhbc_options.compiler_options
157 then Label_rewriter.relabel_function
params body_instrs
158 else params, body_instrs in
160 if is_memoize_wrapper
then 0 else !Iterator.num_iterators
in
161 let num_cls_ref_slots = get_num_cls_ref_slots
body_instrs in
162 let num_iters, num_cls_ref_slots, is_memoize_wrapper
, static_inits
=
163 match function_directives_opt
with
164 | Some
(iters_hhas
, cls_ref_slots_hhas
, is_memoize_wrapper_hhas
, static_inits_hhas
) ->
165 check_num_iters num_iters iters_hhas
,
166 check_num_cls_ref_slots num_cls_ref_slots cls_ref_slots_hhas
,
167 is_memoize_wrapper
|| is_memoize_wrapper_hhas
,
168 deduplicate @@ Core_list.append static_inits static_inits_hhas
169 | None
-> num_iters, num_cls_ref_slots, is_memoize_wrapper
, static_inits
in
176 is_memoize_wrapper_lsb
183 let make_body body_instrs decl_vars is_memoize_wrapper is_memoize_wrapper_lsb
params
184 return_type_info static_inits doc_comment
env =
185 make_body_ None
body_instrs decl_vars is_memoize_wrapper is_memoize_wrapper_lsb
params
186 return_type_info static_inits doc_comment
env
188 let prepare_inline_hhas_blocks decl_vars
params hhas_blocks
=
189 assert (not
@@ List.is_empty hhas_blocks
);
193 ~f
:(fun acc v
-> SSet.add
(Hhas_param.name v
) acc
) in
194 let is_param v
= SSet.mem v
param_names in
196 let lexer = Lexing.from_string
(s ^
"\n") in
197 try s
, Hhas_parser.functionbodywithdirectives
Hhas_lexer.read
lexer
198 with Parsing.Parse_error
->
199 Emit_fatal.raise_fatal_parse
Pos.none
@@
200 "Error parsing inline hhas:\n" ^ s
in
201 let hhas_blocks = List.map
hhas_blocks ~f
:parse_hhas in
203 List.fold_left
hhas_blocks
205 ~f
:(fun acc
(k
, v
) -> SMap.add k v acc
) in
206 let combine (used_vars
, num_iters, num_cls_ref_slots, is_memoize
, static_init
) (_
, b
) =
207 let can_use v
= not
@@ is_param v
in
209 Instruction_sequence.collect_locals
can_use (Hhas_asm.instrs b
) in
210 Core_list.append used_vars
(Unique_list_string.items
locals),
211 check_num_iters num_iters (Hhas_asm.num_iters b
),
212 check_num_cls_ref_slots num_cls_ref_slots (Hhas_asm.num_cls_ref_slots b
),
213 is_memoize
|| (Hhas_asm.is_memoize_wrapper b
),
214 Core_list.append static_init
(Hhas_asm.static_inits b
) in
215 let decl_vars, num_iters, num_cls_ref_slots, is_memoize
, static_init
=
216 List.fold_left
hhas_blocks ~init
:(decl_vars, 0, 0, false, []) ~f
:combine in
217 deduplicate decl_vars,
219 Some
(num_iters, num_cls_ref_slots, is_memoize
, deduplicate static_init
)
221 let emit_return_type_info ~scope ~skipawaitable ~namespace ret
=
223 List.map
(Ast_scope.Scope.get_tparams scope
) (fun (_
, (_
, s
), _
, _
) -> s
) in
226 Hhas_type_info.make
(Some
"") (Hhas_type_constraint.make None
[])
228 Emit_type_hint.(hint_to_type_info
229 ~kind
:Return ~nullable
:false ~skipawaitable ~
tparams ~namespace h
)
231 let emit_deprecation_warning scope
= function
234 (* Drop the indexes *)
235 let args = List.filteri ~f
:(fun i _
-> i
mod 2 <> 0) args in
236 let strip_id id
= SU.strip_global_ns
(snd id
) in
237 let class_name, trait_instrs
, concat_instruction
=
238 match Ast_scope.Scope.get_class scope
with
239 | None
-> "", empty
, empty
240 | Some c
when c
.Ast.c_kind
= Ast.Ctrait
->
241 "::", gather
[instr_self
; instr_clsrefname
;], instr_concat
242 | Some c
-> strip_id c
.Ast.c_name ^
"::", empty
, empty
246 | Ast_scope.ScopeItem.Function fd
:: _
-> strip_id fd
.Ast.f_name
247 | Ast_scope.ScopeItem.Method md
:: _
-> strip_id md
.Ast.m_name
248 | _
-> failwith
"deprecated functions must have names"
250 let deprecation_string = class_name ^
fn_name ^
": " ^
252 | [] -> "deprecated function"
253 | Typed_value.String s
:: _
-> s
254 | _
-> failwith
"deprecated attribute first argument is not a string"
258 | [] | [_
] -> Int64.one
259 | _
:: Typed_value.Int i
:: _
-> i
260 | _
-> failwith
"deprecated attribute second argument is not an integer"
262 let error_code = if Emit_env.is_systemlib
() then
263 8192 (* E_DEPRECATED *) else 16384 (* E_USER_DEPRECATED *)
265 if Int64.to_int
sampling_rate <= 0 then empty
else
268 instr_string
deprecation_string;
270 instr_int64
sampling_rate;
271 instr_int
error_code;
272 instr_trigger_sampled_error
;
276 let rec is_awaitable h
=
278 | _
, A.Happly
((_
, "Awaitable"), ([] | [_
])) -> true
279 | _
, (A.Hsoft h
| A.Hoption h
) -> is_awaitable h
282 let is_mixed_or_dynamic t
=
283 String_utils.string_ends_with t
"HH\\mixed" ||
284 String_utils.string_ends_with t
"HH\\dynamic"
286 let emit_verify_out params =
287 let param_instrs = List.filter_mapi
params ~f
:(fun i
p ->
288 if not
@@ Hhas_param.is_inout
p then None
else
289 let b = match Hhas_param.type_info
p with
291 | Some
{ Hhas_type_info.type_info_user_type
= Some t
; _
} ->
292 not
@@ is_mixed_or_dynamic t
296 instr_cgetl
(Local.Named
(Hhas_param.name
p));
297 if b then instr_verifyOutType
(Param_unnamed i
) else empty
300 let param_instrs = List.rev
param_instrs in
301 let len = List.length
param_instrs in
302 if len = 0 then (0, empty
) else (len, gather
param_instrs)
317 ~doc_comment
params ret body
=
318 if is_return_by_ref
&& Hhbc_options.disable_return_by_reference
!Hhbc_options.compiler_options
320 Emit_fatal.raise_fatal_runtime pos
(
321 "Return by reference is disabled by the " ^
322 "compiler through the option hhvm.disable_return_by_reference " ^
323 "or Eval.DisableReturnByReference")
325 if is_async
&& skipawaitable
328 not
(Option.value_map ret ~default
:true ~f
:is_awaitable) in
333 then "Return type hint for async closure must be awaitable"
337 | Ast_scope.ScopeItem.Function fd
:: _
->
338 "function", Utils.strip_ns
(snd fd
.A.f_name
)
339 | Ast_scope.ScopeItem.Method md
:: Ast_scope.ScopeItem.Class cd
:: _
->
340 "method", Utils.strip_ns
(snd cd
.A.c_name
) ^
"::" ^
(snd md
.A.m_name
)
341 | _
-> assert false in
342 Printf.sprintf
"Return type hint for async %s %s() must be awaitable"
344 Emit_fatal.raise_fatal_runtime pos
message
348 List.map
(Ast_scope.Scope.get_tparams scope
) (fun (_
, (_
, s
), _
, _
) -> s
) in
349 Label.reset_label
();
350 Iterator.reset_iterator
();
351 let return_type_info =
352 emit_return_type_info ~scope ~skipawaitable ~namespace ret
in
353 let return_type_info = if not is_native
then return_type_info else
354 Emit_type_hint.emit_type_constraint_for_native_function
355 tparams ret
return_type_info
357 let is_generator, is_pair_generator
= Generator.is_function_generator body
in
359 return_type_info.Hhas_type_info.type_info_user_type
<> Some
"" &&
360 Hhas_type_info.has_type_constraint return_type_info && not
is_generator in
361 let default_dropthrough =
362 if default_dropthrough <> None
then default_dropthrough else begin
363 if is_async
&& verify_return
364 then Some
(gather
[instr_null
; instr_verifyRetTypeC
; instr_retc
])
370 ~namespace ~
tparams ~generate_defaults
:(not is_memoize
) ~scope
params
372 let params = if is_closure_body
373 then List.map ~f
:Hhas_param.switch_inout_to_reference
params else params in
374 let num_out, verify_out
= if is_closure_body
then 0, empty
else emit_verify_out params in
375 Emit_statement.set_verify_return
verify_return;
376 Emit_statement.set_verify_out verify_out
;
377 Emit_statement.set_num_out
num_out;
378 Emit_statement.set_default_dropthrough
default_dropthrough;
379 Emit_statement.set_default_return_value return_value
;
380 Emit_statement.set_return_by_ref is_return_by_ref
;
381 Emit_statement.set_function_pos pos
;
382 Jump_targets.reset
();
384 let remove_this vars
=
385 List.filter vars
(fun s
-> s
<> "$this") in
388 if List.mem vars
"$this"
389 then remove_this vars
@ ["$this"]
392 let starts_with s prefix
=
393 String.length s
>= String.length prefix
&&
394 String.sub s
0 (String.length prefix
) = prefix
in
396 let has_this = Ast_scope.Scope.has_this scope
in
397 let is_toplevel = Ast_scope.Scope.is_toplevel scope
in
398 (* see comment in decl_vars.ml, method on_efun of declvar_visitor
399 why Decl_vars needs 'explicit_use_set' *)
400 let explicit_use_set = Emit_env.get_explicit_use_set
() in
401 let is_in_static_method = Ast_scope.Scope.is_in_static_method scope
in
402 let needs_local_this, decl_vars =
416 | _
:: _
:: Ast_scope.ScopeItem.Class
ast_class :: _
-> ast_class
417 | _
-> failwith
"impossible, closure scope should be lambda->method->class" in
418 (* reorder decl_vars to match HHVM *)
421 |> List.concat_map ~f
:(fun item
->
423 | Ast.ClassVars
{ Ast.cv_names
= cvl
; _
} ->
424 List.filter_map cvl ~f
:(fun (_
, (_
, id
), _
) ->
425 if not
(starts_with id
"86static_")
426 then Some
("$" ^ id
) else None
)
430 List.filter
(move_this decl_vars) (fun v
-> not
(List.mem
captured_vars v
))
433 | _
:: Ast_scope.ScopeItem.Class _
:: _
-> move_this decl_vars
434 | _
when Ast_scope.Scope.is_toplevel scope
-> move_this decl_vars
437 let function_state_key =
438 let open Ast_scope
in
440 | [] -> Emit_env.get_unique_id_for_main
()
441 | ScopeItem.Method md
:: ScopeItem.Class cls
:: _
442 | ScopeItem.Lambda
:: ScopeItem.Method md
:: ScopeItem.Class cls
:: _
->
443 Emit_env.get_unique_id_for_method cls md
444 | ScopeItem.Function fd
:: _
->
445 Emit_env.get_unique_id_for_function fd
446 | _
-> failwith
@@ "unexpected scope shape:" ^
448 "'ScopeItem.Method md :: ScopeItem.Class cls' for method or " ^
449 "'ScopeItem.Function fd' for function or " ^
450 "empty scope for top level" in
452 let inline_hhas_blocks =
453 Emit_env.get_inline_hhas_blocks
function_state_key in
455 let decl_vars, inline_hhas_blocks, function_directives_opt
=
456 if List.is_empty
inline_hhas_blocks
457 then decl_vars, SMap.empty
, None
458 else prepare_inline_hhas_blocks decl_vars params inline_hhas_blocks in
459 Emit_expression.set_inline_hhas_blocks
inline_hhas_blocks;
461 let should_reserve_locals =
462 SSet.mem
function_state_key @@ Emit_env.get_functions_with_finally
() in
464 begin match SMap.get
function_state_key @@ Emit_env.get_function_to_labels_map
() with
466 Jump_targets.set_function_has_goto
true;
467 Jump_targets.set_labels_in_function s
;
469 Jump_targets.set_function_has_goto
false;
470 Jump_targets.set_labels_in_function
SMap.empty
;
473 Local.reset_local
(List.length
params + List.length
decl_vars);
474 if should_reserve_locals then Local.reserve_retval_and_label_id_locals
();
478 with_namespace namespace
|>
479 with_needs_local_this
needs_local_this |>
481 let stmt_instrs = if is_native
then instr_nativeimpl
else
482 Emit_env.do_function
env body emit_defs
in
483 let begin_label, default_value_setters
=
484 Emit_param.emit_param_default_value_setter ~is_native
env pos
params in
485 let generator_instr =
486 if is_generator then gather
[instr_createcont
; instr_popc
] else empty
488 let svar_map = Static_var.make_static_map body
in
489 let emit_expr env e
=
491 Emit_expression.emit_expr env ~need_ref
:false e
;
492 Emit_pos.emit_pos
(fst e
)
495 rewrite_static_instrseq
svar_map emit_expr env stmt_instrs
497 let first_instruction_is_label =
498 match Instruction_sequence.first
stmt_instrs with
499 | Some
(ILabel _
) -> true
502 let should_emit_init_this = not
is_in_static_method && (needs_local_this ||
503 (is_toplevel && List.exists ~f
:(fun x
-> x
= "$this") decl_vars))
506 (* per comment in emitter.cpp there should be no
507 * jumps to the base of the function - inject EntryNop
508 * if first instruction in the statement list is label
509 * and header content is empty *)
510 let header_content = gather
[
511 if is_native
then empty
else
512 emit_method_prolog ~pos ~
params ~
should_emit_init_this;
513 emit_deprecation_warning scope deprecation_info
;
516 if first_instruction_is_label &&
517 Instruction_sequence.is_empty
header_content
526 let svar_instrs = SMap.ordered_keys
svar_map in
527 let body_instrs = gather
[
530 default_value_setters
;
532 let fault_instrs = extract_fault_funclets
body_instrs in
533 let body_instrs = gather
[body_instrs; fault_instrs] in
535 function_directives_opt
538 false (*is_memoize_wrapper*)
539 false (*is_memoize_wrapper_lsb*)
541 (Some
return_type_info)