Remove non-MSRV implementation and interp-only options
[hiphop-php.git] / hphp / hack / src / hhbc / emit_body.ml
blob780baa90de2b8d6865272cacaa6ba7de6e694b0c
1 (**
2 * Copyright (c) 2017, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
8 *)
9 open Hh_core
10 open Hhbc_ast
11 open Instruction_sequence
12 module SU = Hhbc_string_utils
14 let has_type_constraint ti =
15 match ti with
16 | Some ti when (Hhas_type_info.has_type_constraint ti) -> true
17 | _ -> false
19 let emit_method_prolog ~pos ~params ~should_emit_init_this =
20 let instr_list =
21 (if should_emit_init_this
22 then instr (IMisc (InitThisLoc (Local.Named "$this")))
23 else empty)
25 List.filter_map params (fun p ->
26 if Hhas_param.is_variadic p
27 then None else
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))))
32 else None) in
33 if List.is_empty instr_list
34 then empty
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 =
42 match def with
43 | Ast.Stmt s -> Emit_statement.emit_stmt env s
44 | Ast.Constant c ->
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)
48 then
49 (* replace 'define' in namespace with call to 'define' function *)
50 let env =
51 Emit_env.with_namespace c.Ast.cst_namespace env in
52 let define_call =
53 let p0 = Pos.none in
54 let p_name = fst c.Ast.cst_name in
55 let args = [
56 p_name, Ast.String (SU.strip_global_ns cns_name);
57 c.Ast.cst_value] in
58 p0, Ast.Expr(p0, Ast.Call ((p0, Ast.Id (p0, "define")), [], args, [])) in
59 Emit_statement.emit_stmt env define_call
60 else
61 let cns_id =
62 if c.Ast.cst_kind = Ast.Cst_define
63 then
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
68 gather [
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));
72 instr_popc;
74 (* We assume that SetNamespaceEnv does namespace setting *)
75 | Ast.Namespace(_, defs) ->
76 emit_defs env defs
77 | _ ->
78 empty
80 and check_namespace_update env ns =
81 let p = Pos.none in
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 *)
88 | Some v ->
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
93 ) curr
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 =
102 match defs with
103 | Ast.SetNamespaceEnv ns :: defs ->
104 check_namespace_update env ns;
105 let env = Emit_env.with_namespace ns env in
106 aux env defs
107 | [] -> Emit_statement.emit_dropthrough_return env
108 (* emit last statement in the list as final statement *)
109 | [Ast.Stmt s]
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
114 | [d] ->
115 gather [emit_def env d; Emit_statement.emit_dropthrough_return env]
116 | d::defs ->
117 let i1 = emit_def env d in
118 let i2 = aux env defs in
119 gather [i1; i2]
121 let rec emit_markup env defs =
122 match defs with
123 | Ast.Stmt (_, Ast.Markup ((_, s), echo_expr_opt))::defs ->
124 let i1 =
125 Emit_statement.emit_markup env s echo_expr_opt ~check_for_hashbang:true
127 let i2 = aux env defs in
128 gather [i1; i2]
129 | [_] | [] -> aux env defs
130 | d :: 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]
135 emit_markup env defs
137 let check_redefinition v1 v2 text =
138 if v1 > 0 && v2 > 0
139 then Emit_fatal.raise_fatal_runtime Pos.none ("Multiple " ^ text ^ " directives")
140 else max v1 v2
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"
144 let deduplicate l =
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
152 env =
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
159 let num_iters =
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
170 Hhas_body.make
171 body_instrs
172 decl_vars
173 num_iters
174 num_cls_ref_slots
175 is_memoize_wrapper
176 is_memoize_wrapper_lsb
177 params
178 return_type_info
179 static_inits
180 doc_comment
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);
190 let param_names =
191 List.fold params
192 ~init:SSet.empty
193 ~f:(fun acc v -> SSet.add (Hhas_param.name v) acc) in
194 let is_param v = SSet.mem v param_names in
195 let parse_hhas s =
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
202 let text_to_block =
203 List.fold_left hhas_blocks
204 ~init:SMap.empty
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
208 let locals =
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,
218 text_to_block,
219 Some (num_iters, num_cls_ref_slots, is_memoize, deduplicate static_init)
221 let emit_return_type_info ~scope ~skipawaitable ~namespace ret =
222 let tparams =
223 List.map (Ast_scope.Scope.get_tparams scope) (fun (_, (_, s), _, _) -> s) in
224 match ret with
225 | None ->
226 Hhas_type_info.make (Some "") (Hhas_type_constraint.make None [])
227 | Some h ->
228 Emit_type_hint.(hint_to_type_info
229 ~kind:Return ~nullable:false ~skipawaitable ~tparams ~namespace h)
231 let emit_deprecation_warning scope = function
232 | None -> empty
233 | Some args ->
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
244 let fn_name =
245 match scope with
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 ^ ": " ^
251 match args with
252 | [] -> "deprecated function"
253 | Typed_value.String s :: _ -> s
254 | _ -> failwith "deprecated attribute first argument is not a string"
256 let sampling_rate =
257 match args with
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
266 gather [
267 trait_instrs;
268 instr_string deprecation_string;
269 concat_instruction;
270 instr_int64 sampling_rate;
271 instr_int error_code;
272 instr_trigger_sampled_error;
273 instr_popr;
276 let rec is_awaitable h =
277 match h with
278 | _, A.Happly ((_, "Awaitable"), ([] | [_])) -> true
279 | _, (A.Hsoft h | A.Hoption h) -> is_awaitable h
280 | _ -> false
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
290 | None -> false
291 | Some { Hhas_type_info.type_info_user_type = Some t; _ } ->
292 not @@ is_mixed_or_dynamic t
293 | _ -> true in
294 Some (
295 gather [
296 instr_cgetl (Local.Named (Hhas_param.name p));
297 if b then instr_verifyOutType (Param_unnamed i) else empty
299 )) in
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)
304 let emit_body
305 ~pos
306 ~scope
307 ~is_closure_body
308 ~is_memoize
309 ~is_native
310 ~is_async
311 ~deprecation_info
312 ~skipawaitable
313 ~is_return_by_ref
314 ~default_dropthrough
315 ~return_value
316 ~namespace
317 ~doc_comment params ret body =
318 if is_return_by_ref && Hhbc_options.disable_return_by_reference !Hhbc_options.compiler_options
319 then begin
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")
324 end;
325 if is_async && skipawaitable
326 then begin
327 let report_error =
328 not (Option.value_map ret ~default:true ~f:is_awaitable) in
329 if report_error
330 then begin
331 let message =
332 if is_closure_body
333 then "Return type hint for async closure must be awaitable"
334 else
335 let kind, name =
336 match scope with
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"
343 kind name in
344 Emit_fatal.raise_fatal_runtime pos message
345 end;
346 end;
347 let tparams =
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
358 let verify_return =
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])
365 else None
368 let params =
369 Emit_param.from_asts
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
387 let move_this vars =
388 if List.mem vars "$this"
389 then remove_this vars @ ["$this"]
390 else vars in
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 =
403 Decl_vars.from_ast
404 ~is_closure_body
405 ~has_this
406 ~params
407 ~is_toplevel
408 ~is_in_static_method
409 ~explicit_use_set
410 body in
411 let decl_vars =
412 if is_closure_body
413 then
414 let ast_class =
415 match scope with
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 *)
419 let captured_vars =
420 ast_class.Ast.c_body
421 |> List.concat_map ~f:(fun item ->
422 match item with
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)
427 | _ -> []) in
428 "$0Closure" ::
429 captured_vars @
430 List.filter (move_this decl_vars) (fun v -> not (List.mem captured_vars v))
431 else
432 match scope with
433 | _ :: Ast_scope.ScopeItem.Class _ :: _ -> move_this decl_vars
434 | _ when Ast_scope.Scope.is_toplevel scope -> move_this decl_vars
435 | _ -> decl_vars in
437 let function_state_key =
438 let open Ast_scope in
439 match scope with
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:" ^
447 "expected either " ^
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
465 | Some s ->
466 Jump_targets.set_function_has_goto true;
467 Jump_targets.set_labels_in_function s;
468 | None ->
469 Jump_targets.set_function_has_goto false;
470 Jump_targets.set_labels_in_function SMap.empty;
471 end;
473 Local.reset_local (List.length params + List.length decl_vars);
474 if should_reserve_locals then Local.reserve_retval_and_label_id_locals ();
476 let env = Emit_env.(
477 empty |>
478 with_namespace namespace |>
479 with_needs_local_this needs_local_this |>
480 with_scope scope) in
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 =
490 gather [
491 Emit_expression.emit_expr env ~need_ref:false e;
492 Emit_pos.emit_pos (fst e)
493 ] in
494 let stmt_instrs =
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
500 | _ -> false
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))
505 let header =
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;
514 generator_instr;
515 ] in
516 if first_instruction_is_label &&
517 Instruction_sequence.is_empty header_content
518 then gather [
519 begin_label;
520 instr_entrynop;
522 else gather [
523 begin_label;
524 header_content;
525 ] in
526 let svar_instrs = SMap.ordered_keys svar_map in
527 let body_instrs = gather [
528 header;
529 stmt_instrs;
530 default_value_setters;
531 ] in
532 let fault_instrs = extract_fault_funclets body_instrs in
533 let body_instrs = gather [body_instrs; fault_instrs] in
534 make_body_
535 function_directives_opt
536 body_instrs
537 decl_vars
538 false (*is_memoize_wrapper*)
539 false (*is_memoize_wrapper_lsb*)
540 params
541 (Some return_type_info)
542 svar_instrs
543 doc_comment
544 (Some env),
545 is_generator,
546 is_pair_generator