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