Handle this typehints
[hiphop-php.git] / hphp / hack / src / hhbc / emit_expression.ml
blob41cde264cc90af2725024f4fc39c7a67be98c90f
1 (**
2 * Copyright (c) 2017, Facebook, Inc.
3 * All rights reserved.
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the "hack" directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
9 *)
11 open Hh_core
12 open Hhbc_ast
13 open Instruction_sequence
14 open Ast_class_expr
15 open Emit_pos
17 module A = Ast
18 module H = Hhbc_ast
19 module TC = Hhas_type_constraint
20 module SN = Naming_special_names
21 module SU = Hhbc_string_utils
22 module ULS = Unique_list_string
23 module Opts = Hhbc_options
25 let inline_hhas_blocks_: (Hhas_asm.t SMap.t) ref = ref SMap.empty
26 let set_inline_hhas_blocks s = inline_hhas_blocks_ := s
28 let can_inline_gen_functions () =
29 let opts = !Opts.compiler_options in
30 Emit_env.is_hh_syntax_enabled () &&
31 (Opts.enable_hiphop_syntax opts) &&
32 (Opts.can_inline_gen_functions opts) &&
33 not (Opts.jit_enable_rename_function opts)
35 let max_array_elem_on_stack () =
36 Hhbc_options.max_array_elem_size_on_the_stack !Hhbc_options.compiler_options
38 type genva_inline_context =
39 | GI_expression
40 | GI_ignore_result
41 | GI_list_assignment of A.expr list
43 type is_expr_lhs =
44 | IsExprExpr of A.expr
45 | IsExprUnnamedLocal of Local.t
47 type emit_jmp_result = {
48 (* generated instruction sequence *)
49 instrs: Instruction_sequence.t;
50 (* does instruction sequence fall through *)
51 is_fallthrough: bool;
52 (* was label associated with emit operation used *)
53 is_label_used: bool;
56 (* Locals, array elements, and properties all support the same range of l-value
57 * operations. *)
58 module LValOp = struct
59 type t =
60 | Set
61 | SetRef
62 | SetOp of eq_op
63 | IncDec of incdec_op
64 | Unset
65 end
67 let jit_enable_rename_function () =
68 Hhbc_options.jit_enable_rename_function !Hhbc_options.compiler_options
70 let is_local_this env id =
71 let scope = Emit_env.get_scope env in
72 id = SN.SpecialIdents.this
73 && Ast_scope.Scope.has_this scope
74 && not (Ast_scope.Scope.is_toplevel scope)
76 module InoutLocals = struct
77 (* for every local that appear as a part of inout argument and also mutated inside
78 argument list this record stores:
79 - position of the first argument when local appears as inout
80 - position of the last argument where local is mutated.
81 Within the this range at every usage of the local must be captured to make sure
82 that later when inout arguments will be written back the same value of the
83 local will be used *)
84 type alias_info = {
85 first_inout: int;
86 last_write: int;
87 num_uses: int;
90 let not_aliased =
91 { first_inout = max_int; last_write = min_int; num_uses = 0; }
93 let add_inout i r =
94 if i < r.first_inout then { r with first_inout = i } else r
96 let add_write i r =
97 if i > r.last_write then { r with last_write = i } else r
99 let add_use _i r =
100 { r with num_uses = r.num_uses + 1 }
102 let in_range i r = i > r.first_inout || i <= r.last_write
103 let has_single_ref r = r.num_uses < 2
105 let update name i f m =
106 let r =
107 SMap.get name m
108 |> Option.value ~default:not_aliased
109 |> f i in
110 SMap.add name r m
112 let add_write name i m = update name i add_write m
113 let add_inout name i m = update name i add_inout m
114 let add_use name i m = update name i add_use m
116 let collect_written_variables env args =
117 (* check value of the argument *)
118 let rec handle_arg ~is_top i acc arg =
119 match snd arg with
120 (* inout $v *)
121 | A.Callconv (A.Pinout, (_, A.Lvar (_, id)))
122 when not (is_local_this env id) ->
123 let acc = add_use id i acc in
124 if is_top then add_inout id i acc else add_write id i acc
125 (* &$v *)
126 | A.Unop (A.Uref, (_, A.Lvar (_, id))) ->
127 let acc = add_use id i acc in
128 add_write id i acc
129 (* $v *)
130 | A.Lvar (_, id) ->
131 let acc = add_use id i acc in
133 | _ ->
134 (* dive into argument value *)
135 dive i acc arg
137 (* collect lvars on the left hand side of '=' operator *)
138 and collect_lvars_lhs i acc e =
139 match snd e with
140 | A.Lvar (_, id) when not (is_local_this env id) ->
141 let acc = add_use id i acc in
142 add_write id i acc
143 | A.List exprs ->
144 List.fold_left exprs ~f:(collect_lvars_lhs i) ~init:acc
145 | _ -> acc
147 (* descend into expression *)
148 and dive i acc expr =
149 let visitor = object(_)
150 inherit [_] Ast_visitor.ast_visitor as super
151 (* lhs op= _ *)
152 method! on_binop acc bop l r =
153 let acc =
154 match bop with
155 | A.Eq _ -> collect_lvars_lhs i acc l
156 | _ -> acc in
157 super#on_binop acc bop l r
158 (* $i++ or $i-- *)
159 method! on_unop acc op e =
160 let acc =
161 match op with
162 | A.Uincr | A.Udecr -> collect_lvars_lhs i acc e
163 | _ -> acc in
164 super#on_unop acc op e
165 (* f(inout $v) or f(&$v) *)
166 method! on_call acc _ _ args uargs =
167 let f = handle_arg ~is_top:false i in
168 let acc = List.fold_left args ~init:acc ~f in
169 List.fold_left uargs ~init:acc ~f
170 end in
171 visitor#on_expr acc expr in
172 List.foldi args ~f:(handle_arg ~is_top:true) ~init:SMap.empty
174 (* determines if value of a local 'name' that appear in parameter 'i'
175 should be saved to local because it might be overwritten later *)
176 let should_save_local_value name i aliases =
177 Option.value_map ~default:false ~f:(in_range i) (SMap.get name aliases)
178 let should_move_local_value name aliases =
179 Option.value_map ~default:true ~f:has_single_ref (SMap.get name aliases)
182 (* Describes what kind of value is intended to be stored in local *)
183 type stored_value_kind =
184 | Value_kind_local
185 | Value_kind_expression
187 (* represents sequence of instructions interleaved with temp locals.
188 <i, None :: rest> - is emitted i :: <rest> (commonly used for final instructions in sequence)
189 <i, Some (l, local_kind) :: rest> is emitted as
192 try-fault F {
193 setl/popl l; depending on local_kind
194 <rest>
196 unsetl l
197 F: unset l
198 unwind
200 type instruction_sequence_with_locals =
201 (Instruction_sequence.t * (Local.t * stored_value_kind) option) list
203 (* converts instruction_sequence_with_locals to instruction_sequence.t *)
204 let rebuild_sequence s rest =
205 let rec aux = function
206 | [] -> rest ()
207 | (i, None) :: xs -> gather [ i; aux xs ]
208 | (i, Some (l, kind)) :: xs ->
209 let fault_label = Label.next_fault () in
210 let unset = instr_unsetl l in
211 let set = if kind = Value_kind_expression then instr_setl l else instr_popl l in
212 let try_block = gather [
213 set;
214 aux xs;
215 ] in
216 let fault_block = gather [ unset; instr_unwind; ] in
217 gather [
219 instr_try_fault fault_label try_block fault_block;
220 unset; ] in
221 aux s
223 (* result of emit_array_get *)
224 type array_get_instr =
225 (* normal $a[..] that does not need to spill anything*)
226 | Array_get_regular of Instruction_sequence.t
227 (* subscript expression used as inout argument that need to spill intermediate
228 values:
229 load - instruction_sequence_with_locals to load value
230 store - instruction to set value back (can use locals defined in load part)
232 | Array_get_inout of {
233 load: instruction_sequence_with_locals;
234 store: Instruction_sequence.t
237 type 'a array_get_base_data = {
238 instrs_begin: 'a;
239 instrs_end: Instruction_sequence.t;
240 setup_instrs: Instruction_sequence.t;
241 stack_size: int
244 (* result of emit_base *)
245 type array_get_base =
246 (* normal <base> part in <base>[..] that does not need to spill anything *)
247 | Array_get_base_regular of Instruction_sequence.t array_get_base_data
248 (* base of subscript expression used as inout argument that need to spill
249 intermediate values *)
250 | Array_get_base_inout of {
251 (* instructions to load base part *)
252 load: instruction_sequence_with_locals array_get_base_data;
253 (* instruction to load base part for setting inout argument back *)
254 store: Instruction_sequence.t
257 let is_incdec op =
258 match op with
259 | LValOp.IncDec _ -> true
260 | _ -> false
262 let is_global_namespace env =
263 Namespace_env.is_global_namespace (Emit_env.get_namespace env)
265 let is_special_function env e args =
266 match snd e with
267 | A.Id (_, s) ->
268 begin
269 let n = List.length args in
270 match s with
271 | "isset" -> n > 0
272 | "empty" -> n = 1
273 | "define" when is_global_namespace env ->
274 begin match args with
275 | [_, A.String _; _] -> true
276 | _ -> false
278 | "eval" -> n = 1
279 | "idx" -> not (jit_enable_rename_function ()) && (n = 2 || n = 3)
280 | "class_alias" when is_global_namespace env ->
281 begin
282 match args with
283 | [_, A.String _; _, A.String _]
284 | [_, A.String _; _, A.String _; _] -> true
285 | _ -> false
287 | _ -> false
289 | _ -> false
291 let optimize_null_check () =
292 Hhbc_options.optimize_null_check !Hhbc_options.compiler_options
294 let optimize_cuf () =
295 Hhbc_options.optimize_cuf !Hhbc_options.compiler_options
297 let hack_arr_compat_notices () =
298 Hhbc_options.hack_arr_compat_notices !Hhbc_options.compiler_options
300 let hack_arr_dv_arrs () =
301 Hhbc_options.hack_arr_dv_arrs !Hhbc_options.compiler_options
303 let php7_ltr_assign () =
304 Hhbc_options.php7_ltr_assign !Hhbc_options.compiler_options
306 (* Emit a comment in lieu of instructions for not-yet-implemented features *)
307 let emit_nyi description =
308 instr (IComment (H.nyi ^ ": " ^ description))
310 (* Strict binary operations; assumes that operands are already on stack *)
311 let from_binop op =
312 let ints_overflow_to_ints =
313 Hhbc_options.ints_overflow_to_ints !Hhbc_options.compiler_options in
314 match op with
315 | A.Plus -> instr (IOp (if ints_overflow_to_ints then Add else AddO))
316 | A.Minus -> instr (IOp (if ints_overflow_to_ints then Sub else SubO))
317 | A.Star -> instr (IOp (if ints_overflow_to_ints then Mul else MulO))
318 | A.Slash -> instr (IOp Div)
319 | A.Eqeq -> instr (IOp Eq)
320 | A.EQeqeq -> instr (IOp Same)
321 | A.Starstar -> instr (IOp Pow)
322 | A.Diff -> instr (IOp Neq)
323 | A.Diff2 -> instr (IOp NSame)
324 | A.Lt -> instr (IOp Lt)
325 | A.Lte -> instr (IOp Lte)
326 | A.Gt -> instr (IOp Gt)
327 | A.Gte -> instr (IOp Gte)
328 | A.Dot -> instr (IOp Concat)
329 | A.Amp -> instr (IOp BitAnd)
330 | A.Bar -> instr (IOp BitOr)
331 | A.Ltlt -> instr (IOp Shl)
332 | A.Gtgt -> instr (IOp Shr)
333 | A.Cmp -> instr (IOp Cmp)
334 | A.Percent -> instr (IOp Mod)
335 | A.Xor -> instr (IOp BitXor)
336 | A.LogXor -> instr (IOp Xor)
337 | A.Eq _ -> emit_nyi "Eq"
338 | A.AMpamp
339 | A.BArbar ->
340 failwith "short-circuiting operator cannot be generated as a simple binop"
342 let binop_to_eqop op =
343 let ints_overflow_to_ints =
344 Hhbc_options.ints_overflow_to_ints !Hhbc_options.compiler_options in
345 match op with
346 | A.Plus -> Some (if ints_overflow_to_ints then PlusEqual else PlusEqualO)
347 | A.Minus -> Some (if ints_overflow_to_ints then MinusEqual else MinusEqualO)
348 | A.Star -> Some (if ints_overflow_to_ints then MulEqual else MulEqualO)
349 | A.Slash -> Some DivEqual
350 | A.Starstar -> Some PowEqual
351 | A.Amp -> Some AndEqual
352 | A.Bar -> Some OrEqual
353 | A.Xor -> Some XorEqual
354 | A.Ltlt -> Some SlEqual
355 | A.Gtgt -> Some SrEqual
356 | A.Percent -> Some ModEqual
357 | A.Dot -> Some ConcatEqual
358 | _ -> None
360 let unop_to_incdec_op op =
361 let ints_overflow_to_ints =
362 Hhbc_options.ints_overflow_to_ints !Hhbc_options.compiler_options in
363 match op with
364 | A.Uincr -> Some (if ints_overflow_to_ints then PreInc else PreIncO)
365 | A.Udecr -> Some (if ints_overflow_to_ints then PreDec else PreDecO)
366 | A.Upincr -> Some (if ints_overflow_to_ints then PostInc else PostIncO)
367 | A.Updecr -> Some (if ints_overflow_to_ints then PostDec else PostDecO)
368 | _ -> None
370 let collection_type = function
371 | "Vector" -> CollectionType.Vector
372 | "Map" -> CollectionType.Map
373 | "Set" -> CollectionType.Set
374 | "Pair" -> CollectionType.Pair
375 | "ImmVector" -> CollectionType.ImmVector
376 | "ImmMap" -> CollectionType.ImmMap
377 | "ImmSet" -> CollectionType.ImmSet
378 | x -> failwith ("unknown collection type '" ^ x ^ "'")
380 let istype_op lower_fq_id =
381 match lower_fq_id with
382 | "is_int" | "is_integer" | "is_long" -> Some OpInt
383 | "is_bool" -> Some OpBool
384 | "is_float" | "is_real" | "is_double" -> Some OpDbl
385 | "is_string" -> Some OpStr
386 | "is_array" -> Some OpArr
387 | "is_object" -> Some OpObj
388 | "is_null" -> Some OpNull
389 (* We don't use IsType with the resource type because `is_resource()` does
390 validation in addition to a simple type check. We will use it for
391 is-expressions because they only do type checks.
392 | "is_resource" -> Some OpRes *)
393 | "is_scalar" -> Some OpScalar
394 | "hh\\is_keyset" -> Some OpKeyset
395 | "hh\\is_dict" -> Some OpDict
396 | "hh\\is_vec" -> Some OpVec
397 | "hh\\is_varray" -> Some (if hack_arr_dv_arrs () then OpVec else OpVArray)
398 | "hh\\is_darray" -> Some (if hack_arr_dv_arrs () then OpDict else OpDArray)
399 | _ -> None
401 (* Return the IsType op associated with a given primitive typehint. *)
402 let is_expr_primitive_op id =
403 match id with
404 | "bool" -> Some OpBool
405 | "int" -> Some OpInt
406 | "float" -> Some OpDbl
407 | "string" -> Some OpStr
408 | "resource" -> Some OpRes
409 | "vec" -> Some OpVec
410 | "dict" -> Some OpDict
411 | "keyset" -> Some OpKeyset
412 | "varray" -> Some OpVArray
413 | "darray" -> Some OpDArray
414 | _ -> None
416 (* See EmitterVisitor::getPassByRefKind in emitter.cpp *)
417 let get_passByRefKind is_splatted expr =
418 let open PassByRefKind in
419 let rec from_non_list_assignment permissive_kind expr =
420 match snd expr with
421 | A.New _ | A.Lvar _ | A.Clone _
422 | A.Import ((A.Include | A.IncludeOnce | A.Require), _) -> AllowCell
423 | A.Binop(A.Eq None, (_, A.List _), e) ->
424 from_non_list_assignment WarnOnCell e
425 | A.Array_get(_, Some _) -> permissive_kind
426 | A.Binop(A.Eq _, _, _) -> WarnOnCell
427 | A.Unop((A.Uincr | A.Udecr | A.Usilence), _) -> WarnOnCell
428 | A.Call((_, A.Id (_, "eval")), _, [_], []) ->
429 WarnOnCell
430 | A.Call((_, A.Id (_, "array_key_exists")), _, [_; _], []) ->
431 AllowCell
432 | A.Call((_, A.Id (_, ("idx"))), _, ([_; _] | [_; _; _]), []) ->
433 AllowCell
434 | A.Call((_, A.Id (_, ("hphp_array_idx"))), _, [_; _; _], []) ->
435 AllowCell
436 | A.Xml _ ->
437 AllowCell
438 | A.NewAnonClass _ -> ErrorOnCell
439 | _ -> if is_splatted then AllowCell else ErrorOnCell in
440 from_non_list_assignment AllowCell expr
442 let get_queryMOpMode need_ref op =
443 match op with
444 | QueryOp.InOut -> MemberOpMode.InOut
445 | QueryOp.CGet -> MemberOpMode.Warn
446 | QueryOp.Empty when need_ref -> MemberOpMode.Define
447 | _ -> MemberOpMode.ModeNone
449 let extract_shape_field_name_pstring = function
450 | A.SFlit s ->
451 Emit_type_constant.check_shape_key s; A.String s
452 | A.SFclass_const ((pn, _) as id, p) -> A.Class_const ((pn, A.Id id), p)
454 let rec text_of_expr e_ = match e_ with
455 (* Note we force string literals to become single-quoted, regardless of
456 whether they were single- or double-quoted in the source. Gross. *)
457 | A.String (p, s) -> (p, "'" ^ s ^ "'")
458 | A.Id id | A.Lvar id -> id
459 | A.Array_get ((p, A.Lvar (_, id)), Some (_, e_)) ->
460 (p, id ^ "[" ^ snd (text_of_expr e_) ^ "]")
461 | _ -> Pos.none, "unknown" (* TODO: get text of expression *)
463 let parse_include e =
464 let strip_backslash p =
465 let len = String.length p in
466 if len > 0 && p.[0] = '/' then String.sub p 1 (len-1) else p in
467 let rec split_var_lit = function
468 | _, A.Binop (A.Dot, e1, e2) -> begin
469 let v, l = split_var_lit e2 in
470 if v = ""
471 then let var, lit = split_var_lit e1 in var, lit ^ l
472 else v, ""
474 | _, A.String (_, lit) -> "", lit
475 | _, e_ -> snd (text_of_expr e_), "" in
476 let var, lit = split_var_lit e in
477 let var, lit =
478 if var = "__DIR__" then ("", strip_backslash lit) else (var, lit) in
479 if var = ""
480 then
481 if Filename.is_relative lit
482 then Hhas_symbol_refs.SearchPathRelative lit
483 else Hhas_symbol_refs.Absolute lit
484 else
485 Hhas_symbol_refs.IncludeRootRelative (var, strip_backslash lit)
487 let rec expr_and_new env pos instr_to_add_new instr_to_add = function
488 | A.AFvalue e ->
489 let add_instr =
490 if expr_starts_with_ref e then instr_add_new_elemv else instr_to_add_new
492 gather [emit_expr ~need_ref:false env e; emit_pos pos; add_instr]
493 | A.AFkvalue (k, v) ->
494 let add_instr =
495 if expr_starts_with_ref v then instr_add_elemv else instr_to_add
497 gather [
498 emit_two_exprs env (fst k) k v;
499 add_instr;
502 and get_local env (pos, str) =
503 if str = SN.SpecialIdents.dollardollar
504 then
505 match Emit_env.get_pipe_var env with
506 | None -> Emit_fatal.raise_fatal_runtime pos
507 "Pipe variables must occur only in the RHS of pipe expressions"
508 | Some v -> v
509 else Local.Named str
511 and check_non_pipe_local e =
512 match e with
513 | _, A.Lvar (pos, str) when str = SN.SpecialIdents.dollardollar ->
514 Emit_fatal.raise_fatal_parse pos
515 "Cannot take indirect reference to a pipe variable"
516 | _ -> ()
519 and get_non_pipe_local (pos, str) =
520 if str = SN.SpecialIdents.dollardollar
521 then Emit_fatal.raise_fatal_parse pos
522 "Cannot take indirect reference to a pipe variable"
523 else Local.Named str
526 and emit_local ~notice ~need_ref env ((pos, str) as id) =
527 if SN.Superglobals.is_superglobal str
528 then gather [
529 instr_string (SU.Locals.strip_dollar str);
530 emit_pos pos;
531 instr (IGet (if need_ref then VGetG else CGetG))
533 else
534 let local = get_local env id in
535 if is_local_this env str && not (Emit_env.get_needs_local_this env) then
536 if need_ref then
537 instr_vgetl local
538 else
539 emit_pos_then pos @@ instr (IMisc (BareThis notice))
540 else if need_ref then
541 instr_vgetl local
542 else
543 instr_cgetl local
545 (* Emit CGetL2 for local variables, and return true to indicate that
546 * the result will be just below the top of the stack *)
547 and emit_first_expr env (pos, e as expr) =
548 match e with
549 | A.Lvar ((_, name) as id)
550 when not ((is_local_this env name && not (Emit_env.get_needs_local_this env))
551 || SN.Superglobals.is_superglobal name) ->
552 instr_cgetl2 (get_local env id), true
553 | _ ->
554 emit_expr_and_unbox_if_necessary ~need_ref:false env pos expr, false
556 (* Special case for binary operations to make use of CGetL2 *)
557 and emit_two_exprs env outer_pos e1 e2 =
558 let instrs1, is_under_top = emit_first_expr env e1 in
559 let instrs2 = emit_expr_and_unbox_if_necessary ~need_ref:false env outer_pos e2 in
560 let instrs2_is_var =
561 match e2 with
562 | _, A.Lvar _ -> true
563 | _ -> false in
564 gather @@
565 if is_under_top
566 then
567 if instrs2_is_var
568 then [emit_pos outer_pos; instrs2; instrs1]
569 else [instrs2; emit_pos outer_pos; instrs1]
570 else
571 if instrs2_is_var
572 then [instrs1; emit_pos outer_pos; instrs2]
573 else [instrs1; instrs2; emit_pos outer_pos]
575 and emit_is_null env e =
576 match e with
577 | (_, A.Lvar ((_, str) as id)) when not (is_local_this env str) ->
578 instr_istypel (get_local env id) OpNull
579 | _ ->
580 gather [
581 emit_expr_and_unbox_if_necessary ~need_ref:false env (fst e) e;
582 instr_istypec OpNull
585 and emit_binop env expr op e1 e2 =
586 let default () =
587 gather [
588 emit_two_exprs env (fst expr) e1 e2;
589 from_binop op
590 ] in
591 match op with
592 | A.AMpamp | A.BArbar -> emit_short_circuit_op env expr
593 | A.Eq None ->
594 emit_lval_op env (fst expr) LValOp.Set e1 (Some e2)
595 | A.Eq (Some obop) ->
596 begin match binop_to_eqop obop with
597 | None -> emit_nyi "illegal eq op"
598 | Some op -> emit_lval_op env (fst expr) (LValOp.SetOp op) e1 (Some e2)
600 | _ ->
601 if not (optimize_null_check ())
602 then default ()
603 else
604 match op with
605 | A.EQeqeq when snd e2 = A.Null ->
606 emit_is_null env e1
607 | A.EQeqeq when snd e1 = A.Null ->
608 emit_is_null env e2
609 | A.Diff2 when snd e2 = A.Null ->
610 gather [
611 emit_is_null env e1;
612 instr_not
614 | A.Diff2 when snd e1 = A.Null ->
615 gather [
616 emit_is_null env e2;
617 instr_not
619 | _ ->
620 default ()
622 and emit_box_if_necessary pos need_ref instr =
623 if need_ref then
624 gather [
625 instr;
626 emit_pos pos;
627 instr_box
629 else
630 instr
632 and emit_instanceof env pos e1 e2 =
633 match (e1, e2) with
634 | (_, (_, A.Id _)) ->
635 let lhs = emit_expr ~need_ref:false env e1 in
636 let from_class_ref instrs =
637 gather [
638 lhs;
639 emit_pos pos;
640 instrs;
641 instr_instanceof;
642 ] in
643 let scope = Emit_env.get_scope env in
644 begin match expr_to_class_expr ~resolve_self:true scope e2 with
645 | Class_static ->
646 from_class_ref @@ gather [
647 instr_fcallbuiltin 0 0 "get_called_class";
648 instr_unboxr_nop;
650 | Class_parent ->
651 from_class_ref @@ gather [
652 instr_parent;
653 instr_clsrefname;
655 | Class_self ->
656 from_class_ref @@ gather [
657 instr_self;
658 instr_clsrefname;
660 | Class_id name ->
661 let n, _ =
662 Hhbc_id.Class.elaborate_id (Emit_env.get_namespace env) name in
663 gather [
664 lhs;
665 instr_instanceofd n;
667 | Class_expr _
668 | Class_unnamed_local _ ->
669 failwith "cannot get this shape from from A.Id"
671 | _ ->
672 gather [
673 emit_expr ~need_ref:false env e1;
674 emit_expr ~need_ref:false env e2;
675 instr_instanceof ]
677 and emit_as env pos e h is_nullable =
678 if not @@ Hhbc_options.enable_hackc_only_feature !Hhbc_options.compiler_options
679 then Emit_fatal.raise_fatal_runtime pos "As expression is not allowed"
680 else
681 if is_nullable then begin
682 Local.scope @@ fun () ->
683 let local = Local.get_unnamed_local () in
684 let true_label = Label.next_regular () in
685 let done_label = Label.next_regular () in
686 gather [
687 emit_expr ~need_ref:false env e;
688 instr_popl local;
689 (* (e is h) ? e : null *)
690 emit_is env pos (IsExprUnnamedLocal local) h;
691 instr_jmpnz true_label;
692 instr_null;
693 instr_unsetl local;
694 instr_jmp done_label;
695 instr_label true_label;
696 instr_pushl local;
697 instr_label done_label;
699 end else begin
700 let namespace = Emit_env.get_namespace env in
701 let tv = Emit_type_constant.hint_to_type_constant
702 ~is_typedef:true ~tparams:[] ~namespace h in
703 gather [
704 emit_expr ~need_ref:false env e;
705 instr_astypestruct @@ Emit_adata.get_array_identifier tv
706 ] end
708 and emit_is env pos lhs h =
709 if not @@ Hhbc_options.enable_hackc_only_feature !Hhbc_options.compiler_options
710 then Emit_fatal.raise_fatal_runtime pos "Is expression is not allowed"
711 else
712 match snd h with
713 | A.Happly ((_, id), tyl) when (SU.strip_global_ns id) = "classname" ->
714 begin match lhs with
715 | IsExprExpr e -> emit_is_create_local env pos e h
716 | IsExprUnnamedLocal local ->
717 let true_label = Label.next_regular () in
718 let done_label = Label.next_regular () in
719 let skip_label = Label.next_regular () in
720 gather [
721 instr_istypel local OpStr;
722 instr_jmpz skip_label;
723 begin match List.hd tyl with
724 | None ->
725 emit_is_classname_exists local true_label
726 | Some (_, h2) ->
727 begin match h2 with
728 | A.Happly ((_, id), _)
729 when id = SN.Typehints.mixed
730 || id = SN.Typehints.nonnull
731 || id = SN.Typehints.dynamic ->
732 emit_is_classname_exists local true_label
733 | A.Happly ((_, id), _) ->
734 gather [
735 instr_cgetl local;
736 instr_string id;
737 instr_true;
738 instr_fcallbuiltin 3 3 "is_a";
739 instr_unboxr_nop;
740 instr_jmpnz true_label;
742 | A.Haccess _ ->
743 emit_nyi "Type constants"
744 | A.Hoption _ ->
745 failwith "Invalid classname: nullable typehint"
746 | A.Hfun _ ->
747 failwith "Invalid classname: callable typehint"
748 | A.Htuple _ ->
749 failwith "Invalid classname: tuple typehint"
750 | A.Hshape _ ->
751 failwith "Invalid classname: shape typehint"
752 | A.Hsoft _ ->
753 failwith "Invalid classname: soft typehint"
755 end;
756 instr_label skip_label;
757 instr_false;
758 instr_jmp done_label;
759 instr_label true_label;
760 instr_true;
761 instr_label done_label;
764 | A.Happly ((_, id), _)
765 when id = SN.Typehints.arraykey || id = SN.Typehints.num ->
766 begin match lhs with
767 | IsExprExpr e -> emit_is_create_local env pos e h
768 | IsExprUnnamedLocal local ->
769 let op2 = if id = SN.Typehints.arraykey then OpStr else OpDbl in
770 let its_true = Label.next_regular () in
771 let its_done = Label.next_regular () in
772 gather [
773 instr_istypel local OpInt;
774 instr_jmpnz its_true;
775 instr_istypel local op2;
776 instr_jmpnz its_true;
777 instr_false;
778 instr_jmp its_done;
779 instr_label its_true;
780 instr_true;
781 instr_label its_done;
784 | A.Happly ((_, id), _) when id = SN.Typehints.nonnull ->
785 gather [
786 emit_is_lhs env lhs;
787 instr_istypec OpNull;
788 instr_not;
790 | A.Happly ((_, id), _)
791 when id = SN.Typehints.mixed
792 || id = SN.Typehints.dynamic ->
793 instr_true
794 | A.Happly ((_, id), _) when id = SN.Typehints.this ->
795 begin match lhs with
796 | IsExprExpr e -> emit_is_create_local env pos e h
797 | IsExprUnnamedLocal local ->
798 let true_label = Label.next_regular () in
799 let done_label = Label.next_regular () in
800 let skip_label = Label.next_regular () in
801 gather [
802 instr_istypel local OpObj;
803 instr_jmpz skip_label;
804 instr_cgetl local;
805 instr_fcallbuiltin 1 1 "get_class";
806 instr_unboxr_nop;
807 instr_fcallbuiltin 0 0 "get_called_class";
808 instr_unboxr_nop;
809 instr (IOp Same);
810 instr_jmpnz true_label;
811 instr_label skip_label;
812 instr_false;
813 instr_jmp done_label;
814 instr_label true_label;
815 instr_true;
816 instr_label done_label;
819 | A.Happly ((_, id), _) ->
820 begin match is_expr_primitive_op id with
821 | Some op ->
822 gather [
823 emit_is_lhs env lhs;
824 instr_istypec op;
826 | None ->
827 gather [
828 emit_is_lhs env lhs;
829 instr_isnamed (Hhbc_id.Class.from_raw_string id);
832 | A.Hoption h2 ->
833 begin match lhs with
834 | IsExprExpr e -> emit_is_create_local env pos e h
835 | IsExprUnnamedLocal local ->
836 let its_true = Label.next_regular () in
837 let its_done = Label.next_regular () in
838 gather [
839 instr_istypel local OpNull;
840 instr_jmpnz its_true;
841 emit_is env pos lhs h2;
842 instr_jmpnz its_true;
843 instr_false;
844 instr_jmp its_done;
845 instr_label its_true;
846 instr_true;
847 instr_label its_done
850 | A.Hshape {
851 A.si_allows_unknown_fields = shape_is_open;
852 si_shape_field_list = field_list;
853 } ->
854 begin match lhs with
855 | IsExprExpr e -> emit_is_create_local env pos e h
856 | IsExprUnnamedLocal local ->
857 let pass_label = Label.next_regular () in
858 let done_label = Label.next_regular () in
859 let fail_label = Label.next_regular () in
860 let count_local = Local.get_unnamed_local () in
861 let expected_count =
862 if shape_is_open
863 then None
864 else Some (Local.get_unnamed_local ())
866 let verify_field = emit_is_shape_verify_field
867 env pos local fail_label expected_count
869 gather [
870 instr_istypel local OpDArray;
871 instr_jmpz fail_label;
873 instr_fpushfuncd 1 (Hhbc_id.Function.from_raw_string "count");
874 instr_fpassl 0 local H.Cell;
875 instr_fcall 1;
876 instr_unboxr_nop;
877 instr_setl count_local;
879 emit_is_shape_verify_count count_local fail_label shape_is_open
880 field_list;
882 begin match expected_count with
883 | Some expected_count_local ->
884 gather [
885 instr_int 0;
886 instr_popl expected_count_local;
887 gather (List.map ~f:verify_field field_list);
888 instr_cgetl count_local;
889 instr_cgetl expected_count_local;
890 instr (IOp Gt);
891 instr_jmpnz fail_label;
893 | None ->
894 gather (List.map ~f:verify_field field_list)
895 end;
897 instr_jmp pass_label;
898 instr_label fail_label;
899 instr_false;
900 instr_jmp done_label;
901 instr_label pass_label;
902 instr_true;
903 instr_label done_label;
906 | A.Htuple hl ->
907 begin match lhs with
908 | IsExprExpr e -> emit_is_create_local env pos e h
909 | IsExprUnnamedLocal local ->
910 let its_true = Label.next_regular () in
911 let its_done = Label.next_regular () in
912 let skip_label = Label.next_regular () in
913 gather [
914 instr_istypel local OpVArray;
915 instr_jmpz skip_label;
917 instr_int (List.length hl);
918 instr_fpushfuncd 1 (Hhbc_id.Function.from_raw_string "count");
919 instr_fpassl 0 local H.Cell;
920 instr_fcall 1;
921 instr_unboxr_nop;
922 instr (IOp Same);
923 instr_jmpz skip_label;
925 gather (List.mapi ~f:(emit_is_tuple_elem env pos local skip_label) hl);
926 instr_jmp its_true;
928 instr_label skip_label;
929 instr_false;
930 instr_jmp its_done;
931 instr_label its_true;
932 instr_true;
933 instr_label its_done
936 | A.Haccess _ ->
937 emit_nyi "is expression: unsupported type const access"
938 | A.Hsoft _ ->
939 failwith "Soft typehints cannot be used with `is` expressions"
940 | A.Hfun _ ->
941 failwith "Function typehints cannot be used with `is` expressions"
943 and emit_is_create_local env pos e h =
944 Local.scope @@ fun () ->
945 let local = Local.get_unnamed_local () in
946 gather [
947 emit_expr ~need_ref:false env e;
948 instr_popl local;
949 emit_is env pos (IsExprUnnamedLocal local) h;
950 instr_unsetl local;
953 and emit_is_lhs env lhs =
954 match lhs with
955 | IsExprExpr e -> emit_expr ~need_ref:false env e
956 | IsExprUnnamedLocal local -> instr_cgetl local
958 and emit_is_classname_exists local true_label =
959 gather [
960 instr_fpushfuncd 1 (Hhbc_id.Function.from_raw_string "class_exists");
961 instr_fpassl 0 local H.Cell;
962 instr_fcall 1;
963 instr_unboxr_nop;
964 instr_jmpnz true_label;
965 instr_fpushfuncd 1 (Hhbc_id.Function.from_raw_string "interface_exists");
966 instr_fpassl 0 local H.Cell;
967 instr_fcall 1;
968 instr_unboxr_nop;
969 instr_jmpnz true_label;
972 and emit_is_shape_verify_count count_local fail_label shape_is_open field_list =
973 let num_fields = List.length field_list in
974 let num_required_fields = List.count field_list
975 ~f:(fun field -> not field.A.sf_optional) in
976 if shape_is_open
977 then
978 gather [
979 instr_int num_required_fields;
980 instr (IOp Lt);
981 instr_jmpnz fail_label;
983 else if num_required_fields = num_fields
984 then
985 gather [
986 instr_int num_required_fields;
987 instr (IOp Same);
988 instr_jmpz fail_label;
990 else
991 gather [
992 instr_int num_required_fields;
993 instr (IOp Lt);
994 instr_jmpnz fail_label;
995 instr_cgetl count_local;
996 instr_int num_fields;
997 instr (IOp Gt);
998 instr_jmpnz fail_label;
1001 and emit_is_shape_verify_field env pos local fail_label expected_count field =
1002 let key_instrs = match field.A.sf_name with
1003 | Ast_defs.SFlit (_, field_name) ->
1004 instr_string field_name
1005 | Ast_defs.SFclass_const (cid, (_, const)) ->
1006 emit_class_const_impl env cid const
1008 let local_fname = Local.get_unnamed_local () in
1009 let local_f = Local.get_unnamed_local () in
1010 let skip_label = Label.next_regular () in
1011 gather [
1012 key_instrs;
1013 instr_setl local_fname;
1014 instr_cgetl local;
1015 instr (IMisc AKExists);
1016 instr_jmpz (if field.A.sf_optional then skip_label else fail_label);
1018 instr_basel local MemberOpMode.Warn;
1019 instr_querym 0 H.QueryOp.CGet (MemberKey.EL local_fname);
1020 instr_popl local_f;
1021 emit_is env pos (IsExprUnnamedLocal local_f) field.A.sf_hint;
1022 instr_unsetl local_f;
1023 instr_jmpz fail_label;
1025 begin match expected_count with
1026 | Some expected_count_local ->
1027 gather [
1028 instr (IMutator (IncDecL (expected_count_local, PreInc)));
1029 instr_popc;
1031 | None -> empty
1032 end;
1034 instr_label skip_label;
1037 and emit_is_tuple_elem env pos local skip_label i h =
1038 let local_i = Local.get_unnamed_local () in
1039 gather [
1040 instr_basel local MemberOpMode.Warn;
1041 instr_querym 0 H.QueryOp.CGet (MemberKey.EI (Int64.of_int i));
1042 instr_popl local_i;
1043 emit_is env pos (IsExprUnnamedLocal local_i) h;
1044 instr_unsetl local_i;
1045 instr_jmpz skip_label;
1048 and emit_null_coalesce env pos e1 e2 =
1049 let end_label = Label.next_regular () in
1050 gather [
1051 emit_quiet_expr env pos e1;
1052 instr_dup;
1053 instr_istypec OpNull;
1054 instr_not;
1055 instr_jmpnz end_label;
1056 instr_popc;
1057 emit_expr ~need_ref:false env e2;
1058 instr_label end_label;
1061 and emit_cast env pos hint expr =
1062 let op =
1063 begin match hint with
1064 | A.Happly((_, id), []) ->
1065 let id = String.lowercase_ascii id in
1066 begin match id with
1067 | _ when id = SN.Typehints.int
1068 || id = SN.Typehints.integer -> instr (IOp CastInt)
1069 | _ when id = SN.Typehints.bool
1070 || id = SN.Typehints.boolean -> instr (IOp CastBool)
1071 | _ when id = SN.Typehints.string ||
1072 id = "binary" -> instr (IOp CastString)
1073 | _ when id = SN.Typehints.object_cast -> instr (IOp CastObject)
1074 | _ when id = SN.Typehints.array -> instr (IOp CastArray)
1075 | _ when id = SN.Typehints.real
1076 || id = SN.Typehints.double
1077 || id = SN.Typehints.float -> instr (IOp CastDouble)
1078 | _ when id = "unset" -> gather [ instr_popc; instr_null ]
1079 | _ -> emit_nyi "cast type"
1081 | _ ->
1082 emit_nyi "cast type"
1083 end in
1084 gather [
1085 emit_expr ~last_pos:pos ~need_ref:false env expr;
1086 emit_pos pos;
1090 and emit_conditional_expression env pos etest etrue efalse =
1091 match etrue with
1092 | Some etrue ->
1093 let false_label = Label.next_regular () in
1094 let end_label = Label.next_regular () in
1095 let r = emit_jmpz env etest false_label in
1096 gather [
1097 r.instrs;
1098 (* only emit true branch if there is fallthrough from condition *)
1099 begin if r.is_fallthrough
1100 then gather [
1101 emit_expr ~need_ref:false env etrue;
1102 emit_pos pos;
1103 instr_jmp end_label
1105 else empty
1106 end;
1107 (* only emit false branch if false_label is used *)
1108 begin if r.is_label_used
1109 then gather [
1110 instr_label false_label;
1111 emit_expr ~need_ref:false env efalse;
1113 else empty
1114 end;
1115 (* end_label is used to jump out of true branch so they should be emitted
1116 together *)
1117 begin if r.is_fallthrough
1118 then instr_label end_label
1119 else empty
1120 end;
1122 | None ->
1123 let end_label = Label.next_regular () in
1124 gather [
1125 emit_expr ~last_pos:pos ~need_ref:false env etest;
1126 instr_dup;
1127 instr_jmpnz end_label;
1128 instr_popc;
1129 emit_expr ~need_ref:false env efalse;
1130 instr_label end_label;
1133 and emit_new env pos expr args uargs =
1134 let nargs = List.length args + List.length uargs in
1135 let cexpr = expr_to_class_expr ~resolve_self:true
1136 (Emit_env.get_scope env) expr in
1137 match cexpr with
1138 (* Special case for statically-known class *)
1139 | Class_id id ->
1140 let fq_id, _id_opt =
1141 Hhbc_id.Class.elaborate_id (Emit_env.get_namespace env) id in
1142 Emit_symbol_refs.add_class (Hhbc_id.Class.to_raw_string fq_id);
1143 gather [
1144 emit_pos pos;
1145 instr_fpushctord nargs fq_id;
1146 emit_args_and_call env pos args uargs;
1147 instr_popr
1149 | Class_static ->
1150 gather [
1151 emit_pos pos;
1152 instr_fpushctors nargs SpecialClsRef.Static;
1153 emit_args_and_call env pos args uargs;
1154 instr_popr
1156 | Class_self ->
1157 gather [
1158 emit_pos pos;
1159 instr_fpushctors nargs SpecialClsRef.Self;
1160 emit_args_and_call env pos args uargs;
1161 instr_popr
1163 | Class_parent ->
1164 gather [
1165 emit_pos pos;
1166 instr_fpushctors nargs SpecialClsRef.Parent;
1167 emit_args_and_call env pos args uargs;
1168 instr_popr
1170 | _ ->
1171 gather [
1172 emit_load_class_ref env pos cexpr;
1173 instr_fpushctor nargs 0;
1174 emit_args_and_call env pos args uargs;
1175 instr_popr
1178 and emit_new_anon env pos cls_idx args uargs =
1179 let nargs = List.length args + List.length uargs in
1180 gather [
1181 instr_defcls cls_idx;
1182 instr_fpushctori nargs cls_idx;
1183 emit_args_and_call env pos args uargs;
1184 instr_popr
1187 and emit_clone env expr =
1188 gather [
1189 emit_expr ~need_ref:false env expr;
1190 instr_clone;
1193 and emit_shape env expr fl =
1194 let p = fst expr in
1195 let fl =
1196 List.map fl
1197 ~f:(fun (fn, e) ->
1198 ((p, extract_shape_field_name_pstring fn), e))
1200 emit_expr ~need_ref:false env (p, A.Darray fl)
1202 and emit_call_expr ?last_pos ~need_ref env expr =
1203 let instrs, flavor = emit_flavored_expr env expr in
1204 gather [
1205 instrs;
1206 (* If the instruction has produced a ref then unbox it *)
1207 if flavor = Flavor.ReturnVal then
1208 emit_pos_then (Option.value ~default:(fst expr) last_pos) @@
1209 if need_ref then
1210 instr_boxr
1211 else
1212 instr_unboxr
1213 else
1214 empty
1217 and emit_known_class_id env id =
1218 let fq_id, _ = Hhbc_id.Class.elaborate_id (Emit_env.get_namespace env) id in
1219 Emit_symbol_refs.add_class (Hhbc_id.Class.to_raw_string fq_id);
1220 gather [
1221 instr_string (Hhbc_id.Class.to_raw_string fq_id);
1222 instr_clsrefgetc;
1225 and emit_load_class_ref env pos cexpr =
1226 emit_pos_then pos @@
1227 match cexpr with
1228 | Class_static -> instr (IMisc (LateBoundCls 0))
1229 | Class_parent -> instr (IMisc (Parent 0))
1230 | Class_self -> instr (IMisc (Self 0))
1231 | Class_id id -> emit_known_class_id env id
1232 | Class_unnamed_local l -> instr (IGet (ClsRefGetL (l, 0)))
1233 | Class_expr expr ->
1234 begin match snd expr with
1235 | A.Lvar ((_, id) as pos_id)
1236 when id <> SN.SpecialIdents.this || (Emit_env.get_needs_local_this env) ->
1237 let local = get_local env pos_id in
1238 instr (IGet (ClsRefGetL (local, 0)))
1239 | _ ->
1240 gather [
1241 emit_pos pos;
1242 emit_expr ~need_ref:false env expr;
1243 instr_clsrefgetc
1247 and emit_load_class_const env pos cexpr id =
1248 (* TODO(T21932293): HHVM does not match Zend here.
1249 * Eventually remove this to match PHP7 *)
1250 match Ast_scope.Scope.get_class (Emit_env.get_scope env) with
1251 | Some cd when cd.A.c_kind = A.Ctrait
1252 && cexpr = Class_self
1253 && SU.is_class id ->
1254 emit_pos_then pos @@
1255 instr_string @@ SU.strip_global_ns @@ snd cd.A.c_name
1256 | _ ->
1257 let load_const =
1258 if SU.is_class id
1259 then instr (IMisc (ClsRefName 0))
1260 else instr (ILitConst (ClsCns (Hhbc_id.Const.from_ast_name id, 0)))
1262 gather [
1263 emit_load_class_ref env pos cexpr;
1264 load_const
1267 and emit_class_expr env cexpr prop =
1268 match cexpr with
1269 | Class_expr ((pos, (A.BracedExpr _ |
1270 A.Dollar _ |
1271 A.Call _ |
1272 A.Lvar (_, "$this") |
1273 A.Binop _ |
1274 A.Class_get _)) as e) ->
1275 (* if class is stored as dollar or braced expression (computed dynamically)
1276 it needs to be stored in unnamed local and eventually cleaned.
1277 Here we don't use stash_in_local because shape of the code generated
1278 for class case is different (PopC / UnsetL is the part of try block) *)
1279 let cexpr_local =
1280 Local.scope @@ fun () -> emit_expr ~need_ref:false env e in
1281 empty,
1282 Local.scope @@ fun () ->
1283 let temp = Local.get_unnamed_local () in
1284 let instrs = emit_class_expr env (Class_unnamed_local temp) prop in
1285 let fault_label = Label.next_fault () in
1286 let block =
1287 instr_try_fault
1288 fault_label
1289 (* try block *)
1290 (gather [
1291 instr_popc;
1292 of_pair @@ instrs;
1293 instr_unsetl temp
1295 (* fault block *)
1296 (gather [
1297 instr_unsetl temp;
1298 emit_pos pos;
1299 instr_unwind ]) in
1300 gather [
1301 cexpr_local;
1302 instr_setl temp;
1303 block
1305 | _ ->
1306 let load_prop, load_prop_first =
1307 match prop with
1308 | pos, A.Id (_, id) ->
1309 emit_pos_then pos @@
1310 instr_string id, true
1311 | pos, A.Lvar (_, id) ->
1312 emit_pos_then pos @@
1313 instr_string (SU.Locals.strip_dollar id), true
1314 | _, A.Dollar (_, A.Lvar _ as e) ->
1315 emit_expr ~need_ref:false env e, false
1316 (* The outer dollar just says "class property" *)
1317 | _, A.Dollar e | e ->
1318 emit_expr ~need_ref:false env e, true
1320 let load_cls_ref = emit_load_class_ref env (fst prop) cexpr in
1321 if load_prop_first then load_prop, load_cls_ref
1322 else load_cls_ref, load_prop
1324 and emit_class_get env param_num_opt qop need_ref cid prop =
1325 let cexpr = expr_to_class_expr ~resolve_self:false
1326 (Emit_env.get_scope env) cid
1328 gather [
1329 of_pair @@ emit_class_expr env cexpr prop;
1330 match (param_num_opt, qop) with
1331 | (None, QueryOp.CGet) -> if need_ref then instr_vgets else instr_cgets
1332 | (None, QueryOp.CGetQuiet) -> failwith "emit_class_get: CGetQuiet"
1333 | (None, QueryOp.Isset) -> instr_issets
1334 | (None, QueryOp.Empty) -> instr_emptys
1335 | (None, QueryOp.InOut) -> failwith "emit_class_get: InOut"
1336 | (Some (i, h), _) -> instr (ICall (FPassS (i, 0, h)))
1339 (* Class constant <cid>::<id>.
1340 * We follow the logic for the Construct::KindOfClassConstantExpression
1341 * case in emitter.cpp
1343 and emit_class_const env pos cid (_, id) =
1344 let cexpr = expr_to_class_expr ~resolve_self:true
1345 (Emit_env.get_scope env) cid in
1346 match cexpr with
1347 | Class_id cid ->
1348 emit_class_const_impl env cid id
1349 | _ ->
1350 emit_load_class_const env pos cexpr id
1352 and emit_class_const_impl env cid id =
1353 let fq_id, _id_opt =
1354 Hhbc_id.Class.elaborate_id (Emit_env.get_namespace env) cid in
1355 let fq_id_str = Hhbc_id.Class.to_raw_string fq_id in
1356 Emit_symbol_refs.add_class fq_id_str;
1357 emit_pos_then (fst cid) @@
1358 if SU.is_class id
1359 then instr_string fq_id_str
1360 else instr (ILitConst (ClsCnsD (Hhbc_id.Const.from_ast_name id, fq_id)))
1362 and emit_yield env pos = function
1363 | A.AFvalue e ->
1364 gather [
1365 emit_expr ~last_pos:pos ~need_ref:false env e;
1366 emit_pos pos;
1367 instr_yield;
1369 | A.AFkvalue (e1, e2) ->
1370 gather [
1371 emit_expr ~last_pos:pos ~need_ref:false env e1;
1372 emit_expr ~need_ref:false env e2;
1373 emit_pos pos;
1374 instr_yieldk;
1377 and emit_execution_operator env pos exprs =
1378 let instrs =
1379 match exprs with
1380 (* special handling of ``*)
1381 | [_, A.String (_, "") as e] -> emit_expr ~need_ref:false env e
1382 | _ -> emit_string2 env pos exprs in
1383 gather [
1384 instr_fpushfuncd 1 (Hhbc_id.Function.from_raw_string "shell_exec");
1385 instrs;
1386 instr_fpass PassByRefKind.AllowCell 0 Cell;
1387 instr_fcall 1;
1390 and emit_string2 env pos exprs =
1391 match exprs with
1392 | [e] ->
1393 gather [
1394 emit_expr ~last_pos:pos ~need_ref:false env e;
1395 emit_pos pos;
1396 instr (IOp CastString)
1398 | e1::e2::es ->
1399 gather @@ [
1400 emit_two_exprs env (fst e1) e1 e2;
1401 emit_pos pos;
1402 instr (IOp Concat);
1403 gather (List.map es (fun e ->
1404 gather [emit_expr ~need_ref:false env e;
1405 emit_pos pos; instr (IOp Concat)]))
1408 | [] -> failwith "String2 with zero arguments is impossible"
1411 and emit_lambda env fundef ids =
1412 (* Closure conversion puts the class number used for CreateCl in the "name"
1413 * of the function definition *)
1414 let fundef_name = snd fundef.A.f_name in
1415 let class_num = int_of_string fundef_name in
1416 let explicit_use = SSet.mem fundef_name (Emit_env.get_explicit_use_set ()) in
1417 gather [
1418 gather @@ List.map ids
1419 (fun (x, isref) ->
1420 instr (IGet (
1421 let lid = get_local env x in
1422 if explicit_use
1423 then
1424 if isref then VGetL lid else CGetL lid
1425 else CUGetL lid)));
1426 instr (IMisc (CreateCl (List.length ids, class_num)))
1429 and emit_id env (p, s as id) =
1430 let s = String.uppercase_ascii s in
1431 match s with
1432 | "__FILE__" -> instr (ILitConst File)
1433 | "__DIR__" -> instr (ILitConst Dir)
1434 | "__METHOD__" -> instr (ILitConst Method)
1435 | "__LINE__" ->
1436 (* If the expression goes on multi lines, we return the last line *)
1437 let _, line, _, _ = Pos.info_pos_extended p in
1438 instr_int line
1439 | "__NAMESPACE__" ->
1440 let ns = Emit_env.get_namespace env in
1441 instr_string (Option.value ~default:"" ns.Namespace_env.ns_name)
1442 | "__COMPILER_FRONTEND__" -> instr_string "hackc"
1443 | ("EXIT" | "DIE") ->
1444 emit_exit env None
1445 | _ ->
1446 let fq_id, id_opt, contains_backslash =
1447 Hhbc_id.Const.elaborate_id (Emit_env.get_namespace env) id in
1448 begin match id_opt with
1449 | Some id ->
1450 Emit_symbol_refs.add_constant (Hhbc_id.Const.to_raw_string fq_id);
1451 Emit_symbol_refs.add_constant id;
1452 emit_pos_then p @@
1453 instr (ILitConst (CnsU (fq_id, id)))
1454 | None ->
1455 Emit_symbol_refs.add_constant (snd id);
1456 emit_pos_then p @@
1457 instr (ILitConst
1458 (if contains_backslash then CnsE fq_id else Cns fq_id))
1461 and rename_xhp (p, s) = (p, SU.Xhp.mangle s)
1463 and emit_xhp env p id attributes children =
1464 (* Translate into a constructor call. The arguments are:
1465 * 1) struct-like array of attributes
1466 * 2) vec-like array of children
1467 * 3) filename, for debugging
1468 * 4) line number, for debugging
1470 * Spread operators are injected into the attributes array with placeholder
1471 * keys that the runtime will interpret as a spread. These keys are not
1472 * parseable as user-specified attributes, so they will never collide.
1474 let create_spread p id = (p, "...$" ^ string_of_int(id)) in
1475 let convert_attr (spread_id, attrs) = function
1476 | A.Xhp_simple (name, v) ->
1477 let attr = (A.SFlit name, v) in
1478 (spread_id, attr::attrs)
1479 | A.Xhp_spread e ->
1480 let (p, _) = e in
1481 let attr = (A.SFlit (create_spread p spread_id), e) in
1482 (spread_id + 1, attr::attrs) in
1483 let (_, attributes) = List.fold_left ~f:convert_attr ~init:(0, []) attributes in
1484 let attribute_map = p, A.Shape (List.rev attributes) in
1485 let children_vec = p, A.Varray children in
1486 let filename = p, A.Id (p, "__FILE__") in
1487 let line = p, A.Id (p, "__LINE__") in
1488 let renamed_id = rename_xhp id in
1489 Emit_symbol_refs.add_class (snd renamed_id);
1490 emit_expr ~need_ref:false env @@
1491 (p, A.New (
1492 (p, A.Id renamed_id),
1493 [attribute_map ; children_vec ; filename ; line],
1494 []))
1496 and emit_import env pos flavor e =
1497 let inc = parse_include e in
1498 Emit_symbol_refs.add_include inc;
1499 let e, import_op = match flavor with
1500 | A.Include -> e, IIncludeEvalDefine Incl
1501 | A.Require -> e, IIncludeEvalDefine Req
1502 | A.IncludeOnce -> e, IIncludeEvalDefine InclOnce
1503 | A.RequireOnce ->
1504 let include_roots = Hhbc_options.include_roots !Hhbc_options.compiler_options in
1505 match Hhas_symbol_refs.resolve_to_doc_root_relative inc ~include_roots with
1506 | Hhas_symbol_refs.DocRootRelative path ->
1507 (pos, A.String (fst e, path)), IIncludeEvalDefine ReqDoc
1508 | _ -> e, IIncludeEvalDefine ReqOnce
1510 gather [
1511 emit_expr ~need_ref:false env e;
1512 emit_pos pos;
1513 instr import_op;
1516 and emit_call_isset_expr env outer_pos (pos, expr_ as expr) =
1517 match expr_ with
1518 | A.Array_get ((_, A.Lvar (_, x)), Some e) when x = SN.Superglobals.globals ->
1519 gather [
1520 emit_expr ~need_ref:false env e;
1521 emit_pos outer_pos;
1522 instr (IIsset IssetG)
1524 | A.Array_get (base_expr, opt_elem_expr) ->
1525 emit_array_get ~need_ref:false env pos None QueryOp.Isset base_expr opt_elem_expr
1526 | A.Class_get (cid, id) ->
1527 emit_class_get env None QueryOp.Isset false cid id
1528 | A.Obj_get (expr, prop, nullflavor) ->
1529 emit_obj_get ~need_ref:false env pos None QueryOp.Isset expr prop nullflavor
1530 | A.Lvar ((_, name) as id)
1531 when is_local_this env name && not (Emit_env.get_needs_local_this env) ->
1532 gather [
1533 emit_pos outer_pos;
1534 emit_local ~notice:NoNotice ~need_ref:false env id;
1535 emit_pos outer_pos;
1536 instr_istypec OpNull;
1537 instr_not
1539 | A.Lvar id ->
1540 emit_pos_then outer_pos @@
1541 instr (IIsset (IssetL (get_local env id)))
1542 | A.Dollar e ->
1543 gather [
1544 emit_expr ~need_ref:false env e;
1545 instr_issetn
1547 | _ ->
1548 gather [
1549 emit_expr_and_unbox_if_necessary ~need_ref:false env outer_pos expr;
1550 instr_istypec OpNull;
1551 instr_not
1554 and emit_call_empty_expr env outer_pos (pos, expr_ as expr) =
1555 match expr_ with
1556 | A.Array_get((_, A.Lvar (_, x)), Some e) when x = SN.Superglobals.globals ->
1557 gather [
1558 emit_expr ~need_ref:false env e;
1559 emit_pos outer_pos;
1560 instr_emptyg
1562 | A.Array_get(base_expr, opt_elem_expr) ->
1563 emit_array_get ~need_ref:false env pos None QueryOp.Empty base_expr opt_elem_expr
1564 | A.Class_get (cid, id) ->
1565 emit_class_get env None QueryOp.Empty false cid id
1566 | A.Obj_get (expr, prop, nullflavor) ->
1567 emit_obj_get ~need_ref:false env pos None QueryOp.Empty expr prop nullflavor
1568 | A.Lvar(_, id) when SN.Superglobals.is_superglobal id ->
1569 gather [
1570 instr_string @@ SU.Locals.strip_dollar id;
1571 emit_pos outer_pos;
1572 instr_emptyg
1574 | A.Lvar id ->
1575 if not (is_local_this env (snd id)) ||
1576 Emit_env.get_needs_local_this env
1577 then
1578 emit_pos_then outer_pos @@
1579 instr_emptyl (get_local env id)
1580 else
1581 gather [
1582 emit_pos pos;
1583 instr (IMisc (BareThis NoNotice));
1584 emit_pos outer_pos;
1585 instr_not
1587 | A.Dollar e ->
1588 gather [
1589 emit_expr ~last_pos:outer_pos ~need_ref:false env e;
1590 emit_pos outer_pos;
1591 instr_emptyn
1593 | _ ->
1594 gather [
1595 emit_expr_and_unbox_if_necessary ~need_ref:false env pos expr;
1596 instr_not
1599 and emit_unset_expr env expr =
1600 emit_lval_op_nonlist env (fst expr) LValOp.Unset expr empty 0
1602 and emit_call_isset_exprs env pos exprs =
1603 match exprs with
1604 | [] -> emit_nyi "isset()"
1605 | [expr] -> emit_call_isset_expr env pos expr
1606 | _ ->
1607 let n = List.length exprs in
1608 let its_done = Label.next_regular () in
1609 gather [
1610 gather @@
1611 List.mapi exprs
1612 begin fun i expr ->
1613 gather [
1614 emit_call_isset_expr env pos expr;
1615 if i < n-1 then
1616 gather [
1617 instr_dup;
1618 instr_jmpz its_done;
1619 instr_popc
1620 ] else empty
1622 end;
1623 instr_label its_done
1626 and emit_exit env expr_opt =
1627 gather [
1628 (match expr_opt with
1629 | None -> instr_int 0
1630 | Some e -> emit_expr ~need_ref:false env e);
1631 instr_exit;
1634 and emit_idx env pos es =
1635 let default = if List.length es = 2 then instr_null else empty in
1636 gather [
1637 emit_exprs env pos es;
1638 emit_pos pos;
1639 default;
1640 instr_idx;
1643 and emit_define env pos s e =
1644 gather [
1645 emit_expr ~need_ref:false env e;
1646 emit_pos pos;
1647 instr_defcns s;
1650 and emit_eval env pos e =
1651 gather [
1652 emit_expr ~need_ref:false env e;
1653 emit_pos pos;
1654 instr_eval;
1657 and emit_xhp_obj_get_raw env pos e s nullflavor =
1658 let fn_name = pos, A.Obj_get (e, (pos, A.Id (pos, "getAttribute")), nullflavor) in
1659 let args = [pos, A.String (pos, SU.Xhp.clean s)] in
1660 fst (emit_call env pos fn_name args [])
1662 and emit_xhp_obj_get ~need_ref env pos param_num_opt e s nullflavor =
1663 let call = emit_xhp_obj_get_raw env pos e s nullflavor in
1664 match param_num_opt with
1665 | Some (i, h) -> gather [ call; instr_fpassr i h ]
1666 | None -> gather [ call;
1667 emit_pos pos; if need_ref then instr_boxr else instr_unboxr ]
1669 and emit_get_class_no_args () =
1670 gather [
1671 instr_fpushfuncd 0 (Hhbc_id.Function.from_raw_string "get_class");
1672 instr_fcall 0;
1673 instr_unboxr
1676 and emit_class_alias es =
1677 let c1, c2 = match es with
1678 | (_, A.String (_, c1)) :: (_, A.String (_, c2)) :: _ -> c1, c2
1679 | _ -> failwith "emit_class_alias: impossible"
1681 let default = if List.length es = 2 then instr_true else instr_string c2 in
1682 gather [
1683 default;
1684 instr_alias_cls c1 c2
1687 and try_inline_gen_call env e =
1688 if not (can_inline_gen_functions ()) then None
1689 else match snd e with
1690 | A.Call ((_, A.Id (_, s)), _, [arg], [])
1691 when String.lowercase_ascii (SU.strip_global_ns s) = "gena"->
1692 Some (inline_gena_call env arg)
1693 | _ ->
1694 try_inline_genva_call env e GI_expression
1696 and try_inline_genva_call env e inline_context =
1697 if not (can_inline_gen_functions ()) then None
1698 else match e with
1699 | pos, A.Call ((_, A.Id (_, s)), _, args, uargs)
1700 when String.lowercase_ascii (SU.strip_global_ns s) = "genva"->
1701 try_inline_genva_call_ env pos args uargs inline_context
1702 | _ -> None
1704 and try_fault b f =
1705 let label = Label.next_fault () in
1706 let body = b () in
1707 let fault = f () in
1708 instr_try_fault label body fault
1710 and unset_in_fault temps b =
1711 try_fault b @@ fun () ->
1712 gather [
1713 gather @@ List.map temps ~f:instr_unsetl;
1714 instr_unwind
1717 (* emits iteration over the ~collection where loop body is
1718 produced by ~f *)
1719 and emit_iter ~collection f = Local.scope @@ fun () ->
1720 let loop_end = Label.next_regular () in
1721 let key_local = Local.get_unnamed_local () in
1722 let value_local = Local.get_unnamed_local () in
1723 let iter = Iterator.get_iterator () in
1724 let iter_init = gather [
1725 collection;
1726 instr_iterinitk iter loop_end value_local key_local;
1727 ] in
1728 let loop_next = Label.next_regular () in
1729 let iterate =
1730 (* try-fault to release temp locals *)
1731 unset_in_fault [value_local; key_local] @@ begin fun () ->
1732 (* try-fault to release iterator *)
1733 try_fault
1734 begin fun () ->
1735 gather [
1736 instr_label loop_next;
1737 f value_local key_local;
1738 instr_iternextk iter loop_next value_local key_local;
1739 instr_label loop_end;
1740 instr_unsetl value_local;
1741 instr_unsetl key_local;
1744 begin fun () ->
1745 gather [
1746 instr_iterfree iter;
1747 instr_unwind;
1750 end in
1751 Iterator.free_iterator ();
1752 gather [
1753 iter_init;
1754 iterate;
1757 and inline_gena_call env arg = Local.scope @@ fun () ->
1758 (* convert input to array *)
1759 let load_array = emit_expr ~need_ref:false env arg in
1760 let arr_local = Local.get_unnamed_local () in
1761 gather [
1762 load_array;
1763 if hack_arr_dv_arrs () then instr_cast_dict else instr_cast_darray;
1764 instr_setl arr_local;
1765 instr_popc;
1766 begin
1767 unset_in_fault [arr_local] @@ fun () ->
1768 gather [
1769 instr_fpushclsmethodd 1
1770 (Hhbc_id.Method.from_raw_string
1771 (if hack_arr_dv_arrs () then "fromDict" else "fromDArray"))
1772 (Hhbc_id.Class.from_raw_string "HH\\AwaitAllWaitHandle");
1773 instr_fpassl 0 arr_local Cell;
1774 instr_fcall 1;
1775 instr_unboxr;
1776 instr_await;
1777 instr_popc;
1778 emit_iter ~collection:(instr_cgetl arr_local) @@
1779 begin fun value_local key_local ->
1780 gather [
1781 (* generate code for
1782 arr_local[key_local] = WHResult (value_local) *)
1783 instr_cgetl value_local;
1784 instr_whresult;
1785 instr_basel arr_local MemberOpMode.Define;
1786 instr_setm 0 (MemberKey.EL key_local);
1787 instr_popc;
1789 end;
1791 end;
1792 instr_pushl arr_local;
1795 and try_inline_genva_call_ env pos args uargs inline_context =
1796 let args_count = List.length args in
1797 let is_valid_list_assignment l =
1798 Core_list.findi l ~f:(fun i (_, x) -> i >= args_count && x <> A.Omitted)
1799 |> Option.is_none in
1800 let emit_list_assignment lhs rhs =
1801 let rec combine lhs rhs =
1802 (* ensure that list of values on left hand side and right hand size
1803 has the same length *)
1804 match lhs, rhs with
1805 | l :: lhs, r :: rhs -> (l, r) :: combine lhs rhs
1806 (* left hand size is smaller - pad with omitted expression *)
1807 | [], r :: rhs -> ((Pos.none, A.Omitted), r) :: combine [] rhs
1808 | _, [] -> [] in
1809 let generate values ~is_ltr =
1810 let rec aux lhs_acc set_acc = function
1811 | [] -> (if is_ltr then List.rev lhs_acc else lhs_acc), List.rev set_acc
1812 | ((_, A.Omitted), _) :: tail -> aux lhs_acc set_acc tail
1813 | (lhs, rhs) :: tail ->
1814 let lhs_instrs, set_instrs =
1815 emit_lval_op_list ~last_usage:true env pos (Some rhs) [] lhs in
1816 aux (lhs_instrs::lhs_acc) (set_instrs::set_acc) tail in
1817 aux [] [] (if is_ltr then values else List.rev values) in
1818 let reify = gather @@ Core_list.map rhs ~f:begin fun l ->
1819 gather [
1820 instr_pushl l;
1821 instr_whresult;
1822 instr_popl l;
1824 end in
1825 let pairs = combine lhs rhs in
1826 let lhs, set = generate pairs ~is_ltr:(php7_ltr_assign ()) in
1827 gather [
1828 reify;
1829 gather lhs;
1830 gather set;
1831 gather @@ Core_list.map pairs
1832 ~f:(function (_, A.Omitted), l -> instr_unsetl l | _ -> empty);
1833 ] in
1834 match inline_context with
1835 | GI_list_assignment l when not (is_valid_list_assignment l) ->
1836 None
1837 | _ when not (List.is_empty uargs) ->
1838 Emit_fatal.raise_fatal_runtime pos "do not use ...$args with genva()"
1839 | GI_ignore_result | GI_list_assignment _ when args_count = 0 ->
1840 Some empty
1841 | GI_expression when args_count = 0 ->
1842 Some instr_lit_empty_varray
1843 | _ when args_count > max_array_elem_on_stack () ->
1844 None
1845 | _ ->
1846 Local.scope @@ begin fun () ->
1847 let load_args =
1848 gather @@ Core_list.map args ~f:begin fun arg ->
1849 let label_done = Label.next_regular () in
1850 gather [
1851 emit_expr ~need_ref:false env arg;
1852 instr_dup;
1853 instr_istypec OpNull;
1854 instr_jmpz label_done;
1855 instr_popc;
1856 instr_fpushfuncd 0 (Hhbc_id.Function.from_raw_string "HH\\Asio\\null");
1857 instr_fcall 0;
1858 instr_unboxr;
1859 instr_label label_done;
1861 end in
1862 let reserved_locals =
1863 List.init args_count (fun _ -> Local.get_unnamed_local ()) in
1864 let reserved_locals_reversed =
1865 List.rev reserved_locals in
1866 let init_locals =
1867 gather @@ Core_list.map reserved_locals_reversed ~f:begin fun l ->
1868 gather [
1869 instr_setl l;
1870 instr_popc;
1872 end in
1873 let await_and_process_results =
1874 unset_in_fault reserved_locals @@ begin fun () ->
1875 let await_all =
1876 gather [
1877 instr_awaitall (List.hd_exn reserved_locals_reversed) (args_count - 1);
1878 instr_popc;
1879 ] in
1880 let process_results =
1881 let reify ~pop_result =
1882 gather @@ Core_list.map reserved_locals ~f:begin fun l ->
1883 gather [
1884 instr_pushl l;
1885 instr_whresult;
1886 if pop_result then instr_popc else empty;
1888 end in
1889 match inline_context with
1890 | GI_ignore_result ->
1891 reify ~pop_result:true
1892 | GI_expression ->
1893 gather [
1894 reify ~pop_result:false;
1895 instr_lit_const (if hack_arr_dv_arrs ()
1896 then (NewVecArray args_count)
1897 else (NewVArray args_count));
1899 | GI_list_assignment l ->
1900 emit_list_assignment l reserved_locals in
1901 gather [
1902 await_all;
1903 process_results;
1905 end in
1906 let result =
1907 gather [
1908 load_args;
1909 init_locals;
1910 await_and_process_results;
1911 ] in
1912 Some result
1915 and emit_await env pos e =
1916 begin match try_inline_gen_call env e with
1917 | Some r -> r
1918 | None ->
1919 let after_await = Label.next_regular () in
1920 gather [
1921 emit_expr ~need_ref:false env e;
1922 emit_pos pos;
1923 instr_dup;
1924 instr_istypec OpNull;
1925 instr_jmpnz after_await;
1926 instr_await;
1927 instr_label after_await;
1931 and emit_callconv _env kind _e =
1932 match kind with
1933 | A.Pinout ->
1934 failwith "emit_callconv: This should have been caught at emit_arg"
1936 and emit_inline_hhas s =
1937 match SMap.get s !inline_hhas_blocks_ with
1938 | Some asm ->
1939 let instrs =
1940 Label_rewriter.clone_with_fresh_regular_labels @@ Hhas_asm.instrs asm in
1941 (* TODO: handle case when code after inline hhas is unreachable
1942 i.e. fallthrough return should not be emitted *)
1943 begin match get_estimated_stack_depth instrs with
1944 | 0 -> gather [ instrs; instr_null ]
1945 | 1 -> instrs
1946 | _ ->
1947 Emit_fatal.raise_fatal_runtime Pos.none
1948 "Inline assembly expressions should leave the stack unchanged, \
1949 or push exactly one cell onto the stack."
1951 | None ->
1952 failwith @@ "impossible: cannot find parsed inline hhas for '" ^ s ^ "'"
1954 and emit_expr env ?last_pos ?(need_ref=false) (pos, expr_ as expr) =
1955 match expr_ with
1956 | A.Float _ | A.String _ | A.Int _ | A.Null | A.False | A.True ->
1957 let v = Ast_constant_folder.expr_to_typed_value (Emit_env.get_namespace env) expr in
1958 emit_pos_then pos @@
1959 emit_box_if_necessary pos need_ref @@
1960 instr (ILitConst (TypedValue v))
1961 | A.ParenthesizedExpr e ->
1962 emit_expr ~need_ref env e
1963 | A.Lvar id ->
1964 gather [
1965 emit_pos (Option.value ~default:pos last_pos);
1966 emit_local ~notice:Notice ~need_ref env id
1968 | A.Class_const (cid, id) ->
1969 emit_class_const env pos cid id
1970 | A.Unop (op, e) ->
1971 emit_unop ~need_ref env pos op e
1972 | A.Binop (op, e1, e2) ->
1973 emit_box_if_necessary pos need_ref @@ emit_binop env expr op e1 e2
1974 | A.Pipe (e1, e2) ->
1975 emit_box_if_necessary pos need_ref @@ emit_pipe env pos e1 e2
1976 | A.InstanceOf (e1, e2) ->
1977 emit_box_if_necessary pos need_ref @@ emit_instanceof env pos e1 e2
1978 | A.Is (e, h) ->
1979 emit_box_if_necessary pos need_ref @@ emit_is env pos (IsExprExpr e) h
1980 | A.As (e, h, is_nullable) ->
1981 emit_box_if_necessary pos need_ref @@ emit_as env pos e h is_nullable
1982 | A.NullCoalesce (e1, e2) ->
1983 emit_box_if_necessary pos need_ref @@ emit_null_coalesce env pos e1 e2
1984 | A.Cast((_, hint), e) ->
1985 emit_box_if_necessary pos need_ref @@ emit_cast env pos hint e
1986 | A.Eif (etest, etrue, efalse) ->
1987 emit_box_if_necessary pos need_ref @@
1988 emit_conditional_expression env pos etest etrue efalse
1989 | A.Expr_list es -> gather @@ List.map es ~f:(emit_expr ~need_ref:false env)
1990 | A.Array_get((_, A.Lvar (_, x)), Some e) when x = SN.Superglobals.globals ->
1991 gather [
1992 emit_expr ~need_ref:false env e;
1993 emit_pos pos;
1994 instr (IGet (if need_ref then VGetG else CGetG))
1996 | A.Array_get(base_expr, opt_elem_expr) ->
1997 let query_op = if need_ref then QueryOp.Empty else QueryOp.CGet in
1998 emit_array_get ~need_ref env pos None query_op base_expr opt_elem_expr
1999 | A.Obj_get (expr, prop, nullflavor) ->
2000 let query_op = if need_ref then QueryOp.Empty else QueryOp.CGet in
2001 emit_obj_get ~need_ref env pos None query_op expr prop nullflavor
2002 | A.Call ((_, A.Id (_, "isset")), _, exprs, []) ->
2003 emit_box_if_necessary pos need_ref @@ emit_call_isset_exprs env pos exprs
2004 | A.Call ((_, A.Id (_, "empty")), _, [expr], []) ->
2005 emit_box_if_necessary pos need_ref @@ emit_call_empty_expr env pos expr
2006 | A.Call ((_, A.Id (_, "idx")), _, ([_; _] | [_; _; _] as es), _)
2007 when not (jit_enable_rename_function ()) ->
2008 emit_box_if_necessary pos need_ref @@ emit_idx env pos es
2009 | A.Call ((_, A.Id (_, "define")), _, [(_, A.String (_, s)); e], _)
2010 when is_global_namespace env ->
2011 emit_box_if_necessary pos need_ref @@ emit_define env pos s e
2012 | A.Call ((_, A.Id (_, "eval")), _, [expr], _) ->
2013 emit_box_if_necessary pos need_ref @@ emit_eval env pos expr
2014 | A.Call ((_, A.Id (_, "class_alias")), _, es, _)
2015 when is_global_namespace env ->
2016 emit_pos_then pos @@
2017 emit_box_if_necessary pos need_ref @@ emit_class_alias es
2018 | A.Call ((_, A.Id (_, "get_class")), _, [], _) ->
2019 emit_box_if_necessary pos need_ref @@ emit_get_class_no_args ()
2020 | A.Call ((_, A.Id (_, ("exit" | "die"))), _, es, _) ->
2021 emit_pos_then pos @@
2022 emit_exit env (List.hd es)
2023 | A.Call _
2024 (* execution operator is compiled as call to `shell_exec` and should
2025 be handled in the same way *)
2026 | A.Execution_operator _ ->
2027 emit_call_expr ?last_pos ~need_ref env expr
2028 | A.New (typeexpr, args, uargs) ->
2029 emit_box_if_necessary pos need_ref @@ emit_new env pos typeexpr args uargs
2030 | A.NewAnonClass (args, uargs, { A.c_name = (_, cls_name); _ }) ->
2031 let cls_idx = int_of_string cls_name in
2032 emit_box_if_necessary pos need_ref @@ emit_new_anon env pos cls_idx args uargs
2033 | A.Array es ->
2034 emit_pos_then pos @@
2035 emit_box_if_necessary pos need_ref @@ emit_collection env expr es
2036 | A.Darray es ->
2037 emit_pos_then pos @@
2038 let es2 = List.map ~f:(fun (e1, e2) -> A.AFkvalue (e1, e2)) es in
2039 let darray_e = fst expr, A.Darray es in
2040 emit_box_if_necessary pos need_ref @@ emit_collection env darray_e es2
2041 | A.Varray es ->
2042 emit_pos_then pos @@
2043 let es2 = List.map ~f:(fun e -> A.AFvalue e) es in
2044 let varray_e = fst expr, A.Varray es in
2045 emit_box_if_necessary pos need_ref @@ emit_collection env varray_e es2
2046 | A.Collection ((pos, name), fields) ->
2047 emit_box_if_necessary pos need_ref
2048 @@ emit_named_collection env expr pos name fields
2049 | A.Clone e ->
2050 emit_pos_then pos @@
2051 emit_box_if_necessary pos need_ref @@ emit_clone env e
2052 | A.Shape fl ->
2053 emit_pos_then pos @@
2054 emit_box_if_necessary pos need_ref @@ emit_shape env expr fl
2055 | A.Await e -> emit_await env pos e
2056 | A.Yield e -> emit_yield env pos e
2057 | A.Yield_break ->
2058 failwith "yield break should be in statement position"
2059 | A.Yield_from _ -> failwith "complex yield_from expression"
2060 | A.Lfun _ ->
2061 failwith "expected Lfun to be converted to Efun during closure conversion"
2062 | A.Efun (fundef, ids) ->
2063 emit_pos_then pos @@
2064 emit_lambda env fundef ids
2065 | A.Class_get (cid, id) ->
2066 emit_class_get env None QueryOp.CGet need_ref cid id
2067 | A.String2 es -> emit_string2 env pos es
2068 | A.BracedExpr e -> emit_expr ~need_ref:false env e
2069 | A.Dollar e ->
2070 check_non_pipe_local e;
2071 let instr = emit_expr ?last_pos ~need_ref:false env e in
2072 gather [
2073 instr;
2074 emit_pos (Option.value ~default:pos last_pos);
2075 if need_ref then instr_vgetn else instr_cgetn
2077 | A.Id id ->
2078 emit_pos_then pos @@
2079 emit_id env id
2080 | A.Xml (id, attributes, children) ->
2081 emit_xhp env (fst expr) id attributes children
2082 | A.Callconv (kind, e) ->
2083 emit_box_if_necessary pos need_ref @@ emit_callconv env kind e
2084 | A.Import (flavor, e) -> emit_import env pos flavor e
2085 | A.Id_type_arguments (id, _) ->
2086 emit_pos_then pos @@
2087 emit_id env id
2088 | A.Omitted -> empty
2089 | A.Unsafeexpr _ ->
2090 failwith "Unsafe expression should be removed during closure conversion"
2091 | A.Suspend _ ->
2092 failwith "Codegen for 'suspend' operator is not supported"
2093 | A.List _ ->
2094 failwith "List destructor can only be used as an lvar"
2096 and emit_static_collection ~transform_to_collection pos tv =
2097 let transform_instr =
2098 match transform_to_collection with
2099 | Some collection_type ->
2100 instr_colfromarray collection_type
2101 | _ -> empty
2103 gather [
2104 emit_pos pos;
2105 instr (ILitConst (TypedValue tv));
2106 transform_instr;
2109 and emit_value_only_collection env pos es constructor =
2110 let limit = max_array_elem_on_stack () in
2111 let inline exprs =
2112 gather
2113 [gather @@ List.map exprs
2114 ~f:(function
2115 (* Drop the keys *)
2116 | A.AFkvalue (_, e)
2117 | A.AFvalue e -> emit_expr ~need_ref:false env e);
2118 emit_pos pos;
2119 instr @@ ILitConst (constructor @@ List.length exprs)]
2121 let outofline exprs =
2122 gather @@
2123 List.map exprs
2124 ~f:(function
2125 (* Drop the keys *)
2126 | A.AFkvalue (_, e)
2127 | A.AFvalue e -> gather [emit_expr ~need_ref:false env e; instr_add_new_elemc])
2129 match (List.groupi ~break:(fun i _ _ -> i = limit) es) with
2130 | [] -> empty
2131 | x1 :: [] -> inline x1
2132 | x1 :: x2 :: _ -> gather [inline x1; outofline x2]
2134 and emit_keyvalue_collection name env pos es constructor =
2135 let name = SU.strip_ns name in
2136 let transform_instr =
2137 if name = "dict" || name = "array" then empty else
2138 let collection_type = collection_type name in
2139 instr_colfromarray collection_type
2141 let add_elem_instr =
2142 if name = "array" then instr_add_new_elemc
2143 else gather [instr_dup; instr_add_elemc]
2145 gather [
2146 emit_pos pos;
2147 instr (ILitConst constructor);
2148 gather (List.map es ~f:(expr_and_new env pos add_elem_instr instr_add_elemc));
2149 emit_pos pos;
2150 transform_instr;
2153 and emit_struct_array env pos es ctor =
2154 let es =
2155 List.map es
2156 ~f:(function A.AFkvalue (k, v) ->
2157 let ns = Emit_env.get_namespace env in
2158 (* TODO: Consider reusing folded keys from is_struct_init *)
2159 begin match snd @@ Ast_constant_folder.fold_expr ns k with
2160 | A.String (_, s) -> s, emit_expr ~need_ref:false env v
2161 | _ -> failwith "impossible"
2163 | _ -> failwith "impossible")
2165 gather [
2166 gather @@ List.map es ~f:snd;
2167 emit_pos pos;
2168 ctor @@ List.map es ~f:fst;
2171 (* isPackedInit() returns true if this expression list looks like an
2172 * array with no keys and no ref values *)
2173 and is_packed_init ?(hack_arr_compat=true) es =
2174 let is_only_values =
2175 List.for_all es ~f:(function A.AFkvalue _ -> false | _ -> true)
2177 let keys_are_zero_indexed_properly_formed =
2178 List.foldi es ~init:true ~f:(fun i b f -> b && match f with
2179 | A.AFkvalue ((_, A.Int (_, k)), _) ->
2180 int_of_string k = i
2181 (* arrays with int-like string keys are still considered packed
2182 and should be emitted via NewArray *)
2183 | A.AFkvalue ((_, A.String (_, k)), _) when not hack_arr_compat ->
2184 (try int_of_string k = i with Failure _ -> false)
2185 (* True and False are considered 1 and 0, respectively *)
2186 | A.AFkvalue ((_, A.True), _) ->
2187 i = 1
2188 | A.AFkvalue ((_, A.False), _) ->
2189 i = 0
2190 | A.AFvalue _ ->
2191 true
2192 | _ -> false)
2194 let has_references =
2195 (* Reference can only exist as a value *)
2196 List.exists es
2197 ~f:(function A.AFkvalue (_, e)
2198 | A.AFvalue e -> expr_starts_with_ref e)
2200 let has_bool_keys =
2201 List.exists es
2202 ~f:(function A.AFkvalue ((_, (A.True | A.False)), _) -> true | _ -> false)
2204 (is_only_values || keys_are_zero_indexed_properly_formed)
2205 && not (has_bool_keys && (hack_arr_compat && hack_arr_compat_notices()))
2206 && not has_references
2207 && (List.length es) > 0
2209 and is_struct_init env es allow_numerics =
2210 let has_references =
2211 (* Reference can only exist as a value *)
2212 List.exists es
2213 ~f:(function A.AFkvalue (_, e)
2214 | A.AFvalue e -> expr_starts_with_ref e)
2216 let keys = ULS.empty in
2217 let are_all_keys_non_numeric_strings, keys =
2218 List.fold_right es ~init:(true, keys) ~f:(fun field (b, keys) ->
2219 match field with
2220 | A.AFkvalue (key, _) ->
2221 let ns = Emit_env.get_namespace env in
2222 begin match snd @@ Ast_constant_folder.fold_expr ns key with
2223 | A.String (_, s) ->
2224 b && (Option.is_none
2225 @@ Typed_value.string_to_int_opt
2226 ~allow_following:false ~allow_inf:false s),
2227 ULS.add keys s
2228 | _ -> false, keys
2230 | _ -> false, keys)
2232 let num_keys = List.length es in
2233 let has_duplicate_keys =
2234 ULS.cardinal keys <> num_keys
2236 let limit = max_array_elem_on_stack () in
2237 (allow_numerics || are_all_keys_non_numeric_strings)
2238 && not has_duplicate_keys
2239 && not has_references
2240 && num_keys <= limit
2241 && num_keys != 0
2243 (* transform_to_collection argument keeps track of
2244 * what collection to transform to *)
2245 and emit_dynamic_collection env (pos, expr_) es =
2246 let count = List.length es in
2247 match expr_ with
2248 | A.Collection ((_, "vec"), _) ->
2249 emit_value_only_collection env pos es (fun n -> NewVecArray n)
2250 | A.Collection ((_, "keyset"), _) ->
2251 emit_value_only_collection env pos es (fun n -> NewKeysetArray n)
2252 | A.Collection ((_, "dict"), _) ->
2253 if is_struct_init env es true then
2254 emit_struct_array env pos es instr_newstructdict
2255 else
2256 emit_keyvalue_collection "dict" env pos es (NewDictArray count)
2257 | A.Collection ((_, name), _)
2258 when SU.strip_ns name = "Set"
2259 || SU.strip_ns name = "ImmSet"
2260 || SU.strip_ns name = "Map"
2261 || SU.strip_ns name = "ImmMap" ->
2262 if is_struct_init env es true then
2263 gather [
2264 emit_struct_array env pos es instr_newstructdict;
2265 emit_pos pos;
2266 instr_colfromarray (collection_type (SU.strip_ns name));
2268 else
2269 emit_keyvalue_collection name env pos es (NewDictArray count)
2271 | A.Varray _ ->
2272 emit_value_only_collection env pos es
2273 (fun n -> if hack_arr_dv_arrs () then (NewVecArray n) else (NewVArray n))
2274 | A.Darray _ ->
2275 if is_struct_init env es false then
2276 emit_struct_array env pos es
2277 (fun arg -> emit_pos_then pos @@
2278 if hack_arr_dv_arrs () then instr_newstructdict arg else instr_newstructdarray arg)
2279 else
2280 emit_keyvalue_collection "array" env pos es
2281 (if hack_arr_dv_arrs () then (NewDictArray count) else (NewDArray count))
2282 | _ ->
2283 (* From here on, we're only dealing with PHP arrays *)
2284 if is_packed_init es then
2285 emit_value_only_collection env pos es (fun n -> NewPackedArray n)
2286 else if is_struct_init env es false then
2287 emit_struct_array env pos es instr_newstructarray
2288 else if is_packed_init ~hack_arr_compat:false es then
2289 emit_keyvalue_collection "array" env pos es (NewArray count)
2290 else
2291 emit_keyvalue_collection "array" env pos es (NewMixedArray count)
2293 and emit_named_collection env expr pos name fields =
2294 let name = SU.Types.fix_casing @@ SU.strip_ns name in
2295 match name with
2296 | "dict" | "vec" | "keyset" ->
2297 emit_pos_then pos @@
2298 emit_collection env expr fields
2299 | "Vector" | "ImmVector" ->
2300 let collection_type = collection_type name in
2301 if fields = []
2302 then
2303 emit_pos_then pos @@
2304 instr_newcol collection_type
2305 else
2306 gather [
2307 emit_collection env (pos, A.Collection ((pos, "vec"), fields)) fields;
2308 instr_colfromarray collection_type;
2310 | "Map" | "ImmMap" | "Set" | "ImmSet" ->
2311 let collection_type = collection_type name in
2312 if fields = []
2313 then
2314 emit_pos_then pos @@
2315 instr_newcol collection_type
2316 else
2317 emit_collection
2318 ~transform_to_collection:collection_type
2320 expr
2321 fields
2322 | "Pair" ->
2323 gather [
2324 gather (List.map fields (function
2325 | A.AFvalue e -> emit_expr ~need_ref:false env e
2326 | _ -> failwith "impossible Pair argument"));
2327 instr (ILitConst NewPair);
2329 | _ -> failwith @@ "collection: " ^ name ^ " does not exist"
2331 and is_php_array = function
2332 | _, A.Array _ -> true
2333 | _, A.Varray _ -> not (hack_arr_dv_arrs ())
2334 | _, A.Darray _ -> not (hack_arr_dv_arrs ())
2335 | _ -> false
2337 and emit_collection ?(transform_to_collection) env expr es =
2338 match Ast_constant_folder.expr_to_opt_typed_value
2339 ~allow_maps:true
2340 ~restrict_keys:(not @@ is_php_array expr)
2341 (Emit_env.get_namespace env)
2342 expr
2343 with
2344 | Some tv ->
2345 emit_static_collection ~transform_to_collection (fst expr) tv
2346 | None ->
2347 emit_dynamic_collection env expr es
2349 and emit_pipe env pos e1 e2 =
2350 stash_in_local ~always_stash:true env pos e1
2351 begin fun temp _break_label ->
2352 let env = Emit_env.with_pipe_var temp env in
2353 emit_expr ~need_ref:false env e2
2356 (* Emit code that is equivalent to
2357 * <code for expr>
2358 * JmpZ label
2359 * Generate specialized code in case expr is statically known, and for
2360 * !, && and || expressions
2362 and emit_jmpz env (pos, expr_ as expr) label: emit_jmp_result =
2363 let with_pos i = emit_pos_then pos i in
2364 let opt = optimize_null_check () in
2365 match Ast_constant_folder.expr_to_opt_typed_value (Emit_env.get_namespace env) expr with
2366 | Some v ->
2367 let b = Typed_value.to_bool v in
2368 if b then
2369 { instrs = with_pos empty;
2370 is_fallthrough = true;
2371 is_label_used = false; }
2372 else
2373 { instrs = with_pos @@ instr_jmp label;
2374 is_fallthrough = false;
2375 is_label_used = true; }
2376 | None ->
2377 begin match expr_ with
2378 | A.Unop(A.Unot, e) ->
2379 emit_jmpnz env e label
2380 | A.Binop(A.BArbar, e1, e2) ->
2381 let skip_label = Label.next_regular () in
2382 let r1 = emit_jmpnz env e1 skip_label in
2383 if not r1.is_fallthrough
2384 then
2385 let instrs =
2386 if r1.is_label_used then gather [ r1.instrs; instr_label skip_label; ]
2387 else r1.instrs in
2388 { instrs = with_pos instrs;
2389 is_fallthrough = r1.is_label_used;
2390 is_label_used = false }
2391 else
2392 let r2 = emit_jmpz env e2 label in
2393 let instrs = gather [
2394 r1.instrs;
2395 r2.instrs;
2396 optional r1.is_label_used [instr_label skip_label];
2397 ] in
2398 { instrs = with_pos instrs;
2399 is_fallthrough = r2.is_fallthrough || r1.is_label_used;
2400 is_label_used = r2.is_label_used }
2401 | A.Binop(A.AMpamp, e1, e2) ->
2402 let r1 = emit_jmpz env e1 label in
2403 if not r1.is_fallthrough
2404 then
2405 { instrs = with_pos r1.instrs;
2406 is_fallthrough = false;
2407 is_label_used = r1.is_label_used }
2408 else
2409 let r2 = emit_jmpz env e2 label in
2410 { instrs = with_pos @@ gather [ r1.instrs; r2.instrs; ];
2411 is_fallthrough = r2.is_fallthrough;
2412 is_label_used = r1.is_label_used || r2.is_label_used }
2413 | A.Binop(A.EQeqeq, e, (_, A.Null))
2414 | A.Binop(A.EQeqeq, (_, A.Null), e) when opt ->
2415 { instrs = with_pos @@ gather [
2416 emit_is_null env e;
2417 instr_jmpz label;
2419 is_fallthrough = true;
2420 is_label_used = true; }
2421 | A.Binop(A.Diff2, e, (_, A.Null))
2422 | A.Binop(A.Diff2, (_, A.Null), e) when opt ->
2423 { instrs = with_pos @@ gather [
2424 emit_is_null env e;
2425 instr_jmpnz label;
2427 is_fallthrough = true;
2428 is_label_used = true; }
2429 | _ ->
2430 { instrs = with_pos @@ gather [
2431 emit_expr_and_unbox_if_necessary ~need_ref:false env pos expr;
2432 instr_jmpz label;
2434 is_fallthrough = true;
2435 is_label_used = true; }
2438 (* Emit code that is equivalent to
2439 * <code for expr>
2440 * JmpNZ label
2441 * Generate specialized code in case expr is statically known, and for
2442 * !, && and || expressions
2444 and emit_jmpnz env (pos, expr_ as expr) label: emit_jmp_result =
2445 let with_pos i = emit_pos_then pos i in
2446 let opt = optimize_null_check () in
2447 match Ast_constant_folder.expr_to_opt_typed_value (Emit_env.get_namespace env) expr with
2448 | Some v ->
2449 if Typed_value.to_bool v
2450 then
2451 { instrs = with_pos @@ instr_jmp label;
2452 is_fallthrough = false;
2453 is_label_used = true }
2454 else
2455 { instrs = with_pos empty;
2456 is_fallthrough = true;
2457 is_label_used = false }
2458 | None ->
2459 begin match expr_ with
2460 | A.Unop(A.Unot, e) ->
2461 emit_jmpz env e label
2462 | A.Binop(A.BArbar, e1, e2) ->
2463 let r1 = emit_jmpnz env e1 label in
2464 if not r1.is_fallthrough then r1
2465 else
2466 let r2 = emit_jmpnz env e2 label in
2467 { instrs = with_pos @@ gather [ r1.instrs; r2.instrs ];
2468 is_fallthrough = r2.is_fallthrough;
2469 is_label_used = r1.is_label_used || r2.is_label_used }
2470 | A.Binop(A.AMpamp, e1, e2) ->
2471 let skip_label = Label.next_regular () in
2472 let r1 = emit_jmpz env e1 skip_label in
2473 if not r1.is_fallthrough
2474 then
2475 { instrs = with_pos @@ gather [
2476 r1.instrs;
2477 optional r1.is_label_used [instr_label skip_label]
2479 is_fallthrough = r1.is_label_used;
2480 is_label_used = false }
2481 else begin
2482 let r2 = emit_jmpnz env e2 label in
2483 { instrs = with_pos @@ gather [
2484 r1.instrs;
2485 r2.instrs;
2486 optional r1.is_label_used [instr_label skip_label]
2488 is_fallthrough = r2.is_fallthrough || r1.is_label_used;
2489 is_label_used = r2.is_label_used }
2491 | A.Binop(A.EQeqeq, e, (_, A.Null))
2492 | A.Binop(A.EQeqeq, (_, A.Null), e) when opt ->
2493 { instrs = with_pos @@ gather [
2494 emit_is_null env e;
2495 instr_jmpnz label;
2497 is_fallthrough = true;
2498 is_label_used = true; }
2499 | A.Binop(A.Diff2, e, (_, A.Null))
2500 | A.Binop(A.Diff2, (_, A.Null), e) when opt ->
2501 { instrs = with_pos @@ gather [
2502 emit_is_null env e;
2503 instr_jmpz label;
2505 is_fallthrough = true;
2506 is_label_used = true; }
2507 | _ ->
2508 { instrs = with_pos @@ gather [
2509 emit_expr_and_unbox_if_necessary ~need_ref:false env pos expr;
2510 instr_jmpnz label;
2512 is_fallthrough = true;
2513 is_label_used = true; }
2516 and emit_short_circuit_op env expr =
2517 let its_true = Label.next_regular () in
2518 let its_done = Label.next_regular () in
2519 let r1 = emit_jmpnz env expr its_true in
2520 let if_true =
2521 if r1.is_label_used then gather [
2522 instr_label its_true;
2523 emit_pos (fst expr);
2524 instr_true;
2526 else empty in
2527 if r1.is_fallthrough
2528 then gather [
2529 r1.instrs;
2530 emit_pos (fst expr);
2531 instr_false;
2532 instr_jmp its_done;
2533 if_true;
2534 instr_label its_done ]
2535 else gather [
2536 r1.instrs;
2537 if_true; ]
2539 and emit_quiet_expr env pos (_, expr_ as expr) =
2540 match expr_ with
2541 | A.Lvar (name_pos, name) when name = SN.Superglobals.globals ->
2542 gather [
2543 emit_pos name_pos;
2544 instr_string (SU.Locals.strip_dollar name);
2545 emit_pos pos;
2546 instr (IGet CGetQuietG)
2548 | A.Lvar ((_, name) as id) when not (is_local_this env name) ->
2549 instr_cgetquietl (get_local env id)
2550 | A.Dollar e ->
2551 gather [
2552 emit_expr ~need_ref:false env e;
2553 emit_pos pos;
2554 instr_cgetquietn
2556 | A.Array_get((_, A.Lvar (_, x)), Some e) when x = SN.Superglobals.globals ->
2557 gather [
2558 emit_expr ~need_ref:false env e;
2559 emit_pos pos;
2560 instr (IGet CGetQuietG)
2562 | A.Array_get(base_expr, opt_elem_expr) ->
2563 emit_array_get ~need_ref:false env pos None QueryOp.CGetQuiet base_expr opt_elem_expr
2564 | A.Obj_get (expr, prop, nullflavor) ->
2565 emit_obj_get ~need_ref:false env pos None QueryOp.CGetQuiet expr prop nullflavor
2566 | _ ->
2567 emit_expr ~need_ref:false env expr
2569 (* returns instruction that will represent setter for $base[local] where
2570 is_base is true when result cell is base for another subscript operator and
2571 false when it is final left hand side of the assignment *)
2572 and emit_store_for_simple_base ~is_base env pos elem_stack_size param_num_opt base_expr local =
2573 let base_expr_instrs_begin,
2574 base_expr_instrs_end,
2575 base_setup_instrs,
2577 emit_base ~is_object:false ~notice:Notice env MemberOpMode.Define
2578 elem_stack_size param_num_opt base_expr in
2579 let expr =
2580 let mk = MemberKey.EL local in
2581 if is_base then instr_dim MemberOpMode.Define mk else instr_setm 0 mk in
2582 gather [
2583 base_expr_instrs_begin;
2584 base_expr_instrs_end;
2585 emit_pos pos;
2586 base_setup_instrs;
2587 expr;
2590 (* get LocalTempKind option for a given expression
2591 - None - expression can be emitted as is
2592 - Some Value_kind_local - expression represents local that will be
2593 overwritten later
2594 - Some Value_kind_expression - spilled non-trivial expression *)
2595 and get_local_temp_kind inout_param_info env e_opt =
2596 match e_opt, inout_param_info with
2597 (* not inout case - no need to save *)
2598 | _, None -> None
2599 (* local that will later be overwritten *)
2600 | Some (_, A.Lvar (_, id)), Some (i, aliases)
2601 when InoutLocals.should_save_local_value id i aliases -> Some Value_kind_local
2602 (* non-trivial expression *)
2603 | Some e, _ -> if is_trivial env e then None else Some Value_kind_expression
2604 | None, _ -> None
2606 and is_trivial env (_, e) =
2607 match e with
2608 | A.Int _ | A.String _ ->
2609 true
2610 | A.Lvar (_, s) ->
2611 not (is_local_this env s) || Emit_env.get_needs_local_this env
2612 | A.Array_get (b, None) -> is_trivial env b
2613 | A.Array_get (b, Some e) -> is_trivial env b && is_trivial env e
2614 | _ ->
2615 false
2617 (* Emit code for e1[e2] or isset(e1[e2]).
2618 * If param_num_opt = Some i
2619 * then this is the i'th parameter to a function
2622 and emit_array_get ?(no_final=false) ?mode ~need_ref
2623 env outer_pos param_num_hint_opt qop base_expr opt_elem_expr =
2624 let result =
2625 emit_array_get_worker ~no_final ?mode ~need_ref ~inout_param_info:None
2626 env outer_pos param_num_hint_opt qop base_expr opt_elem_expr in
2627 match result with
2628 | Array_get_regular i -> i
2629 | Array_get_inout _ -> failwith "unexpected inout"
2631 and emit_array_get_worker ?(no_final=false) ?mode
2632 ~need_ref ~inout_param_info
2633 env outer_pos param_num_hint_opt qop base_expr opt_elem_expr =
2634 (* Disallow use of array(..)[] *)
2635 match base_expr, opt_elem_expr with
2636 | (pos, A.Array _), None ->
2637 Emit_fatal.raise_fatal_parse pos "Can't use array() as base in write context"
2638 | (pos, _), None when not (Emit_env.does_env_allow_array_append env)->
2639 Emit_fatal.raise_fatal_runtime pos "Can't use [] for reading"
2640 | _ ->
2641 let local_temp_kind =
2642 get_local_temp_kind inout_param_info env opt_elem_expr in
2643 let param_num_hint_opt =
2644 if qop = QueryOp.InOut then None else param_num_hint_opt in
2645 let mode = Option.value mode ~default:(get_queryMOpMode need_ref qop) in
2646 let elem_expr_instrs, elem_stack_size =
2647 emit_elem_instrs ~local_temp_kind env opt_elem_expr in
2648 let param_num_opt = Option.map ~f:(fun (n, _h) -> n) param_num_hint_opt in
2649 let mk = get_elem_member_key env 0 opt_elem_expr in
2650 let base_result =
2651 emit_base_worker ~is_object:false ~inout_param_info
2652 ~notice:(match qop with QueryOp.Isset -> NoNotice | _ -> Notice)
2653 env mode elem_stack_size param_num_opt base_expr in
2654 let make_final param_num_hint_opt total_stack_size =
2655 if no_final then empty else
2656 instr (IFinal (
2657 match param_num_hint_opt with
2658 | None ->
2659 if need_ref then
2660 VGetM (total_stack_size, mk)
2661 else
2662 QueryM (total_stack_size, qop, mk)
2663 | Some (i, h) -> FPassM (i, total_stack_size, mk, h)
2664 )) in
2665 match base_result, local_temp_kind with
2666 | Array_get_base_regular base, None ->
2667 (* both base and expression don't need to store anything *)
2668 Array_get_regular (gather [
2669 base.instrs_begin;
2670 elem_expr_instrs;
2671 base.instrs_end;
2672 emit_pos outer_pos;
2673 base.setup_instrs;
2674 make_final param_num_hint_opt (base.stack_size + elem_stack_size);
2676 | Array_get_base_regular base, Some local_kind ->
2677 (* base does not need temp locals but index expression does *)
2678 let local = Local.get_unnamed_local () in
2679 let load =
2681 (* load base and indexer, value of indexer will be saved in local *)
2682 gather [
2683 base.instrs_begin;
2684 elem_expr_instrs
2685 ], Some (local, local_kind);
2686 (* finish loading the value *)
2687 gather [
2688 base.instrs_end;
2689 emit_pos outer_pos;
2690 base.setup_instrs;
2691 make_final None (base.stack_size + elem_stack_size);
2692 ], None
2693 ] in
2694 let store =
2695 emit_store_for_simple_base ~is_base:false env outer_pos elem_stack_size
2696 param_num_opt base_expr local in
2697 Array_get_inout { load; store }
2699 | Array_get_base_inout base, None ->
2700 (* base needs temp locals, indexer - does not,
2701 simply concat two instruction sequences *)
2702 let load = base.load.instrs_begin @ [
2703 gather [
2704 elem_expr_instrs;
2705 base.load.instrs_end;
2706 emit_pos outer_pos;
2707 base.load.setup_instrs;
2708 make_final None (base.load.stack_size + elem_stack_size);
2709 ], None
2710 ] in
2711 let store = gather [
2712 base.store;
2713 instr_setm 0 mk;
2714 ] in
2715 Array_get_inout { load; store }
2717 | Array_get_base_inout base, Some local_kind ->
2718 (* both base and index need temp locals,
2719 create local for index value *)
2720 let local = Local.get_unnamed_local () in
2721 let load =
2722 (* load base *)
2723 base.load.instrs_begin @ [
2724 (* load index, value will be saved in local *)
2725 elem_expr_instrs, Some (local, local_kind);
2726 gather [
2727 base.load.instrs_end;
2728 emit_pos outer_pos;
2729 base.load.setup_instrs;
2730 make_final None (base.load.stack_size + elem_stack_size);
2731 ], None
2732 ] in
2733 let store = gather [
2734 base.store;
2735 instr_setm 0 (MemberKey.EL local);
2736 ] in
2737 Array_get_inout { load; store }
2739 (* Emit code for e1->e2 or e1?->e2 or isset(e1->e2).
2740 * If param_num_opt = Some i
2741 * then this is the i'th parameter to a function
2743 and emit_obj_get ~need_ref env pos param_num_hint_opt qop expr prop null_flavor =
2744 match snd expr with
2745 | A.Lvar (pos, id)
2746 when id = SN.SpecialIdents.this && null_flavor = A.OG_nullsafe ->
2747 Emit_fatal.raise_fatal_parse
2748 pos "?-> is not allowed with $this"
2749 | _ ->
2750 begin match snd prop with
2751 | A.Id (_, s) when SU.Xhp.is_xhp s ->
2752 emit_xhp_obj_get ~need_ref env pos param_num_hint_opt expr s null_flavor
2753 | _ ->
2754 let param_num_opt = Option.map ~f:(fun (n, _h) -> n) param_num_hint_opt in
2755 let mode = get_queryMOpMode need_ref qop in
2756 let mk, prop_expr_instrs, prop_stack_size =
2757 emit_prop_expr env null_flavor 0 prop in
2758 let base_expr_instrs_begin,
2759 base_expr_instrs_end,
2760 base_setup_instrs,
2761 base_stack_size =
2762 emit_base
2763 ~is_object:true ~notice:Notice
2764 env mode prop_stack_size param_num_opt expr
2766 let total_stack_size = prop_stack_size + base_stack_size in
2767 let final_instr =
2768 instr (IFinal (
2769 match param_num_hint_opt with
2770 | None ->
2771 if need_ref then
2772 VGetM (total_stack_size, mk)
2773 else
2774 QueryM (total_stack_size, qop, mk)
2775 | Some (i, h) -> FPassM (i, total_stack_size, mk, h)
2776 )) in
2777 gather [
2778 base_expr_instrs_begin;
2779 prop_expr_instrs;
2780 base_expr_instrs_end;
2781 emit_pos pos;
2782 base_setup_instrs;
2783 final_instr
2787 and is_special_class_constant_accessed_with_class_id env (_, cName) id =
2788 (* TODO(T21932293): HHVM does not match Zend here.
2789 * Eventually remove this to match PHP7 *)
2790 SU.is_class id &&
2791 (not (SU.is_self cName || SU.is_parent cName || SU.is_static cName)
2792 || (Ast_scope.Scope.is_in_trait (Emit_env.get_scope env)) && SU.is_self cName)
2794 and emit_elem_instrs env ~local_temp_kind opt_elem_expr =
2795 match opt_elem_expr with
2796 (* These all have special inline versions of member keys *)
2797 | Some (_, (A.Int _ | A.String _)) -> empty, 0
2798 | Some (_, (A.Lvar ((_, id) as pid))) when not (is_local_this env id) ->
2799 if Option.is_some local_temp_kind
2800 then instr_cgetquietl (get_local env pid), 0
2801 else empty, 0
2802 | Some (_, (A.Class_const ((_, A.Id cid), (_, id))))
2803 when is_special_class_constant_accessed_with_class_id env cid id -> empty, 0
2804 | Some expr -> emit_expr ~need_ref:false env expr, 1
2805 | None -> empty, 0
2807 (* Get the member key for an array element expression: the `elem` in
2808 * expressions of the form `base[elem]`.
2809 * If the array element is missing, use the special key `W`.
2811 and get_elem_member_key env stack_index opt_expr =
2812 match opt_expr with
2813 (* Special case for local *)
2814 | Some (_, A.Lvar id) when not (is_local_this env (snd id)) ->
2815 MemberKey.EL (get_local env id)
2816 (* Special case for literal integer *)
2817 | Some (_, A.Int (_, str) as int_expr)->
2818 let open Ast_constant_folder in
2819 let namespace = Emit_env.get_namespace env in
2820 begin match expr_to_typed_value namespace int_expr with
2821 | TV.Int i -> MemberKey.EI i
2822 | _ -> failwith (str ^ " is not a valid integer index")
2824 (* Special case for literal string *)
2825 | Some (_, A.String (_, str)) -> MemberKey.ET str
2826 (* Special case for class name *)
2827 | Some (_, (A.Class_const ((_, A.Id (p, cName as cid)), (_, id))))
2828 when is_special_class_constant_accessed_with_class_id env cid id ->
2829 (* Special case for self::class in traits *)
2830 (* TODO(T21932293): HHVM does not match Zend here.
2831 * Eventually remove this to match PHP7 *)
2832 let cName =
2833 match SU.is_self cName,
2834 Ast_scope.Scope.get_class (Emit_env.get_scope env)
2835 with
2836 | true, Some cd -> SU.strip_global_ns @@ snd cd.A.c_name
2837 | _ -> cName
2839 let fq_id, _ =
2840 Hhbc_id.Class.elaborate_id (Emit_env.get_namespace env) (p, cName) in
2841 MemberKey.ET (Hhbc_id.Class.to_raw_string fq_id)
2842 (* General case *)
2843 | Some _ -> MemberKey.EC stack_index
2844 (* ELement missing (so it's array append) *)
2845 | None -> MemberKey.W
2847 (* Get the member key for a property, and return any instructions and
2848 * the size of the stack in the case that the property cannot be
2849 * placed inline in the instruction. *)
2850 and emit_prop_expr env null_flavor stack_index prop_expr =
2851 let mk =
2852 match snd prop_expr with
2853 | A.Id ((_, name) as id) when String_utils.string_starts_with name "$" ->
2854 MemberKey.PL (get_local env id)
2855 (* Special case for known property name *)
2856 | A.Id (_, id)
2857 | A.String (_, id) ->
2858 let pid = Hhbc_id.Prop.from_ast_name id in
2859 begin match null_flavor with
2860 | Ast.OG_nullthrows -> MemberKey.PT pid
2861 | Ast.OG_nullsafe -> MemberKey.QT pid
2863 | A.Lvar ((_, name) as id) when not (is_local_this env name) ->
2864 MemberKey.PL (get_local env id)
2865 (* General case *)
2866 | _ ->
2867 MemberKey.PC stack_index
2869 (* For nullsafe access, insist that property is known *)
2870 begin match mk with
2871 | MemberKey.PL _ | MemberKey.PC _ ->
2872 if null_flavor = A.OG_nullsafe then
2873 Emit_fatal.raise_fatal_parse (fst prop_expr)
2874 "?-> can only be used with scalar property names"
2875 | _ -> ()
2876 end;
2877 match mk with
2878 | MemberKey.PC _ ->
2879 mk, emit_expr ~need_ref:false env prop_expr, 1
2880 | _ ->
2881 mk, empty, 0
2883 (* Emit code for a base expression `expr` that forms part of
2884 * an element access `expr[elem]` or field access `expr->fld`.
2885 * The instructions are divided into three sections:
2886 * 1. base and element/property expression instructions:
2887 * push non-trivial base and key values on the stack
2888 * 2. base selector instructions: a sequence of Base/Dim instructions that
2889 * actually constructs the base address from "member keys" that are inlined
2890 * in the instructions, or pulled from the key values that
2891 * were pushed on the stack in section 1.
2892 * 3. (constructed by the caller) a final accessor e.g. QueryM or setter
2893 * e.g. SetOpM instruction that has the final key inlined in the
2894 * instruction, or pulled from the key values that were pushed on the
2895 * stack in section 1.
2896 * The function returns a triple (base_instrs, base_setup_instrs, stack_size)
2897 * where base_instrs is section 1 above, base_setup_instrs is section 2, and
2898 * stack_size is the number of values pushed onto the stack by section 1.
2900 * For example, the r-value expression $arr[3][$ix+2]
2901 * will compile to
2902 * # Section 1, pushing the value of $ix+2 on the stack
2903 * Int 2
2904 * CGetL2 $ix
2905 * AddO
2906 * # Section 2, constructing the base address of $arr[3]
2907 * BaseL $arr Warn
2908 * Dim Warn EI:3
2909 * # Section 3, indexing the array using the value at stack position 0 (EC:0)
2910 * QueryM 1 CGet EC:0
2913 and emit_base ~is_object ~notice env mode base_offset param_num_opt e =
2914 let result = emit_base_worker ~is_object ~notice ~inout_param_info:None
2915 env mode base_offset param_num_opt e in
2916 match result with
2917 | Array_get_base_regular i ->
2918 i.instrs_begin,
2919 i.instrs_end,
2920 i.setup_instrs,
2921 i.stack_size
2922 | Array_get_base_inout _ -> failwith "unexpected inout"
2924 and emit_base_worker ~is_object ~notice ~inout_param_info env mode base_offset
2925 param_num_opt (pos, expr_ as expr) =
2926 let base_mode =
2927 if mode = MemberOpMode.InOut then MemberOpMode.Warn else mode in
2928 let local_temp_kind =
2929 get_local_temp_kind inout_param_info env (Some expr) in
2930 (* generic handler that will try to save local into temp if this is necessary *)
2931 let emit_default instrs_begin instrs_end setup_instrs stack_size =
2932 match local_temp_kind with
2933 | Some local_temp ->
2934 let local = Local.get_unnamed_local () in
2935 Array_get_base_inout {
2936 load = {
2937 (* run begin part, result will be stored into temp *)
2938 instrs_begin = [instrs_begin, Some (local, local_temp)];
2939 instrs_end;
2940 setup_instrs;
2941 stack_size };
2942 store = instr_basel local MemberOpMode.Define
2944 | _ ->
2945 Array_get_base_regular {
2946 instrs_begin; instrs_end; setup_instrs; stack_size }
2948 match expr_ with
2949 | A.Lvar (name_pos, x) when SN.Superglobals.is_superglobal x ->
2950 emit_default
2951 (emit_pos_then name_pos @@ instr_string (SU.Locals.strip_dollar x))
2952 empty
2953 (instr (IBase (
2954 match param_num_opt with
2955 | None -> BaseGC (base_offset, base_mode)
2956 | Some i -> FPassBaseGC (i, base_offset)
2960 | A.Lvar (thispos, x) when is_object && x = SN.SpecialIdents.this ->
2961 emit_default
2962 (emit_pos_then thispos @@ instr (IMisc CheckThis))
2963 empty
2964 (instr (IBase BaseH))
2967 | A.Lvar ((_, str) as id)
2968 when not (is_local_this env str) || Emit_env.get_needs_local_this env ->
2969 let v = get_local env id in
2970 if Option.is_some local_temp_kind
2971 then begin
2972 emit_default
2973 (instr_cgetquietl v)
2974 empty
2975 (instr_basel v base_mode)
2978 else begin
2979 emit_default
2980 empty
2981 empty
2982 (instr (IBase (
2983 match param_num_opt with
2984 | None -> BaseL (v, base_mode)
2985 | Some i -> FPassBaseL (i, v)
2990 | A.Lvar id ->
2991 emit_default
2992 (emit_local ~notice ~need_ref:false env id)
2993 empty
2994 (instr (IBase (BaseC base_offset)))
2997 | A.Array_get((_, A.Lvar (_, x)), Some (_, A.Lvar y))
2998 when x = SN.Superglobals.globals ->
2999 let v = get_local env y in
3000 emit_default
3001 empty
3002 empty
3003 (instr (IBase (
3004 match param_num_opt with
3005 | None -> BaseGL (v, base_mode)
3006 | Some i -> FPassBaseGL (i, v)
3010 | A.Array_get((_, A.Lvar (_, x)), Some e) when x = SN.Superglobals.globals ->
3011 let elem_expr_instrs = emit_expr ~need_ref:false env e in
3012 emit_default
3013 elem_expr_instrs
3014 empty
3015 (instr (IBase (
3016 match param_num_opt with
3017 | None -> BaseGC (base_offset, base_mode)
3018 | Some i -> FPassBaseGC (i, base_offset)
3021 (* $a[] can not be used as the base of an array get unless as an lval *)
3022 | 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) ->
3028 let local_temp_kind =
3029 get_local_temp_kind inout_param_info env opt_elem_expr in
3030 let elem_expr_instrs, elem_stack_size =
3031 emit_elem_instrs ~local_temp_kind env opt_elem_expr in
3032 let base_result =
3033 emit_base_worker
3034 ~notice ~is_object:false ~inout_param_info
3035 env mode (base_offset + elem_stack_size) param_num_opt base_expr
3037 let mk = get_elem_member_key env base_offset opt_elem_expr in
3038 let make_setup_instrs base_setup_instrs =
3039 gather [
3040 base_setup_instrs;
3041 instr (IBase (
3042 match param_num_opt with
3043 | None -> Dim (mode, mk)
3044 | Some i -> FPassDim (i, mk)
3046 ] in
3047 begin match base_result, local_temp_kind with
3048 (* both base and index don't use temps - fallback to default handler *)
3049 | Array_get_base_regular base, None ->
3050 emit_default
3051 (gather [
3052 base.instrs_begin;
3053 elem_expr_instrs;
3055 base.instrs_end
3056 (make_setup_instrs base.setup_instrs)
3057 (base.stack_size + elem_stack_size)
3058 | Array_get_base_regular base, Some local_temp ->
3059 (* base does not need temps but index does *)
3060 let local = Local.get_unnamed_local () in
3061 let instrs_begin = gather [
3062 base.instrs_begin;
3063 elem_expr_instrs;
3064 ] in
3065 Array_get_base_inout {
3066 load = {
3067 (* store result of instr_begin to temp *)
3068 instrs_begin = [instrs_begin, Some (local, local_temp)];
3069 instrs_end = base.instrs_end;
3070 setup_instrs = make_setup_instrs base.setup_instrs;
3071 stack_size = base.stack_size + elem_stack_size };
3072 store = emit_store_for_simple_base ~is_base:true env pos elem_stack_size
3073 param_num_opt base_expr local
3075 | Array_get_base_inout base, None ->
3076 (* base needs temps, index - does not *)
3077 Array_get_base_inout {
3078 load = {
3079 (* concat index evaluation to base *)
3080 instrs_begin = base.load.instrs_begin @ [elem_expr_instrs, None];
3081 instrs_end = base.load.instrs_end;
3082 setup_instrs = make_setup_instrs base.load.setup_instrs;
3083 stack_size = base.load.stack_size + elem_stack_size };
3084 store = gather [
3085 base.store;
3086 instr_dim MemberOpMode.Define mk;
3089 | Array_get_base_inout base, Some local_kind ->
3090 (* both base and index needs locals *)
3091 let local = Local.get_unnamed_local () in
3092 Array_get_base_inout {
3093 load = {
3094 instrs_begin =
3095 base.load.instrs_begin @ [
3096 (* evaluate index, result will be stored in local *)
3097 elem_expr_instrs, Some (local, local_kind)
3099 instrs_end = base.load.instrs_end;
3100 setup_instrs = make_setup_instrs base.load.setup_instrs;
3101 stack_size = base.load.stack_size + elem_stack_size };
3102 store = gather [
3103 base.store;
3104 instr_dim MemberOpMode.Define (MemberKey.EL local);
3109 | A.Obj_get(base_expr, prop_expr, null_flavor) ->
3110 begin match snd prop_expr with
3111 | A.Id (_, s) when SU.Xhp.is_xhp s ->
3112 emit_default
3113 (emit_xhp_obj_get_raw env pos base_expr s null_flavor)
3114 empty
3115 (gather [ instr_baser base_offset ])
3117 | _ ->
3118 let mk, prop_expr_instrs, prop_stack_size =
3119 emit_prop_expr env null_flavor base_offset prop_expr in
3120 let base_expr_instrs_begin,
3121 base_expr_instrs_end,
3122 base_setup_instrs,
3123 base_stack_size =
3124 emit_base ~notice:Notice ~is_object:true
3125 env mode (base_offset + prop_stack_size) param_num_opt base_expr
3127 let total_stack_size = prop_stack_size + base_stack_size in
3128 let final_instr =
3129 instr (IBase (
3130 match param_num_opt with
3131 | None -> Dim (mode, mk)
3132 | Some i -> FPassDim (i, mk)
3133 )) in
3134 emit_default
3135 (gather [
3136 base_expr_instrs_begin;
3137 prop_expr_instrs;
3139 base_expr_instrs_end
3140 (gather [
3141 base_setup_instrs;
3142 final_instr
3144 total_stack_size
3147 | A.Class_get(cid, (_, A.Dollar (_, A.Lvar id))) ->
3148 let cexpr = expr_to_class_expr ~resolve_self:false
3149 (Emit_env.get_scope env) cid in
3150 (* special case for $x->$$y: use BaseSL *)
3151 emit_default
3152 (emit_load_class_ref env pos cexpr)
3153 empty
3154 (emit_pos_then pos @@
3155 instr_basesl (get_local env id))
3157 | A.Class_get(cid, prop) ->
3158 let cexpr = expr_to_class_expr ~resolve_self:false
3159 (Emit_env.get_scope env) cid in
3160 let cexpr_begin, cexpr_end = emit_class_expr env cexpr prop in
3161 emit_default
3162 cexpr_begin
3163 cexpr_end
3164 (instr_basesc base_offset)
3166 | A.Dollar (_, A.Lvar id as e) ->
3167 check_non_pipe_local e;
3168 let local = get_local env id in
3169 emit_default
3170 empty
3171 empty
3172 (emit_pos_then pos @@
3173 match param_num_opt with
3174 | None -> instr_basenl local base_mode
3175 | Some i -> instr (IBase (FPassBaseNL (i, local))
3178 | A.Dollar e ->
3179 let base_expr_instrs = emit_expr ~need_ref:false env e in
3180 emit_default
3181 base_expr_instrs
3182 empty
3183 (emit_pos_then pos @@
3184 instr_basenc base_offset base_mode)
3186 | _ ->
3187 let base_expr_instrs, flavor = emit_flavored_expr env expr in
3188 emit_default
3189 (if binary_assignment_rhs_starts_with_ref expr
3190 then gather [base_expr_instrs; instr_unbox]
3191 else base_expr_instrs)
3192 empty
3193 (emit_pos_then pos @@
3194 instr (IBase (if flavor = Flavor.ReturnVal
3195 then BaseR base_offset else BaseC base_offset)))
3198 and get_pass_by_ref_hint expr =
3199 if Emit_env.is_systemlib () || not (Emit_env.is_hh_syntax_enabled ())
3200 then Any else (if expr_starts_with_ref expr then Ref else Cell)
3202 and strip_ref e =
3203 match snd e with
3204 | A.Unop (A.Uref, e) -> e
3205 | _ -> e
3207 and emit_ignored_expr env ?(pop_pos = Pos.none) e =
3208 match snd e with
3209 | A.Expr_list es -> gather @@ List.map ~f:(emit_ignored_expr env ~pop_pos) es
3210 | _ ->
3211 let instrs, flavor = emit_flavored_expr ~last_pos:pop_pos env e in
3212 gather [
3213 instrs;
3214 emit_pos_then pop_pos @@ instr_pop flavor;
3217 (* Emit code to construct the argument frame and then make the call *)
3218 and emit_args_and_call env call_pos args uargs =
3219 let args_count = List.length args in
3220 let all_args = args @ uargs in
3221 let aliases =
3222 if has_inout_args args
3223 then InoutLocals.collect_written_variables env args
3224 else SMap.empty in
3226 (* generic emit function *)
3227 let default_emit i expr hint =
3228 let instrs, flavor = emit_flavored_expr env ~last_pos:call_pos expr in
3229 let is_splatted = i >= args_count in
3230 let instrs =
3231 if is_splatted && flavor = Flavor.ReturnVal
3232 then gather [ instrs; instr_unboxr ] else instrs
3234 let fpass_kind =
3235 match is_splatted, flavor with
3236 | false, Flavor.Ref -> instr_fpassv i hint
3237 | false, Flavor.ReturnVal -> instr_fpassr i hint
3238 | false, Flavor.Cell
3239 | true, _ -> instr_fpass (get_passByRefKind is_splatted expr) i hint
3241 gather [
3242 instrs;
3243 emit_pos call_pos;
3244 fpass_kind; ]
3246 let rec aux i args inout_setters =
3247 match args with
3248 | [] ->
3249 let msrv =
3250 Hhbc_options.use_msrv_for_inout !Hhbc_options.compiler_options in
3251 let use_unpack = (uargs != []) in
3252 let num_inout = List.length inout_setters in
3253 let use_callm = msrv && (num_inout > 0) in
3254 let nargs = List.length all_args in
3255 let instr_call = match (use_callm, use_unpack) with
3256 | (false, false) -> instr (ICall (FCall nargs))
3257 | (false, true) -> instr (ICall (FCallUnpack nargs))
3258 | (true, false) -> instr (ICall (FCallM (nargs, num_inout + 1)))
3259 | (true, true) -> instr (ICall (FCallUnpackM (nargs, num_inout + 1))) in
3260 gather [
3261 (* emit call*)
3262 instr_call;
3263 (* propagate inout values back *)
3264 if List.is_empty inout_setters
3265 then empty
3266 else begin
3267 let local = Local.get_unnamed_local () in
3268 gather [
3269 if msrv then empty else instr_unboxr;
3270 Emit_inout_helpers.emit_list_set_for_inout_call local
3271 (List.rev inout_setters)
3273 end; ]
3275 | expr :: rest ->
3276 let next c = gather [ c; aux (i + 1) rest inout_setters ] in
3277 let is_inout, expr =
3278 match snd expr with
3279 | A.Callconv (A.Pinout, e) -> true, e
3280 | _ -> false, expr
3282 let hint = get_pass_by_ref_hint expr in
3283 let pos, expr_ = strip_ref expr in
3284 if i >= args_count then
3285 next @@ default_emit i expr hint
3286 else
3287 match expr_ with
3288 | A.Lvar (name_pos, x) when SN.Superglobals.is_superglobal x ->
3289 next @@ gather [
3290 emit_pos name_pos;
3291 instr_string (SU.Locals.strip_dollar x);
3292 instr_fpassg i hint;
3294 | A.Lvar ((_, s) as id) when is_inout ->
3295 let inout_setters =
3296 (instr_setl @@ Local.Named s) :: inout_setters in
3297 let not_in_try = not (Emit_env.is_in_try env) in
3298 let move_instrs =
3299 if not_in_try && (InoutLocals.should_move_local_value s aliases)
3300 then gather [ instr_null; instr_popl (get_local env id) ]
3301 else empty in
3302 gather [
3303 emit_expr ~need_ref:false env expr;
3304 move_instrs;
3305 emit_pos call_pos;
3306 instr_fpassc i hint;
3307 aux (i + 1) rest inout_setters
3309 | A.Lvar ((_, str) as id)
3310 when not (is_local_this env str) || Emit_env.get_needs_local_this env ->
3311 next @@ instr_fpassl i (get_local env id) hint
3312 | A.BracedExpr e ->
3313 next @@ emit_expr ~need_ref:false env e
3314 | A.Dollar e ->
3315 check_non_pipe_local e;
3316 next @@ gather [
3317 emit_expr ~need_ref:false env e;
3318 instr_fpassn i hint;
3320 | A.Array_get ((_, A.Lvar (_, x)), Some e) when x = SN.Superglobals.globals ->
3321 next @@ gather [
3322 emit_expr ~need_ref:false env e;
3323 instr_fpassg i hint;
3326 | A.Array_get (base_expr, opt_elem_expr) ->
3327 if is_inout
3328 then begin
3329 let array_get_result =
3330 emit_array_get_worker ~need_ref:false
3331 ~inout_param_info:(Some (i, aliases)) env pos (Some (i, hint))
3332 QueryOp.InOut base_expr opt_elem_expr in
3333 match array_get_result with
3334 | Array_get_regular instrs ->
3335 let setter =
3336 let base =
3337 emit_array_get ~no_final:true ~need_ref:false
3338 ~mode:MemberOpMode.Define
3339 env pos None QueryOp.InOut base_expr opt_elem_expr in
3340 gather [
3341 base;
3342 instr_setm 0 (get_elem_member_key env 0 opt_elem_expr);
3343 ] in
3344 gather [
3345 instrs;
3346 instr_fpassc i hint;
3347 aux (i + 1) rest (setter :: inout_setters) ]
3348 | Array_get_inout { load; store } ->
3349 rebuild_sequence load @@ begin fun () ->
3350 gather [
3351 instr_fpassc i hint;
3352 aux (i + 1) rest (store :: inout_setters)
3356 else
3357 next @@ emit_array_get
3358 ~need_ref:false
3359 { env with Emit_env.env_allows_array_append = true }
3360 pos (Some (i, hint))
3361 QueryOp.CGet base_expr opt_elem_expr
3363 | A.Obj_get (e1, e2, nullflavor) ->
3364 next @@ emit_obj_get ~need_ref:false env pos (Some (i, hint)) QueryOp.CGet e1 e2 nullflavor
3366 | A.Class_get (cid, e) ->
3367 next @@ emit_class_get env (Some (i, hint)) QueryOp.CGet false cid e
3369 | A.Binop (A.Eq None, (_, A.List _ as e), (_, A.Lvar id)) ->
3370 let local = get_local env id in
3371 let lhs_instrs, set_instrs =
3372 emit_lval_op_list env pos (Some local) [] e in
3373 next @@ gather [
3374 lhs_instrs;
3375 set_instrs;
3376 instr_fpassl i local hint;
3378 | _ when expr_starts_with_ref expr ->
3379 (* pass expression with a stripped reference but
3380 use hint from the original expression *)
3381 next @@ default_emit i (pos, expr_) hint
3382 | _ ->
3383 next @@ default_emit i expr hint
3385 Local.scope @@ fun () -> aux 0 all_args []
3387 (* Expression that appears in an object context, such as expr->meth(...) *)
3388 and emit_object_expr env ?last_pos (_, expr_ as expr) =
3389 match expr_ with
3390 | A.Lvar(_, x) when is_local_this env x ->
3391 instr_this
3392 | _ -> emit_expr ?last_pos ~need_ref:false env expr
3394 and emit_call_lhs_with_this env instrs = Local.scope @@ fun () ->
3395 let id = Pos.none, SN.SpecialIdents.this in
3396 let temp = Local.get_unnamed_local () in
3397 gather [
3398 emit_local ~notice:Notice ~need_ref:false env id;
3399 instr_setl temp;
3400 with_temp_local temp
3401 begin fun temp _ -> gather [
3402 instr_popc;
3403 instrs;
3404 instr (IGet (ClsRefGetL (temp, 0)));
3405 instr_unsetl temp;
3410 and has_inout_args es =
3411 List.exists es ~f:(function _, A.Callconv (A.Pinout, _) -> true | _ -> false)
3413 and emit_call_lhs env outer_pos (pos, expr_ as expr) nargs has_splat inout_arg_positions =
3414 let has_inout_args = List.length inout_arg_positions <> 0 in
3415 match expr_ with
3416 | A.Obj_get (obj, (_, A.Id ((_, str) as id)), null_flavor)
3417 when str.[0] = '$' ->
3418 gather [
3419 emit_object_expr ~last_pos:outer_pos env obj;
3420 instr_cgetl (get_local env id);
3421 instr_fpushobjmethod nargs null_flavor inout_arg_positions;
3423 | A.Obj_get (obj, (_, A.String (_, id)), null_flavor)
3424 | A.Obj_get (obj, (_, A.Id (_, id)), null_flavor) ->
3425 let name = Hhbc_id.Method.from_ast_name id in
3426 let name =
3427 if has_inout_args
3428 then Hhbc_id.Method.add_suffix name
3429 (Emit_inout_helpers.inout_suffix inout_arg_positions)
3430 else name in
3431 gather [
3432 emit_object_expr env ~last_pos:outer_pos obj;
3433 emit_pos outer_pos;
3434 instr_fpushobjmethodd nargs name null_flavor;
3436 | A.Obj_get(obj, method_expr, null_flavor) ->
3437 gather [
3438 emit_pos outer_pos;
3439 emit_object_expr env ~last_pos:outer_pos obj;
3440 emit_expr ~need_ref:false env method_expr;
3441 instr_fpushobjmethod nargs null_flavor inout_arg_positions;
3444 | A.Class_const (cid, (_, id)) ->
3445 let cexpr = expr_to_class_expr ~resolve_self:false
3446 (Emit_env.get_scope env) cid in
3447 let method_id = Hhbc_id.Method.from_ast_name id in
3448 let method_id =
3449 if has_inout_args
3450 then Hhbc_id.Method.add_suffix method_id
3451 (Emit_inout_helpers.inout_suffix inout_arg_positions)
3452 else method_id in
3453 begin match cexpr with
3454 (* Statically known *)
3455 | Class_id cid ->
3456 let fq_cid, _ = Hhbc_id.Class.elaborate_id (Emit_env.get_namespace env) cid in
3457 instr_fpushclsmethodd nargs method_id fq_cid
3458 | Class_static ->
3459 instr_fpushclsmethodsd nargs SpecialClsRef.Static method_id
3460 | Class_self ->
3461 instr_fpushclsmethodsd nargs SpecialClsRef.Self method_id
3462 | Class_parent ->
3463 instr_fpushclsmethodsd nargs SpecialClsRef.Parent method_id
3464 | Class_expr (_, A.Lvar (_, x)) when x = SN.SpecialIdents.this ->
3465 let method_name = Hhbc_id.Method.to_raw_string method_id in
3466 gather [
3467 emit_call_lhs_with_this env @@ instr_string method_name;
3468 instr_fpushclsmethod nargs []
3470 | _ ->
3471 let method_name = Hhbc_id.Method.to_raw_string method_id in
3472 gather [
3473 of_pair @@ emit_class_expr env cexpr (Pos.none, A.Id (Pos.none, method_name));
3474 instr_fpushclsmethod nargs []
3478 | A.Class_get (cid, e) ->
3479 let cexpr = expr_to_class_expr ~resolve_self:false
3480 (Emit_env.get_scope env) cid in
3481 let expr_instrs = emit_expr ~need_ref:false env e in
3482 begin match cexpr with
3483 | Class_static ->
3484 gather [expr_instrs;
3485 emit_pos outer_pos; instr_fpushclsmethods nargs SpecialClsRef.Static]
3486 | Class_self ->
3487 gather [expr_instrs;
3488 emit_pos outer_pos; instr_fpushclsmethods nargs SpecialClsRef.Self]
3489 | Class_parent ->
3490 gather [expr_instrs;
3491 emit_pos outer_pos; instr_fpushclsmethods nargs SpecialClsRef.Parent]
3492 | Class_expr (_, A.Lvar (_, x)) when x = SN.SpecialIdents.this ->
3493 gather [
3494 emit_call_lhs_with_this env expr_instrs;
3495 emit_pos outer_pos;
3496 instr_fpushclsmethod nargs inout_arg_positions
3498 | _ ->
3499 gather [
3500 expr_instrs;
3501 emit_load_class_ref env pos cexpr;
3502 emit_pos outer_pos;
3503 instr_fpushclsmethod nargs inout_arg_positions
3507 | A.Id (_, s as id)->
3508 let fq_id, id_opt =
3509 Hhbc_id.Function.elaborate_id_with_builtins (Emit_env.get_namespace env) id in
3510 let fq_id, id_opt =
3511 match id_opt, SU.strip_global_ns s with
3512 | None, "min" when nargs = 2 && not has_splat ->
3513 Hhbc_id.Function.from_raw_string "__SystemLib\\min2", None
3514 | None, "max" when nargs = 2 && not has_splat ->
3515 Hhbc_id.Function.from_raw_string "__SystemLib\\max2", None
3516 | _ -> fq_id, id_opt in
3517 let fq_id = if has_inout_args
3518 then Hhbc_id.Function.add_suffix
3519 fq_id (Emit_inout_helpers.inout_suffix inout_arg_positions)
3520 else fq_id in
3521 emit_pos_then outer_pos @@
3522 begin match id_opt with
3523 | Some id -> instr (ICall (FPushFuncU (nargs, fq_id, id)))
3524 | None -> instr (ICall (FPushFuncD (nargs, fq_id)))
3526 | A.String (_, s) ->
3527 emit_pos_then outer_pos @@
3528 instr_fpushfuncd nargs (Hhbc_id.Function.from_raw_string s)
3529 | _ ->
3530 gather [
3531 emit_expr ~need_ref:false env expr;
3532 emit_pos outer_pos;
3533 instr_fpushfunc nargs inout_arg_positions
3536 (* Retuns whether the function is a call_user_func function,
3537 min args, max args *)
3538 and get_call_user_func_info = function
3539 | "call_user_func" -> (true, 1, max_int)
3540 | "call_user_func_array" -> (true, 2, 2)
3541 | "forward_static_call" -> (true, 1, max_int)
3542 | "forward_static_call_array" -> (true, 2, 2)
3543 | "fb_call_user_func_safe" -> (true, 1, max_int)
3544 | "fb_call_user_func_array_safe" -> (true, 2, 2)
3545 | "fb_call_user_func_safe_return" -> (true, 2, max_int)
3546 | _ -> (false, 0, 0)
3548 and is_call_user_func id num_args =
3549 let (is_fn, min_args, max_args) = get_call_user_func_info id in
3550 is_fn && num_args >= min_args && num_args <= max_args
3552 and get_call_builtin_func_info lower_fq_id =
3553 match lower_fq_id with
3554 | "array_key_exists" -> Some (2, IMisc AKExists)
3555 | "hphp_array_idx" -> Some (3, IMisc ArrayIdx)
3556 | "intval" -> Some (1, IOp CastInt)
3557 | "boolval" -> Some (1, IOp CastBool)
3558 | "strval" -> Some (1, IOp CastString)
3559 | "floatval" | "doubleval" -> Some (1, IOp CastDouble)
3560 | "hh\\vec" -> Some (1, IOp CastVec)
3561 | "hh\\keyset" -> Some (1, IOp CastKeyset)
3562 | "hh\\dict" -> Some (1, IOp CastDict)
3563 | "hh\\varray" -> Some (1, IOp (if hack_arr_dv_arrs () then CastVec else CastVArray))
3564 | "hh\\darray" -> Some (1, IOp (if hack_arr_dv_arrs () then CastDict else CastDArray))
3565 | _ -> None
3567 and emit_call_user_func_arg env f i expr =
3568 let hint = get_pass_by_ref_hint expr in
3569 let hint, warning, expr =
3570 if hint = Ref
3571 then
3572 (* for warning - adjust the argument id *)
3573 let param_id = Param_unnamed (i + 1) in
3574 (* emitter.cpp:
3575 The passthrough type of call_user_func is always cell or any, so
3576 any call to a function taking a ref will result in a warning *)
3577 Cell, instr_raise_fpass_warning hint f param_id, strip_ref expr
3578 else hint, empty, expr in
3579 gather [
3580 emit_expr ~need_ref:false env expr;
3581 warning;
3582 instr_fpass PassByRefKind.AllowCell i hint;
3585 and emit_call_user_func env id arg args =
3586 let return_default, args = match id with
3587 | "fb_call_user_func_safe_return" ->
3588 begin match args with
3589 | [] -> failwith "fb_call_user_func_safe_return - requires default arg"
3590 | a :: args -> emit_expr ~need_ref:false env a, args
3592 | _ -> empty, args
3594 let num_params = List.length args in
3595 let begin_instr = match id with
3596 | "forward_static_call"
3597 | "forward_static_call_array" -> instr_fpushcuff num_params
3598 | "fb_call_user_func_safe"
3599 | "fb_call_user_func_array_safe" ->
3600 gather [instr_null; instr_fpushcuf_safe num_params]
3601 | "fb_call_user_func_safe_return" ->
3602 gather [return_default; instr_fpushcuf_safe num_params]
3603 | _ -> instr_fpushcuf num_params
3605 let call_instr = match id with
3606 | "call_user_func_array"
3607 | "forward_static_call_array"
3608 | "fb_call_user_func_array_safe" -> instr (ICall FCallArray)
3609 | _ -> instr (ICall (FCall num_params))
3611 let end_instr = match id with
3612 | "fb_call_user_func_safe_return" -> instr (ICall CufSafeReturn)
3613 | "fb_call_user_func_safe"
3614 | "fb_call_user_func_array_safe" -> instr (ICall CufSafeArray)
3615 | _ -> empty
3617 let flavor = match id with
3618 | "fb_call_user_func_safe"
3619 | "fb_call_user_func_array_safe" -> Flavor.Cell
3620 | _ -> Flavor.ReturnVal
3622 gather [
3623 (* first arg is always emitted as cell *)
3624 emit_expr ~need_ref:false env (strip_ref arg);
3625 begin_instr;
3626 gather (List.mapi args (emit_call_user_func_arg env id));
3627 call_instr;
3628 end_instr;
3629 ], flavor
3631 (* TODO: work out what HHVM does special here *)
3632 and emit_name_string env e =
3633 emit_expr ~need_ref:false env e
3635 and emit_special_function env pos id args uargs default =
3636 let nargs = List.length args + List.length uargs in
3637 let fq_id, _ =
3638 Hhbc_id.Function.elaborate_id_with_builtins (Emit_env.get_namespace env) (Pos.none, id) in
3639 (* Make sure that we do not treat a special function that is aliased as not
3640 * aliased *)
3641 let lower_fq_name =
3642 String.lowercase_ascii (Hhbc_id.Function.to_raw_string fq_id) in
3643 let hh_enabled = Emit_env.is_hh_syntax_enabled () in
3644 match lower_fq_name, args with
3645 | id, _ when id = SN.SpecialFunctions.echo ->
3646 let instrs = gather @@ List.mapi args begin fun i arg ->
3647 gather [
3648 emit_expr ~need_ref:false env arg;
3649 emit_pos pos;
3650 instr (IOp Print);
3651 if i = nargs-1 then empty else instr_popc
3652 ] end in
3653 Some (instrs, Flavor.Cell)
3655 | "array_slice", [
3656 _, A.Call ((_, A.Id (_, s)), _, [], []); (_, A.Int _ as count)
3657 ] when not (jit_enable_rename_function ())
3658 && String.lowercase_ascii @@ SU.strip_ns s = "func_get_args"->
3659 let p = Pos.none in
3660 Some (emit_call env pos (p,
3661 A.Id (p, "\\__SystemLib\\func_slice_args")) [count] [])
3663 | "hh\\asm", [_, A.String (_, s)] ->
3664 Some (emit_inline_hhas s, Flavor.Cell)
3666 | id, _ when
3667 (optimize_cuf ()) && (is_call_user_func id (List.length args)) ->
3668 if List.length uargs != 0 then
3669 failwith "Using argument unpacking for a call_user_func is not supported";
3670 begin match args with
3671 | [] -> failwith "call_user_func - needs a name"
3672 | arg :: args ->
3673 Some (emit_call_user_func env id arg args)
3676 | "hh\\invariant", e::rest when hh_enabled ->
3677 let l = Label.next_regular () in
3678 let p = Pos.none in
3679 let expr_id = p, A.Id (p, "\\hh\\invariant_violation") in
3680 Some (gather [
3681 (* Could use emit_jmpnz for better code *)
3682 emit_expr ~need_ref:false env e;
3683 instr_jmpnz l;
3684 emit_ignored_expr env (p, A.Call (expr_id, [], rest, uargs));
3685 Emit_fatal.emit_fatal_runtime p "invariant_violation";
3686 instr_label l;
3687 instr_null;
3688 ], Flavor.Cell)
3690 | "assert", _ ->
3691 let l0 = Label.next_regular () in
3692 let l1 = Label.next_regular () in
3693 Some (gather [
3694 instr_string "zend.assertions";
3695 instr_fcallbuiltin 1 1 "ini_get";
3696 instr_unboxr_nop;
3697 instr_int 0;
3698 instr_gt;
3699 instr_jmpz l0;
3700 fst @@ default ();
3701 instr_unboxr;
3702 instr_jmp l1;
3703 instr_label l0;
3704 instr_true;
3705 instr_label l1;
3706 ], Flavor.Cell)
3708 | ("class_exists" | "interface_exists" | "trait_exists" as id), arg1::_
3709 when nargs = 1 || nargs = 2 ->
3710 let class_kind =
3711 match id with
3712 | "class_exists" -> KClass
3713 | "interface_exists" -> KInterface
3714 | "trait_exists" -> KTrait
3715 | _ -> failwith "class_kind" in
3716 Some (gather [
3717 emit_name_string env arg1;
3718 instr (IOp CastString);
3719 if nargs = 1 then instr_true
3720 else gather [
3721 emit_expr ~need_ref:false env (List.nth_exn args 1);
3722 instr (IOp CastBool)
3724 instr (IMisc (OODeclExists class_kind))
3725 ], Flavor.Cell)
3727 | ("exit" | "die"), _ when nargs = 0 || nargs = 1 ->
3728 Some (emit_exit env (List.hd args), Flavor.Cell)
3730 | _ ->
3731 begin match args, istype_op lower_fq_name with
3732 | [(_, A.Lvar (_, arg_str as arg_id))], Some i
3733 when not (is_local_this env arg_str) ->
3734 Some (instr (IIsset (IsTypeL (get_local env arg_id, i))), Flavor.Cell)
3735 | [arg_expr], Some i ->
3736 Some (gather [
3737 emit_expr ~need_ref:false env arg_expr;
3738 emit_pos pos;
3739 instr (IIsset (IsTypeC i))
3740 ], Flavor.Cell)
3741 | _ ->
3742 begin match get_call_builtin_func_info lower_fq_name with
3743 | Some (nargs, i) when nargs = List.length args ->
3744 Some (
3745 gather [
3746 emit_exprs env pos args;
3747 emit_pos pos;
3748 instr i
3749 ], Flavor.Cell)
3750 | _ -> None
3754 and get_inout_arg_positions args =
3755 List.filter_mapi args
3756 ~f:(fun i -> function
3757 | _, A.Callconv (A.Pinout, _) -> Some i
3758 | _ -> None)
3760 and emit_call env pos (_, expr_ as expr) args uargs =
3761 (match expr_ with
3762 | A.Id (_, s) -> Emit_symbol_refs.add_function s
3763 | _ -> ());
3764 let nargs = List.length args + List.length uargs in
3765 let inout_arg_positions = get_inout_arg_positions args in
3766 let msrv = Hhbc_options.use_msrv_for_inout !Hhbc_options.compiler_options in
3767 let num_uninit = if msrv then List.length inout_arg_positions else 0 in
3768 let default () =
3769 let flavor = if List.is_empty inout_arg_positions then
3770 Flavor.ReturnVal else Flavor.Cell in
3771 gather [
3772 gather @@ List.init num_uninit ~f:(fun _ -> instr_nulluninit);
3773 emit_call_lhs
3774 env pos expr nargs (not (List.is_empty uargs)) inout_arg_positions;
3775 emit_args_and_call env pos args uargs;
3776 ], flavor in
3778 match expr_, args with
3779 | A.Id (_, id), _ ->
3780 let special_fn_opt = emit_special_function env pos id args uargs default in
3781 begin match special_fn_opt with
3782 | Some (instrs, flavor) -> instrs, flavor
3783 | None -> default ()
3785 | _ -> default ()
3788 (* Emit code for an expression that might leave a cell or reference on the
3789 * stack. Return which flavor it left.
3791 and emit_flavored_expr env ?last_pos (pos, expr_ as expr) =
3792 match expr_ with
3793 | A.Call (e, _, args, uargs)
3794 when not (is_special_function env e args) ->
3795 let instrs, flavor = emit_call env pos e args uargs in
3796 emit_pos_then pos instrs, flavor
3797 | A.Execution_operator es ->
3798 emit_execution_operator env pos es, Flavor.ReturnVal
3799 | _ ->
3800 let flavor =
3801 if binary_assignment_rhs_starts_with_ref expr
3802 then Flavor.Ref
3803 else Flavor.Cell
3805 emit_expr ?last_pos env expr, flavor
3807 and emit_final_member_op stack_index op mk =
3808 match op with
3809 | LValOp.Set -> instr (IFinal (SetM (stack_index, mk)))
3810 | LValOp.SetRef -> instr (IFinal (BindM (stack_index, mk)))
3811 | LValOp.SetOp op -> instr (IFinal (SetOpM (stack_index, op, mk)))
3812 | LValOp.IncDec op -> instr (IFinal (IncDecM (stack_index, op, mk)))
3813 | LValOp.Unset -> instr (IFinal (UnsetM (stack_index, mk)))
3815 and emit_final_local_op pos op lid =
3816 emit_pos_then pos @@
3817 match op with
3818 | LValOp.Set -> instr (IMutator (SetL lid))
3819 | LValOp.SetRef -> instr (IMutator (BindL lid))
3820 | LValOp.SetOp op -> instr (IMutator (SetOpL (lid, op)))
3821 | LValOp.IncDec op -> instr (IMutator (IncDecL (lid, op)))
3822 | LValOp.Unset -> instr (IMutator (UnsetL lid))
3824 and emit_final_named_local_op pos op =
3825 match op with
3826 | LValOp.Set -> emit_pos_then pos @@ instr (IMutator SetN)
3827 | LValOp.SetRef -> instr (IMutator BindN)
3828 | LValOp.SetOp op -> instr (IMutator (SetOpN op))
3829 | LValOp.IncDec op -> instr (IMutator (IncDecN op))
3830 | LValOp.Unset -> instr (IMutator UnsetN)
3832 and emit_final_global_op pos op =
3833 match op with
3834 | LValOp.Set -> emit_pos_then pos @@ instr (IMutator SetG)
3835 | LValOp.SetRef -> instr (IMutator BindG)
3836 | LValOp.SetOp op -> instr (IMutator (SetOpG op))
3837 | LValOp.IncDec op -> instr (IMutator (IncDecG op))
3838 | LValOp.Unset -> emit_pos_then pos @@ instr (IMutator UnsetG)
3840 and emit_final_static_op cid prop op =
3841 match op with
3842 | LValOp.Set -> instr (IMutator (SetS 0))
3843 | LValOp.SetRef -> instr (IMutator (BindS 0))
3844 | LValOp.SetOp op -> instr (IMutator (SetOpS (op, 0)))
3845 | LValOp.IncDec op -> instr (IMutator (IncDecS (op, 0)))
3846 | LValOp.Unset ->
3847 let cid = text_of_expr cid in
3848 let id = text_of_expr (snd prop) in
3849 Emit_fatal.emit_fatal_runtime (fst id)
3850 ("Attempt to unset static property " ^ snd cid ^ "::" ^ snd id)
3852 (* Given a local $local and a list of integer array indices i_1, ..., i_n,
3853 * generate code to extract the value of $local[i_n]...[i_1]:
3854 * BaseL $local Warn
3855 * Dim Warn EI:i_n ...
3856 * Dim Warn EI:i_2
3857 * QueryM 0 CGet EI:i_1
3859 and emit_array_get_fixed last_usage local indices =
3860 let base, stack_count =
3861 if last_usage then gather [
3862 instr_pushl local;
3863 instr_basec 0;
3864 ], 1
3865 else instr_basel local MemberOpMode.Warn, 0 in
3866 let indices =
3867 gather @@ List.rev_mapi indices
3868 begin fun i ix ->
3869 let mk = MemberKey.EI (Int64.of_int ix) in
3870 if i = 0
3871 then instr (IFinal (QueryM (stack_count, QueryOp.CGet, mk)))
3872 else instr (IBase (Dim (MemberOpMode.Warn, mk)))
3873 end in
3874 gather [
3875 base;
3876 indices;
3879 and can_use_as_rhs_in_list_assignment expr =
3880 match expr with
3881 | A.Call ((_, A.Id (_, s)), _, _, _) when String.lowercase_ascii s = "echo" ->
3882 false
3883 | A.Lvar _
3884 | A.Dollar _
3885 | A.Array_get _
3886 | A.Obj_get _
3887 | A.Class_get _
3888 | A.Call _
3889 | A.New _
3890 | A.Expr_list _
3891 | A.Yield _
3892 | A.NullCoalesce _
3893 | A.Cast _
3894 | A.Eif _
3895 | A.Array _
3896 | A.Varray _
3897 | A.Darray _
3898 | A.Collection _
3899 | A.Clone _
3900 | A.Unop _
3901 | A.Await _ -> true
3902 | A.Pipe (_, (_, r))
3903 | A.Binop ((A.Eq None), (_, A.List _), (_, r)) ->
3904 can_use_as_rhs_in_list_assignment r
3905 | A.Binop (A.Plus, _, _)
3906 | A.Binop (A.Eq _, _, _) -> true
3907 | _ -> false
3910 (* Generate code for each lvalue assignment in a list destructuring expression.
3911 * Lvalues are assigned right-to-left, regardless of the nesting structure. So
3912 * list($a, list($b, $c)) = $d
3913 * and list(list($a, $b), $c) = $d
3914 * will both assign to $c, $b and $a in that order.
3915 * Returns a pair of instructions:
3916 * 1. initialization part of the left hand side
3917 * 2. assignment
3918 * this is necessary to handle cases like:
3919 * list($a[$f()]) = b();
3920 * here f() should be invoked before b()
3922 and emit_lval_op_list ?(last_usage=false) env outer_pos local indices expr =
3923 let is_ltr = php7_ltr_assign () in
3924 match snd expr with
3925 | A.List exprs ->
3926 let last_non_omitted =
3927 (* last usage of the local will happen when processing last non-omitted
3928 element in the list - find it *)
3929 if last_usage
3930 then begin
3931 if is_ltr
3932 then
3933 exprs
3934 |> Core_list.foldi ~init:None
3935 ~f:(fun i acc (_, v) -> if v = A.Omitted then acc else Some i)
3936 (* in right-to-left case result list will be reversed
3937 so we need to find first non-omitted expression *)
3938 else
3939 exprs
3940 |> Core_list.findi ~f:(fun _ (_, v) -> v <> A.Omitted)
3941 |> Option.map ~f:fst
3943 else None in
3944 let lhs_instrs, set_instrs =
3945 List.mapi exprs (fun i expr ->
3946 emit_lval_op_list ~last_usage:(Some i = last_non_omitted)
3947 env outer_pos local (i::indices) expr)
3948 |> List.unzip in
3949 gather lhs_instrs,
3950 gather (if not is_ltr then List.rev set_instrs else set_instrs)
3951 | A.Omitted -> empty, empty
3952 | _ ->
3953 (* Generate code to access the element from the array *)
3954 let access_instrs =
3955 match local, indices with
3956 | Some local, _ :: _ -> emit_array_get_fixed last_usage local indices
3957 | Some local, [] ->
3958 if last_usage then instr_pushl local
3959 else instr_cgetl local
3960 | None, _ -> instr_null
3962 (* Generate code to assign to the lvalue *)
3963 (* Return pair: side effects to initialize lhs + assignment *)
3964 let lhs_instrs, rhs_instrs, set_op =
3965 emit_lval_op_nonlist_steps env outer_pos LValOp.Set expr access_instrs 1 in
3966 let lhs = if is_ltr then empty else lhs_instrs in
3967 let rest =
3968 gather [
3969 if is_ltr then lhs_instrs else empty;
3970 rhs_instrs;
3971 set_op;
3972 instr_popc;
3975 lhs, rest
3977 and expr_starts_with_ref = function
3978 | _, A.Unop (A.Uref, _) -> true
3979 | _ -> false
3981 and binary_assignment_rhs_starts_with_ref = function
3982 | _, A.Binop (A.Eq None, _, e) when expr_starts_with_ref e -> true
3983 | _ -> false
3985 and emit_expr_and_unbox_if_necessary ?last_pos ~need_ref env pos e =
3986 let need_unboxing =
3987 match e with
3988 | _, A.Expr_list es ->
3989 Core_list.last es
3990 |> Option.value_map ~default:false ~f:binary_assignment_rhs_starts_with_ref
3991 | e ->
3992 binary_assignment_rhs_starts_with_ref e in
3993 gather [
3994 emit_expr ?last_pos ~need_ref env e;
3995 if need_unboxing then emit_pos_then pos @@ instr_unbox else empty;
3998 (* Emit code for an l-value operation *)
3999 and emit_lval_op env pos op expr1 opt_expr2 =
4000 let op =
4001 match op, opt_expr2 with
4002 | LValOp.Set, Some e when expr_starts_with_ref e -> LValOp.SetRef
4003 | _ -> op
4005 match op, expr1, opt_expr2 with
4006 (* Special case for list destructuring, only on assignment *)
4007 | LValOp.Set, (_, A.List l), Some expr2 ->
4008 let has_elements =
4009 List.exists l ~f: (function
4010 | _, A.Omitted -> false
4011 | _ -> true)
4013 if has_elements then
4014 stash_in_local_with_prefix
4015 ~always_stash:(php7_ltr_assign ()) ~leave_on_stack:true env pos expr2
4016 begin fun local _break_label ->
4017 let local =
4018 if can_use_as_rhs_in_list_assignment (snd expr2) then
4019 Some local
4020 else
4021 None
4023 emit_lval_op_list env pos local [] expr1
4025 else
4026 Local.scope @@ fun () ->
4027 let local = Local.get_unnamed_local () in
4028 gather [
4029 emit_expr ~need_ref:false env expr2;
4030 instr_setl local;
4031 instr_popc;
4032 instr_pushl local;
4034 | _ ->
4035 Local.scope @@ fun () ->
4036 let rhs_instrs, rhs_stack_size =
4037 match opt_expr2 with
4038 | None -> empty, 0
4039 | Some (_, A.Yield af) ->
4040 let temp = Local.get_unnamed_local () in
4041 gather [
4042 emit_yield env pos af;
4043 instr_setl temp;
4044 instr_popc;
4045 instr_pushl temp;
4046 ], 1
4047 | Some (pos, A.Unop (A.Uref, (_, A.Obj_get (_, _, A.OG_nullsafe)
4048 | _, A.Array_get ((_,
4049 A.Obj_get (_, _, A.OG_nullsafe)), _)))) ->
4050 Emit_fatal.raise_fatal_runtime
4051 pos "?-> is not allowed in write context"
4052 | Some e -> emit_expr_and_unbox_if_necessary ~need_ref:false env pos e, 1
4054 emit_lval_op_nonlist env pos op expr1 rhs_instrs rhs_stack_size
4056 and emit_lval_op_nonlist env pos op e rhs_instrs rhs_stack_size =
4057 let (lhs, rhs, setop) =
4058 emit_lval_op_nonlist_steps env pos op e rhs_instrs rhs_stack_size
4060 gather [
4061 lhs;
4062 rhs;
4063 setop;
4066 and emit_lval_op_nonlist_steps env outer_pos op (pos, expr_) rhs_instrs rhs_stack_size =
4067 let env =
4068 match op with
4069 (* Unbelieveably, $test[] += 5; is legal in PHP, but $test[] = $test[] + 5 is not *)
4070 | LValOp.SetRef
4071 | LValOp.Set
4072 | LValOp.SetOp _
4073 | LValOp.IncDec _ -> { env with Emit_env.env_allows_array_append = true }
4074 | _ -> env in
4075 let handle_dollar e final_op =
4076 match e with
4077 _, A.Lvar id ->
4078 let instruction =
4079 let local = (get_local env id) in
4080 match op with
4081 | LValOp.Unset | LValOp.IncDec _ -> instr_cgetl local
4082 | _ -> instr_cgetl2 local
4084 empty,
4085 rhs_instrs,
4086 gather [
4087 emit_pos outer_pos;
4088 instruction;
4089 final_op op
4091 | _ ->
4093 let instrs = emit_expr ~need_ref:false env e in
4094 instrs,
4095 rhs_instrs,
4096 final_op op
4098 match expr_ with
4099 | A.Lvar (name_pos, id) when SN.Superglobals.is_superglobal id ->
4100 emit_pos_then name_pos @@ instr_string @@ SU.Locals.strip_dollar id,
4101 rhs_instrs,
4102 emit_final_global_op outer_pos op
4104 | A.Lvar ((_, str) as id) when is_local_this env str && is_incdec op ->
4105 emit_local ~notice:Notice ~need_ref:false env id,
4106 rhs_instrs,
4107 empty
4109 | A.Lvar id when not (is_local_this env (snd id)) || op = LValOp.Unset ->
4110 empty,
4111 rhs_instrs,
4112 emit_final_local_op outer_pos op (get_local env id)
4114 | A.Dollar e ->
4115 handle_dollar e (emit_final_named_local_op pos)
4117 | A.Array_get ((_, A.Lvar (_, x)), Some e) when x = SN.Superglobals.globals ->
4118 let final_global_op_instrs = emit_final_global_op pos op in
4119 if rhs_stack_size = 0
4120 then
4121 emit_expr ~need_ref:false env e,
4122 empty,
4123 final_global_op_instrs
4124 else
4125 let index_instrs, under_top = emit_first_expr env e in
4126 if under_top
4127 then
4128 empty,
4129 gather [
4130 rhs_instrs;
4131 index_instrs
4133 final_global_op_instrs
4134 else
4135 index_instrs,
4136 rhs_instrs,
4137 final_global_op_instrs
4138 | A.Array_get (_, None) when not (Emit_env.does_env_allow_array_append env) ->
4139 Emit_fatal.raise_fatal_runtime pos "Can't use [] for reading"
4140 | A.Array_get (base_expr, opt_elem_expr) ->
4141 let mode =
4142 match op with
4143 | LValOp.Unset -> MemberOpMode.Unset
4144 | _ -> MemberOpMode.Define in
4145 let elem_expr_instrs, elem_stack_size =
4146 emit_elem_instrs ~local_temp_kind:None env opt_elem_expr in
4147 let base_offset = elem_stack_size + rhs_stack_size in
4148 let base_expr_instrs_begin,
4149 base_expr_instrs_end,
4150 base_setup_instrs,
4151 base_stack_size =
4152 emit_base ~notice:Notice ~is_object:false env
4153 mode base_offset None base_expr
4155 let mk = get_elem_member_key env rhs_stack_size opt_elem_expr in
4156 let total_stack_size = elem_stack_size + base_stack_size in
4157 let final_instr =
4158 emit_pos_then pos @@
4159 emit_final_member_op total_stack_size op mk in
4160 gather [
4161 base_expr_instrs_begin;
4162 elem_expr_instrs;
4163 base_expr_instrs_end;
4165 rhs_instrs,
4166 gather [
4167 emit_pos pos;
4168 base_setup_instrs;
4169 final_instr
4172 | A.Obj_get (e1, e2, null_flavor) ->
4173 if null_flavor = A.OG_nullsafe then
4174 Emit_fatal.raise_fatal_parse pos "?-> is not allowed in write context";
4175 let mode =
4176 match op with
4177 | LValOp.Unset -> MemberOpMode.Unset
4178 | _ -> MemberOpMode.Define in
4179 let mk, prop_expr_instrs, prop_stack_size =
4180 emit_prop_expr env null_flavor rhs_stack_size e2 in
4181 let base_offset = prop_stack_size + rhs_stack_size in
4182 let base_expr_instrs_begin,
4183 base_expr_instrs_end,
4184 base_setup_instrs,
4185 base_stack_size =
4186 emit_base
4187 ~notice:Notice ~is_object:true
4188 env mode base_offset None e1
4190 let total_stack_size = prop_stack_size + base_stack_size in
4191 let final_instr =
4192 emit_pos_then pos @@
4193 emit_final_member_op total_stack_size op mk in
4194 gather [
4195 base_expr_instrs_begin;
4196 prop_expr_instrs;
4197 base_expr_instrs_end;
4199 rhs_instrs,
4200 gather [
4201 base_setup_instrs;
4202 final_instr
4205 | A.Class_get (cid, prop) ->
4206 let cexpr = expr_to_class_expr ~resolve_self:false
4207 (Emit_env.get_scope env) cid in
4208 begin match snd prop with
4209 | A.Dollar (_, A.Lvar _ as e) ->
4210 let final_instr = emit_final_static_op (snd cid) prop op in
4211 let instrs, under_top = emit_first_expr env e in
4212 if under_top
4213 then
4214 emit_load_class_ref env pos cexpr,
4215 rhs_instrs,
4216 gather [instrs; final_instr]
4217 else
4218 gather [instrs; emit_load_class_ref env pos cexpr],
4219 rhs_instrs,
4220 final_instr
4221 | _ ->
4222 let final_instr =
4223 emit_pos_then pos @@
4224 emit_final_static_op (snd cid) prop op in
4225 of_pair @@ emit_class_expr env cexpr prop,
4226 rhs_instrs,
4227 final_instr
4230 | A.Unop (uop, e) ->
4231 empty,
4232 rhs_instrs,
4233 gather [
4234 emit_lval_op_nonlist env pos op e empty rhs_stack_size;
4235 from_unop uop;
4238 | _ ->
4239 Emit_fatal.raise_fatal_parse pos "Can't use return value in write context"
4241 and from_unop op =
4242 let ints_overflow_to_ints =
4243 Hhbc_options.ints_overflow_to_ints !Hhbc_options.compiler_options
4245 match op with
4246 | A.Utild -> instr (IOp BitNot)
4247 | A.Unot -> instr (IOp Not)
4248 | A.Uplus -> instr (IOp (if ints_overflow_to_ints then Add else AddO))
4249 | A.Uminus -> instr (IOp (if ints_overflow_to_ints then Sub else SubO))
4250 | A.Uincr | A.Udecr | A.Upincr | A.Updecr | A.Uref | A.Usilence ->
4251 emit_nyi "unop - probably does not need translation"
4253 and emit_expr_as_ref env e =
4254 emit_expr ~need_ref:true { env with Emit_env.env_allows_array_append = true} e
4256 and emit_unop ~need_ref env pos op e =
4257 let unop_instr = emit_pos_then pos @@ from_unop op in
4258 match op with
4259 | A.Utild ->
4260 emit_box_if_necessary pos need_ref @@ gather [
4261 emit_expr ~last_pos:pos ~need_ref:false env e; unop_instr
4263 | A.Unot ->
4264 emit_box_if_necessary pos need_ref @@ gather [
4265 emit_expr ~last_pos:pos ~need_ref:false env e; unop_instr
4267 | A.Uplus ->
4268 emit_box_if_necessary pos need_ref @@ gather
4269 [emit_pos pos;
4270 instr (ILitConst (Int (Int64.zero)));
4271 emit_expr ~last_pos:pos ~need_ref:false env e;
4272 unop_instr]
4273 | A.Uminus ->
4274 emit_box_if_necessary pos need_ref @@ gather
4275 [emit_pos pos;
4276 instr (ILitConst (Int (Int64.zero)));
4277 emit_expr ~last_pos:pos ~need_ref:false env e;
4278 unop_instr]
4279 | A.Uincr | A.Udecr | A.Upincr | A.Updecr ->
4280 begin match unop_to_incdec_op op with
4281 | None -> emit_nyi "incdec"
4282 | Some incdec_op ->
4283 let instr = emit_lval_op env pos (LValOp.IncDec incdec_op) e None in
4284 emit_box_if_necessary pos need_ref instr
4286 | A.Uref -> emit_expr_as_ref env e
4287 | A.Usilence ->
4288 Local.scope @@ fun () ->
4289 let enclosing_span = Ast_scope.Scope.get_span env.Emit_env.env_scope in
4290 let fault_label = Label.next_fault () in
4291 let temp_local = Local.get_unnamed_local () in
4292 let cleanup = instr_silence_end temp_local in
4293 let body =
4294 gather [emit_expr ~need_ref:false env e; emit_pos pos; cleanup] in
4295 let fault = gather [emit_pos enclosing_span; cleanup; instr_unwind] in
4296 gather [
4297 emit_pos pos;
4298 instr_silence_start temp_local;
4299 instr_try_fault fault_label body fault
4302 and emit_exprs env pos exprs =
4303 match exprs with
4304 | [] -> empty
4305 | expr::exprs ->
4306 gather (emit_expr_and_unbox_if_necessary ~last_pos:pos ~need_ref:false env pos expr ::
4307 List.map exprs (emit_expr_and_unbox_if_necessary ~last_pos:pos ~need_ref:false env pos))
4309 (* allows to create a block of code that will
4310 - get a fresh temporary local
4311 - be wrapped in a try/fault where fault will clean temporary from the previous
4312 bulletpoint*)
4313 and with_temp_local temp f =
4314 let _, block =
4315 with_temp_local_with_prefix temp (fun temp label -> empty, f temp label) in
4316 block
4318 (* similar to with_temp_local with addition that
4319 function 'f' that creates result block of code can generate an
4320 additional prefix instruction sequence that should be
4321 executed before the result block *)
4322 and with_temp_local_with_prefix temp f =
4323 let break_label = Label.next_regular () in
4324 let prefix, block = f temp break_label in
4325 if is_empty block then prefix, block
4326 else
4327 let fault_label = Label.next_fault () in
4328 prefix,
4329 gather [
4330 instr_try_fault
4331 fault_label
4332 (* try block *)
4333 block
4334 (* fault block *)
4335 (gather [
4336 instr_unsetl temp;
4337 instr_unwind ]);
4338 instr_label break_label;
4341 (* Similar to stash_in_local with addition that function that
4342 creates a block of code can yield a prefix instrution
4343 that will be executed as the first instruction in the result instruction set *)
4344 and stash_in_local_with_prefix ?(always_stash=false) ?(leave_on_stack=false)
4345 ?(always_stash_this=false) env pos e f =
4346 match e with
4347 | (_, A.Lvar id) when not always_stash
4348 && not (is_local_this env (snd id) &&
4349 ((Emit_env.get_needs_local_this env) || always_stash_this)) ->
4350 let break_label = Label.next_regular () in
4351 let prefix_instr, result_instr =
4352 f (get_local env id) break_label in
4353 gather [
4354 prefix_instr;
4355 result_instr;
4356 instr_label break_label;
4357 if leave_on_stack then instr_cgetl (get_local env id) else empty;
4359 | _ ->
4360 let generate_value =
4361 Local.scope @@ fun () ->
4362 emit_expr_and_unbox_if_necessary ~need_ref:false env pos e in
4363 Local.scope @@ fun () ->
4364 let temp = Local.get_unnamed_local () in
4365 let prefix_instr, result_instr =
4366 with_temp_local_with_prefix temp f in
4367 gather [
4368 prefix_instr;
4369 generate_value;
4370 instr_setl temp;
4371 instr_popc;
4372 result_instr;
4373 emit_pos pos;
4374 if leave_on_stack then instr_pushl temp else instr_unsetl temp
4376 (* Generate code to evaluate `e`, and, if necessary, store its value in a
4377 * temporary local `temp` (unless it is itself a local). Then use `f` to
4378 * generate code that uses this local and branches or drops through to
4379 * `break_label`:
4380 * temp := e
4381 * <code generated by `f temp break_label`>
4382 * break_label:
4383 * push `temp` on stack if `leave_on_stack` is true.
4385 and stash_in_local ?(always_stash=false) ?(leave_on_stack=false)
4386 ?(always_stash_this=false) env pos e f =
4387 stash_in_local_with_prefix ~always_stash ~leave_on_stack ~always_stash_this
4388 env pos e (fun temp label -> empty, f temp label)