Remove case insensitivity for special names in emit_expression
[hiphop-php.git] / hphp / hack / src / hhbc / emit_expression.ml
blob1432a160bf809506121a929cdbe84a88656dc4dc
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 *)
10 open Core_kernel
11 open Hhbc_ast
12 open Instruction_sequence
13 open Ast_class_expr
14 open Emit_pos
15 module H = Hhbc_ast
16 module TC = Hhas_type_constraint
17 module SN = Naming_special_names
18 module SU = Hhbc_string_utils
19 module ULS = Unique_list_string
20 module Opts = Hhbc_options
22 let can_inline_gen_functions () =
23 not (Opts.jit_enable_rename_function !Opts.compiler_options)
25 let max_array_elem_on_stack () =
26 Hhbc_options.max_array_elem_size_on_the_stack !Hhbc_options.compiler_options
28 type genva_inline_context =
29 | GI_expression
30 | GI_ignore_result
31 | GI_list_assignment of Tast.expr list
33 type emit_jmp_result = {
34 (* generated instruction sequence *)
35 instrs: Instruction_sequence.t;
36 (* does instruction sequence fall through *)
37 is_fallthrough: bool;
38 (* was label associated with emit operation used *)
39 is_label_used: bool;
42 (* Locals, array elements, and properties all support the same range of l-value
43 * operations. *)
44 module LValOp = struct
45 type t =
46 | Set
47 | SetOp of eq_op
48 | IncDec of incdec_op
49 | Unset
50 end
52 let jit_enable_rename_function () =
53 Hhbc_options.jit_enable_rename_function !Hhbc_options.compiler_options
55 let is_local_this env (lid : Aast.local_id) : bool =
56 let id = Local_id.get_name lid in
57 let scope = Emit_env.get_scope env in
58 id = SN.SpecialIdents.this
59 && Ast_scope.Scope.has_this scope
60 && not (Ast_scope.Scope.is_toplevel scope)
62 module InoutLocals = struct
63 (* for every local that appear as a part of inout argument and also mutated inside
64 argument list this record stores:
65 - position of the first argument when local appears as inout
66 - position of the last argument where local is mutated.
67 Within the this range at every usage of the local must be captured to make sure
68 that later when inout arguments will be written back the same value of the
69 local will be used *)
70 type alias_info = {
71 first_inout: int;
72 last_write: int;
73 num_uses: int;
76 let not_aliased =
77 { first_inout = Int.max_value; last_write = Int.min_value; num_uses = 0 }
79 let add_inout i r =
80 if i < r.first_inout then
81 { r with first_inout = i }
82 else
85 let add_write i r =
86 if i > r.last_write then
87 { r with last_write = i }
88 else
91 let add_use _i r = { r with num_uses = r.num_uses + 1 }
93 let in_range i r = i > r.first_inout || i <= r.last_write
95 let has_single_ref r = r.num_uses < 2
97 let update name i f m =
98 let r = SMap.get name m |> Option.value ~default:not_aliased |> f i in
99 SMap.add name r m
101 let add_write name i m = update (Local_id.get_name name) i add_write m
103 let add_inout name i m = update (Local_id.get_name name) i add_inout m
105 let add_use name i m = update (Local_id.get_name name) i add_use m
107 let collect_written_variables env (args : Tast.expr list) : alias_info SMap.t
109 (* check value of the argument *)
110 let rec handle_arg ~is_top i acc (arg : Tast.expr) =
111 match snd arg with
112 (* inout $v *)
113 | A.Callconv (Ast_defs.Pinout, (_, A.Lvar (_, id)))
114 when not (is_local_this env id) ->
115 let acc = add_use id i acc in
116 if is_top then
117 add_inout id i acc
118 else
119 add_write id i acc
120 (* $v *)
121 | A.Lvar (_, id) ->
122 let acc = add_use id i acc in
124 | _ ->
125 (* dive into argument value *)
126 dive i acc arg
127 (* collect lvars on the left hand side of '=' operator *)
128 and collect_lvars_lhs i acc (e : Tast.expr) =
129 match snd e with
130 | A.Lvar (_, id) when not (is_local_this env id) ->
131 let acc = add_use id i acc in
132 add_write id i acc
133 | A.List exprs -> List.fold_left exprs ~f:(collect_lvars_lhs i) ~init:acc
134 | _ -> acc
135 (* descend into expression *)
136 and dive i (acc : alias_info SMap.t) expr : alias_info SMap.t =
137 let state = ref acc in
138 let visitor =
139 object
140 inherit [_] A.iter as super
142 (* lhs op= _ *)
143 method! on_Binop _ bop l r =
144 let _ =
145 match bop with
146 | Ast_defs.Eq _ -> state := collect_lvars_lhs i !state l
147 | _ -> ()
149 super#on_Binop () bop l r
151 (* $i++ or $i-- *)
152 method! on_Unop _ op e =
153 let _ =
154 match op with
155 | Ast_defs.Uincr
156 | Ast_defs.Udecr ->
157 state := collect_lvars_lhs i !state e
158 | _ -> ()
160 super#on_Unop () op e
162 (* $v *)
163 method! on_Lvar _ (_p, id) =
164 let _ = state := add_use id 0 !state in
165 super#on_Lvar () (_p, id)
167 (* f(inout $v) or f(&$v) *)
168 method! on_Call _ _ _ _ args uargs =
169 let f = handle_arg ~is_top:false i in
170 List.iter args ~f:(fun arg -> state := f !state arg);
171 List.iter uargs ~f:(fun arg -> state := f !state arg)
174 visitor#on_expr () expr;
175 !state
177 List.foldi args ~f:(handle_arg ~is_top:true) ~init:SMap.empty
179 (* determines if value of a local 'name' that appear in parameter 'i'
180 should be saved to local because it might be overwritten later *)
181 let should_save_local_value name i aliases =
182 Option.value_map ~default:false ~f:(in_range i) (SMap.get name aliases)
184 let should_move_local_value local aliases =
185 match local with
186 | Local.Named name ->
187 Option.value_map ~default:true ~f:has_single_ref (SMap.get name aliases)
188 | Local.Unnamed _ -> false
191 (* Describes what kind of value is intended to be stored in local *)
192 type stored_value_kind =
193 | Value_kind_local
194 | Value_kind_expression
196 (* represents sequence of instructions interleaved with temp locals.
197 <i, None :: rest> - is emitted i :: <rest> (commonly used for final instructions in sequence)
198 <i, Some (l, local_kind) :: rest> is emitted as
201 .try {
202 setl/popl l; depending on local_kind
203 <rest>
204 } .catch {
205 unset l
206 throw
208 unsetl l
210 type instruction_sequence_with_locals =
211 (Instruction_sequence.t * (Local.t * stored_value_kind) option) list
213 (* converts instruction_sequence_with_locals of loads into instruction_sequence.t *)
214 let rebuild_load_store load store =
215 let rec aux = function
216 | [] -> ([], [])
217 | (i, None) :: xs ->
218 let (ld, st) = aux xs in
219 (i :: ld, st)
220 | (i, Some (l, kind)) :: xs ->
221 let (ld, st) = aux xs in
222 let set =
223 if kind = Value_kind_expression then
224 instr_setl l
225 else
226 instr_popl l
228 let unset = instr_unsetl l in
229 (i :: set :: ld, unset :: st)
231 let (ld, st) = aux load in
232 (gather ld, gather (store :: st))
234 (* result of emit_array_get *)
235 type array_get_instr =
236 (* normal $a[..] that does not need to spill anything*)
237 | Array_get_regular of Instruction_sequence.t
238 (* subscript expression used as inout argument that need to spill intermediate
239 values:
240 load - instruction_sequence_with_locals to load value
241 store - instruction to set value back (can use locals defined in load part)
243 | Array_get_inout of {
244 load: instruction_sequence_with_locals;
245 store: Instruction_sequence.t;
248 type 'a array_get_base_data = {
249 base_instrs: 'a;
250 cls_instrs: Instruction_sequence.t;
251 setup_instrs: Instruction_sequence.t;
252 base_stack_size: int;
253 cls_stack_size: int;
256 (* result of emit_base *)
257 type array_get_base =
258 (* normal <base> part in <base>[..] that does not need to spill anything *)
259 | Array_get_base_regular of Instruction_sequence.t array_get_base_data
260 (* base of subscript expression used as inout argument that need to spill
261 intermediate values *)
262 | Array_get_base_inout of {
263 (* instructions to load base part *)
264 load: instruction_sequence_with_locals array_get_base_data;
265 (* instruction to load base part for setting inout argument back *)
266 store: Instruction_sequence.t;
269 let is_incdec op =
270 match op with
271 | LValOp.IncDec _ -> true
272 | _ -> false
274 let is_global_namespace env =
275 Namespace_env.is_global_namespace (Emit_env.get_namespace env)
277 let enable_intrinsics_extension () =
278 Hhbc_options.enable_intrinsics_extension !Hhbc_options.compiler_options
280 let optimize_null_checks () =
281 Hhbc_options.optimize_null_checks !Hhbc_options.compiler_options
283 let hack_arr_compat_notices () =
284 Hhbc_options.hack_arr_compat_notices !Hhbc_options.compiler_options
286 let hack_arr_dv_arrs () =
287 Hhbc_options.hack_arr_dv_arrs !Hhbc_options.compiler_options
289 let php7_ltr_assign () =
290 Hhbc_options.php7_ltr_assign !Hhbc_options.compiler_options
292 (* Strict binary operations; assumes that operands are already on stack *)
293 let from_binop op =
294 let check_int_overflow =
295 Hhbc_options.check_int_overflow !Hhbc_options.compiler_options
297 match op with
298 | Ast_defs.Plus ->
299 instr
300 (IOp
301 ( if check_int_overflow then
302 AddO
303 else
304 Add ))
305 | Ast_defs.Minus ->
306 instr
307 (IOp
308 ( if check_int_overflow then
309 SubO
310 else
311 Sub ))
312 | Ast_defs.Star ->
313 instr
314 (IOp
315 ( if check_int_overflow then
316 MulO
317 else
318 Mul ))
319 | Ast_defs.Slash -> instr (IOp Div)
320 | Ast_defs.Eqeq -> instr (IOp Eq)
321 | Ast_defs.Eqeqeq -> instr (IOp Same)
322 | Ast_defs.Starstar -> instr (IOp Pow)
323 | Ast_defs.Diff -> instr (IOp Neq)
324 | Ast_defs.Diff2 -> instr (IOp NSame)
325 | Ast_defs.Lt -> instr (IOp Lt)
326 | Ast_defs.Lte -> instr (IOp Lte)
327 | Ast_defs.Gt -> instr (IOp Gt)
328 | Ast_defs.Gte -> instr (IOp Gte)
329 | Ast_defs.Dot -> instr (IOp Concat)
330 | Ast_defs.Amp -> instr (IOp BitAnd)
331 | Ast_defs.Bar -> instr (IOp BitOr)
332 | Ast_defs.Ltlt -> instr (IOp Shl)
333 | Ast_defs.Gtgt -> instr (IOp Shr)
334 | Ast_defs.Cmp -> instr (IOp Cmp)
335 | Ast_defs.Percent -> instr (IOp Mod)
336 | Ast_defs.Xor -> instr (IOp BitXor)
337 | Ast_defs.LogXor -> instr (IOp Xor)
338 | Ast_defs.Eq _ -> failwith "assignment is emitted differently"
339 | Ast_defs.QuestionQuestion ->
340 failwith "null coalescence is emitted differently"
341 | Ast_defs.Ampamp
342 | Ast_defs.Barbar ->
343 failwith "short-circuiting operator cannot be generated as a simple binop"
345 let binop_to_eqop op =
346 let check_int_overflow =
347 Hhbc_options.check_int_overflow !Hhbc_options.compiler_options
349 match op with
350 | Ast_defs.Plus ->
351 Some
352 ( if check_int_overflow then
353 PlusEqualO
354 else
355 PlusEqual )
356 | Ast_defs.Minus ->
357 Some
358 ( if check_int_overflow then
359 MinusEqualO
360 else
361 MinusEqual )
362 | Ast_defs.Star ->
363 Some
364 ( if check_int_overflow then
365 MulEqualO
366 else
367 MulEqual )
368 | Ast_defs.Slash -> Some DivEqual
369 | Ast_defs.Starstar -> Some PowEqual
370 | Ast_defs.Amp -> Some AndEqual
371 | Ast_defs.Bar -> Some OrEqual
372 | Ast_defs.Xor -> Some XorEqual
373 | Ast_defs.Ltlt -> Some SlEqual
374 | Ast_defs.Gtgt -> Some SrEqual
375 | Ast_defs.Percent -> Some ModEqual
376 | Ast_defs.Dot -> Some ConcatEqual
377 | _ -> None
379 let unop_to_incdec_op op =
380 let check_int_overflow =
381 Hhbc_options.check_int_overflow !Hhbc_options.compiler_options
383 match op with
384 | Ast_defs.Uincr ->
385 if check_int_overflow then
386 PreIncO
387 else
388 PreInc
389 | Ast_defs.Udecr ->
390 if check_int_overflow then
391 PreDecO
392 else
393 PreDec
394 | Ast_defs.Upincr ->
395 if check_int_overflow then
396 PostIncO
397 else
398 PostInc
399 | Ast_defs.Updecr ->
400 if check_int_overflow then
401 PostDecO
402 else
403 PostDec
404 | _ -> failwith "invalid incdec op"
406 let istype_op id =
407 match id with
408 | "is_int"
409 | "is_integer"
410 | "is_long" ->
411 Some OpInt
412 | "is_bool" -> Some OpBool
413 | "is_float"
414 | "is_real"
415 | "is_double" ->
416 Some OpDbl
417 | "is_string" -> Some OpStr
418 | "is_array" -> Some OpArr
419 | "is_object" -> Some OpObj
420 | "is_null" -> Some OpNull
421 (* We don't use IsType with the resource type because `is_resource()` does
422 validation in addition to a simple type check. We will use it for
423 is-expressions because they only do type checks.
424 | "is_resource" -> Some OpRes *)
425 | "is_scalar" -> Some OpScalar
426 | "HH\\is_keyset" -> Some OpKeyset
427 | "HH\\is_dict" -> Some OpDict
428 | "HH\\is_vec" -> Some OpVec
429 | "HH\\is_varray" ->
430 Some
431 ( if hack_arr_dv_arrs () then
432 OpVec
433 else
434 OpVArray )
435 | "HH\\is_darray" ->
436 Some
437 ( if hack_arr_dv_arrs () then
438 OpDict
439 else
440 OpDArray )
441 | "HH\\is_any_array" -> Some OpArrLike
442 | "HH\\is_class_meth" -> Some OpClsMeth
443 | "HH\\is_fun" -> Some OpFunc
444 | _ -> None
446 let is_isexp_op lower_fq_id : Aast.hint option =
447 let h n = (Pos.none, Aast.Happly ((Pos.none, n), [])) in
448 match lower_fq_id with
449 | "is_int"
450 | "is_integer"
451 | "is_long" ->
452 Some (h "\\HH\\int")
453 | "is_bool" -> Some (h "\\HH\\bool")
454 | "is_float"
455 | "is_real"
456 | "is_double" ->
457 Some (h "\\HH\\float")
458 | "is_string" -> Some (h "\\HH\\string")
459 | "is_null" -> Some (h "\\HH\\void")
460 | "HH\\is_keyset" -> Some (h "\\HH\\keyset")
461 | "HH\\is_dict" -> Some (h "\\HH\\dict")
462 | "HH\\is_vec" -> Some (h "\\HH\\vec")
463 | _ -> None
465 let get_queryMOpMode op =
466 match op with
467 | QueryOp.InOut -> MemberOpMode.InOut
468 | QueryOp.CGet -> MemberOpMode.Warn
469 | _ -> MemberOpMode.ModeNone
471 (* Returns either Some (index, is_soft) or None *)
472 let is_reified_tparam ~(is_fun : bool) (env : Emit_env.t) (name : string) =
473 let scope = Emit_env.get_scope env in
474 let tparams =
475 if is_fun then
476 Ast_scope.Scope.get_fun_tparams scope
477 else
478 (Ast_scope.Scope.get_class_tparams scope).A.c_tparam_list
480 let is_soft =
481 List.exists ~f:(function { A.ua_name = n; _ } ->
482 snd n = SN.UserAttributes.uaSoft)
484 List.find_mapi
485 tparams
486 ~f:(fun i
488 A.tp_name = (_, id);
489 A.tp_reified = reified;
490 A.tp_user_attributes = ual;
494 if (reified = A.Reified || reified = A.SoftReified) && id = name then
495 Some (i, is_soft ual)
496 else
497 None)
499 let extract_shape_field_name_pstring env annot = function
500 | Ast_defs.SFlit_int s -> A.Int (snd s)
501 | Ast_defs.SFlit_str s -> A.String (snd s)
502 | Ast_defs.SFclass_const (((pn, name) as id), p) ->
504 Option.is_some (is_reified_tparam ~is_fun:true env name)
505 || Option.is_some (is_reified_tparam ~is_fun:false env name)
506 then
507 Emit_fatal.raise_fatal_parse
509 "Reified generics cannot be used in shape keys";
510 A.Class_const ((annot, A.CI id), p)
512 let rec text_of_expr (e : Tast.expr) =
513 match e with
514 (* Note we force string literals to become single-quoted, regardless of
515 whether they were single- or double-quoted in the source. Gross. *)
516 | (_, A.String s) -> "'" ^ s ^ "'"
517 | (_, A.Id (_, id)) -> id
518 | (_, A.Lvar (_, id)) -> Local_id.get_name id
519 | (_, A.Array_get ((_, A.Lvar (_, id)), Some e)) ->
520 Local_id.get_name id ^ "[" ^ text_of_expr e ^ "]"
521 | _ ->
522 (* TODO: get text of expression *)
523 "unknown"
525 let text_of_class_id (cid : Tast.class_id) =
526 match snd cid with
527 | A.CIparent -> "parent"
528 | A.CIself -> "self"
529 | A.CIstatic -> "static"
530 | A.CIexpr e -> text_of_expr e
531 | A.CI (_, id) -> id
533 let text_of_prop (prop : Tast.class_get_expr) =
534 match prop with
535 | A.CGstring (_, s) -> s
536 | A.CGexpr e -> text_of_expr e
538 let from_ast_null_flavor = function
539 | A.OG_nullsafe -> Hhbc_ast.Obj_null_safe
540 | A.OG_nullthrows -> Hhbc_ast.Obj_null_throws
542 let parse_include (e : Tast.expr) =
543 let strip_backslash p =
544 let len = String.length p in
545 if len > 0 && p.[0] = '/' then
546 String.sub p 1 (len - 1)
547 else
550 let rec split_var_lit = function
551 | (_, A.Binop (Ast_defs.Dot, e1, e2)) ->
552 let (v, l) = split_var_lit e2 in
553 if v = "" then
554 let (var, lit) = split_var_lit e1 in
555 (var, lit ^ l)
556 else
557 (v, "")
558 | (_, A.String lit) -> ("", lit)
559 | e -> (text_of_expr e, "")
561 let (var, lit) = split_var_lit e in
562 let (var, lit) =
563 if var = "__DIR__" then
564 ("", strip_backslash lit)
565 else
566 (var, lit)
568 if var = "" then
569 if Filename.is_relative lit then
570 Hhas_symbol_refs.SearchPathRelative lit
571 else
572 Hhas_symbol_refs.Absolute lit
573 else
574 Hhas_symbol_refs.IncludeRootRelative (var, strip_backslash lit)
576 let rec expr_and_new env pos instr_to_add_new instr_to_add = function
577 | A.AFvalue e -> gather [emit_expr env e; emit_pos pos; instr_to_add_new]
578 | A.AFkvalue (k, v) ->
579 gather [emit_two_exprs env (fst @@ fst k) k v; instr_to_add]
581 and get_local env (pos, (str : string)) : Hhbc_ast.local_id =
582 if str = SN.SpecialIdents.dollardollar then
583 match Emit_env.get_pipe_var env with
584 | None ->
585 Emit_fatal.raise_fatal_runtime
587 "Pipe variables must occur only in the RHS of pipe expressions"
588 | Some v -> v
589 else if SN.SpecialIdents.is_tmp_var str then
590 Local.get_unnamed_local_for_tempname str
591 else
592 Local.Named str
594 and emit_local ~notice env (lid : Aast.lid) =
595 let (pos, id) = lid in
596 let str = Local_id.get_name id in
597 if SN.Superglobals.globals = str || SN.Superglobals.is_superglobal str then
598 gather
600 instr_string (SU.Locals.strip_dollar str);
601 emit_pos pos;
602 instr (IGet CGetG);
604 else
605 let local = get_local env (pos, str) in
606 if is_local_this env id && not (Emit_env.get_needs_local_this env) then
607 emit_pos_then pos @@ instr (IMisc (BareThis notice))
608 else
609 instr_cgetl local
611 (* Emit CGetL2 for local variables, and return true to indicate that
612 * the result will be just below the top of the stack *)
613 and emit_first_expr env (expr : Tast.expr) =
614 match snd expr with
615 | A.Lvar (pos, id)
616 when not
617 ( (is_local_this env id && not (Emit_env.get_needs_local_this env))
618 || Local_id.get_name id = SN.Superglobals.globals
619 || SN.Superglobals.is_superglobal (Local_id.get_name id) ) ->
620 (instr_cgetl2 (get_local env (pos, Local_id.get_name id)), true)
621 | _ -> (emit_expr env expr, false)
623 (* Special case for binary operations to make use of CGetL2 *)
624 and emit_two_exprs env (outer_pos : Pos.t) (e1 : Tast.expr) (e2 : Tast.expr) =
625 let (instrs1, is_under_top) = emit_first_expr env e1 in
626 let instrs2 = emit_expr env e2 in
627 let instrs2_is_var =
628 match e2 with
629 | (_, A.Lvar _) -> true
630 | _ -> false
632 gather
634 if is_under_top then
635 if instrs2_is_var then
636 [emit_pos outer_pos; instrs2; instrs1]
637 else
638 [instrs2; emit_pos outer_pos; instrs1]
639 else if instrs2_is_var then
640 [instrs1; emit_pos outer_pos; instrs2]
641 else
642 [instrs1; instrs2; emit_pos outer_pos]
644 and emit_is_null env (e : Tast.expr) =
645 match e with
646 | (_, A.Lvar (pos, id)) when not (is_local_this env id) ->
647 instr_istypel (get_local env (pos, Local_id.get_name id)) OpNull
648 | _ -> gather [emit_expr env e; instr_istypec OpNull]
650 and emit_binop env annot op (e1 : Tast.expr) (e2 : Tast.expr) =
651 let (pos, _) = annot in
652 let default () = gather [emit_two_exprs env pos e1 e2; from_binop op] in
653 match op with
654 | Ast_defs.Ampamp
655 | Ast_defs.Barbar ->
656 emit_short_circuit_op env annot (A.Binop (op, e1, e2))
657 | Ast_defs.Eq None -> emit_lval_op env pos LValOp.Set e1 (Some e2)
658 | Ast_defs.Eq (Some Ast_defs.QuestionQuestion) ->
659 emit_null_coalesce_assignment env pos e1 e2
660 | Ast_defs.Eq (Some obop) ->
661 begin
662 match binop_to_eqop obop with
663 | None -> failwith "illegal eq op"
664 | Some op -> emit_lval_op env pos (LValOp.SetOp op) e1 (Some e2)
666 | Ast_defs.QuestionQuestion ->
667 let end_label = Label.next_regular () in
668 gather
670 fst (emit_quiet_expr env pos e1);
671 instr_dup;
672 instr_istypec OpNull;
673 instr_not;
674 instr_jmpnz end_label;
675 instr_popc;
676 emit_expr env e2;
677 instr_label end_label;
679 | _ ->
680 if not (optimize_null_checks ()) then
681 default ()
682 else (
683 match op with
684 | Ast_defs.Eqeqeq when snd e2 = A.Null -> emit_is_null env e1
685 | Ast_defs.Eqeqeq when snd e1 = A.Null -> emit_is_null env e2
686 | Ast_defs.Diff2 when snd e2 = A.Null ->
687 gather [emit_is_null env e1; instr_not]
688 | Ast_defs.Diff2 when snd e1 = A.Null ->
689 gather [emit_is_null env e2; instr_not]
690 | _ -> default ()
693 and get_type_structure_for_hint ~targ_map ~tparams (h : Aast.hint) =
694 let tv = Emit_type_constant.hint_to_type_constant ~tparams ~targ_map h in
695 let i = Emit_adata.get_array_identifier tv in
696 if hack_arr_dv_arrs () then
697 instr (ILitConst (Dict i))
698 else
699 instr (ILitConst (Array i))
701 (* NOTE: Make sure the type structure retrieval code is synced with emit_is. *)
702 and emit_as env pos e h is_nullable =
703 Local.scope
704 @@ fun () ->
705 let arg_local = Local.get_unnamed_local () in
706 let type_struct_local = Local.get_unnamed_local () in
707 let (ts_instrs, is_static) = emit_reified_arg env ~isas:true pos h in
708 let then_label = Label.next_regular () in
709 let done_label = Label.next_regular () in
710 let main_block ts_instrs resolve =
711 gather
713 ts_instrs;
714 instr_setl type_struct_local;
715 instr_istypestructc resolve;
716 instr_jmpnz then_label;
717 ( if is_nullable then
718 gather [instr_null; instr_jmp done_label]
719 else
720 gather
722 instr_pushl arg_local;
723 instr_pushl type_struct_local;
724 instr_throwastypestructexception;
725 ] );
728 (* Set aside the argument value. *)
729 gather
731 emit_expr env e;
732 instr_setl arg_local;
733 (* Store type struct in a variable and reuse it. *)
734 ( if is_static then
735 main_block
736 (get_type_structure_for_hint ~targ_map:SMap.empty ~tparams:[] h)
737 Resolve
738 else
739 main_block ts_instrs DontResolve );
740 instr_label then_label;
741 instr_pushl arg_local;
742 instr_unsetl type_struct_local;
743 instr_label done_label;
746 and emit_is env pos (h : Aast.hint) =
747 let (ts_instrs, is_static) = emit_reified_arg env ~isas:true pos h in
748 if is_static then
749 match snd h with
750 | Aast.Happly ((_, id), []) when SU.strip_hh_ns id = SN.Typehints.this ->
751 instr_islateboundcls
752 | _ ->
753 gather
755 get_type_structure_for_hint ~targ_map:SMap.empty ~tparams:[] h;
756 instr_istypestructc Resolve;
758 else
759 gather [ts_instrs; instr_istypestructc DontResolve]
761 and emit_cast env pos hint expr =
762 let op =
763 match hint with
764 | Aast.Happly ((_, id), []) ->
765 let id = SU.strip_ns id in
766 let id = SU.strip_hh_ns id in
767 begin
768 match id with
769 | _ when id = SN.Typehints.int -> instr (IOp CastInt)
770 | _ when id = SN.Typehints.bool -> instr (IOp CastBool)
771 | _ when id = SN.Typehints.string -> instr (IOp CastString)
772 | _ when id = SN.Typehints.array -> instr (IOp CastArray)
773 | _ when id = SN.Typehints.float -> instr (IOp CastDouble)
774 (* TODO: Is unset a real typehint? *)
775 | _ when id = "unset" -> gather [instr_popc; instr_null]
776 | _ ->
777 Emit_fatal.raise_fatal_parse
779 ("Invalid cast type: " ^ SU.strip_global_ns id)
781 | _ -> Emit_fatal.raise_fatal_parse pos "Invalid cast type"
783 gather [emit_expr env expr; emit_pos pos; op]
785 and emit_conditional_expression
786 env pos (etest : Tast.expr) (etrue : Tast.expr option) (efalse : Tast.expr)
788 match etrue with
789 | Some etrue ->
790 let false_label = Label.next_regular () in
791 let end_label = Label.next_regular () in
792 let r = emit_jmpz env etest false_label in
793 gather
795 r.instrs;
796 (* only emit true branch if there is fallthrough from condition *)
797 begin
798 if r.is_fallthrough then
799 gather [emit_expr env etrue; emit_pos pos; instr_jmp end_label]
800 else empty
801 end;
802 (* only emit false branch if false_label is used *)
803 begin
804 if r.is_label_used then
805 gather [instr_label false_label; emit_expr env efalse]
806 else empty
807 end;
808 (* end_label is used to jump out of true branch so they should be emitted
809 together *)
810 begin
811 if r.is_fallthrough then
812 instr_label end_label
813 else empty
814 end;
816 | None ->
817 let end_label = Label.next_regular () in
818 gather
820 emit_expr env etest;
821 instr_dup;
822 instr_jmpnz end_label;
823 instr_popc;
824 emit_expr env efalse;
825 instr_label end_label;
828 and get_erased_tparams env =
829 Ast_scope.Scope.get_tparams (Emit_env.get_scope env)
830 |> List.filter_map ~f:(function
831 | { A.tp_name = (_, name); A.tp_reified; _ } ->
832 Option.some_if (tp_reified = A.Erased) name)
834 and has_non_tparam_generics env (targs : Aast.hint list) =
835 let erased_tparams = get_erased_tparams env in
836 List.exists targs ~f:(function
837 | (_, Aast.Happly ((_, id), _))
838 when List.mem ~equal:String.equal erased_tparams id ->
839 false
840 | _ -> true)
842 and has_non_tparam_generics_targs env (targs : Tast.targ list) =
843 let erased_tparams = get_erased_tparams env in
844 List.exists targs ~f:(function
845 | (_, (_, Aast.Happly ((_, id), _)))
846 when List.mem ~equal:String.equal erased_tparams id ->
847 false
848 | _ -> true)
850 and emit_reified_targs env pos targs =
851 let len = List.length targs in
852 let scope = Emit_env.get_scope env in
853 let current_fun_tparams = Ast_scope.Scope.get_fun_tparams scope in
854 let current_cls_tparam = Ast_scope.Scope.get_class_tparams scope in
855 let is_in_lambda = Ast_scope.Scope.is_in_lambda (Emit_env.get_scope env) in
856 let is_soft { A.tp_user_attributes = ua; _ } =
857 List.exists ua ~f:(function { A.ua_name = n; _ } ->
858 snd n = SN.UserAttributes.uaSoft)
860 let is_same tparam =
861 List.length tparam = len
862 && List.for_all2_exn tparam targs ~f:(fun tp ta ->
863 match (tp, ta) with
864 | ({ A.tp_name = (_, name1); _ }, (_, A.Happly ((_, name2), []))) ->
865 name1 = name2 && not (is_soft tp)
866 | (_, _) -> false)
868 if (not is_in_lambda) && is_same current_fun_tparams then
869 instr_cgetl (Local.Named SU.Reified.reified_generics_local_name)
870 else if (not is_in_lambda) && is_same current_cls_tparam.A.c_tparam_list then
871 gather
873 instr_checkthis;
874 instr_baseh;
875 instr_querym
877 QueryOp.CGet
878 (MemberKey.PT
879 (Hhbc_id.Prop.from_raw_string SU.Reified.reified_prop_name));
881 (* TODO(T31677864): If the full generic array is static and does not require
882 * resolution, emit it as static array *)
883 else
884 gather
886 gather
887 @@ List.map targs ~f:(fun h ->
888 fst @@ emit_reified_arg env ~isas:false pos h);
889 instr_lit_const
890 ( if hack_arr_dv_arrs () then
891 NewVecArray len
892 else
893 NewVArray len );
896 and emit_new
899 (cid : Tast.class_id)
900 (targs : Tast.targ list)
901 (args : Tast.expr list)
902 (uargs : Tast.expr list) =
903 if has_inout_args args then
904 Emit_fatal.raise_fatal_parse pos "Unexpected inout arg in new expr";
905 let scope = Emit_env.get_scope env in
906 (* If `new self` or `new parent `when self or parent respectively has
907 * reified generics, do not resolve *)
908 let resolve_self =
909 match cid with
910 | (_, A.CIexpr (_, A.Id (_, n))) when SU.is_self n ->
911 (Ast_scope.Scope.get_class_tparams scope).A.c_tparam_list
912 |> List.for_all ~f:(fun t -> t.A.tp_reified = A.Erased)
913 | (_, A.CIexpr (_, A.Id (_, n))) when SU.is_parent n ->
914 let cls = Ast_scope.Scope.get_class scope in
915 Option.value_map cls ~default:true ~f:(fun cls ->
916 match cls.A.c_extends with
917 | (_, Aast.Happly (_, l)) :: _ ->
918 not @@ has_non_tparam_generics env l
919 | _ -> true)
920 | _ -> true
922 let cexpr = class_id_to_class_expr ~resolve_self scope cid in
923 let (cexpr, has_generics) =
924 match cexpr with
925 | Class_id (_, name) ->
926 begin
927 match emit_reified_type_opt env pos name with
928 | Some instrs ->
929 if not @@ List.is_empty targs then
930 Emit_fatal.raise_fatal_parse
932 "Cannot have higher kinded reified generics";
933 (Class_reified instrs, H.MaybeGenerics)
934 | None when not (has_non_tparam_generics_targs env targs) ->
935 (cexpr, H.NoGenerics)
936 | None -> (cexpr, H.HasGenerics)
938 | _ -> (cexpr, H.NoGenerics)
940 let newobj_instrs =
941 match cexpr with
942 (* Special case for statically-known class *)
943 | Class_id id ->
944 let fq_id = Hhbc_id.Class.elaborate_id id in
945 Emit_symbol_refs.add_class (Hhbc_id.Class.to_raw_string fq_id);
946 begin
947 match has_generics with
948 | H.NoGenerics -> gather [emit_pos pos; instr_newobjd fq_id]
949 | H.HasGenerics ->
950 gather
952 emit_pos pos;
953 emit_reified_targs env pos (List.map ~f:snd targs);
954 instr_newobjrd fq_id;
956 | H.MaybeGenerics ->
957 failwith "Internal error: This case should have been transformed"
959 | Class_special cls_ref -> gather [emit_pos pos; instr_newobjs cls_ref]
960 | Class_reified instrs when has_generics = H.MaybeGenerics ->
961 gather [instrs; instr_classgetts; instr_newobjr]
962 | _ -> gather [emit_load_class_ref env pos cexpr; instr_newobj]
964 Scope.with_unnamed_locals
965 @@ fun () ->
966 let (instr_args, _) = emit_args_and_inout_setters env args in
967 let instr_uargs =
968 match uargs with
969 | [] -> empty
970 | uargs :: _ -> emit_expr env uargs
972 ( empty,
973 gather
975 newobj_instrs;
976 instr_dup;
977 instr_nulluninit;
978 instr_nulluninit;
979 instr_args;
980 instr_uargs;
981 emit_pos pos;
982 instr_fcallctor
983 (get_fcall_args ~lock_while_unwinding:true args uargs None);
984 instr_popc;
985 instr_lockobj;
987 empty )
989 (* TODO(T36697624) more efficient bytecode for static records *)
990 and emit_record env pos id is_array es =
991 let fq_id = Hhbc_id.Class.elaborate_id id in
992 let instr =
993 if is_array then
994 instr_new_recordarray
995 else
996 instr_new_record
998 Emit_symbol_refs.add_class (Hhbc_id.Class.to_raw_string fq_id);
999 emit_struct_array env pos es (instr fq_id)
1001 and emit_clone env expr = gather [emit_expr env expr; instr_clone]
1003 and emit_shape
1004 env (expr : Tast.expr) (fl : (Ast_defs.shape_field_name * Tast.expr) list)
1006 let p = fst expr in
1007 let fl =
1008 List.map fl ~f:(fun (fn, e) ->
1009 ((p, extract_shape_field_name_pstring env p fn), e))
1011 emit_expr env (p, A.Darray (None, fl))
1013 and emit_call_expr env pos e targs args uargs async_eager_label =
1014 match (snd e, targs, args, uargs) with
1015 | (A.Id (_, id), _, [(_, A.String data)], [])
1016 when id = SN.SpecialFunctions.hhas_adata ->
1017 let v = Typed_value.HhasAdata data in
1018 emit_pos_then pos @@ instr (ILitConst (TypedValue v))
1019 | (A.Id (_, id), _, _, []) when id = SN.PseudoFunctions.isset ->
1020 emit_call_isset_exprs env pos args
1021 | (A.Id (_, id), _, [arg1], []) when id = SN.PseudoFunctions.empty ->
1022 emit_call_empty_expr env pos arg1
1023 | (A.Id (_, id), _, ([_; _] | [_; _; _]), [])
1024 when id = SN.FB.idx && not (jit_enable_rename_function ()) ->
1025 emit_idx env pos args
1026 | (A.Id (_, id), _, [arg1], []) when id = SN.EmitterSpecialFunctions.eval ->
1027 emit_eval env pos arg1
1028 | (A.Id (_, s), _, [(_, A.String c1); (_, A.String c2)], [])
1029 when is_global_namespace env && s = SN.EmitterSpecialFunctions.class_alias
1031 gather [emit_pos pos; instr_true; instr_alias_cls c1 c2]
1032 | (A.Id (_, s), _, [(_, A.String c1); (_, A.String c2); arg3], [])
1033 when is_global_namespace env && s = SN.EmitterSpecialFunctions.class_alias
1035 gather [emit_expr env arg3; emit_pos pos; instr_alias_cls c1 c2]
1036 | (A.Id (_, id), _, [arg1], [])
1037 when id = SN.EmitterSpecialFunctions.set_frame_metadata ->
1038 gather
1040 emit_expr env arg1;
1041 emit_pos pos;
1042 instr_popl (Local.Named "$86metadata");
1043 instr_null;
1045 | (A.Id (_, s), _, [], [])
1046 when s = SN.PseudoFunctions.exit || s = SN.PseudoFunctions.die ->
1047 emit_pos_then pos @@ emit_exit env None
1048 | (A.Id (_, s), _, [arg1], [])
1049 when s = SN.PseudoFunctions.exit || s = SN.PseudoFunctions.die ->
1050 emit_pos_then pos @@ emit_exit env (Some arg1)
1051 | (_, _, _, _) ->
1052 let instrs = emit_call env pos e targs args uargs async_eager_label in
1053 emit_pos_then pos instrs
1055 and emit_known_class_id id =
1056 let fq_id = Hhbc_id.Class.elaborate_id id in
1057 Emit_symbol_refs.add_class (Hhbc_id.Class.to_raw_string fq_id);
1058 gather [instr_string (Hhbc_id.Class.to_raw_string fq_id); instr_classgetc]
1060 and emit_load_class_ref env pos cexpr =
1061 emit_pos_then pos
1063 match cexpr with
1064 | Class_special SpecialClsRef.Self -> instr_self
1065 | Class_special SpecialClsRef.Static -> instr_lateboundcls
1066 | Class_special SpecialClsRef.Parent -> instr_parent
1067 | Class_id id -> emit_known_class_id id
1068 | Class_expr expr ->
1069 gather [emit_pos pos; emit_expr env expr; instr_classgetc]
1070 | Class_reified instrs -> gather [emit_pos pos; instrs; instr_classgetc]
1072 and emit_load_class_const env pos (cexpr : Ast_class_expr.class_expr) id =
1073 let load_const =
1074 if SU.is_class id then
1075 instr (IMisc ClassName)
1076 else
1077 instr (ILitConst (ClsCns (Hhbc_id.Const.from_ast_name id)))
1079 gather [emit_load_class_ref env pos cexpr; load_const]
1081 and emit_class_expr
1082 env (cexpr : Ast_class_expr.class_expr) (prop : Tast.class_get_expr) =
1083 let load_prop () =
1084 match prop with
1085 | A.CGstring (pos, id) ->
1086 emit_pos_then pos @@ instr_string (SU.Locals.strip_dollar id)
1087 | A.CGexpr e -> emit_expr env e
1089 let aux e =
1090 let cexpr_local = emit_expr env e in
1091 ( empty,
1092 gather
1094 cexpr_local;
1095 Scope.stash_top_in_unnamed_local load_prop;
1096 instr_classgetc;
1099 match cexpr with
1100 | Class_expr
1101 ((_, (A.BracedExpr _ | A.Call _ | A.Binop _ | A.Class_get _)) as e) ->
1102 aux e
1103 | Class_expr ((_, A.Lvar (_, id)) as e) when Local_id.get_name id = "$this"
1105 aux e
1106 | _ ->
1107 let pos =
1108 match prop with
1109 | A.CGstring (pos, _) -> pos
1110 | A.CGexpr ((pos, _), _) -> pos
1112 (load_prop (), emit_load_class_ref env pos cexpr)
1114 and emit_class_get env qop (cid : Tast.class_id) (prop : Tast.class_get_expr) =
1115 let cexpr =
1116 class_id_to_class_expr ~resolve_self:false (Emit_env.get_scope env) cid
1118 gather
1120 of_pair @@ emit_class_expr env cexpr prop;
1121 (match qop with
1122 | QueryOp.CGet -> instr_cgets
1123 | QueryOp.CGetQuiet -> failwith "emit_class_get: CGetQuiet"
1124 | QueryOp.Isset -> instr_issets
1125 | QueryOp.Empty -> instr_emptys
1126 | QueryOp.InOut -> failwith "emit_class_get: InOut");
1129 (* Class constant <cid>::<id>.
1130 * We follow the logic for the Construct::KindOfClassConstantExpression
1131 * case in emitter.cpp
1133 and emit_class_const env pos (cid : Tast.class_id) (_, id) :
1134 Instruction_sequence.t =
1135 let cexpr =
1136 class_id_to_class_expr ~resolve_self:true (Emit_env.get_scope env) cid
1138 let cexpr =
1139 match cexpr with
1140 | Class_id (_, name) ->
1141 Option.value ~default:cexpr (get_reified_var_cexpr env pos name)
1142 | _ -> cexpr
1144 match cexpr with
1145 | Class_id cid -> emit_class_const_impl cid id
1146 | _ -> emit_load_class_const env pos cexpr id
1148 and emit_class_const_impl cid id =
1149 let fq_id = Hhbc_id.Class.elaborate_id cid in
1150 let fq_id_str = Hhbc_id.Class.to_raw_string fq_id in
1151 emit_pos_then (fst cid)
1153 if SU.is_class id then
1154 instr_string fq_id_str
1155 else (
1156 Emit_symbol_refs.add_class fq_id_str;
1157 instr (ILitConst (ClsCnsD (Hhbc_id.Const.from_ast_name id, fq_id)))
1160 and emit_yield env pos = function
1161 | A.AFvalue e -> gather [emit_expr env e; emit_pos pos; instr_yield]
1162 | A.AFkvalue (e1, e2) ->
1163 gather [emit_expr env e1; emit_expr env e2; emit_pos pos; instr_yieldk]
1165 and emit_string2 env pos (exprs : Tast.expr list) =
1166 match exprs with
1167 | [e] -> gather [emit_expr env e; emit_pos pos; instr (IOp CastString)]
1168 | e1 :: e2 :: es ->
1169 gather
1170 @@ [
1171 emit_two_exprs env (fst (fst e1)) e1 e2;
1172 emit_pos pos;
1173 instr (IOp Concat);
1174 gather
1175 (List.map es (fun e ->
1176 gather [emit_expr env e; emit_pos pos; instr (IOp Concat)]));
1178 | [] -> failwith "String2 with zero arguments is impossible"
1180 and emit_lambda (env : Emit_env.t) (fundef : Tast.fun_) (ids : Aast.lid list) =
1181 (* Closure conversion puts the class number used for CreateCl in the "name"
1182 * of the function definition *)
1183 let fundef_name = snd fundef.A.f_name in
1184 let class_num = int_of_string fundef_name in
1185 let explicit_use = SSet.mem fundef_name (Emit_env.get_explicit_use_set ()) in
1186 let is_in_lambda = Ast_scope.Scope.is_in_lambda (Emit_env.get_scope env) in
1187 gather
1189 gather
1190 @@ List.map ids (fun (pos, id) ->
1191 match SU.Reified.is_captured_generic @@ Local_id.get_name id with
1192 | Some (is_fun, i) ->
1193 if is_in_lambda then
1194 instr_cgetl
1195 (Local.Named
1196 (SU.Reified.reified_generic_captured_name is_fun i))
1197 else
1198 emit_reified_generic_instrs Pos.none ~is_fun i
1199 | None ->
1200 let lid = get_local env (pos, Local_id.get_name id) in
1201 if explicit_use then
1202 instr_cgetl lid
1203 else
1204 instr_cugetl lid);
1205 instr (IMisc (CreateCl (List.length ids, class_num)));
1208 and emit_id (env : Emit_env.t) ((p, s) : Aast.sid) =
1209 match String.uppercase s with
1210 | "__FILE__" -> instr (ILitConst File)
1211 | "__DIR__" -> instr (ILitConst Dir)
1212 | "__CLASS__" -> gather [instr_self; instr_classname]
1213 | "__METHOD__" -> instr (ILitConst Method)
1214 | "__FUNCTION_CREDENTIAL__" -> instr (ILitConst FuncCred)
1215 | "__LINE__" ->
1216 (* If the expression goes on multi lines, we return the last line *)
1217 let (_, line, _, _) = Pos.info_pos_extended p in
1218 instr_int line
1219 | "__NAMESPACE__" ->
1220 let ns = Emit_env.get_namespace env in
1221 instr_string (Option.value ~default:"" ns.Namespace_env.ns_name)
1222 | "__COMPILER_FRONTEND__" -> instr_string "hackc"
1223 | "EXIT"
1224 | "DIE" ->
1225 emit_exit env None
1226 | _ ->
1227 let fq_id = Hhbc_id.Const.from_ast_name s in
1228 Emit_symbol_refs.add_constant s;
1229 emit_pos_then p @@ instr (ILitConst (CnsE fq_id))
1231 and rename_xhp (p, s) = (p, SU.Xhp.mangle s)
1233 and emit_xhp (env : Emit_env.t) annot id attributes (children : Tast.expr list)
1235 (* Translate into a constructor call. The arguments are:
1236 * 1) struct-like array of attributes
1237 * 2) vec-like array of children
1238 * 3) filename, for debugging
1239 * 4) line number, for debugging
1241 * Spread operators are injected into the attributes array with placeholder
1242 * keys that the runtime will interpret as a spread. These keys are not
1243 * parseable as user-specified attributes, so they will never collide.
1245 let (pos, _) = annot in
1246 let create_spread p id = (p, "...$" ^ string_of_int id) in
1247 let convert_attr (spread_id, attrs) = function
1248 | A.Xhp_simple (name, v) ->
1249 let attr = (Ast_defs.SFlit_str name, v) in
1250 (spread_id, attr :: attrs)
1251 | A.Xhp_spread e ->
1252 let ((p, _), _) = e in
1253 let attr = (Ast_defs.SFlit_str (create_spread p spread_id), e) in
1254 (spread_id + 1, attr :: attrs)
1256 let (_, attributes) =
1257 List.fold_left ~f:convert_attr ~init:(0, []) attributes
1259 let attribute_map = (annot, A.Shape (List.rev attributes)) in
1260 let children_vec = (annot, A.Varray (None, children)) in
1261 let filename = (annot, A.Id (pos, "__FILE__")) in
1262 let line = (annot, A.Id (pos, "__LINE__")) in
1263 let renamed_id = rename_xhp id in
1264 Emit_symbol_refs.add_class (snd renamed_id);
1265 emit_expr env
1266 @@ ( annot,
1267 A.New
1268 ( (annot, A.CI renamed_id),
1270 [attribute_map; children_vec; filename; line],
1272 annot ) )
1274 and emit_import env annot (flavor : Aast.import_flavor) (e : Tast.expr) =
1275 let (pos, _) = annot in
1276 let inc = parse_include e in
1277 Emit_symbol_refs.add_include inc;
1278 let (e, import_op) =
1279 match flavor with
1280 | Aast.Include -> (e, IIncludeEvalDefine Incl)
1281 | Aast.Require -> (e, IIncludeEvalDefine Req)
1282 | Aast.IncludeOnce -> (e, IIncludeEvalDefine InclOnce)
1283 | Aast.RequireOnce ->
1284 let include_roots =
1285 Hhbc_options.include_roots !Hhbc_options.compiler_options
1287 (match
1288 Hhas_symbol_refs.resolve_to_doc_root_relative inc ~include_roots
1289 with
1290 | Hhas_symbol_refs.DocRootRelative path ->
1291 ((annot, A.String path), IIncludeEvalDefine ReqDoc)
1292 | _ -> (e, IIncludeEvalDefine ReqOnce))
1294 gather [emit_expr env e; emit_pos pos; instr import_op]
1296 and emit_call_isset_expr env outer_pos (expr : Tast.expr) =
1297 let ((pos, _), expr_) = expr in
1298 match expr_ with
1299 | A.Array_get ((_, A.Lvar (_, x)), Some e)
1300 when Local_id.get_name x = SN.Superglobals.globals ->
1301 gather [emit_expr env e; emit_pos outer_pos; instr_issetg]
1302 | A.Array_get (base_expr, opt_elem_expr) ->
1303 fst (emit_array_get env pos QueryOp.Isset base_expr opt_elem_expr)
1304 | A.Class_get (cid, id) -> emit_class_get env QueryOp.Isset cid id
1305 | A.Obj_get (expr, prop, nullflavor) ->
1306 fst (emit_obj_get env pos QueryOp.Isset expr prop nullflavor)
1307 | A.Lvar (_, n)
1308 when SN.Superglobals.is_superglobal (Local_id.get_name n)
1309 || Local_id.get_name n = SN.Superglobals.globals ->
1310 gather
1312 emit_pos outer_pos;
1313 instr_string @@ SU.Locals.strip_dollar (Local_id.get_name n);
1314 emit_pos outer_pos;
1315 instr_issetg;
1317 | A.Lvar ((_, name) as id)
1318 when is_local_this env name && not (Emit_env.get_needs_local_this env) ->
1319 gather
1321 emit_pos outer_pos;
1322 emit_local ~notice:NoNotice env id;
1323 emit_pos outer_pos;
1324 instr_istypec OpNull;
1325 instr_not;
1327 | A.Lvar (pos, id) ->
1328 emit_pos_then outer_pos
1329 @@ instr (IIsset (IssetL (get_local env (pos, Local_id.get_name id))))
1330 | _ -> gather [emit_expr env expr; instr_istypec OpNull; instr_not]
1332 and emit_call_empty_expr env outer_pos ((annot, expr_) as expr) =
1333 let (pos, _) = annot in
1334 match expr_ with
1335 | A.Array_get ((_, A.Lvar (_, x)), Some e)
1336 when Local_id.get_name x = SN.Superglobals.globals ->
1337 gather [emit_expr env e; emit_pos outer_pos; instr_emptyg]
1338 | A.Array_get (base_expr, opt_elem_expr) ->
1339 fst (emit_array_get env pos QueryOp.Empty base_expr opt_elem_expr)
1340 | A.Class_get (cid, id) -> emit_class_get env QueryOp.Empty cid id
1341 | A.Obj_get (expr, prop, nullflavor) ->
1342 fst (emit_obj_get env pos QueryOp.Empty expr prop nullflavor)
1343 | A.Lvar (_, id)
1344 when SN.Superglobals.is_superglobal (Local_id.get_name id)
1345 || Local_id.get_name id = SN.Superglobals.globals ->
1346 gather
1348 instr_string @@ SU.Locals.strip_dollar (Local_id.get_name id);
1349 emit_pos outer_pos;
1350 instr_emptyg;
1352 | A.Lvar (pos, id) ->
1353 if (not (is_local_this env id)) || Emit_env.get_needs_local_this env then
1354 emit_pos_then outer_pos
1355 @@ instr_emptyl (get_local env (pos, Local_id.get_name id))
1356 else
1357 gather
1359 emit_pos pos;
1360 instr (IMisc (BareThis NoNotice));
1361 emit_pos outer_pos;
1362 instr_not;
1364 | _ -> gather [emit_expr env expr; instr_not]
1366 and emit_unset_expr env expr =
1367 emit_lval_op_nonlist env (fst (fst expr)) LValOp.Unset expr empty 0
1369 and emit_set_range_expr env pos name kind args =
1370 let raise_fatal msg =
1371 Emit_fatal.raise_fatal_parse pos (Printf.sprintf "%s %s" name msg)
1373 let (range_op, size, allow_count) = kind in
1374 let (base, offset, src, args) =
1375 match args with
1376 | b :: o :: s :: rest -> (b, o, s, rest)
1377 | _ -> raise_fatal "expects at least 3 arguments"
1379 let count_instrs =
1380 match (args, allow_count) with
1381 | ([c], true) -> emit_expr env c
1382 | ([], _) -> instr_int (-1)
1383 | (_, false) -> raise_fatal "expects no more than 3 arguments"
1384 | (_, true) -> raise_fatal "expects no more than 4 arguments"
1386 let (base_expr, cls_expr, base_setup, base_stack, cls_stack) =
1387 emit_base ~notice:Notice ~is_object:false env MemberOpMode.Define 3 3 base
1389 gather
1391 base_expr;
1392 cls_expr;
1393 emit_expr env offset;
1394 emit_expr env src;
1395 count_instrs;
1396 base_setup;
1397 instr (IFinal (SetRangeM (base_stack + cls_stack, range_op, size)));
1400 and emit_call_isset_exprs env pos (exprs : Tast.expr list) =
1401 match exprs with
1402 | [] ->
1403 Emit_fatal.raise_fatal_parse pos "Cannot use isset() without any arguments"
1404 | [expr] -> emit_call_isset_expr env pos expr
1405 | _ ->
1406 let n = List.length exprs in
1407 let its_done = Label.next_regular () in
1408 gather
1410 gather
1411 @@ List.mapi exprs (fun i expr ->
1412 gather
1414 emit_call_isset_expr env pos expr;
1415 ( if i < n - 1 then
1416 gather [instr_dup; instr_jmpz its_done; instr_popc]
1417 else
1418 empty );
1420 instr_label its_done;
1423 and emit_exit env (expr_opt : Tast.expr option) =
1424 gather
1426 (match expr_opt with
1427 | None -> instr_int 0
1428 | Some e -> emit_expr env e);
1429 instr_exit;
1432 and emit_idx env pos (es : Tast.expr list) =
1433 let default =
1434 if List.length es = 2 then
1435 instr_null
1436 else
1437 empty
1439 gather [emit_exprs env es; emit_pos pos; default; instr_idx]
1441 and emit_eval env pos e = gather [emit_expr env e; emit_pos pos; instr_eval]
1443 and emit_xhp_obj_get env pos (_, ty) (e : Tast.expr) s nullflavor =
1444 let annot = (pos, ty) in
1445 let fn_name =
1446 (annot, A.Obj_get (e, (annot, A.Id (pos, "getAttribute")), nullflavor))
1448 let args = [(annot, A.String (SU.Xhp.clean s))] in
1449 emit_call env pos fn_name [] args [] None
1451 and try_inline_gen_call env (e : Tast.expr) =
1452 if not (can_inline_gen_functions ()) then
1453 None
1454 else
1455 match snd e with
1456 | A.Call (_, (_, A.Id (_, s)), _, [arg], [])
1457 when SU.strip_global_ns s = "gena" ->
1458 Some (inline_gena_call env arg)
1459 | _ -> None
1461 (* emits iteration over the ~collection where loop body is
1462 produced by ~f *)
1463 and emit_iter ~collection f =
1464 Scope.with_unnamed_locals_and_iterators
1465 @@ fun () ->
1466 let iter = Iterator.get_iterator () in
1467 let value_local = Local.get_unnamed_local () in
1468 let key_local = Local.get_unnamed_local () in
1469 let loop_end = Label.next_regular () in
1470 let loop_next = Label.next_regular () in
1471 let iter_init =
1472 gather [collection; instr_iterinitk iter loop_end value_local key_local]
1474 let iterate =
1475 gather
1477 instr_label loop_next;
1478 f value_local key_local;
1479 instr_iternextk iter loop_next value_local key_local;
1482 let iter_done =
1483 gather
1484 [instr_unsetl value_local; instr_unsetl key_local; instr_label loop_end]
1486 (iter_init, iterate, iter_done)
1488 and inline_gena_call env (arg : Tast.expr) =
1489 Local.scope
1490 @@ fun () ->
1491 (* convert input to array *)
1492 let load_array = emit_expr env arg in
1493 Scope.with_unnamed_local
1494 @@ fun arr_local ->
1495 let async_eager_label = Label.next_regular () in
1496 (* before *)
1497 ( gather
1499 load_array;
1500 ( if hack_arr_dv_arrs () then
1501 instr_cast_dict
1502 else
1503 instr_cast_darray );
1504 instr_popl arr_local;
1506 (* inner *)
1507 gather
1509 instr_nulluninit;
1510 instr_nulluninit;
1511 instr_nulluninit;
1512 instr_cgetl arr_local;
1513 instr_fcallclsmethodd
1514 (make_fcall_args ~async_eager_label 1)
1515 (Hhbc_id.Method.from_raw_string
1516 ( if hack_arr_dv_arrs () then
1517 "fromDict"
1518 else
1519 "fromDArray" ))
1520 (Hhbc_id.Class.from_raw_string "HH\\AwaitAllWaitHandle");
1521 instr_await;
1522 instr_label async_eager_label;
1523 instr_popc;
1524 ( emit_iter ~collection:(instr_cgetl arr_local)
1525 @@ fun value_local key_local ->
1526 gather
1528 (* generate code for
1529 arr_local[key_local] = WHResult (value_local) *)
1530 instr_cgetl value_local;
1531 instr_whresult;
1532 instr_basel arr_local MemberOpMode.Define;
1533 instr_setm 0 (MemberKey.EL key_local);
1534 instr_popc;
1535 ] );
1537 (* after *)
1538 instr_pushl arr_local )
1540 and emit_await env pos (expr : Tast.expr) =
1541 match try_inline_gen_call env expr with
1542 | Some r -> r
1543 | None ->
1544 let after_await = Label.next_regular () in
1545 let instrs =
1546 match snd expr with
1547 | A.Call (_, e, targs, args, uargs) ->
1548 emit_call_expr env pos e targs args uargs (Some after_await)
1549 | _ -> emit_expr env expr
1551 gather
1553 instrs;
1554 emit_pos pos;
1555 instr_dup;
1556 instr_istypec OpNull;
1557 instr_jmpnz after_await;
1558 instr_await;
1559 instr_label after_await;
1562 and emit_callconv _env kind _e =
1563 match kind with
1564 | Ast_defs.Pinout ->
1565 failwith "emit_callconv: This should have been caught at emit_arg"
1567 and get_reified_var_cexpr env pos name : Ast_class_expr.class_expr option =
1568 match emit_reified_type_opt env pos name with
1569 | None -> None
1570 | Some instrs ->
1571 Some
1572 (Class_reified
1573 (gather
1575 instrs;
1576 instr_basec 0 MemberOpMode.Warn;
1577 instr_querym 1 QueryOp.CGet (MemberKey.ET "classname");
1580 and emit_reified_generic_instrs pos ~is_fun index =
1581 let base =
1582 if is_fun then
1583 instr_basel
1584 (Local.Named SU.Reified.reified_generics_local_name)
1585 MemberOpMode.Warn
1586 else
1587 gather
1589 instr_checkthis;
1590 instr_baseh;
1591 instr_dim_warn_pt
1592 @@ Hhbc_id.Prop.from_raw_string SU.Reified.reified_prop_name;
1595 emit_pos_then pos
1596 @@ gather
1597 [base; instr_querym 0 QueryOp.CGet (MemberKey.EI (Int64.of_int index))]
1599 and emit_reified_type_opt (env : Emit_env.t) pos name =
1600 let is_in_lambda = Ast_scope.Scope.is_in_lambda (Emit_env.get_scope env) in
1601 let cget_instr is_fun i =
1602 instr_cgetl
1603 (Local.Named (SU.Reified.reified_generic_captured_name is_fun i))
1605 let check is_soft =
1606 if not is_soft then
1608 else
1609 Emit_fatal.raise_fatal_parse
1611 ( name
1612 ^ " is annotated to be a soft reified generic,"
1613 ^ " it cannot be used until the __Soft annotation is removed" )
1615 let rec aux ~is_fun =
1616 match is_reified_tparam ~is_fun env name with
1617 | Some (i, is_soft) ->
1618 check is_soft;
1619 Some
1620 ( if is_in_lambda then
1621 cget_instr is_fun i
1622 else
1623 emit_reified_generic_instrs pos ~is_fun i )
1624 | None ->
1625 if is_fun then
1626 aux ~is_fun:false
1627 else
1628 None
1630 aux ~is_fun:true
1632 and emit_reified_type env pos name =
1633 match emit_reified_type_opt env pos name with
1634 | Some instrs -> instrs
1635 | None -> Emit_fatal.raise_fatal_runtime Pos.none "Invalid reified param"
1637 and emit_expr (env : Emit_env.t) (expr : Tast.expr) =
1638 let (((pos, _) as annot), expr_) = expr in
1639 match expr_ with
1640 | A.Float _
1641 | A.String _
1642 | A.Int _
1643 | A.Null
1644 | A.False
1645 | A.True ->
1646 let v =
1647 Ast_constant_folder.expr_to_typed_value (Emit_env.get_namespace env) expr
1649 emit_pos_then pos @@ instr (ILitConst (TypedValue v))
1650 | A.PrefixedString (_, e)
1651 | A.ParenthesizedExpr e ->
1652 emit_expr env e
1653 | A.Lvar ((pos, _) as lid) ->
1654 gather [emit_pos pos; emit_local ~notice:Notice env lid]
1655 | A.Class_const (cid, id) -> emit_class_const env pos cid id
1656 | A.Unop (op, e) -> emit_unop env pos op e
1657 | A.Binop (op, e1, e2) -> emit_binop env annot op e1 e2
1658 | A.Pipe (_, e1, e2) -> emit_pipe env e1 e2
1659 | A.Is (e, h) -> gather [emit_expr env e; emit_is env pos h]
1660 | A.As (e, h, is_nullable) -> emit_as env pos e h is_nullable
1661 | A.Cast ((_, hint), e) -> emit_cast env pos hint e
1662 | A.Eif (etest, etrue, efalse) ->
1663 emit_conditional_expression env pos etest etrue efalse
1664 | A.Expr_list es -> gather @@ List.map es ~f:(emit_expr env)
1665 | A.Array_get ((_, A.Lvar (_, x)), Some e)
1666 when Local_id.get_name x = SN.Superglobals.globals ->
1667 gather [emit_expr env e; emit_pos pos; instr (IGet CGetG)]
1668 | A.Array_get (base_expr, opt_elem_expr) ->
1669 fst (emit_array_get env pos QueryOp.CGet base_expr opt_elem_expr)
1670 | A.Obj_get (expr, prop, nullflavor) ->
1671 fst (emit_obj_get env pos QueryOp.CGet expr prop nullflavor)
1672 | A.Call (_, e, targs, args, uargs) ->
1673 emit_call_expr env pos e targs args uargs None
1674 | A.New (cid, targs, args, uargs, _constructor_annot) ->
1675 emit_new env pos cid targs args uargs
1676 | A.Record (cid, is_array, es) ->
1677 let es2 = List.map ~f:(fun (e1, e2) -> A.AFkvalue (e1, e2)) es in
1678 emit_record env pos cid is_array es2
1679 | A.Array es -> emit_pos_then pos @@ emit_collection env expr es
1680 | A.Darray (ta, es) ->
1681 emit_pos_then pos
1683 let es2 = List.map ~f:(fun (e1, e2) -> A.AFkvalue (e1, e2)) es in
1684 let darray_e = (fst expr, A.Darray (ta, es)) in
1685 emit_collection env darray_e es2
1686 | A.Varray (ta, es) ->
1687 emit_pos_then pos
1689 let es2 = List.map ~f:(fun e -> A.AFvalue e) es in
1690 let varray_e = (fst expr, A.Varray (ta, es)) in
1691 emit_collection env varray_e es2
1692 | A.Collection ((pos, name), _, fields) ->
1693 emit_named_collection_str env expr pos name fields
1694 | A.ValCollection (name, _, el) ->
1695 let fields = List.map el ~f:(fun e -> A.AFvalue e) in
1696 let emit n = emit_named_collection env expr pos n fields in
1697 (match name with
1698 | A.Vector -> emit CollectionType.Vector
1699 | A.ImmVector -> emit CollectionType.ImmVector
1700 | A.Set -> emit CollectionType.Set
1701 | A.ImmSet -> emit CollectionType.ImmSet
1702 | _ -> emit_collection env expr fields)
1703 | A.Pair (e1, e2) ->
1704 let fields = [A.AFvalue e1; A.AFvalue e2] in
1705 emit_named_collection env expr pos CollectionType.Pair fields
1706 | A.KeyValCollection (name, _, fields) ->
1707 let fields = List.map fields ~f:(fun (e1, e2) -> A.AFkvalue (e1, e2)) in
1708 let emit n = emit_named_collection env expr pos n fields in
1709 (match name with
1710 | A.Map -> emit CollectionType.Map
1711 | A.ImmMap -> emit CollectionType.ImmMap
1712 | _ -> emit_collection env expr fields)
1713 | A.Clone e -> emit_pos_then pos @@ emit_clone env e
1714 | A.Shape fl -> emit_pos_then pos @@ emit_shape env expr fl
1715 | A.Await e -> emit_await env pos e
1716 | A.Yield e -> emit_yield env pos e
1717 | A.Yield_break -> failwith "yield break should be in statement position"
1718 | A.Yield_from _ -> failwith "complex yield_from expression"
1719 | A.Lfun _ ->
1720 failwith
1721 "expected Lfun to be converted to Efun during closure conversion emit_expr"
1722 | A.Efun (fundef, ids) -> emit_pos_then pos @@ emit_lambda env fundef ids
1723 | A.Class_get (cid, id) -> emit_class_get env QueryOp.CGet cid id
1724 | A.String2 es -> emit_string2 env pos es
1725 | A.BracedExpr e -> emit_expr env e
1726 | A.Id id -> emit_pos_then pos @@ emit_id env id
1727 | A.Xml (id, attributes, children) ->
1728 emit_xhp env (fst expr) id attributes children
1729 | A.Callconv (kind, e) -> emit_callconv env kind e
1730 | A.Import (flavor, e) -> emit_import env annot flavor e
1731 | A.Omitted -> empty
1732 | A.Suspend _ -> failwith "Codegen for 'suspend' operator is not supported"
1733 | A.List _ ->
1734 Emit_fatal.raise_fatal_parse
1736 "list() can only be used as an lvar. Did you mean to use tuple()?"
1737 | A.Any -> failwith "Cannot codegen from an Any node"
1738 | A.This
1739 | A.Lplaceholder _
1740 | A.Dollardollar _ ->
1741 failwith "TODO Codegen after naming pass on AAST"
1742 | A.Typename _ -> failwith "Typename should not occur in expressions"
1743 | A.ImmutableVar _ -> failwith "Codegen for 'let' is not supported"
1744 | A.PU_atom _
1745 | A.PU_identifier _ ->
1746 failwith "TODO(T35357243): Pocket Universes syntax must be erased by now"
1747 | A.Fun_id _ -> failwith "TODO Unimplemented expression Fun"
1748 | A.Method_id (_, _) -> failwith "TODO Unimplemented expression Method"
1749 | A.Method_caller (_, _) -> failwith "TODO Unimplemented expression Method"
1750 | A.Smethod_id (_, _) -> failwith "TODO Unimplemented expression Smethod"
1751 | A.Special_func _ -> failwith "TODO Unimplemented expression Special"
1752 | A.Assert _ -> failwith "TODO Unimplemented expression Assert"
1754 and emit_static_collection env ~transform_to_collection pos tv =
1755 let arrprov_enabled =
1756 Hhbc_options.array_provenance !Hhbc_options.compiler_options
1758 let transform_instr =
1759 match transform_to_collection with
1760 | Some collection_type -> instr_colfromarray collection_type
1761 | _ -> empty
1763 let col =
1764 gather [emit_pos pos; instr (ILitConst (TypedValue tv)); transform_instr]
1766 (* This is a nasty hack to make sure that static arrays in a PSF function are tagged
1767 * using dynamic provenance information *)
1769 arrprov_enabled
1770 && Ast_scope.Scope.has_function_attribute
1771 (Emit_env.get_scope env)
1772 "__ProvenanceSkipFrame"
1773 then
1774 gather
1776 instr_nulluninit;
1777 instr_nulluninit;
1778 instr_nulluninit;
1779 col;
1780 instr_fcallfuncd
1781 (make_fcall_args 1)
1782 (Hhbc_id.Function.from_raw_string "HH\\tag_provenance_here");
1784 else
1787 and emit_value_only_collection env pos es constructor =
1788 let limit = max_array_elem_on_stack () in
1789 let inline exprs =
1790 gather
1792 gather
1793 @@ List.map exprs ~f:(function
1794 (* Drop the keys *)
1795 | A.AFkvalue (_, e)
1796 | A.AFvalue e
1797 -> emit_expr env e);
1798 emit_pos pos;
1799 instr @@ ILitConst (constructor @@ List.length exprs);
1802 let outofline exprs =
1803 gather
1804 @@ List.map exprs ~f:(function
1805 (* Drop the keys *)
1806 | A.AFkvalue (_, e)
1807 | A.AFvalue e
1808 -> gather [emit_expr env e; instr_add_new_elemc])
1810 match List.groupi ~break:(fun i _ _ -> i = limit) es with
1811 | [] -> empty
1812 | [x1] -> inline x1
1813 | x1 :: x2 :: _ -> gather [inline x1; outofline x2]
1815 and emit_keyvalue_collection ctype env pos es constructor =
1816 let (transform_instr, add_elem_instr) =
1817 match ctype with
1818 | CollectionType.Dict
1819 | CollectionType.Array ->
1820 (empty, instr_add_new_elemc)
1821 | _ -> (instr_colfromarray ctype, gather [instr_dup; instr_add_elemc])
1823 gather
1825 emit_pos pos;
1826 instr (ILitConst constructor);
1827 gather
1828 (List.map es ~f:(expr_and_new env pos add_elem_instr instr_add_elemc));
1829 emit_pos pos;
1830 transform_instr;
1833 and emit_struct_array env pos es ctor =
1834 let es =
1835 List.map es ~f:(function
1836 | A.AFkvalue (k, v) ->
1837 let ns = Emit_env.get_namespace env in
1838 (* TODO: Consider reusing folded keys from is_struct_init *)
1839 begin
1840 match snd @@ Ast_constant_folder.fold_expr ns k with
1841 | A.String s -> (s, emit_expr env v)
1842 | _ -> failwith "Key must be a string"
1844 | _ -> failwith "impossible")
1846 gather
1847 [gather @@ List.map es ~f:snd; emit_pos pos; ctor @@ List.map es ~f:fst]
1849 (* isPackedInit() returns true if this expression list looks like an
1850 * array with no keys and no ref values *)
1851 and is_packed_init ?(hack_arr_compat = true) es =
1852 let is_only_values =
1853 List.for_all es ~f:(function
1854 | A.AFkvalue _ -> false
1855 | _ -> true)
1857 let keys_are_zero_indexed_properly_formed =
1858 List.foldi es ~init:true ~f:(fun i b f ->
1861 match f with
1862 | A.AFkvalue ((_, A.Int k), _) -> int_of_string k = i
1863 (* arrays with int-like string keys are still considered packed
1864 and should be emitted via NewArray *)
1865 | A.AFkvalue ((_, A.String k), _) when not hack_arr_compat ->
1866 (try int_of_string k = i with Failure _ -> false)
1867 (* True and False are considered 1 and 0, respectively *)
1868 | A.AFkvalue ((_, A.True), _) -> i = 1
1869 | A.AFkvalue ((_, A.False), _) -> i = 0
1870 | A.AFvalue _ -> true
1871 | _ -> false)
1873 let has_bool_keys =
1874 List.exists es ~f:(function
1875 | A.AFkvalue ((_, (A.True | A.False)), _) -> true
1876 | _ -> false)
1878 (is_only_values || keys_are_zero_indexed_properly_formed)
1879 && (not (has_bool_keys && hack_arr_compat && hack_arr_compat_notices ()))
1880 && List.length es > 0
1882 and is_struct_init env es allow_numerics =
1883 let keys = ULS.empty in
1884 let (are_all_keys_non_numeric_strings, keys) =
1885 List.fold_right es ~init:(true, keys) ~f:(fun field (b, keys) ->
1886 match field with
1887 | A.AFkvalue (key, _) ->
1888 let ns = Emit_env.get_namespace env in
1889 begin
1890 match snd @@ Ast_constant_folder.fold_expr ns key with
1891 | A.String s ->
1893 && Option.is_none
1894 @@ Typed_value.string_to_int_opt
1895 ~allow_following:false
1896 ~allow_inf:false
1898 ULS.add keys s )
1899 | _ -> (false, keys)
1901 | _ -> (false, keys))
1903 let num_keys = List.length es in
1904 let has_duplicate_keys = ULS.cardinal keys <> num_keys in
1905 let limit = max_array_elem_on_stack () in
1906 (allow_numerics || are_all_keys_non_numeric_strings)
1907 && (not has_duplicate_keys)
1908 && num_keys <= limit
1909 && num_keys <> 0
1911 (* transform_to_collection argument keeps track of
1912 * what collection to transform to *)
1913 and emit_dynamic_collection env (expr : Tast.expr) es =
1914 let ((pos, _), expr_) = expr in
1915 let count = List.length es in
1916 let emit_collection_helper ctype =
1917 if is_struct_init env es true then
1918 gather
1920 emit_struct_array env pos es instr_newstructdict;
1921 emit_pos pos;
1922 instr_colfromarray ctype;
1924 else
1925 emit_keyvalue_collection ctype env pos es (NewDictArray count)
1927 match expr_ with
1928 | A.ValCollection (A.Vec, _, _)
1929 | A.Collection ((_, "vec"), _, _) ->
1930 emit_value_only_collection env pos es (fun n -> NewVecArray n)
1931 | A.ValCollection (A.Keyset, _, _)
1932 | A.Collection ((_, "keyset"), _, _) ->
1933 emit_value_only_collection env pos es (fun n -> NewKeysetArray n)
1934 | A.KeyValCollection (A.Dict, _, _)
1935 | A.Collection ((_, "dict"), _, _) ->
1936 if is_struct_init env es true then
1937 emit_struct_array env pos es instr_newstructdict
1938 else
1939 emit_keyvalue_collection
1940 CollectionType.Dict
1944 (NewDictArray count)
1945 | A.Collection ((_, name), _, _) when SU.strip_ns name = "Set" ->
1946 emit_collection_helper CollectionType.Set
1947 | A.ValCollection (A.Set, _, _) -> emit_collection_helper CollectionType.Set
1948 | A.Collection ((_, name), _, _) when SU.strip_ns name = "ImmSet" ->
1949 emit_collection_helper CollectionType.ImmSet
1950 | A.ValCollection (A.ImmSet, _, _) ->
1951 emit_collection_helper CollectionType.ImmSet
1952 | A.Collection ((_, name), _, _) when SU.strip_ns name = "Map" ->
1953 emit_collection_helper CollectionType.Map
1954 | A.KeyValCollection (A.Map, _, _) ->
1955 emit_collection_helper CollectionType.Map
1956 | A.Collection ((_, name), _, _) when SU.strip_ns name = "ImmMap" ->
1957 emit_collection_helper CollectionType.ImmMap
1958 | A.KeyValCollection (A.ImmMap, _, _) ->
1959 emit_collection_helper CollectionType.ImmMap
1960 | A.Varray _ ->
1961 emit_value_only_collection env pos es (fun n ->
1962 if hack_arr_dv_arrs () then
1963 NewVecArray n
1964 else
1965 NewVArray n)
1966 | A.Darray _ ->
1967 if is_struct_init env es false then
1968 emit_struct_array env pos es (fun arg ->
1969 emit_pos_then pos
1971 if hack_arr_dv_arrs () then
1972 instr_newstructdict arg
1973 else
1974 instr_newstructdarray arg)
1975 else
1976 emit_keyvalue_collection
1977 CollectionType.Array
1981 ( if hack_arr_dv_arrs () then
1982 NewDictArray count
1983 else
1984 NewDArray count )
1985 | _ ->
1986 (* From here on, we're only dealing with PHP arrays *)
1987 if is_packed_init es then
1988 emit_value_only_collection env pos es (fun n -> NewPackedArray n)
1989 else if is_struct_init env es false then
1990 emit_struct_array env pos es instr_newstructarray
1991 else if is_packed_init ~hack_arr_compat:false es then
1992 emit_keyvalue_collection CollectionType.Array env pos es (NewArray count)
1993 else
1994 emit_keyvalue_collection
1995 CollectionType.Array
1999 (NewMixedArray count)
2001 and emit_named_collection_str env (expr : Tast.expr) pos name fields =
2002 let name = SU.Types.fix_casing @@ SU.strip_ns name in
2003 let ctype =
2004 match name with
2005 | "dict" -> CollectionType.Dict
2006 | "vec" -> CollectionType.Vec
2007 | "keyset" -> CollectionType.Keyset
2008 | "Vector" -> CollectionType.Vector
2009 | "ImmVector" -> CollectionType.ImmVector
2010 | "Map" -> CollectionType.Map
2011 | "ImmMap" -> CollectionType.ImmMap
2012 | "Set" -> CollectionType.Set
2013 | "ImmSet" -> CollectionType.ImmSet
2014 | "Pair" -> CollectionType.Pair
2015 | _ -> failwith @@ "collection: " ^ name ^ " does not exist"
2017 emit_named_collection env expr pos ctype fields
2019 and emit_named_collection env (expr : Tast.expr) pos ctype fields =
2020 let emit_vector_like collection_type =
2021 if fields = [] then
2022 emit_pos_then pos @@ instr_newcol collection_type
2023 else
2024 let ((_, ty), _) = expr in
2025 let annot = (pos, ty) in
2026 gather
2028 emit_collection
2030 (annot, A.Collection ((pos, "vec"), None, fields))
2031 fields;
2032 instr_colfromarray collection_type;
2035 let emit_map_or_set collection_type =
2036 if fields = [] then
2037 emit_pos_then pos @@ instr_newcol collection_type
2038 else
2039 emit_collection ~transform_to_collection:collection_type env expr fields
2041 match ctype with
2042 | CollectionType.Dict
2043 | CollectionType.Vec
2044 | CollectionType.Keyset ->
2045 emit_pos_then pos @@ emit_collection env expr fields
2046 | CollectionType.Vector
2047 | CollectionType.ImmVector ->
2048 emit_vector_like ctype
2049 | CollectionType.Map
2050 | CollectionType.ImmMap
2051 | CollectionType.Set
2052 | CollectionType.ImmSet ->
2053 emit_map_or_set ctype
2054 | CollectionType.Pair ->
2055 gather
2057 gather
2058 (List.map fields (function
2059 | A.AFvalue e -> emit_expr env e
2060 | _ -> failwith "impossible Pair argument"));
2061 instr (ILitConst NewPair);
2063 | _ -> failwith "Unexpected named collection type"
2065 and is_php_array = function
2066 | (_, A.Array _) -> true
2067 | (_, A.Varray _) -> not (hack_arr_dv_arrs ())
2068 | (_, A.Darray _) -> not (hack_arr_dv_arrs ())
2069 | _ -> false
2071 and emit_collection ?transform_to_collection env (expr : Tast.expr) es =
2072 let pos = Tast_annotate.get_pos expr in
2073 match
2074 Ast_constant_folder.expr_to_opt_typed_value
2075 ~allow_maps:true
2076 ~restrict_keys:(not @@ is_php_array expr)
2077 (Emit_env.get_namespace env)
2078 expr
2079 with
2080 | Some tv -> emit_static_collection env ~transform_to_collection pos tv
2081 | None -> emit_dynamic_collection env expr es
2083 and emit_pipe env (e1 : Tast.expr) (e2 : Tast.expr) =
2084 let lhs_instrs = emit_expr env e1 in
2085 Scope.with_unnamed_local
2086 @@ fun temp ->
2087 let env = Emit_env.with_pipe_var temp env in
2088 let rhs_instrs = emit_expr env e2 in
2089 (gather [lhs_instrs; instr_popl temp], rhs_instrs, instr_unsetl temp)
2091 (* Emit code that is equivalent to
2092 * <code for expr>
2093 * JmpZ label
2094 * Generate specialized code in case expr is statically known, and for
2095 * !, && and || expressions
2097 and emit_jmpz env (expr : Tast.expr) label : emit_jmp_result =
2098 let ((pos, _), expr_) = expr in
2099 let with_pos i = emit_pos_then pos i in
2100 let opt = optimize_null_checks () in
2101 let ns = Emit_env.get_namespace env in
2102 match Ast_constant_folder.expr_to_opt_typed_value ns expr with
2103 | Some v ->
2104 let b = Typed_value.to_bool v in
2105 if b then
2106 { instrs = with_pos empty; is_fallthrough = true; is_label_used = false }
2107 else
2109 instrs = with_pos @@ instr_jmp label;
2110 is_fallthrough = false;
2111 is_label_used = true;
2113 | None ->
2114 begin
2115 match expr_ with
2116 | A.Unop (Ast_defs.Unot, e) ->
2117 let (annot, expr_) = e in
2118 emit_jmpnz env annot expr_ label
2119 | A.Binop (Ast_defs.Barbar, e1, e2) ->
2120 let skip_label = Label.next_regular () in
2121 let (e1_annot, e1_expr_) = e1 in
2122 let r1 = emit_jmpnz env e1_annot e1_expr_ skip_label in
2123 if not r1.is_fallthrough then
2124 let instrs =
2125 if r1.is_label_used then
2126 gather [r1.instrs; instr_label skip_label]
2127 else
2128 r1.instrs
2131 instrs = with_pos instrs;
2132 is_fallthrough = r1.is_label_used;
2133 is_label_used = false;
2135 else
2136 let r2 = emit_jmpz env e2 label in
2137 let instrs =
2138 gather
2140 r1.instrs;
2141 r2.instrs;
2142 optional r1.is_label_used [instr_label skip_label];
2146 instrs = with_pos instrs;
2147 is_fallthrough = r2.is_fallthrough || r1.is_label_used;
2148 is_label_used = r2.is_label_used;
2150 | A.Binop (Ast_defs.Ampamp, e1, e2) ->
2151 let r1 = emit_jmpz env e1 label in
2152 if not r1.is_fallthrough then
2154 instrs = with_pos r1.instrs;
2155 is_fallthrough = false;
2156 is_label_used = r1.is_label_used;
2158 else
2159 let r2 = emit_jmpz env e2 label in
2161 instrs = with_pos @@ gather [r1.instrs; r2.instrs];
2162 is_fallthrough = r2.is_fallthrough;
2163 is_label_used = r1.is_label_used || r2.is_label_used;
2165 | A.Binop (Ast_defs.Eqeqeq, e, (_, A.Null))
2166 | A.Binop (Ast_defs.Eqeqeq, (_, A.Null), e)
2167 when opt ->
2169 instrs = with_pos @@ gather [emit_is_null env e; instr_jmpz label];
2170 is_fallthrough = true;
2171 is_label_used = true;
2173 | A.Binop (Ast_defs.Diff2, e, (_, A.Null))
2174 | A.Binop (Ast_defs.Diff2, (_, A.Null), e)
2175 when opt ->
2177 instrs = with_pos @@ gather [emit_is_null env e; instr_jmpnz label];
2178 is_fallthrough = true;
2179 is_label_used = true;
2181 | _ ->
2183 instrs = with_pos @@ gather [emit_expr env expr; instr_jmpz label];
2184 is_fallthrough = true;
2185 is_label_used = true;
2189 (* Emit code that is equivalent to
2190 * <code for expr>
2191 * JmpNZ label
2192 * Generate specialized code in case expr is statically known, and for
2193 * !, && and || expressions
2195 and emit_jmpnz env annot (expr_ : Tast.expr_) label : emit_jmp_result =
2196 let (pos, _) = annot in
2197 let with_pos i = emit_pos_then pos i in
2198 let opt = optimize_null_checks () in
2199 match
2200 Ast_constant_folder.expr_to_opt_typed_value
2201 (Emit_env.get_namespace env)
2202 (annot, expr_)
2203 with
2204 | Some v ->
2205 if Typed_value.to_bool v then
2207 instrs = with_pos @@ instr_jmp label;
2208 is_fallthrough = false;
2209 is_label_used = true;
2211 else
2212 { instrs = with_pos empty; is_fallthrough = true; is_label_used = false }
2213 | None ->
2214 begin
2215 match expr_ with
2216 | A.Unop (Ast_defs.Unot, e) -> emit_jmpz env e label
2217 | A.Binop (Ast_defs.Barbar, (annot1, e1), (annot2, e2)) ->
2218 let r1 = emit_jmpnz env annot1 e1 label in
2219 if not r1.is_fallthrough then
2221 else
2222 let r2 = emit_jmpnz env annot2 e2 label in
2224 instrs = with_pos @@ gather [r1.instrs; r2.instrs];
2225 is_fallthrough = r2.is_fallthrough;
2226 is_label_used = r1.is_label_used || r2.is_label_used;
2228 | A.Binop (Ast_defs.Ampamp, e1, (annot2, e2)) ->
2229 let skip_label = Label.next_regular () in
2230 let r1 = emit_jmpz env e1 skip_label in
2231 if not r1.is_fallthrough then
2233 instrs =
2234 with_pos
2235 @@ gather
2237 r1.instrs;
2238 optional r1.is_label_used [instr_label skip_label];
2240 is_fallthrough = r1.is_label_used;
2241 is_label_used = false;
2243 else
2244 let r2 = emit_jmpnz env annot2 e2 label in
2246 instrs =
2247 with_pos
2248 @@ gather
2250 r1.instrs;
2251 r2.instrs;
2252 optional r1.is_label_used [instr_label skip_label];
2254 is_fallthrough = r2.is_fallthrough || r1.is_label_used;
2255 is_label_used = r2.is_label_used;
2257 | A.Binop (Ast_defs.Eqeqeq, e, (_, A.Null))
2258 | A.Binop (Ast_defs.Eqeqeq, (_, A.Null), e)
2259 when opt ->
2261 instrs = with_pos @@ gather [emit_is_null env e; instr_jmpnz label];
2262 is_fallthrough = true;
2263 is_label_used = true;
2265 | A.Binop (Ast_defs.Diff2, e, (_, A.Null))
2266 | A.Binop (Ast_defs.Diff2, (_, A.Null), e)
2267 when opt ->
2269 instrs = with_pos @@ gather [emit_is_null env e; instr_jmpz label];
2270 is_fallthrough = true;
2271 is_label_used = true;
2273 | _ ->
2275 instrs =
2276 with_pos
2277 @@ gather [emit_expr env (annot, expr_); instr_jmpnz label];
2278 is_fallthrough = true;
2279 is_label_used = true;
2283 and emit_short_circuit_op env annot (expr : Tast.expr_) =
2284 let (pos, _) = annot in
2285 let its_true = Label.next_regular () in
2286 let its_done = Label.next_regular () in
2287 let r1 = emit_jmpnz env annot expr its_true in
2288 let if_true =
2289 if r1.is_label_used then
2290 gather [instr_label its_true; emit_pos pos; instr_true]
2291 else
2292 empty
2294 if r1.is_fallthrough then
2295 gather
2297 r1.instrs;
2298 emit_pos pos;
2299 instr_false;
2300 instr_jmp its_done;
2301 if_true;
2302 instr_label its_done;
2304 else
2305 gather [r1.instrs; if_true]
2307 and emit_null_coalesce_assignment env pos (e1 : Tast.expr) (e2 : Tast.expr) =
2308 let end_label = Label.next_regular () in
2309 let do_set_label = Label.next_regular () in
2310 let l_nonnull = Local.get_unnamed_local () in
2311 let (quiet_instr, querym_n_unpopped) =
2312 emit_quiet_expr ~null_coalesce_assignment:true env pos e1
2314 let emit_popc_n n_unpopped =
2315 match n_unpopped with
2316 | Some n -> gather (List.init n (fun _ -> instr_popc))
2317 | None -> empty
2319 gather
2321 quiet_instr;
2322 instr_dup;
2323 instr_istypec OpNull;
2324 instr_jmpnz do_set_label;
2325 instr_popl l_nonnull;
2326 emit_popc_n querym_n_unpopped;
2327 instr_pushl l_nonnull;
2328 instr_jmp end_label;
2329 instr_label do_set_label;
2330 instr_popc;
2331 emit_lval_op
2332 ~null_coalesce_assignment:true
2335 LValOp.Set
2337 (Some e2);
2338 instr_label end_label;
2341 and emit_quiet_expr
2342 ?(null_coalesce_assignment = false)
2343 (env : Emit_env.t)
2345 (expr : Tast.expr) : Instruction_sequence.t * Hhbc_ast.num_params option =
2346 let (_, expr_) = expr in
2347 match expr_ with
2348 | A.Lvar (name_pos, name)
2349 when Local_id.get_name name = SN.Superglobals.globals ->
2350 ( gather
2352 emit_pos name_pos;
2353 instr_string (SU.Locals.strip_dollar (Local_id.get_name name));
2354 emit_pos pos;
2355 instr (IGet CGetG);
2357 None )
2358 | A.Lvar (_, name) when not (is_local_this env name) ->
2359 (instr_cgetquietl (get_local env (pos, Local_id.get_name name)), None)
2360 | A.Array_get ((_, A.Lvar (_, x)), Some e)
2361 when Local_id.get_name x = SN.Superglobals.globals ->
2362 (gather [emit_expr env e; emit_pos pos; instr (IGet CGetG)], None)
2363 | A.Array_get (base_expr, opt_elem_expr) ->
2364 emit_array_get
2365 ~null_coalesce_assignment
2368 QueryOp.CGetQuiet
2369 base_expr
2370 opt_elem_expr
2371 | A.Obj_get (expr, prop, nullflavor) ->
2372 emit_obj_get
2373 ~null_coalesce_assignment
2376 QueryOp.CGetQuiet
2377 expr
2378 prop
2379 nullflavor
2380 | _ -> (emit_expr env expr, None)
2382 (* returns instruction that will represent setter for $base[local] where
2383 is_base is true when result cell is base for another subscript operator and
2384 false when it is final left hand side of the assignment *)
2385 and emit_store_for_simple_base
2386 ~is_base env pos elem_stack_size (base_expr : Tast.expr) local =
2387 let (base_expr_instrs_begin, base_expr_instrs_end, base_setup_instrs, _, _) =
2388 emit_base
2389 ~is_object:false
2390 ~notice:Notice
2392 MemberOpMode.Define
2393 elem_stack_size
2395 base_expr
2397 let expr =
2398 let mk = MemberKey.EL local in
2399 if is_base then
2400 instr_dim MemberOpMode.Define mk
2401 else
2402 instr_setm 0 mk
2404 gather
2406 base_expr_instrs_begin;
2407 base_expr_instrs_end;
2408 emit_pos pos;
2409 base_setup_instrs;
2410 expr;
2413 (* get LocalTempKind option for a given expression
2414 - None - expression can be emitted as is
2415 - Some Value_kind_local - expression represents local that will be
2416 overwritten later
2417 - Some Value_kind_expression - spilled non-trivial expression *)
2418 and get_local_temp_kind
2419 ~is_base inout_param_info env (e_opt : Tast.expr option) =
2420 match (e_opt, inout_param_info) with
2421 (* not inout case - no need to save *)
2422 | (_, None) -> None
2423 (* local that will later be overwritten *)
2424 | (Some (_, A.Lvar (_, id)), Some (i, aliases))
2425 when InoutLocals.should_save_local_value (Local_id.get_name id) i aliases
2427 Some Value_kind_local
2428 (* non-trivial expression *)
2429 | (Some e, _) ->
2430 if is_trivial ~is_base env e then
2431 None
2432 else
2433 Some Value_kind_expression
2434 | (None, _) -> None
2436 and is_trivial ~is_base env (_, e) =
2437 match e with
2438 | A.Int _
2439 | A.String _ ->
2440 true
2441 | A.Lvar (_, s) ->
2442 (not (is_local_this env s)) || Emit_env.get_needs_local_this env
2443 | A.Array_get _ when not is_base -> false
2444 | A.Array_get (b, None) -> is_trivial ~is_base env b
2445 | A.Array_get (b, Some e) ->
2446 is_trivial ~is_base env b && is_trivial ~is_base env e
2447 | _ -> false
2449 (* Emit code for e1[e2] or isset(e1[e2]).
2451 and emit_array_get
2452 ?(null_coalesce_assignment = false)
2453 ?(no_final = false)
2454 ?mode
2456 outer_pos
2458 (base_expr : Tast.expr)
2459 (opt_elem_expr : Tast.expr option) =
2460 let result =
2461 emit_array_get_worker
2462 ~null_coalesce_assignment
2463 ~no_final
2464 ?mode
2465 ~inout_param_info:None
2467 outer_pos
2469 base_expr
2470 opt_elem_expr
2472 match result with
2473 | (Array_get_regular i, querym_n_unpopped) -> (i, querym_n_unpopped)
2474 | (Array_get_inout _, _) -> failwith "unexpected inout"
2476 and emit_array_get_worker
2477 ?(null_coalesce_assignment = false)
2478 ?(no_final = false)
2479 ?mode
2480 ~inout_param_info
2482 outer_pos
2484 (base_expr : Tast.expr)
2485 (opt_elem_expr : Tast.expr option) =
2486 (* Disallow use of array(..)[] *)
2487 match (base_expr, opt_elem_expr) with
2488 | (((pos, _), A.Array _), None) ->
2489 Emit_fatal.raise_fatal_parse
2491 "Can't use array() as base in write context"
2492 | (((pos, _), _), None) when not (Emit_env.does_env_allow_array_append env)
2494 Emit_fatal.raise_fatal_runtime pos "Can't use [] for reading"
2495 | _ ->
2496 let local_temp_kind =
2497 get_local_temp_kind ~is_base:false inout_param_info env opt_elem_expr
2499 let mode =
2500 if null_coalesce_assignment then
2501 MemberOpMode.Warn
2502 else
2503 Option.value mode ~default:(get_queryMOpMode qop)
2505 let querym_n_unpopped = ref None in
2506 let (elem_expr_instrs, elem_stack_size) =
2507 emit_elem_instrs
2508 ~local_temp_kind
2509 ~null_coalesce_assignment
2511 opt_elem_expr
2513 let base_result =
2514 emit_base_worker
2515 ~is_object:false
2516 ~inout_param_info
2517 ~notice:
2518 (match qop with
2519 | QueryOp.Isset -> NoNotice
2520 | _ -> Notice)
2521 ~null_coalesce_assignment
2523 mode
2524 elem_stack_size
2526 base_expr
2528 let cls_stack_size =
2529 match base_result with
2530 | Array_get_base_regular base -> base.cls_stack_size
2531 | Array_get_base_inout base -> base.load.cls_stack_size
2533 let mk =
2534 get_elem_member_key
2535 ~null_coalesce_assignment
2537 cls_stack_size
2538 opt_elem_expr
2540 let make_final total_stack_size =
2541 if no_final then
2542 empty
2543 else
2544 instr
2545 (IFinal
2546 ( if null_coalesce_assignment then (
2547 querym_n_unpopped := Some total_stack_size;
2548 QueryM (0, qop, mk)
2549 ) else
2550 QueryM (total_stack_size, qop, mk) ))
2552 let instr =
2553 match (base_result, local_temp_kind) with
2554 | (Array_get_base_regular base, None) ->
2555 (* both base and expression don't need to store anything *)
2556 Array_get_regular
2557 (gather
2559 base.base_instrs;
2560 elem_expr_instrs;
2561 base.cls_instrs;
2562 emit_pos outer_pos;
2563 base.setup_instrs;
2564 make_final
2565 (base.base_stack_size + base.cls_stack_size + elem_stack_size);
2567 | (Array_get_base_regular base, Some local_kind) ->
2568 (* base does not need temp locals but index expression does *)
2569 let local = Local.get_unnamed_local () in
2570 let load =
2572 (* load base and indexer, value of indexer will be saved in local *)
2573 ( gather [base.base_instrs; elem_expr_instrs],
2574 Some (local, local_kind) );
2575 (* finish loading the value *)
2576 ( gather
2578 base.base_instrs;
2579 emit_pos outer_pos;
2580 base.setup_instrs;
2581 make_final
2582 ( base.base_stack_size
2583 + base.cls_stack_size
2584 + elem_stack_size );
2586 None );
2589 let store =
2590 gather
2592 emit_store_for_simple_base
2593 ~is_base:false
2595 outer_pos
2596 elem_stack_size
2597 base_expr
2598 local;
2599 instr_popc;
2602 Array_get_inout { load; store }
2603 | (Array_get_base_inout base, None) ->
2604 (* base needs temp locals, indexer - does not,
2605 simply concat two instruction sequences *)
2606 let load =
2607 base.load.base_instrs
2609 ( gather
2611 elem_expr_instrs;
2612 base.load.cls_instrs;
2613 emit_pos outer_pos;
2614 base.load.setup_instrs;
2615 make_final
2616 ( base.load.base_stack_size
2617 + base.load.cls_stack_size
2618 + elem_stack_size );
2620 None );
2623 let store = gather [base.store; instr_setm 0 mk; instr_popc] in
2624 Array_get_inout { load; store }
2625 | (Array_get_base_inout base, Some local_kind) ->
2626 (* both base and index need temp locals,
2627 create local for index value *)
2628 let local = Local.get_unnamed_local () in
2629 let load =
2630 (* load base *)
2631 base.load.base_instrs
2633 (* load index, value will be saved in local *)
2634 (elem_expr_instrs, Some (local, local_kind));
2635 ( gather
2637 base.load.cls_instrs;
2638 emit_pos outer_pos;
2639 base.load.setup_instrs;
2640 make_final
2641 ( base.load.base_stack_size
2642 + base.load.cls_stack_size
2643 + elem_stack_size );
2645 None );
2648 let store =
2649 gather [base.store; instr_setm 0 (MemberKey.EL local); instr_popc]
2651 Array_get_inout { load; store }
2653 (instr, !querym_n_unpopped)
2655 (* Emit code for e1->e2 or e1?->e2 or isset(e1->e2).
2657 and emit_obj_get
2658 ?(null_coalesce_assignment = false)
2662 (expr : Tast.expr)
2663 (prop : Tast.expr)
2664 null_flavor =
2665 let (annot, expr_) = expr in
2666 match expr_ with
2667 | A.Lvar (pos, id)
2668 when Local_id.get_name id = SN.SpecialIdents.this
2669 && null_flavor = Ast_defs.OG_nullsafe ->
2670 Emit_fatal.raise_fatal_parse pos "?-> is not allowed with $this"
2671 | _ ->
2672 begin
2673 match snd prop with
2674 | A.Id (_, s) when SU.Xhp.is_xhp s ->
2675 (emit_xhp_obj_get env pos annot expr s null_flavor, None)
2676 | _ ->
2677 let mode =
2678 if null_coalesce_assignment then
2679 MemberOpMode.Warn
2680 else
2681 get_queryMOpMode qop
2683 let (_, _, prop_stack_size) =
2684 emit_prop_expr ~null_coalesce_assignment env null_flavor 0 prop
2686 let ( base_expr_instrs_begin,
2687 base_expr_instrs_end,
2688 base_setup_instrs,
2689 base_stack_size,
2690 cls_stack_size ) =
2691 emit_base
2692 ~is_object:true
2693 ~notice:Notice
2694 ~null_coalesce_assignment
2696 mode
2697 prop_stack_size
2699 expr
2701 let (mk, prop_expr_instrs, _) =
2702 emit_prop_expr
2703 ~null_coalesce_assignment
2705 null_flavor
2706 cls_stack_size
2707 prop
2709 let total_stack_size =
2710 prop_stack_size + base_stack_size + cls_stack_size
2712 let num_params =
2713 if null_coalesce_assignment then
2715 else
2716 total_stack_size
2718 let final_instr = instr (IFinal (QueryM (num_params, qop, mk))) in
2719 let querym_n_unpopped =
2720 if null_coalesce_assignment then
2721 Some total_stack_size
2722 else
2723 None
2725 let instr =
2726 gather
2728 base_expr_instrs_begin;
2729 prop_expr_instrs;
2730 base_expr_instrs_end;
2731 emit_pos pos;
2732 base_setup_instrs;
2733 final_instr;
2736 (instr, querym_n_unpopped)
2739 and is_special_class_constant_accessed_with_class_id cName (id : string) =
2740 let is_self_parent_or_static =
2741 match cName with
2742 | A.CIexpr (_, A.Id (_, cName))
2743 when SU.is_self cName || SU.is_parent cName || SU.is_static cName ->
2744 true
2745 | _ -> false
2747 SU.is_class id && not is_self_parent_or_static
2749 and emit_elem_instrs
2751 ~local_temp_kind
2752 ?(null_coalesce_assignment = false)
2753 (opt_elem_expr : Tast.expr option) =
2754 match opt_elem_expr with
2755 (* These all have special inline versions of member keys *)
2756 | Some (_, (A.Int _ | A.String _)) -> (empty, 0)
2757 | Some (_, A.Lvar (pos, id)) when not (is_local_this env id) ->
2758 if Option.is_some local_temp_kind then
2759 (instr_cgetquietl (get_local env (pos, Local_id.get_name id)), 0)
2760 else if null_coalesce_assignment then
2761 (instr_cgetl (get_local env (pos, Local_id.get_name id)), 1)
2762 else
2763 (empty, 0)
2764 (* Handle Foo::class but not self::class. *)
2765 | Some (_, A.Class_const ((_, cid), (_, id)))
2766 when is_special_class_constant_accessed_with_class_id cid id ->
2767 (empty, 0)
2768 | Some expr -> (emit_expr env expr, 1)
2769 | None -> (empty, 0)
2771 (* Get the member key for an array element expression: the `elem` in
2772 * expressions of the form `base[elem]`.
2773 * If the array element is missing, use the special key `W`.
2775 and get_elem_member_key
2776 ?(null_coalesce_assignment = false)
2778 stack_index
2779 (opt_expr : Tast.expr option) =
2780 match opt_expr with
2781 (* Special case for local *)
2782 | Some (_, A.Lvar (p, id)) when not (is_local_this env id) ->
2783 if null_coalesce_assignment then
2784 MemberKey.EC stack_index
2785 else
2786 MemberKey.EL (get_local env (p, Local_id.get_name id))
2787 (* Special case for literal integer *)
2788 | Some ((_, A.Int str) as int_expr) ->
2789 Ast_constant_folder.(
2790 let namespace = Emit_env.get_namespace env in
2791 begin
2792 match expr_to_typed_value namespace int_expr with
2793 | TV.Int i -> MemberKey.EI i
2794 | _ -> failwith (str ^ " is not a valid integer index")
2795 end)
2796 (* Special case for literal string *)
2797 | Some (_, A.String str) -> MemberKey.ET str
2798 (* Special case for class name *)
2799 | Some (_, A.Class_const ((_, cid), (p, id)))
2800 when is_special_class_constant_accessed_with_class_id cid id ->
2801 let cName =
2802 match (cid, Ast_scope.Scope.get_class (Emit_env.get_scope env)) with
2803 | (A.CIself, Some cd) -> SU.strip_global_ns @@ snd cd.A.c_name
2804 | (A.CIexpr (_, A.Id (_, id)), _)
2805 | (A.CI (_, id), _) ->
2806 SU.strip_global_ns id
2807 | _ ->
2808 failwith
2809 "Unreachable due to is_special_class_constant_accessed_with_class_id"
2811 let fq_id = Hhbc_id.Class.elaborate_id (p, cName) in
2812 MemberKey.ET (Hhbc_id.Class.to_raw_string fq_id)
2813 (* General case *)
2814 | Some _ -> MemberKey.EC stack_index
2815 (* ELement missing (so it's array append) *)
2816 | None -> MemberKey.W
2818 (* Get the member key for a property, and return any instructions and
2819 * the size of the stack in the case that the property cannot be
2820 * placed inline in the instruction. *)
2821 and emit_prop_expr
2822 ?(null_coalesce_assignment = false)
2824 null_flavor
2825 stack_index
2826 (prop_expr : Tast.expr) =
2827 let mk =
2828 match snd prop_expr with
2829 | A.Id (pos, name) when String_utils.string_starts_with name "$" ->
2830 MemberKey.PL (get_local env (pos, name))
2831 (* Special case for known property name *)
2832 | A.Id (_, id)
2833 | A.String id ->
2834 let pid = Hhbc_id.Prop.from_ast_name id in
2835 begin
2836 match null_flavor with
2837 | Ast_defs.OG_nullthrows -> MemberKey.PT pid
2838 | Ast_defs.OG_nullsafe -> MemberKey.QT pid
2840 | A.Lvar (pos, name) when not (is_local_this env name) ->
2841 MemberKey.PL (get_local env (pos, Local_id.get_name name))
2842 (* General case *)
2843 | _ -> MemberKey.PC stack_index
2845 (* For nullsafe access, insist that property is known *)
2846 begin
2847 match mk with
2848 | MemberKey.PL _
2849 | MemberKey.PC _ ->
2850 let ((pos, _), _) = prop_expr in
2851 if null_flavor = Ast_defs.OG_nullsafe then
2852 Emit_fatal.raise_fatal_parse
2854 "?-> can only be used with scalar property names"
2855 | _ -> ()
2856 end;
2857 match mk with
2858 | MemberKey.PC _ -> (mk, emit_expr env prop_expr, 1)
2859 | MemberKey.PL local ->
2860 if null_coalesce_assignment then
2861 (MemberKey.PC stack_index, instr_cgetl local, 1)
2862 else
2863 (mk, empty, 0)
2864 | _ -> (mk, empty, 0)
2866 (* Emit code for a base expression `expr` that forms part of
2867 * an element access `expr[elem]` or field access `expr->fld`.
2868 * The instructions are divided into three sections:
2869 * 1. base and element/property expression instructions:
2870 * push non-trivial base and key values on the stack
2871 * 2. base selector instructions: a sequence of Base/Dim instructions that
2872 * actually constructs the base address from "member keys" that are inlined
2873 * in the instructions, or pulled from the key values that
2874 * were pushed on the stack in section 1.
2875 * 3. (constructed by the caller) a final accessor e.g. QueryM or setter
2876 * e.g. SetOpM instruction that has the final key inlined in the
2877 * instruction, or pulled from the key values that were pushed on the
2878 * stack in section 1.
2879 * The function returns a triple (base_instrs, base_setup_instrs, stack_size)
2880 * where base_instrs is section 1 above, base_setup_instrs is section 2, and
2881 * stack_size is the number of values pushed onto the stack by section 1.
2883 * For example, the r-value expression $arr[3][$ix+2]
2884 * will compile to
2885 * # Section 1, pushing the value of $ix+2 on the stack
2886 * Int 2
2887 * CGetL2 $ix
2888 * AddO
2889 * # Section 2, constructing the base address of $arr[3]
2890 * BaseL $arr Warn
2891 * Dim Warn EI:3
2892 * # Section 3, indexing the array using the value at stack position 0 (EC:0)
2893 * QueryM 1 CGet EC:0
2895 and emit_base
2896 ~is_object
2897 ~notice
2898 ?(null_coalesce_assignment = false)
2900 mode
2901 base_offset
2902 rhs_stack_size
2903 (e : Tast.expr) =
2904 let result =
2905 emit_base_worker
2906 ~is_object
2907 ~notice
2908 ~inout_param_info:None
2909 ~null_coalesce_assignment
2911 mode
2912 base_offset
2913 rhs_stack_size
2916 match result with
2917 | Array_get_base_regular i ->
2918 ( i.base_instrs,
2919 i.cls_instrs,
2920 i.setup_instrs,
2921 i.base_stack_size,
2922 i.cls_stack_size )
2923 | Array_get_base_inout _ -> failwith "unexpected inout"
2925 and emit_base_worker
2926 ~is_object
2927 ~notice
2928 ~inout_param_info
2929 ?(null_coalesce_assignment = false)
2931 mode
2932 base_offset
2933 rhs_stack_size
2934 (expr : Tast.expr) =
2935 let (((pos, _) as annot), expr_) = expr in
2936 let base_mode =
2937 if mode = MemberOpMode.InOut then
2938 MemberOpMode.Warn
2939 else
2940 mode
2942 let local_temp_kind =
2943 get_local_temp_kind ~is_base:true inout_param_info env (Some expr)
2945 (* generic handler that will try to save local into temp if this is necessary *)
2946 let emit_default
2947 base_instrs cls_instrs setup_instrs base_stack_size cls_stack_size =
2948 match local_temp_kind with
2949 | Some local_temp ->
2950 let local = Local.get_unnamed_local () in
2951 Array_get_base_inout
2953 load =
2955 (* run begin part, result will be stored into temp *)
2956 base_instrs = [(base_instrs, Some (local, local_temp))];
2957 cls_instrs;
2958 setup_instrs;
2959 base_stack_size;
2960 cls_stack_size;
2962 store = instr_basel local MemberOpMode.Define;
2964 | _ ->
2965 Array_get_base_regular
2967 base_instrs;
2968 cls_instrs;
2969 setup_instrs;
2970 base_stack_size;
2971 cls_stack_size;
2974 match expr_ with
2975 | A.Lvar (name_pos, x)
2976 when SN.Superglobals.is_superglobal (Local_id.get_name x)
2977 || Local_id.get_name x = SN.Superglobals.globals ->
2978 emit_default
2979 ( emit_pos_then name_pos
2980 @@ instr_string (SU.Locals.strip_dollar (Local_id.get_name x)) )
2981 empty
2982 (instr (IBase (BaseGC (base_offset, base_mode))))
2985 | A.Lvar (thispos, x)
2986 when is_object && Local_id.get_name x = SN.SpecialIdents.this ->
2987 emit_default
2988 (emit_pos_then thispos @@ instr (IMisc CheckThis))
2989 empty
2990 (instr (IBase BaseH))
2993 | A.Lvar (pos, str)
2994 when (not (is_local_this env str)) || Emit_env.get_needs_local_this env ->
2995 let v = get_local env (pos, Local_id.get_name str) in
2996 if Option.is_some local_temp_kind then
2997 emit_default (instr_cgetquietl v) empty (instr_basel v base_mode) 0 0
2998 else
2999 emit_default empty empty (instr (IBase (BaseL (v, base_mode)))) 0 0
3000 | A.Lvar lid ->
3001 emit_default
3002 (emit_local ~notice env lid)
3003 empty
3004 (instr (IBase (BaseC (base_offset, base_mode))))
3007 | A.Array_get ((_, A.Lvar (_, x)), Some (_, A.Lvar (y_pos, y_id)))
3008 when Local_id.get_name x = SN.Superglobals.globals ->
3009 let v = get_local env (y_pos, Local_id.get_name y_id) in
3010 emit_default empty empty (instr (IBase (BaseGL (v, base_mode)))) 0 0
3011 | A.Array_get ((_, A.Lvar (_, x)), Some e)
3012 when Local_id.get_name x = SN.Superglobals.globals ->
3013 let elem_expr_instrs = emit_expr env e in
3014 emit_default
3015 elem_expr_instrs
3016 empty
3017 (instr (IBase (BaseGC (base_offset, base_mode))))
3020 (* $a[] can not be used as the base of an array get unless as an lval *)
3021 | A.Array_get (_, None) when not (Emit_env.does_env_allow_array_append env)
3023 Emit_fatal.raise_fatal_runtime pos "Can't use [] for reading"
3024 (* base is in turn array_get - do a specific handling for inout params
3025 if necessary *)
3026 | A.Array_get (base_expr, opt_elem_expr) ->
3027 let local_temp_kind =
3028 get_local_temp_kind ~is_base:false inout_param_info env opt_elem_expr
3030 let (elem_expr_instrs, elem_stack_size) =
3031 emit_elem_instrs
3032 ~local_temp_kind
3033 ~null_coalesce_assignment
3035 opt_elem_expr
3037 let base_result =
3038 emit_base_worker
3039 ~notice
3040 ~is_object:false
3041 ~inout_param_info
3042 ~null_coalesce_assignment
3044 mode
3045 (base_offset + elem_stack_size)
3046 rhs_stack_size
3047 base_expr
3049 let cls_stack_size =
3050 match base_result with
3051 | Array_get_base_regular base -> base.cls_stack_size
3052 | Array_get_base_inout base -> base.load.cls_stack_size
3054 let mk =
3055 get_elem_member_key
3056 ~null_coalesce_assignment
3058 (base_offset + cls_stack_size)
3059 opt_elem_expr
3061 let make_setup_instrs base_setup_instrs =
3062 gather [base_setup_instrs; instr (IBase (Dim (mode, mk)))]
3064 begin
3065 match (base_result, local_temp_kind) with
3066 (* both base and index don't use temps - fallback to default handler *)
3067 | (Array_get_base_regular base, None) ->
3068 emit_default
3069 (gather [base.base_instrs; elem_expr_instrs])
3070 base.cls_instrs
3071 (make_setup_instrs base.setup_instrs)
3072 (base.base_stack_size + elem_stack_size)
3073 base.cls_stack_size
3074 | (Array_get_base_regular base, Some local_temp) ->
3075 (* base does not need temps but index does *)
3076 let local = Local.get_unnamed_local () in
3077 let base_instrs = gather [base.base_instrs; elem_expr_instrs] in
3078 Array_get_base_inout
3080 load =
3082 (* store result of instr_begin to temp *)
3083 base_instrs = [(base_instrs, Some (local, local_temp))];
3084 cls_instrs = base.cls_instrs;
3085 setup_instrs = make_setup_instrs base.setup_instrs;
3086 base_stack_size = base.base_stack_size + elem_stack_size;
3087 cls_stack_size = base.cls_stack_size;
3089 store =
3090 emit_store_for_simple_base
3091 ~is_base:true
3094 elem_stack_size
3095 base_expr
3096 local;
3098 | (Array_get_base_inout base, None) ->
3099 (* base needs temps, index - does not *)
3100 Array_get_base_inout
3102 load =
3104 (* concat index evaluation to base *)
3105 base_instrs =
3106 base.load.base_instrs @ [(elem_expr_instrs, None)];
3107 cls_instrs = base.load.cls_instrs;
3108 setup_instrs = make_setup_instrs base.load.setup_instrs;
3109 base_stack_size = base.load.base_stack_size + elem_stack_size;
3110 cls_stack_size = base.load.cls_stack_size;
3112 store = gather [base.store; instr_dim MemberOpMode.Define mk];
3114 | (Array_get_base_inout base, Some local_kind) ->
3115 (* both base and index needs locals *)
3116 let local = Local.get_unnamed_local () in
3117 Array_get_base_inout
3119 load =
3121 base_instrs =
3122 base.load.base_instrs
3124 (* evaluate index, result will be stored in local *)
3125 (elem_expr_instrs, Some (local, local_kind));
3127 cls_instrs = base.load.cls_instrs;
3128 setup_instrs = make_setup_instrs base.load.setup_instrs;
3129 base_stack_size = base.load.base_stack_size + elem_stack_size;
3130 cls_stack_size = base.load.cls_stack_size;
3132 store =
3133 gather
3135 base.store;
3136 instr_dim MemberOpMode.Define (MemberKey.EL local);
3140 | A.Obj_get (base_expr, prop_expr, null_flavor) ->
3141 begin
3142 match snd prop_expr with
3143 | A.Id (_, s) when SU.Xhp.is_xhp s ->
3144 emit_default
3145 (emit_xhp_obj_get env pos annot base_expr s null_flavor)
3146 empty
3147 (gather [instr_basec base_offset base_mode])
3150 | _ ->
3151 let (_, _, prop_stack_size) =
3152 emit_prop_expr ~null_coalesce_assignment env null_flavor 0 prop_expr
3154 let ( base_expr_instrs_begin,
3155 base_expr_instrs_end,
3156 base_setup_instrs,
3157 base_stack_size,
3158 cls_stack_size ) =
3159 emit_base
3160 ~notice:Notice
3161 ~is_object:true
3162 ~null_coalesce_assignment
3164 mode
3165 (base_offset + prop_stack_size)
3166 rhs_stack_size
3167 base_expr
3169 let (mk, prop_expr_instrs, _) =
3170 emit_prop_expr
3171 ~null_coalesce_assignment
3173 null_flavor
3174 (base_offset + cls_stack_size)
3175 prop_expr
3177 let total_stack_size = prop_stack_size + base_stack_size in
3178 let final_instr = instr (IBase (Dim (mode, mk))) in
3179 emit_default
3180 (gather [base_expr_instrs_begin; prop_expr_instrs])
3181 base_expr_instrs_end
3182 (gather [base_setup_instrs; final_instr])
3183 total_stack_size
3184 cls_stack_size
3186 | A.Class_get (cid, prop) ->
3187 let cexpr =
3188 class_id_to_class_expr ~resolve_self:false (Emit_env.get_scope env) cid
3190 let (cexpr_begin, cexpr_end) = emit_class_expr env cexpr prop in
3191 emit_default
3192 cexpr_begin
3193 cexpr_end
3194 (instr_basesc (base_offset + 1) rhs_stack_size base_mode)
3197 | _ ->
3198 let base_expr_instrs = emit_expr env expr in
3199 emit_default
3200 base_expr_instrs
3201 empty
3202 (emit_pos_then pos @@ instr (IBase (BaseC (base_offset, base_mode))))
3206 and emit_ignored_expr env ?(pop_pos : Pos.t = Pos.none) (e : Tast.expr) =
3207 match snd e with
3208 | A.Expr_list es -> gather @@ List.map ~f:(emit_ignored_expr env ~pop_pos) es
3209 | _ -> gather [emit_expr env e; emit_pos_then pop_pos @@ instr_popc]
3212 * Replaces erased generics with underscores or
3213 * raises a parse error if used with is/as expressions
3215 and fixup_type_arg (env : Emit_env.t) ~isas (hint : Aast.hint) =
3216 let erased_tparams = get_erased_tparams env in
3217 let rec aux ((p, hint) as _x : Aast.hint) =
3218 match hint with
3219 | Aast.Hoption h -> (p, Aast.Hoption (aux h))
3220 | Aast.Hlike h -> (p, Aast.Hlike (aux h))
3221 | Aast.Hfun
3222 Aast.
3224 hf_reactive_kind;
3225 hf_is_coroutine;
3226 hf_param_tys;
3227 hf_param_kinds;
3228 hf_param_mutability;
3229 hf_variadic_ty;
3230 hf_return_ty;
3231 hf_is_mutable_return;
3232 } ->
3233 ( p,
3234 Aast.Hfun
3235 Aast.
3237 hf_reactive_kind;
3238 hf_is_coroutine;
3239 hf_param_tys = List.map ~f:aux hf_param_tys;
3240 hf_param_kinds;
3241 hf_param_mutability;
3242 (* TODO: shouldn't we also replace the hint in here? *)
3243 hf_variadic_ty;
3244 hf_return_ty = aux hf_return_ty;
3245 hf_is_mutable_return;
3247 | Aast.Htuple hl -> (p, Aast.Htuple (List.map ~f:aux hl))
3248 | Aast.Happly ((_, id), _)
3249 when List.mem ~equal:String.equal erased_tparams id ->
3250 if isas then
3251 Emit_fatal.raise_fatal_parse
3253 "Erased generics are not allowed in is/as expressions"
3254 else
3255 (p, Aast.Happly ((p, "_"), []))
3256 | Aast.Happly (id, hl) -> (p, Aast.Happly (id, List.map ~f:aux hl))
3257 | Aast.Hshape
3258 { Aast.nsi_allows_unknown_fields = uf; Aast.nsi_field_map = fm } ->
3259 ( p,
3260 Aast.Hshape
3262 Aast.nsi_allows_unknown_fields = uf;
3263 Aast.nsi_field_map = List.map ~f:aux_sf fm;
3265 | Aast.Haccess _ -> (p, hint)
3266 | Aast.Hsoft h -> (p, Aast.Hsoft (aux h))
3267 | _ -> failwith "todo"
3268 and aux_sf sfi = { sfi with Aast.sfi_hint = aux sfi.Aast.sfi_hint } in
3269 aux hint
3271 and emit_reified_arg env ~isas pos (hint : Aast.hint) =
3272 let hint = fixup_type_arg env ~isas hint in
3273 let scope = Emit_env.get_scope env in
3274 let f is_fun tparam acc =
3275 match tparam with
3276 | { A.tp_name = (_, id); A.tp_reified; _ } when not (tp_reified = A.Erased)
3278 SMap.add id is_fun acc
3279 | _ -> acc
3281 let current_targs =
3282 List.fold_right
3283 (Ast_scope.Scope.get_fun_tparams scope)
3284 ~init:SMap.empty
3285 ~f:(f true)
3287 let current_targs =
3288 List.fold_right
3289 (Ast_scope.Scope.get_class_tparams scope).A.c_tparam_list
3290 ~init:current_targs
3291 ~f:(f false)
3293 let acc = ref (0, SMap.empty) in
3294 let visitor =
3295 object
3296 inherit [_] A.iter as super
3298 method! on_hint_ _ h =
3299 let add_name name =
3300 let (i, map) = !acc in
3301 match SMap.get name current_targs with
3302 | Some _ when not (SMap.mem name map) ->
3303 acc := (i + 1, SMap.add name i map)
3304 | _ -> ()
3306 match h with
3307 | A.Haccess (_, sids) -> List.iter sids (fun sid -> add_name (snd sid))
3308 | A.Happly ((_, name), h) ->
3309 add_name name;
3310 let _ = List.map ~f:(super#on_hint ()) h in
3312 | A.Habstr name -> add_name name
3313 | _ ->
3315 super#on_hint_ () h
3318 visitor#on_hint () hint;
3319 let (count, targ_map) = !acc in
3320 match snd hint with
3321 | Aast.Happly ((_, name), []) when SMap.mem name current_targs ->
3322 (emit_reified_type env pos name, false)
3323 | _ ->
3324 let ts = get_type_structure_for_hint ~targ_map ~tparams:[] hint in
3325 let ts_list =
3326 if count = 0 then
3328 else
3329 (* Sort map from key 0 to count and convert each identified into cgetl *)
3330 let values =
3331 SMap.bindings targ_map
3332 |> List.sort ~compare:(fun (_, x) (_, y) -> Pervasives.compare x y)
3333 |> List.map ~f:(fun (v, _) -> emit_reified_type env pos v)
3335 gather [gather values; ts]
3337 ( gather [ts_list; instr_combine_and_resolve_type_struct (count + 1)],
3338 count = 0 )
3340 (* Emit arguments of a function call and inout setter for inout args *)
3341 and emit_args_and_inout_setters env (args : Tast.expr list) =
3342 let aliases =
3343 if has_inout_args args then
3344 InoutLocals.collect_written_variables env args
3345 else
3346 SMap.empty
3348 let emit_arg_and_inout_setter i (arg : Tast.expr) =
3349 match snd arg with
3350 (* inout $var *)
3351 | A.Callconv (Ast_defs.Pinout, (_, A.Lvar (pos, id))) ->
3352 let local = get_local env (pos, Local_id.get_name id) in
3353 let not_in_try = not (Emit_env.is_in_try env) in
3354 let move_instrs =
3355 if not_in_try && InoutLocals.should_move_local_value local aliases then
3356 gather [instr_null; instr_popl local]
3357 else
3358 empty
3360 (gather [instr_cgetl local; move_instrs], instr_popl local)
3361 (* inout $arr[...][...] *)
3362 | A.Callconv
3363 (Ast_defs.Pinout, ((pos, _), A.Array_get (base_expr, opt_elem_expr)))
3365 let array_get_result =
3367 (emit_array_get_worker
3368 ~inout_param_info:(Some (i, aliases))
3371 QueryOp.InOut
3372 base_expr
3373 opt_elem_expr)
3375 begin
3376 match array_get_result with
3377 | Array_get_regular instrs ->
3378 let setter_base =
3380 (emit_array_get
3381 ~no_final:true
3382 ~mode:MemberOpMode.Define
3385 QueryOp.InOut
3386 base_expr
3387 opt_elem_expr)
3389 let setter =
3390 gather
3392 setter_base;
3393 instr_setm 0 (get_elem_member_key env 0 opt_elem_expr);
3394 instr_popc;
3397 (instrs, setter)
3398 | Array_get_inout { load; store } -> rebuild_load_store load store
3400 (* unsupported inout *)
3401 | A.Callconv (Ast_defs.Pinout, _) ->
3402 failwith "emit_arg_and_inout_setter: Unexpected inout expression type"
3403 (* regular argument *)
3404 | _ -> (emit_expr env arg, empty)
3406 let rec aux i (args : Tast.expr list) =
3407 match args with
3408 | [] -> (empty, empty)
3409 | arg :: rem_args ->
3410 let (this_arg, this_setter) = emit_arg_and_inout_setter i arg in
3411 let (rem_args, rem_setters) = aux (i + 1) rem_args in
3412 (gather [this_arg; rem_args], gather [this_setter; rem_setters])
3414 let (instr_args, instr_setters) = aux 0 args in
3415 if has_inout_args args then
3416 let retval = Local.get_unnamed_local () in
3417 (instr_args, gather [instr_popl retval; instr_setters; instr_pushl retval])
3418 else
3419 (instr_args, empty)
3421 (* Create fcall_args for a given call *)
3422 and get_fcall_args ?(lock_while_unwinding = false) args uargs async_eager_label
3424 let num_args = List.length args in
3425 let num_rets =
3426 List.fold_left args ~init:1 ~f:(fun acc arg ->
3427 if is_inout_arg arg then
3428 acc + 1
3429 else
3430 acc)
3432 let flags =
3433 { default_fcall_flags with has_unpack = uargs <> []; lock_while_unwinding }
3435 let inouts = List.map args is_inout_arg in
3436 make_fcall_args ~flags ~num_rets ~inouts ?async_eager_label num_args
3438 (* Expression that appears in an object context, such as expr->meth(...) *)
3439 and emit_object_expr env (expr : Tast.expr) =
3440 let (_, expr_) = expr in
3441 match expr_ with
3442 | A.Lvar (_, x) when is_local_this env x -> instr_this
3443 | _ -> emit_expr env expr
3445 and is_inout_arg = function
3446 | (_, A.Callconv (Ast_defs.Pinout, _)) -> true
3447 | _ -> false
3449 and has_inout_args es = List.exists es ~f:is_inout_arg
3451 and emit_call_lhs_and_fcall
3452 env (expr : Tast.expr) fcall_args (targs : Tast.targ list) =
3453 let ((pos, _), expr_) = expr in
3454 let does_not_have_non_tparam_generics =
3455 not (has_non_tparam_generics_targs env targs)
3457 let emit_generics (flags, a, b, c, d) =
3458 if does_not_have_non_tparam_generics then
3459 (empty, (flags, a, b, c, d))
3460 else
3461 ( emit_reified_targs env pos (List.map ~f:snd targs),
3462 ({ flags with has_generics = true }, a, b, c, d) )
3464 match expr_ with
3465 | A.Obj_get (obj, (_, A.String id), null_flavor)
3466 | A.Obj_get (obj, (_, A.Id (_, id)), null_flavor) ->
3467 let name = Hhbc_id.Method.from_ast_name id in
3468 let obj = emit_object_expr env obj in
3469 let (generics, fcall_args) = emit_generics fcall_args in
3470 let null_flavor = from_ast_null_flavor null_flavor in
3471 ( gather [obj; instr_nulluninit; instr_nulluninit],
3472 gather [generics; instr_fcallobjmethodd fcall_args name null_flavor] )
3473 | A.Obj_get (obj, method_expr, null_flavor) ->
3474 let obj = emit_object_expr env obj in
3475 let tmp = Local.get_unnamed_local () in
3476 let null_flavor = from_ast_null_flavor null_flavor in
3477 ( gather
3479 obj;
3480 instr_nulluninit;
3481 instr_nulluninit;
3482 emit_expr env method_expr;
3483 instr_popl tmp;
3485 gather [instr_pushl tmp; instr_fcallobjmethod fcall_args null_flavor] )
3486 | A.Class_const (cid, (_, id)) ->
3487 let cexpr =
3488 class_id_to_class_expr ~resolve_self:false (Emit_env.get_scope env) cid
3490 let method_id = Hhbc_id.Method.from_ast_name id in
3491 let method_id_string = Hhbc_id.Method.to_raw_string method_id in
3492 let cexpr =
3493 match cexpr with
3494 | Class_id (_, name) ->
3495 Option.value ~default:cexpr (get_reified_var_cexpr env pos name)
3496 | _ -> cexpr
3498 begin
3499 match cexpr with
3500 (* Statically known *)
3501 | Class_id cid ->
3502 let fq_cid = Hhbc_id.Class.elaborate_id cid in
3503 let fq_cid_string = Hhbc_id.Class.to_raw_string fq_cid in
3504 Emit_symbol_refs.add_class fq_cid_string;
3505 let (generics, fcall_args) = emit_generics fcall_args in
3506 ( gather [instr_nulluninit; instr_nulluninit; instr_nulluninit],
3507 gather [generics; instr_fcallclsmethodd fcall_args method_id fq_cid]
3509 | Class_special clsref ->
3510 let (generics, fcall_args) = emit_generics fcall_args in
3511 ( gather [instr_nulluninit; instr_nulluninit; instr_nulluninit],
3512 gather [generics; instr_fcallclsmethodsd fcall_args clsref method_id]
3514 | Class_expr expr ->
3515 let (generics, fcall_args) = emit_generics fcall_args in
3516 let emit_fcall instr_meth =
3517 gather
3519 generics;
3520 instr_meth;
3521 emit_expr env expr;
3522 instr_classgetc;
3523 instr_fcallclsmethod
3524 ~is_log_as_dynamic_call:DontLogAsDynamicCall
3525 fcall_args;
3528 ( gather [instr_nulluninit; instr_nulluninit; instr_nulluninit],
3529 emit_fcall (instr_string method_id_string) )
3530 | Class_reified instrs ->
3531 (* TODO(T31677864): Implement reification here *)
3532 let tmp = Local.get_unnamed_local () in
3533 ( gather
3535 instr_nulluninit;
3536 instr_nulluninit;
3537 instr_nulluninit;
3538 instrs;
3539 instr_popl tmp;
3541 gather
3543 instr_string method_id_string;
3544 instr_pushl tmp;
3545 instr_classgetc;
3546 instr_fcallclsmethod fcall_args;
3549 | A.Class_get (cid, e) ->
3550 let cexpr =
3551 class_id_to_class_expr ~resolve_self:false (Emit_env.get_scope env) cid
3553 let emit_meth_name () =
3554 match e with
3555 | A.CGstring (pos, id) ->
3556 emit_pos_then pos @@ instr_cgetl (Local.Named id)
3557 | A.CGexpr e -> emit_expr env e
3559 let cexpr =
3560 match cexpr with
3561 | Class_id (_, name) ->
3562 Option.value ~default:cexpr (get_reified_var_cexpr env pos name)
3563 | _ -> cexpr
3565 begin
3566 match cexpr with
3567 | Class_id cid ->
3568 let tmp = Local.get_unnamed_local () in
3569 ( gather
3571 instr_nulluninit;
3572 instr_nulluninit;
3573 instr_nulluninit;
3574 emit_meth_name ();
3575 instr_popl tmp;
3577 gather
3579 instr_pushl tmp;
3580 emit_known_class_id cid;
3581 instr_fcallclsmethod fcall_args;
3583 | Class_special clsref ->
3584 let tmp = Local.get_unnamed_local () in
3585 ( gather
3587 instr_nulluninit;
3588 instr_nulluninit;
3589 instr_nulluninit;
3590 emit_meth_name ();
3591 instr_popl tmp;
3593 gather [instr_pushl tmp; instr_fcallclsmethods fcall_args clsref] )
3594 | Class_expr expr ->
3595 let cls = Local.get_unnamed_local () in
3596 let meth = Local.get_unnamed_local () in
3597 ( gather
3599 instr_nulluninit;
3600 instr_nulluninit;
3601 instr_nulluninit;
3602 emit_expr env expr;
3603 instr_popl cls;
3604 emit_meth_name ();
3605 instr_popl meth;
3607 gather
3609 instr_pushl meth;
3610 instr_pushl cls;
3611 instr_classgetc;
3612 instr_fcallclsmethod fcall_args;
3614 | Class_reified instrs ->
3615 let cls = Local.get_unnamed_local () in
3616 let meth = Local.get_unnamed_local () in
3617 ( gather
3619 instr_nulluninit;
3620 instr_nulluninit;
3621 instr_nulluninit;
3622 instrs;
3623 instr_popl cls;
3624 emit_meth_name ();
3625 instr_popl meth;
3627 gather
3629 instr_pushl meth;
3630 instr_pushl cls;
3631 instr_classgetc;
3632 instr_fcallclsmethod fcall_args;
3635 | A.Id (_, s) ->
3636 let fq_id = Hhbc_id.Function.from_ast_name s in
3637 let fq_id =
3638 let (flags, num_args, _, _, _) = fcall_args in
3639 match SU.strip_global_ns s with
3640 | "min" when num_args = 2 && not flags.has_unpack ->
3641 Hhbc_id.Function.from_raw_string "__SystemLib\\min2"
3642 | "max" when num_args = 2 && not flags.has_unpack ->
3643 Hhbc_id.Function.from_raw_string "__SystemLib\\max2"
3644 | _ -> fq_id
3646 let (generics, fcall_args) = emit_generics fcall_args in
3647 ( gather [instr_nulluninit; instr_nulluninit; instr_nulluninit],
3648 gather [generics; instr_fcallfuncd fcall_args fq_id] )
3649 | A.String s ->
3650 let fq_id = Hhbc_id.Function.from_raw_string s in
3651 let (generics, fcall_args) = emit_generics fcall_args in
3652 ( gather [instr_nulluninit; instr_nulluninit; instr_nulluninit],
3653 gather [generics; instr_fcallfuncd fcall_args fq_id] )
3654 | _ ->
3655 let tmp = Local.get_unnamed_local () in
3656 ( gather
3658 instr_nulluninit;
3659 instr_nulluninit;
3660 instr_nulluninit;
3661 emit_expr env expr;
3662 instr_popl tmp;
3664 gather [instr_pushl tmp; instr_fcallfunc fcall_args] )
3666 and get_call_builtin_func_info id =
3667 match id with
3668 | "array_key_exists" -> Some (2, IMisc AKExists)
3669 | "hphp_array_idx" -> Some (3, IMisc ArrayIdx)
3670 | "intval" -> Some (1, IOp CastInt)
3671 | "boolval" -> Some (1, IOp CastBool)
3672 | "strval" -> Some (1, IOp CastString)
3673 | "floatval"
3674 | "doubleval" ->
3675 Some (1, IOp CastDouble)
3676 | "HH\\vec" -> Some (1, IOp CastVec)
3677 | "HH\\keyset" -> Some (1, IOp CastKeyset)
3678 | "HH\\dict" -> Some (1, IOp CastDict)
3679 | "HH\\varray" ->
3680 Some
3681 ( 1,
3683 ( if hack_arr_dv_arrs () then
3684 CastVec
3685 else
3686 CastVArray ) )
3687 | "HH\\darray" ->
3688 Some
3689 ( 1,
3691 ( if hack_arr_dv_arrs () then
3692 CastDict
3693 else
3694 CastDArray ) )
3695 | "HH\\global_get" -> Some (1, IGet CGetG)
3696 | "HH\\global_isset" -> Some (1, IIsset IssetG)
3697 | _ -> None
3699 (* TODO: work out what HHVM does special here *)
3700 and emit_name_string env e = emit_expr env e
3702 and emit_special_function
3703 env pos annot id (args : Tast.expr list) (uargs : Tast.expr list) default =
3704 let nargs = List.length args + List.length uargs in
3705 let lower_fq_name =
3706 Hhbc_id.Function.from_ast_name id
3707 |> Hhbc_id.Function.to_raw_string
3709 (* Make sure that we do not treat a special function that is aliased as not
3710 * aliased *)
3711 match (lower_fq_name, args) with
3712 | (id, _) when id = SN.SpecialFunctions.echo ->
3713 let instrs =
3714 gather
3715 @@ List.mapi args (fun i arg ->
3716 gather
3718 emit_expr env arg;
3719 emit_pos pos;
3720 instr (IOp Print);
3721 ( if i = nargs - 1 then
3722 empty
3723 else
3724 instr_popc );
3727 Some instrs
3728 | ("HH\\invariant", e :: rest) ->
3729 let l = Label.next_regular () in
3730 let annot = (pos, snd annot) in
3731 (* TODO: Can we capitalize hh to HH? *)
3732 let expr_id = (annot, A.Id (pos, "\\hh\\invariant_violation")) in
3733 Some
3734 (gather
3736 (* Could use emit_jmpnz for better code *)
3737 emit_expr env e;
3738 instr_jmpnz l;
3739 emit_ignored_expr
3741 (annot, A.Call (Aast.Cnormal, expr_id, [], rest, uargs));
3742 Emit_fatal.emit_fatal_runtime pos "invariant_violation";
3743 instr_label l;
3744 instr_null;
3746 | ("assert", _) ->
3747 let l0 = Label.next_regular () in
3748 let l1 = Label.next_regular () in
3749 Some
3750 (gather
3752 instr_string "zend.assertions";
3753 instr_fcallbuiltin 1 1 0 "ini_get";
3754 instr_int 0;
3755 instr_gt;
3756 instr_jmpz l0;
3757 default ();
3758 instr_jmp l1;
3759 instr_label l0;
3760 instr_true;
3761 instr_label l1;
3763 | ("HH\\sequence", []) -> Some instr_null
3764 | ("HH\\sequence", args) ->
3765 Some
3766 (gather (List.intersperse (List.map args ~f:(emit_expr env)) instr_popc))
3767 | ((("class_exists" | "interface_exists" | "trait_exists") as id), arg1 :: _)
3768 when nargs = 1 || nargs = 2 ->
3769 let class_kind =
3770 match id with
3771 | "class_exists" -> KClass
3772 | "interface_exists" -> KInterface
3773 | "trait_exists" -> KTrait
3774 | _ -> failwith "class_kind"
3776 Some
3777 (gather
3779 emit_name_string env arg1;
3780 instr (IOp CastString);
3781 ( if nargs = 1 then
3782 instr_true
3783 else
3784 gather [emit_expr env (List.nth_exn args 1); instr (IOp CastBool)]
3786 instr (IMisc (OODeclExists class_kind));
3788 | (("exit" | "die"), _) when nargs = 0 || nargs = 1 ->
3789 Some (emit_exit env (List.hd args))
3790 | ("HH\\fun", _) ->
3791 if nargs <> 1 then
3792 Emit_fatal.raise_fatal_runtime
3794 ("fun() expects exactly 1 parameter, " ^ string_of_int nargs ^ " given")
3795 else (
3796 match args with
3797 | [(_, A.String func_name)] ->
3798 let func_name = SU.strip_global_ns func_name in
3799 if Hhbc_options.emit_func_pointers !Hhbc_options.compiler_options then
3800 Some
3801 (instr_resolve_func @@ Hhbc_id.Function.from_raw_string func_name)
3802 else
3803 Some (instr_string func_name)
3804 | _ ->
3805 Emit_fatal.raise_fatal_runtime pos "Constant string expected in fun()"
3807 | ("__systemlib\\fun", _) ->
3808 (* used by meth_caller() to directly emit func ptr *)
3809 if nargs <> 1 then
3810 Emit_fatal.raise_fatal_runtime
3812 ("fun() expects exactly 1 parameter, " ^ string_of_int nargs ^ " given")
3813 else (
3814 match args with
3815 | [(_, A.String func_name)] ->
3816 let func_name = SU.strip_global_ns func_name in
3817 Some (instr_resolve_func @@ Hhbc_id.Function.from_raw_string func_name)
3818 | _ ->
3819 Emit_fatal.raise_fatal_runtime pos "Constant string expected in fun()"
3821 | ("HH\\inst_meth", _) ->
3822 begin
3823 match args with
3824 | [obj_expr; method_name] ->
3825 Some
3826 (gather
3828 emit_expr env obj_expr;
3829 emit_expr env method_name;
3830 ( if
3831 Hhbc_options.emit_inst_meth_pointers
3832 !Hhbc_options.compiler_options
3833 then
3834 instr_resolve_obj_method
3835 else
3836 instr (ILitConst (NewVArray 2)) );
3838 | _ ->
3839 Emit_fatal.raise_fatal_runtime
3841 ( "inst_meth() expects exactly 2 parameters, "
3842 ^ string_of_int nargs
3843 ^ " given" )
3845 | ("HH\\class_meth", _) ->
3846 begin
3847 match args with
3849 ((_, A.Class_const _) as class_name);
3850 ((_, A.String _) as method_name);
3852 | [((_, A.String _) as class_name); ((_, A.String _) as method_name)] ->
3853 Some
3854 (gather
3856 emit_expr env class_name;
3857 emit_expr env method_name;
3858 ( if
3859 Hhbc_options.emit_cls_meth_pointers
3860 !Hhbc_options.compiler_options
3861 then
3862 instr_resolve_cls_method NoWarn
3863 else
3864 instr (ILitConst (NewVArray 2)) );
3866 | [class_name; method_name] ->
3867 Some
3868 (gather
3870 emit_expr env class_name;
3871 emit_expr env method_name;
3872 ( if
3873 Hhbc_options.emit_cls_meth_pointers
3874 !Hhbc_options.compiler_options
3875 then
3876 instr_resolve_cls_method Warn
3877 else
3878 instr (ILitConst (NewVArray 2)) );
3880 | _ ->
3881 Emit_fatal.raise_fatal_runtime
3883 ( "class_meth() expects exactly 2 parameters, "
3884 ^ string_of_int nargs
3885 ^ " given" )
3887 | ("HH\\global_set", _) ->
3888 begin
3889 match args with
3890 | [gkey; gvalue] ->
3891 Some
3892 (gather
3894 emit_expr env gkey;
3895 emit_expr env gvalue;
3896 emit_pos pos;
3897 instr (IMutator SetG);
3898 instr_popc;
3899 instr_null;
3901 | _ ->
3902 Emit_fatal.raise_fatal_runtime
3904 ( "global_set() expects exactly 2 parameters, "
3905 ^ string_of_int nargs
3906 ^ " given" )
3908 | ("HH\\global_unset", _) ->
3909 begin
3910 match args with
3911 | [gkey] ->
3912 Some
3913 (gather
3915 emit_expr env gkey;
3916 emit_pos pos;
3917 instr (IMutator UnsetG);
3918 instr_null;
3920 | _ ->
3921 Emit_fatal.raise_fatal_runtime
3923 ( "global_unset() expects exactly 1 parameter, "
3924 ^ string_of_int nargs
3925 ^ " given" )
3927 | ("__hhvm_internal_whresult", [(_, A.Lvar (_, param))])
3928 when Emit_env.is_systemlib () ->
3929 Some
3930 (gather
3931 [instr_cgetl (Local.Named (Local_id.get_name param)); instr_whresult])
3932 | ("__hhvm_internal_newlikearrayl", [(_, A.Lvar (_, param)); (_, A.Int n)])
3933 when Emit_env.is_systemlib () ->
3934 Some
3935 (instr
3936 (ILitConst
3937 (NewLikeArrayL
3938 (Local.Named (Local_id.get_name param), int_of_string n))))
3939 | _ ->
3940 begin
3941 match (args, istype_op lower_fq_name, is_isexp_op lower_fq_name) with
3942 | ([arg_expr], _, Some h) ->
3943 Some (gather [emit_expr env arg_expr; emit_is env pos h])
3944 | ([(_, A.Lvar ((_, arg_str) as arg_id))], Some i, _)
3945 when SN.Superglobals.is_superglobal (Local_id.get_name arg_str)
3946 || Local_id.get_name arg_str = SN.Superglobals.globals ->
3947 Some
3948 (gather
3950 emit_local ~notice:NoNotice env arg_id;
3951 emit_pos pos;
3952 instr (IIsset (IsTypeC i));
3954 | ([(_, A.Lvar (arg_pos, arg_str))], Some i, _)
3955 when not (is_local_this env arg_str) ->
3956 Some
3957 (instr
3958 (IIsset
3959 (IsTypeL (get_local env (arg_pos, Local_id.get_name arg_str), i))))
3960 | ([arg_expr], Some i, _) ->
3961 Some
3962 (gather
3963 [emit_expr env arg_expr; emit_pos pos; instr (IIsset (IsTypeC i))])
3964 | _ ->
3965 begin
3966 match get_call_builtin_func_info lower_fq_name with
3967 | Some (nargs, i) when nargs = List.length args ->
3968 Some (gather [emit_exprs env args; emit_pos pos; instr i])
3969 | _ -> None
3973 and emit_call
3976 expr
3977 (targs : Tast.targ list)
3978 (args : Tast.expr list)
3979 (uargs : Tast.expr list)
3980 async_eager_label =
3981 let (annot, expr_) = expr in
3982 (match expr_ with
3983 | A.Id (_, s) -> Emit_symbol_refs.add_function s
3984 | _ -> ());
3985 let fcall_args = get_fcall_args args uargs async_eager_label in
3986 let (_flags, _args, num_ret, _inouts, _eager) = fcall_args in
3987 let num_uninit = num_ret - 1 in
3988 let default () =
3989 Scope.with_unnamed_locals
3990 @@ fun () ->
3991 let (instr_lhs, instr_fcall) =
3992 emit_call_lhs_and_fcall env expr fcall_args targs
3994 let (instr_args, instr_inout_setters) =
3995 emit_args_and_inout_setters env args
3997 let instr_uargs =
3998 match uargs with
3999 | [] -> empty
4000 | uargs :: _ -> emit_expr env uargs
4002 ( empty,
4003 gather
4005 gather @@ List.init num_uninit ~f:(fun _ -> instr_nulluninit);
4006 instr_lhs;
4007 instr_args;
4008 instr_uargs;
4009 emit_pos pos;
4010 instr_fcall;
4011 instr_inout_setters;
4013 empty )
4015 match (expr_, args) with
4016 | (A.Id (_, id), _) ->
4017 let special_fn_opt =
4018 emit_special_function env pos annot id args uargs default
4020 begin
4021 match special_fn_opt with
4022 | Some instrs -> instrs
4023 | None -> default ()
4025 | _ -> default ()
4027 and emit_final_member_op stack_index op mk =
4028 match op with
4029 | LValOp.Set -> instr (IFinal (SetM (stack_index, mk)))
4030 | LValOp.SetOp op -> instr (IFinal (SetOpM (stack_index, op, mk)))
4031 | LValOp.IncDec op -> instr (IFinal (IncDecM (stack_index, op, mk)))
4032 | LValOp.Unset -> instr (IFinal (UnsetM (stack_index, mk)))
4034 and emit_final_local_op pos op lid =
4035 emit_pos_then pos
4037 match op with
4038 | LValOp.Set -> instr (IMutator (SetL lid))
4039 | LValOp.SetOp op -> instr (IMutator (SetOpL (lid, op)))
4040 | LValOp.IncDec op -> instr (IMutator (IncDecL (lid, op)))
4041 | LValOp.Unset -> instr (IMutator (UnsetL lid))
4043 and emit_final_global_op pos op =
4044 match op with
4045 | LValOp.Set -> emit_pos_then pos @@ instr (IMutator SetG)
4046 | LValOp.SetOp op -> instr (IMutator (SetOpG op))
4047 | LValOp.IncDec op -> instr (IMutator (IncDecG op))
4048 | LValOp.Unset -> emit_pos_then pos @@ instr (IMutator UnsetG)
4050 and emit_final_static_op cid (prop : Tast.class_get_expr) op =
4051 match op with
4052 | LValOp.Set -> instr (IMutator SetS)
4053 | LValOp.SetOp op -> instr (IMutator (SetOpS op))
4054 | LValOp.IncDec op -> instr (IMutator (IncDecS op))
4055 | LValOp.Unset ->
4056 let pos =
4057 match prop with
4058 | A.CGexpr ((pos, _), _) -> pos
4059 | A.CGstring (pos, _) -> pos
4061 let cid = text_of_class_id cid in
4062 let id = text_of_prop prop in
4063 Emit_fatal.emit_fatal_runtime
4065 ("Attempt to unset static property " ^ SU.strip_ns cid ^ "::" ^ id)
4067 (* Given a local $local and a list of integer array indices i_1, ..., i_n,
4068 * generate code to extract the value of $local[i_n]...[i_1]:
4069 * BaseL $local Warn
4070 * Dim Warn EI:i_n ...
4071 * Dim Warn EI:i_2
4072 * QueryM 0 CGet EI:i_1
4074 and emit_array_get_fixed last_usage local indices =
4075 let (base, stack_count) =
4076 if last_usage then
4077 (gather [instr_pushl local; instr_basec 0 MemberOpMode.Warn], 1)
4078 else
4079 (instr_basel local MemberOpMode.Warn, 0)
4081 let indices =
4082 gather
4083 @@ List.rev_mapi indices (fun i ix ->
4084 let mk = MemberKey.EI (Int64.of_int ix) in
4085 if i = 0 then
4086 instr (IFinal (QueryM (stack_count, QueryOp.CGet, mk)))
4087 else
4088 instr (IBase (Dim (MemberOpMode.Warn, mk))))
4090 gather [base; indices]
4092 and can_use_as_rhs_in_list_assignment (expr : Tast.expr_) =
4093 Aast.(
4094 match expr with
4095 | Call (_, (_, Id (_, s)), _, _, _) when s = SN.SpecialFunctions.echo ->
4096 false
4097 | Lvar _
4098 | Array_get _
4099 | Obj_get _
4100 | Class_get _
4101 | PU_atom _
4102 | Call _
4103 | New _
4104 | Record _
4105 | Expr_list _
4106 | Yield _
4107 | Cast _
4108 | Eif _
4109 | Array _
4110 | Varray _
4111 | Darray _
4112 | Collection _
4113 | Clone _
4114 | Unop _
4115 | As _
4116 | Await _ ->
4117 true
4118 | Pipe (_, _, (_, r))
4119 | Binop (Ast_defs.Eq None, (_, List _), (_, r)) ->
4120 can_use_as_rhs_in_list_assignment r
4121 | Binop (Ast_defs.Plus, _, _)
4122 | Binop (Ast_defs.QuestionQuestion, _, _)
4123 | Binop (Ast_defs.Eq _, _, _)
4124 | Class_const _ ->
4125 true
4126 (* Everything below is false *)
4127 | This
4128 | Any
4129 | ValCollection _
4130 | KeyValCollection _
4131 | ImmutableVar _
4132 | Dollardollar _
4133 | Lplaceholder _
4134 | Fun_id _
4135 | Method_id (_, _)
4136 | Method_caller (_, _)
4137 | Smethod_id (_, _)
4138 | Special_func _
4139 | Pair (_, _)
4140 | Assert _
4141 | Typename _
4142 | Binop _
4143 | Shape _
4144 | Null
4145 | True
4146 | False
4147 | Omitted
4148 | Id _
4149 | Int _
4150 | Float _
4151 | String _
4152 | String2 _
4153 | PrefixedString _
4154 | Yield_break
4155 | Yield_from _
4156 | Suspend _
4157 | Is _
4158 | BracedExpr _
4159 | ParenthesizedExpr _
4160 | Efun _
4161 | Lfun _
4162 | Xml _
4163 | Import _
4164 | Callconv _
4165 | List _ ->
4166 false
4167 | PU_identifier _ ->
4168 failwith "TODO(T35357243): Pocket Universes syntax must be erased by now")
4170 (* Generate code for each lvalue assignment in a list destructuring expression.
4171 * Lvalues are assigned right-to-left, regardless of the nesting structure. So
4172 * list($a, list($b, $c)) = $d
4173 * and list(list($a, $b), $c) = $d
4174 * will both assign to $c, $b and $a in that order.
4175 * Returns a pair of instructions:
4176 * 1. initialization part of the left hand side
4177 * 2. assignment
4178 * this is necessary to handle cases like:
4179 * list($a[$f()]) = b();
4180 * here f() should be invoked before b()
4182 and emit_lval_op_list
4183 ?(last_usage = false)
4184 (env : Emit_env.t)
4185 (outer_pos : Pos.t)
4186 local
4187 (indices : int list)
4188 (expr : Tast.expr) =
4189 let is_ltr = php7_ltr_assign () in
4190 match snd expr with
4191 | A.List exprs ->
4192 let last_non_omitted =
4193 (* last usage of the local will happen when processing last non-omitted
4194 element in the list - find it *)
4195 if last_usage then
4196 if is_ltr then
4197 exprs
4198 |> List.foldi ~init:None ~f:(fun i acc (_, v) ->
4199 if v = A.Omitted then
4201 else
4202 Some i)
4203 (* in right-to-left case result list will be reversed
4204 so we need to find first non-omitted expression *)
4205 else
4206 exprs
4207 |> List.findi ~f:(fun _ (_, v) -> v <> A.Omitted)
4208 |> Option.map ~f:fst
4209 else
4210 None
4212 let (lhs_instrs, set_instrs) =
4213 List.mapi exprs (fun i expr ->
4214 emit_lval_op_list
4215 ~last_usage:(Some i = last_non_omitted)
4217 outer_pos
4218 local
4219 (i :: indices)
4220 expr)
4221 |> List.unzip
4223 ( gather lhs_instrs,
4224 gather
4225 ( if not is_ltr then
4226 List.rev set_instrs
4227 else
4228 set_instrs ) )
4229 | A.Omitted -> (empty, empty)
4230 | _ ->
4231 (* Generate code to access the element from the array *)
4232 let access_instrs =
4233 match (local, indices) with
4234 | (Some local, _ :: _) -> emit_array_get_fixed last_usage local indices
4235 | (Some local, []) ->
4236 if last_usage then
4237 instr_pushl local
4238 else
4239 instr_cgetl local
4240 | (None, _) -> instr_null
4242 (* Generate code to assign to the lvalue *)
4243 (* Return pair: side effects to initialize lhs + assignment *)
4244 let (lhs_instrs, rhs_instrs, set_op) =
4245 emit_lval_op_nonlist_steps env outer_pos LValOp.Set expr access_instrs 1
4247 let lhs =
4248 if is_ltr then
4249 empty
4250 else
4251 lhs_instrs
4253 let rest =
4254 gather
4256 ( if is_ltr then
4257 lhs_instrs
4258 else
4259 empty );
4260 rhs_instrs;
4261 set_op;
4262 instr_popc;
4265 (lhs, rest)
4267 (* Emit code for an l-value operation *)
4268 and emit_lval_op
4269 ?(null_coalesce_assignment = false)
4270 (env : Emit_env.t)
4273 (expr1 : Tast.expr)
4274 opt_expr2 =
4275 match (op, expr1, opt_expr2) with
4276 (* Special case for list destructuring, only on assignment *)
4277 | (LValOp.Set, (_, A.List l), Some expr2) ->
4278 let instr_rhs = emit_expr env expr2 in
4279 let has_elements =
4280 List.exists l ~f:(function
4281 | (_, A.Omitted) -> false
4282 | _ -> true)
4284 if not has_elements then
4285 instr_rhs
4286 else
4287 Scope.with_unnamed_local
4288 @@ fun local ->
4289 let loc =
4290 if can_use_as_rhs_in_list_assignment (snd expr2) then
4291 Some local
4292 else
4293 None
4295 let (instr_lhs, instr_assign) = emit_lval_op_list env pos loc [] expr1 in
4296 (* before *)
4297 ( gather [instr_lhs; instr_rhs; instr_popl local],
4298 (* innner *)
4299 instr_assign,
4300 (* after *)
4301 instr_pushl local )
4302 | _ ->
4303 Local.scope
4304 @@ fun () ->
4305 let (rhs_instrs, rhs_stack_size) =
4306 match opt_expr2 with
4307 | None -> (empty, 0)
4308 | Some (_, A.Yield af) ->
4309 let temp = Local.get_unnamed_local () in
4310 ( gather
4312 emit_yield env pos af;
4313 instr_setl temp;
4314 instr_popc;
4315 instr_pushl temp;
4318 | Some e -> (emit_expr env e, 1)
4320 emit_lval_op_nonlist
4321 ~null_coalesce_assignment
4325 expr1
4326 rhs_instrs
4327 rhs_stack_size
4329 and emit_lval_op_nonlist
4330 ?(null_coalesce_assignment = false) env pos op e rhs_instrs rhs_stack_size
4332 let (lhs, rhs, setop) =
4333 emit_lval_op_nonlist_steps
4334 ~null_coalesce_assignment
4339 rhs_instrs
4340 rhs_stack_size
4342 gather [lhs; rhs; setop]
4344 and emit_lval_op_nonlist_steps
4345 ?(null_coalesce_assignment = false)
4346 (env : Emit_env.t)
4347 outer_pos
4349 (expr : Tast.expr)
4350 rhs_instrs
4351 rhs_stack_size =
4352 let ((pos, _), expr_) = expr in
4353 let env =
4354 match op with
4355 (* Unbelieveably, $test[] += 5; is legal in PHP, but $test[] = $test[] + 5 is not *)
4356 | LValOp.Set
4357 | LValOp.SetOp _
4358 | LValOp.IncDec _ ->
4359 { env with Emit_env.env_allows_array_append = true }
4360 | _ -> env
4362 match expr_ with
4363 | A.Lvar (name_pos, id)
4364 when SN.Superglobals.is_superglobal (Local_id.get_name id)
4365 || Local_id.get_name id = SN.Superglobals.globals ->
4366 ( emit_pos_then name_pos
4367 @@ instr_string
4368 @@ SU.Locals.strip_dollar (Local_id.get_name id),
4369 rhs_instrs,
4370 emit_final_global_op outer_pos op )
4371 | A.Lvar ((_, str) as id) when is_local_this env str && is_incdec op ->
4372 (emit_local ~notice:Notice env id, rhs_instrs, empty)
4373 | A.Lvar (pos, name) when (not (is_local_this env name)) || op = LValOp.Unset
4375 ( empty,
4376 rhs_instrs,
4377 emit_final_local_op
4378 outer_pos
4380 (get_local env (pos, Local_id.get_name name)) )
4381 | A.Array_get ((_, A.Lvar (_, x)), Some e)
4382 when Local_id.get_name x = SN.Superglobals.globals ->
4383 let final_global_op_instrs = emit_final_global_op pos op in
4384 if rhs_stack_size = 0 then
4385 (emit_expr env e, empty, final_global_op_instrs)
4386 else
4387 let (index_instrs, under_top) = emit_first_expr env e in
4388 if under_top then
4389 (empty, gather [rhs_instrs; index_instrs], final_global_op_instrs)
4390 else
4391 (index_instrs, rhs_instrs, final_global_op_instrs)
4392 | A.Array_get (_, None) when not (Emit_env.does_env_allow_array_append env)
4394 Emit_fatal.raise_fatal_runtime pos "Can't use [] for reading"
4395 | A.Array_get (base_expr, opt_elem_expr) ->
4396 let mode =
4397 match op with
4398 | LValOp.Unset -> MemberOpMode.Unset
4399 | _ -> MemberOpMode.Define
4401 let (elem_expr_instrs, elem_stack_size) =
4402 emit_elem_instrs
4403 ~local_temp_kind:None
4404 ~null_coalesce_assignment
4406 opt_elem_expr
4408 let elem_expr_instrs =
4409 if null_coalesce_assignment then
4410 empty
4411 else
4412 elem_expr_instrs
4414 let base_offset = elem_stack_size + rhs_stack_size in
4415 let ( base_expr_instrs_begin,
4416 base_expr_instrs_end,
4417 base_setup_instrs,
4418 base_stack_size,
4419 cls_stack_size ) =
4420 emit_base
4421 ~is_object:false
4422 ~notice:Notice
4423 ~null_coalesce_assignment
4425 mode
4426 base_offset
4427 rhs_stack_size
4428 base_expr
4430 let mk =
4431 get_elem_member_key
4432 ~null_coalesce_assignment
4434 (rhs_stack_size + cls_stack_size)
4435 opt_elem_expr
4437 let total_stack_size =
4438 elem_stack_size + base_stack_size + cls_stack_size
4440 let final_instr =
4441 emit_pos_then pos @@ emit_final_member_op total_stack_size op mk
4443 ( gather
4445 ( if null_coalesce_assignment then
4446 empty
4447 else
4448 base_expr_instrs_begin );
4449 elem_expr_instrs;
4450 ( if null_coalesce_assignment then
4451 empty
4452 else
4453 base_expr_instrs_end );
4455 rhs_instrs,
4456 gather [emit_pos pos; base_setup_instrs; final_instr] )
4457 | A.Obj_get (e1, e2, null_flavor) ->
4458 if null_flavor = Ast_defs.OG_nullsafe then
4459 Emit_fatal.raise_fatal_parse pos "?-> is not allowed in write context";
4460 let mode =
4461 match op with
4462 | LValOp.Unset -> MemberOpMode.Unset
4463 | _ -> MemberOpMode.Define
4465 let (_, _, prop_stack_size) =
4466 emit_prop_expr ~null_coalesce_assignment env null_flavor 0 e2
4468 let base_offset = prop_stack_size + rhs_stack_size in
4469 let ( base_expr_instrs_begin,
4470 base_expr_instrs_end,
4471 base_setup_instrs,
4472 base_stack_size,
4473 cls_stack_size ) =
4474 emit_base
4475 ~notice:Notice
4476 ~is_object:true
4477 ~null_coalesce_assignment
4479 mode
4480 base_offset
4481 rhs_stack_size
4484 let (mk, prop_expr_instrs, _) =
4485 emit_prop_expr
4486 ~null_coalesce_assignment
4488 null_flavor
4489 (rhs_stack_size + cls_stack_size)
4492 let prop_expr_instrs =
4493 if null_coalesce_assignment then
4494 empty
4495 else
4496 prop_expr_instrs
4498 let total_stack_size =
4499 prop_stack_size + base_stack_size + cls_stack_size
4501 let final_instr =
4502 emit_pos_then pos @@ emit_final_member_op total_stack_size op mk
4504 ( gather
4506 ( if null_coalesce_assignment then
4507 empty
4508 else
4509 base_expr_instrs_begin );
4510 prop_expr_instrs;
4511 ( if null_coalesce_assignment then
4512 empty
4513 else
4514 base_expr_instrs_end );
4516 rhs_instrs,
4517 gather [base_setup_instrs; final_instr] )
4518 | A.Class_get (cid, prop) ->
4519 let cexpr =
4520 class_id_to_class_expr ~resolve_self:false (Emit_env.get_scope env) cid
4522 let final_instr = emit_pos_then pos @@ emit_final_static_op cid prop op in
4523 (of_pair @@ emit_class_expr env cexpr prop, rhs_instrs, final_instr)
4524 | A.Unop (uop, e) ->
4525 ( empty,
4526 rhs_instrs,
4527 gather
4528 [emit_lval_op_nonlist env pos op e empty rhs_stack_size; from_unop uop]
4530 | _ ->
4531 Emit_fatal.raise_fatal_parse pos "Can't use return value in write context"
4533 and from_unop op =
4534 let check_int_overflow =
4535 Hhbc_options.check_int_overflow !Hhbc_options.compiler_options
4537 match op with
4538 | Ast_defs.Utild -> instr (IOp BitNot)
4539 | Ast_defs.Unot -> instr (IOp Not)
4540 | Ast_defs.Uplus ->
4541 instr
4542 (IOp
4543 ( if check_int_overflow then
4544 AddO
4545 else
4546 Add ))
4547 | Ast_defs.Uminus ->
4548 instr
4549 (IOp
4550 ( if check_int_overflow then
4551 SubO
4552 else
4553 Sub ))
4554 | Ast_defs.Uincr
4555 | Ast_defs.Udecr
4556 | Ast_defs.Upincr
4557 | Ast_defs.Updecr
4558 | Ast_defs.Usilence ->
4559 failwith "this unary operation cannot be translated"
4561 and emit_unop env pos op e =
4562 match op with
4563 | Ast_defs.Utild ->
4564 gather [emit_expr env e; emit_pos_then pos @@ from_unop op]
4565 | Ast_defs.Unot ->
4566 gather [emit_expr env e; emit_pos_then pos @@ from_unop op]
4567 | Ast_defs.Uplus ->
4568 gather
4570 emit_pos pos;
4571 instr (ILitConst (Int Int64.zero));
4572 emit_expr env e;
4573 emit_pos_then pos @@ from_unop op;
4575 | Ast_defs.Uminus ->
4576 gather
4578 emit_pos pos;
4579 instr (ILitConst (Int Int64.zero));
4580 emit_expr env e;
4581 emit_pos_then pos @@ from_unop op;
4583 | Ast_defs.Uincr
4584 | Ast_defs.Udecr
4585 | Ast_defs.Upincr
4586 | Ast_defs.Updecr ->
4587 emit_lval_op env pos (LValOp.IncDec (unop_to_incdec_op op)) e None
4588 | Ast_defs.Usilence ->
4589 Local.scope
4590 @@ fun () ->
4591 let temp_local = Local.get_unnamed_local () in
4592 gather
4594 emit_pos pos;
4595 instr_silence_start temp_local;
4596 create_try_catch
4597 (emit_expr env e)
4598 (gather [emit_pos pos; instr_silence_end temp_local]);
4599 emit_pos pos;
4600 instr_silence_end temp_local;
4603 and emit_exprs env (exprs : Tast.expr list) =
4604 match exprs with
4605 | [] -> empty
4606 | expr :: exprs ->
4607 gather (emit_expr env expr :: List.map exprs (emit_expr env))