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 is_memoize_wrapper
150 params return_type_info static_inits doc_comment
152 let body_instrs = rewrite_user_labels
body_instrs in
153 let body_instrs = rewrite_class_refs
body_instrs in
154 let params, body_instrs =
155 if Hhbc_options.relabel
!Hhbc_options.compiler_options
156 then Label_rewriter.relabel_function
params body_instrs
157 else params, body_instrs in
159 if is_memoize_wrapper
then 0 else !Iterator.num_iterators
in
160 let num_cls_ref_slots = get_num_cls_ref_slots
body_instrs in
161 let num_iters, num_cls_ref_slots, is_memoize_wrapper
, static_inits
=
162 match function_directives_opt
with
163 | Some
(iters_hhas
, cls_ref_slots_hhas
, is_memoize_wrapper_hhas
, static_inits_hhas
) ->
164 check_num_iters num_iters iters_hhas
,
165 check_num_cls_ref_slots num_cls_ref_slots cls_ref_slots_hhas
,
166 is_memoize_wrapper
|| is_memoize_wrapper_hhas
,
167 deduplicate @@ Core_list.append static_inits static_inits_hhas
168 | None
-> num_iters, num_cls_ref_slots, is_memoize_wrapper
, static_inits
in
181 let make_body body_instrs decl_vars is_memoize_wrapper
params
182 return_type_info static_inits doc_comment
env =
183 make_body_ None
body_instrs decl_vars is_memoize_wrapper
params
184 return_type_info static_inits doc_comment
env
186 let prepare_inline_hhas_blocks decl_vars
params hhas_blocks
=
187 assert (not
@@ List.is_empty hhas_blocks
);
191 ~f
:(fun acc v
-> SSet.add
(Hhas_param.name v
) acc
) in
192 let is_param v
= SSet.mem v
param_names in
194 let lexer = Lexing.from_string
(s ^
"\n") in
195 try s
, Hhas_parser.functionbodywithdirectives
Hhas_lexer.read
lexer
196 with Parsing.Parse_error
->
197 Emit_fatal.raise_fatal_parse
Pos.none
@@
198 "Error parsing inline hhas:\n" ^ s
in
199 let hhas_blocks = List.map
hhas_blocks ~f
:parse_hhas in
201 List.fold_left
hhas_blocks
203 ~f
:(fun acc
(k
, v
) -> SMap.add k v acc
) in
204 let combine (used_vars
, num_iters, num_cls_ref_slots, is_memoize
, static_init
) (_
, b
) =
205 let can_use v
= not
@@ is_param v
in
207 Instruction_sequence.collect_locals
can_use (Hhas_asm.instrs b
) in
208 Core_list.append used_vars
(Unique_list_string.items
locals),
209 check_num_iters num_iters (Hhas_asm.num_iters b
),
210 check_num_cls_ref_slots num_cls_ref_slots (Hhas_asm.num_cls_ref_slots b
),
211 is_memoize
|| (Hhas_asm.is_memoize_wrapper b
),
212 Core_list.append static_init
(Hhas_asm.static_inits b
) in
213 let decl_vars, num_iters, num_cls_ref_slots, is_memoize
, static_init
=
214 List.fold_left
hhas_blocks ~init
:(decl_vars, 0, 0, false, []) ~f
:combine in
215 deduplicate decl_vars,
217 Some
(num_iters, num_cls_ref_slots, is_memoize
, deduplicate static_init
)
219 let emit_return_type_info ~scope ~skipawaitable ~namespace ret
=
221 List.map
(Ast_scope.Scope.get_tparams scope
) (fun (_
, (_
, s
), _
) -> s
) in
224 Hhas_type_info.make
(Some
"") (Hhas_type_constraint.make None
[])
226 Emit_type_hint.(hint_to_type_info
227 ~kind
:Return ~nullable
:false ~skipawaitable ~
tparams ~namespace h
)
229 let emit_deprecation_warning scope
= function
232 (* Drop the indexes *)
233 let args = List.filteri ~f
:(fun i _
-> i
mod 2 <> 0) args in
234 let strip_id id
= SU.strip_global_ns
(snd id
) in
235 let class_name, trait_instrs
, concat_instruction
=
236 match Ast_scope.Scope.get_class scope
with
237 | None
-> "", empty
, empty
238 | Some c
when c
.Ast.c_kind
= Ast.Ctrait
->
239 "::", gather
[instr_self
; instr_clsrefname
;], instr_concat
240 | Some c
-> strip_id c
.Ast.c_name ^
"::", empty
, empty
244 | Ast_scope.ScopeItem.Function fd
:: _
-> strip_id fd
.Ast.f_name
245 | Ast_scope.ScopeItem.Method md
:: _
-> strip_id md
.Ast.m_name
246 | _
-> failwith
"deprecated functions must have names"
248 let deprecation_string = class_name ^
fn_name ^
": " ^
250 | [] -> "deprecated function"
251 | Typed_value.String s
:: _
-> s
252 | _
-> failwith
"deprecated attribute first argument is not a string"
256 | [] | [_
] -> Int64.one
257 | _
:: Typed_value.Int i
:: _
-> i
258 | _
-> failwith
"deprecated attribute second argument is not an integer"
260 let error_code = if Emit_env.is_systemlib
() then
261 8192 (* E_DEPRECATED *) else 16384 (* E_USER_DEPRECATED *)
263 if Int64.to_int
sampling_rate <= 0 then empty
else
266 instr_string
deprecation_string;
268 instr_int64
sampling_rate;
269 instr_int
error_code;
270 instr_trigger_sampled_error
;
274 let rec is_awaitable h
=
276 | _
, A.Happly
((_
, "Awaitable"), ([] | [_
])) -> true
277 | _
, (A.Hsoft h
| A.Hoption h
) -> is_awaitable h
280 let is_mixed_or_dynamic t
=
281 String_utils.string_ends_with t
"HH\\mixed" ||
282 String_utils.string_ends_with t
"HH\\dynamic"
284 let emit_verify_out params =
285 let msrv = Hhbc_options.use_msrv_for_inout
!Hhbc_options.compiler_options
in
286 let param_instrs = List.filter_mapi
params ~f
:(fun i
p ->
287 if not
@@ Hhas_param.is_inout
p then None
else
288 let b = match Hhas_param.type_info
p with
290 | Some
{ Hhas_type_info.type_info_user_type
= Some t
; _
} ->
291 not
@@ is_mixed_or_dynamic t
295 instr_cgetl
(Local.Named
(Hhas_param.name
p));
296 if b then instr_verifyOutType
(Param_unnamed i
) else empty
299 let param_instrs = if msrv then List.rev
param_instrs else param_instrs in
300 let len = List.length
param_instrs in
301 if len = 0 then (0, empty
) else (len, gather
param_instrs)
316 ~doc_comment
params ret body
=
317 if is_return_by_ref
&& Hhbc_options.disable_return_by_reference
!Hhbc_options.compiler_options
319 Emit_fatal.raise_fatal_runtime pos
(
320 "Return by reference is disabled by the " ^
321 "compiler through the option hhvm.disable_return_by_reference " ^
322 "or Eval.DisableReturnByReference")
324 if is_async
&& skipawaitable
327 not
(Option.value_map ret ~default
:true ~f
:is_awaitable) in
332 then "Return type hint for async closure must be awaitable"
336 | Ast_scope.ScopeItem.Function fd
:: _
->
337 "function", Utils.strip_ns
(snd fd
.A.f_name
)
338 | Ast_scope.ScopeItem.Method md
:: Ast_scope.ScopeItem.Class cd
:: _
->
339 "method", Utils.strip_ns
(snd cd
.A.c_name
) ^
"::" ^
(snd md
.A.m_name
)
340 | _
-> assert false in
341 Printf.sprintf
"Return type hint for async %s %s() must be awaitable"
343 Emit_fatal.raise_fatal_runtime pos
message
347 List.map
(Ast_scope.Scope.get_tparams scope
) (fun (_
, (_
, s
), _
) -> s
) in
348 Label.reset_label
();
349 Iterator.reset_iterator
();
350 let return_type_info =
351 emit_return_type_info ~scope ~skipawaitable ~namespace ret
in
352 let return_type_info = if not is_native
then return_type_info else
353 Emit_type_hint.emit_type_constraint_for_native_function
354 tparams ret
return_type_info
356 let is_generator, is_pair_generator
= Generator.is_function_generator body
in
358 return_type_info.Hhas_type_info.type_info_user_type
<> Some
"" &&
359 Hhas_type_info.has_type_constraint return_type_info && not
is_generator in
360 let default_dropthrough =
361 if default_dropthrough <> None
then default_dropthrough else begin
362 if is_async
&& verify_return
363 then Some
(gather
[instr_null
; instr_verifyRetTypeC
; instr_retc
])
369 ~namespace ~
tparams ~generate_defaults
:(not is_memoize
) ~scope
params
371 let params = if is_closure_body
372 then List.map ~f
:Hhas_param.switch_inout_to_reference
params else params in
373 let num_out, verify_out
= if is_closure_body
then 0, empty
else emit_verify_out params in
374 Emit_statement.set_verify_return
verify_return;
375 Emit_statement.set_verify_out verify_out
;
376 Emit_statement.set_num_out
num_out;
377 Emit_statement.set_default_dropthrough
default_dropthrough;
378 Emit_statement.set_default_return_value return_value
;
379 Emit_statement.set_return_by_ref is_return_by_ref
;
380 Emit_statement.set_function_pos pos
;
381 Jump_targets.reset
();
383 let remove_this vars
=
384 List.filter vars
(fun s
-> s
<> "$this") in
387 if List.mem vars
"$this"
388 then remove_this vars
@ ["$this"]
391 let starts_with s prefix
=
392 String.length s
>= String.length prefix
&&
393 String.sub s
0 (String.length prefix
) = prefix
in
395 let has_this = Ast_scope.Scope.has_this scope
in
396 let is_toplevel = Ast_scope.Scope.is_toplevel scope
in
397 (* see comment in decl_vars.ml, method on_efun of declvar_visitor
398 why Decl_vars needs 'explicit_use_set' *)
399 let explicit_use_set = Emit_env.get_explicit_use_set
() in
400 let is_in_static_method = Ast_scope.Scope.is_in_static_method scope
in
401 let needs_local_this, decl_vars =
415 | _
:: _
:: Ast_scope.ScopeItem.Class
ast_class :: _
-> ast_class
416 | _
-> failwith
"impossible, closure scope should be lambda->method->class" in
417 (* reorder decl_vars to match HHVM *)
420 |> List.concat_map ~f
:(fun item
->
422 | Ast.ClassVars
{ Ast.cv_names
= cvl
; _
} ->
423 List.filter_map cvl ~f
:(fun (_
, (_
, id
), _
) ->
424 if not
(starts_with id
"86static_")
425 then Some
("$" ^ id
) else None
)
429 List.filter
(move_this decl_vars) (fun v
-> not
(List.mem
captured_vars v
))
432 | _
:: Ast_scope.ScopeItem.Class _
:: _
-> move_this decl_vars
433 | _
when Ast_scope.Scope.is_toplevel scope
-> move_this decl_vars
436 let function_state_key =
437 let open Ast_scope
in
439 | [] -> Emit_env.get_unique_id_for_main
()
440 | ScopeItem.Method md
:: ScopeItem.Class cls
:: _
441 | ScopeItem.Lambda
:: ScopeItem.Method md
:: ScopeItem.Class cls
:: _
->
442 Emit_env.get_unique_id_for_method cls md
443 | ScopeItem.Function fd
:: _
->
444 Emit_env.get_unique_id_for_function fd
445 | _
-> failwith
@@ "unexpected scope shape:" ^
447 "'ScopeItem.Method md :: ScopeItem.Class cls' for method or " ^
448 "'ScopeItem.Function fd' for function or " ^
449 "empty scope for top level" in
451 let inline_hhas_blocks =
452 Emit_env.get_inline_hhas_blocks
function_state_key in
454 let decl_vars, inline_hhas_blocks, function_directives_opt
=
455 if List.is_empty
inline_hhas_blocks
456 then decl_vars, SMap.empty
, None
457 else prepare_inline_hhas_blocks decl_vars params inline_hhas_blocks in
458 Emit_expression.set_inline_hhas_blocks
inline_hhas_blocks;
460 let should_reserve_locals =
461 SSet.mem
function_state_key @@ Emit_env.get_functions_with_finally
() in
463 begin match SMap.get
function_state_key @@ Emit_env.get_function_to_labels_map
() with
465 Jump_targets.set_function_has_goto
true;
466 Jump_targets.set_labels_in_function s
;
468 Jump_targets.set_function_has_goto
false;
469 Jump_targets.set_labels_in_function
SMap.empty
;
472 Local.reset_local
(List.length
params + List.length
decl_vars);
473 if should_reserve_locals then Local.reserve_retval_and_label_id_locals
();
477 with_namespace namespace
|>
478 with_needs_local_this
needs_local_this |>
480 let stmt_instrs = if is_native
then instr_nativeimpl
else
481 Emit_env.do_function
env body emit_defs
in
482 let begin_label, default_value_setters
=
483 Emit_param.emit_param_default_value_setter ~is_native
env pos
params in
484 let generator_instr =
485 if is_generator then gather
[instr_createcont
; instr_popc
] else empty
487 let svar_map = Static_var.make_static_map body
in
488 let emit_expr env e
=
490 Emit_expression.emit_expr env ~need_ref
:false e
;
491 Emit_pos.emit_pos
(fst e
)
494 rewrite_static_instrseq
svar_map emit_expr env stmt_instrs
496 let first_instruction_is_label =
497 match Instruction_sequence.first
stmt_instrs with
498 | Some
(ILabel _
) -> true
501 let should_emit_init_this = not
is_in_static_method && (needs_local_this ||
502 (is_toplevel && List.exists ~f
:(fun x
-> x
= "$this") decl_vars))
505 (* per comment in emitter.cpp there should be no
506 * jumps to the base of the function - inject EntryNop
507 * if first instruction in the statement list is label
508 * and header content is empty *)
509 let header_content = gather
[
510 if is_native
then empty
else
511 emit_method_prolog ~pos ~
params ~
should_emit_init_this;
512 emit_deprecation_warning scope deprecation_info
;
515 if first_instruction_is_label &&
516 Instruction_sequence.is_empty
header_content
525 let svar_instrs = SMap.ordered_keys
svar_map in
526 let body_instrs = gather
[
529 default_value_setters
;
531 let fault_instrs = extract_fault_funclets
body_instrs in
532 let body_instrs = gather
[body_instrs; fault_instrs] in
534 function_directives_opt
537 false (*is_memoize_wrapper*)
539 (Some
return_type_info)