2 * Copyright (c) 2017, Facebook, Inc.
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.
13 open Instruction_sequence
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
=
40 | GI_list_assignment
of A.expr list
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 *)
51 (* was label associated with emit operation used *)
55 (* Locals, array elements, and properties all support the same range of l-value
57 module LValOp
= struct
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
90 { first_inout
= max_int
; last_write
= min_int
; num_uses
= 0; }
93 if i
< r
.first_inout
then { r
with first_inout
= i
} else r
96 if i
> r
.last_write
then { r
with last_write
= i
} else 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
=
107 |> Option.value ~default
:not_aliased
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
=
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
125 | A.Unop
(A.Uref
, (_
, A.Lvar
(_
, id
))) ->
126 let acc = add_use id i
acc in
130 let acc = add_use id i
acc in
133 (* dive into argument value *)
136 (* collect lvars on the left hand side of '=' operator *)
137 and collect_lvars_lhs i
acc e
=
139 | A.Lvar
(_
, id
) when not
(is_local_this env id
) ->
140 let acc = add_use id i
acc in
143 List.fold_left exprs ~f
:(collect_lvars_lhs i
) ~init
:acc
146 (* descend into expression *)
147 and dive i
acc expr
=
148 let visitor = object(_
)
149 inherit [_
] Ast_visitor.ast_visitor
as super
151 method! on_binop
acc bop l
r =
154 | A.Eq _
-> collect_lvars_lhs i
acc l
156 super#on_binop
acc bop l
r
158 method! on_unop
acc op e
=
161 | A.Uincr
| A.Udecr
-> collect_lvars_lhs i
acc e
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
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
=
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
192 setl/popl l; depending on local_kind
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
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
[
215 let fault_block = gather
[ unset; instr_unwind
; ] in
218 instr_try_fault
fault_label try_block fault_block;
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
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
= {
238 instrs_end
: Instruction_sequence.t
;
239 setup_instrs
: Instruction_sequence.t
;
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
258 | LValOp.IncDec _
-> true
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
=
268 let n = List.length args
in
272 | "define" when is_global_namespace env
->
273 begin match args
with
274 | [_
, A.String _
; _
] -> true
278 | "idx" -> not
(jit_enable_rename_function ()) && (n = 2 || n = 3)
279 | "class_alias" when is_global_namespace env
->
282 | [_
, A.String _
; _
, A.String _
]
283 | [_
, A.String _
; _
, A.String _
; _
] -> true
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 *)
311 let ints_overflow_to_ints =
312 Hhbc_options.ints_overflow_to_ints !Hhbc_options.compiler_options
in
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"
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
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
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
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
)
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
)
400 (* Return the IsType op associated with a given primitive typehint. *)
401 let is_expr_primitive_op id
=
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
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
=
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")), _
, [_
], []) ->
429 | A.Call
((_
, A.Id
(_
, "array_key_exists")), _
, [_
; _
], []) ->
431 | A.Call
((_
, A.Id
(_
, ("idx"))), _
, ([_
; _
] | [_
; _
; _
]), []) ->
433 | A.Call
((_
, A.Id
(_
, ("hphp_array_idx"))), _
, [_
; _
; _
], []) ->
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
=
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
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
472 then let var, lit
= split_var_lit e1
in var, lit ^ l
475 | _
, A.String
(_
, lit
) -> "", lit
476 | _
, e_
-> snd
(text_of_expr e_
), "" in
477 let var, lit
= split_var_lit e
in
479 if var = "__DIR__" then ("", strip_backslash lit
) else (var, lit
) in
483 if not
(Filename.is_relative lit
)
484 then Hhas_symbol_refs.Absolute lit
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
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) ->
500 if expr_starts_with_ref
v then instr_add_elemv
else instr_to_add
503 emit_two_exprs env
(fst k
) k
v;
507 and get_local env
(pos
, str
) =
508 if str
= SN.SpecialIdents.dollardollar
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"
516 and check_non_pipe_local e
=
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"
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"
531 and emit_local ~notice ~need_ref env
((pos
, str
) as id
) =
532 if SN.Superglobals.is_superglobal str
534 instr_string
(SU.Locals.strip_dollar str
);
535 Emit_pos.emit_pos pos
;
536 instr
(IGet
(if need_ref
then VGetG
else CGetG
))
539 let local = get_local env id
in
540 if is_local_this env str
&& not
(Emit_env.get_needs_local_this env
) then
544 instr
(IMisc
(BareThis notice
))
545 else if need_ref
then
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
) =
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
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
567 | _
, A.Lvar _
-> true
573 then [Emit_pos.emit_pos outer_pos
; instrs2; instrs1]
574 else [instrs2; Emit_pos.emit_pos outer_pos
; instrs1]
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
=
582 | (_
, A.Lvar
((_
, str
) as id
)) when not
(is_local_this env str
) ->
583 instr_istypel
(get_local env id
) OpNull
586 emit_expr_and_unbox_if_necessary ~need_ref
:false env e
;
590 and emit_binop env expr op e1 e2
=
593 emit_two_exprs env
(fst expr
) e1 e2
;
597 | A.AMpamp
| A.BArbar
-> emit_short_circuit_op env expr
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
)
606 if not
(optimize_null_check ())
610 | A.EQeqeq
when snd e2
= A.Null
->
612 | A.EQeqeq
when snd e1
= A.Null
->
614 | A.Diff2
when snd e2
= A.Null
->
619 | A.Diff2
when snd e1
= A.Null
->
627 and emit_box_if_necessary need_ref instr
=
636 and emit_instanceof env e1 e2
=
638 | (_
, (_
, A.Id _
)) ->
639 let lhs = emit_expr ~need_ref
:false env e1
in
640 let from_class_ref instrs
=
646 let scope = Emit_env.get_scope env
in
647 begin match expr_to_class_expr ~resolve_self
:true scope e2
with
649 from_class_ref @@ gather
[
650 instr_fcallbuiltin
0 0 "get_called_class";
654 from_class_ref @@ gather
[
659 from_class_ref @@ gather
[
665 Hhbc_id.Class.elaborate_id
(Emit_env.get_namespace env
) name
in
671 | Class_unnamed_local _
->
672 failwith
"cannot get this shape from from A.Id"
676 emit_expr ~need_ref
:false env e1
;
677 emit_expr ~need_ref
:false env e2
;
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"
685 | A.Happly
((_
, id
), tyl
) when (SU.strip_global_ns id
) = "classname" ->
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
693 instr_istypel
local OpStr
;
694 instr_jmpz
skip_label;
696 | (_
, A.Happly
((_
, id
), _
))::_
->
701 instr_fcallbuiltin
3 3 "is_a";
703 instr_jmpnz
true_label;
707 instr_fpushfuncd
1 (Hhbc_id.Function.from_raw_string
"class_exists");
708 instr_fpassl
0 local H.Cell
;
711 instr_jmpnz
true_label;
712 instr_fpushfuncd
1 (Hhbc_id.Function.from_raw_string
"interface_exists");
713 instr_fpassl
0 local H.Cell
;
716 instr_jmpnz
true_label;
719 emit_nyi "is expression: unsupported classname (T22779957)"
721 instr_label
skip_label;
723 instr_jmp
done_label;
724 instr_label
true_label;
726 instr_label
done_label;
729 | A.Happly
((_
, id
), _
)
730 when id
= SN.Typehints.arraykey
|| id
= SN.Typehints.num
->
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
738 instr_istypel
local OpInt
;
739 instr_jmpnz
its_true;
740 instr_istypel
local op2;
741 instr_jmpnz
its_true;
744 instr_label
its_true;
746 instr_label
its_done;
749 | A.Happly
((_
, id
), _
) when id
= SN.Typehints.nonnull
->
752 instr_istypec OpNull
;
755 | A.Happly
((_
, id
), _
) when id
= SN.Typehints.mixed
->
757 | A.Happly
((_
, id
), _
) ->
758 begin match is_expr_primitive_op id
with
764 | None
-> emit_nyi "is expression: unsupported id (T22779957)"
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
773 instr_istypel
local OpNull
;
774 instr_jmpnz
its_true;
775 emit_is env pos
lhs h2
;
776 instr_jmpnz
its_true;
779 instr_label
its_true;
785 A.si_allows_unknown_fields
= shape_is_open
;
786 si_shape_field_list
= field_list
;
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
798 else Some
(Local.get_unnamed_local
())
800 let verify_field = emit_is_shape_verify_field
801 env pos
local fail_label expected_count
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
;
811 instr_setl
count_local;
813 emit_is_shape_verify_count
count_local fail_label shape_is_open
816 begin match expected_count with
817 | Some expected_count_local
->
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
;
825 instr_jmpnz
fail_label;
828 gather
(List.map ~
f:verify_field field_list
)
831 instr_jmp
pass_label;
832 instr_label
fail_label;
834 instr_jmp
done_label;
835 instr_label
pass_label;
837 instr_label
done_label;
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
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
;
857 instr_jmpz
skip_label;
859 gather
(List.mapi ~
f:(emit_is_tuple_elem env pos
local skip_label) hl
);
862 instr_label
skip_label;
865 instr_label
its_true;
871 emit_nyi "is expression: unsupported type const access"
873 failwith
"Soft typehints cannot be used with `is` expressions"
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
881 emit_expr ~need_ref
:false env e
;
883 emit_is env pos
(IsExprUnnamedLocal
local) h
;
887 and emit_is_lhs env
lhs =
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
899 instr_int
num_required_fields;
901 instr_jmpnz
fail_label;
903 else if num_required_fields = num_fields
906 instr_int
num_required_fields;
908 instr_jmpz
fail_label;
912 instr_int
num_required_fields;
914 instr_jmpnz
fail_label;
915 instr_cgetl
count_local;
916 instr_int
num_fields;
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
933 instr_setl
local_fname;
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);
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
->
948 instr
(IMutator
(IncDecL
(expected_count_local
, PreInc
)));
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
960 instr_basel
local MemberOpMode.Warn
;
961 instr_querym
0 H.QueryOp.CGet
(MemberKey.EI
(Int64.of_int 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
971 emit_quiet_expr env pos e1
;
973 instr_istypec OpNull
;
975 instr_jmpnz
end_label;
977 emit_expr ~need_ref
:false env e2
;
978 instr_label
end_label;
981 and emit_cast env pos hint expr
=
983 begin match hint
with
984 | A.Happly
((_
, id
), []) ->
985 let id = String.lowercase_ascii
id in
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"
1002 emit_nyi "cast type"
1005 emit_expr ~need_ref
:false env expr
;
1006 Emit_pos.emit_pos pos
;
1010 and emit_conditional_expression env pos etest etrue efalse
=
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
1018 (* only emit true branch if there is fallthrough from condition *)
1019 begin if r.is_fallthrough
1021 emit_expr ~need_ref
:false env etrue
;
1022 Emit_pos.emit_pos pos
;
1027 (* only emit false branch if false_label is used *)
1028 begin if r.is_label_used
1030 instr_label
false_label;
1031 emit_expr ~need_ref
:false env efalse
;
1035 (* end_label is used to jump out of true branch so they should be emitted
1037 begin if r.is_fallthrough
1038 then instr_label
end_label
1043 let end_label = Label.next_regular
() in
1045 emit_expr ~need_ref
:false env etest
;
1047 instr_jmpnz
end_label;
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
1058 (* Special case for statically-known class *)
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);
1064 instr_fpushctord
nargs fq_id;
1065 emit_args_and_call env pos args uargs
;
1070 instr_fpushctors
nargs SpecialClsRef.Static
;
1071 emit_args_and_call env pos args uargs
;
1076 instr_fpushctors
nargs SpecialClsRef.Self
;
1077 emit_args_and_call env pos args uargs
;
1082 instr_fpushctors
nargs SpecialClsRef.Parent
;
1083 emit_args_and_call env pos args uargs
;
1088 emit_load_class_ref env pos
cexpr;
1089 instr_fpushctor
nargs 0;
1090 emit_args_and_call env pos args uargs
;
1094 and emit_new_anon env pos cls_idx args uargs
=
1095 let nargs = List.length args
+ List.length uargs
in
1097 instr_defcls cls_idx
;
1098 instr_fpushctori
nargs cls_idx
;
1099 emit_args_and_call env pos args uargs
;
1103 and emit_clone env expr
=
1105 emit_expr ~need_ref
:false env expr
;
1109 and emit_shape env expr fl
=
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
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
) @@
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);
1137 instr_string
(Hhbc_id.Class.to_raw_string
fq_id);
1141 and emit_load_class_ref env pos
cexpr =
1142 Emit_pos.emit_pos_then pos
@@
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)))
1157 emit_expr ~need_ref
:false env expr
;
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
1173 then instr
(IMisc
(ClsRefName
0))
1174 else instr
(ILitConst
(ClsCns
(Hhbc_id.Const.from_ast_name
id, 0)))
1177 emit_load_class_ref env pos
cexpr;
1181 and emit_class_expr_parts env
cexpr prop
=
1182 let load_prop, load_prop_first
=
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
=
1201 | Class_expr
((pos
, (A.BracedExpr _
|
1204 A.Lvar
(_
, "$this") |
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) *)
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
1229 Emit_pos.emit_pos pos
;
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
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
1264 emit_class_const_impl env cid
id
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;
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
1280 emit_expr ~need_ref
:false env e
;
1281 Emit_pos.emit_pos pos
;
1284 | A.AFkvalue
(e1
, e2
) ->
1286 emit_expr ~need_ref
:false env e1
;
1287 emit_expr ~need_ref
:false env e2
;
1288 Emit_pos.emit_pos pos
;
1292 and emit_execution_operator env pos exprs
=
1295 (* special handling of ``*)
1296 | [_
, A.String
(_
, "") as e
] -> emit_expr ~need_ref
:false env e
1297 | _
-> emit_string2 env pos exprs
in
1299 instr_fpushfuncd
1 (Hhbc_id.Function.from_raw_string
"shell_exec");
1301 instr_fpass
PassByRefKind.AllowCell
0 Cell
;
1305 and emit_string2 env pos exprs
=
1309 emit_expr ~need_ref
:false env e
;
1310 instr
(IOp CastString
)
1314 emit_two_exprs env
(fst e1
) e1 e2
;
1315 Emit_pos.emit_pos pos
;
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
1332 gather
@@ List.map ids
1335 let lid = get_local env x
in
1338 if isref
then VGetL
lid else CGetL
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
1346 | "__FILE__" -> instr
(ILitConst File
)
1347 | "__DIR__" -> instr
(ILitConst Dir
)
1348 | "__METHOD__" -> instr
(ILitConst Method
)
1350 (* If the expression goes on multi lines, we return the last line *)
1351 let _, line
, _, _ = Pos.info_pos_extended
p in
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") ->
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
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)))
1368 Emit_symbol_refs.add_constant
(snd
id);
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
)
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
@@
1405 (p, A.Id
renamed_id),
1406 [attribute_map ; children_vec ; filename ; line],
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
1418 emit_expr ~need_ref
:false env e
;
1419 Emit_pos.emit_pos pos
;
1423 and emit_call_isset_expr env outer_pos
(pos
, expr_
as expr
) =
1425 | A.Array_get
((_, A.Lvar
(_, x
)), Some e
) when x
= SN.Superglobals.globals
->
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
) ->
1440 emit_local ~notice
:NoNotice ~need_ref
:false env
id;
1441 Emit_pos.emit_pos outer_pos
;
1442 instr_istypec OpNull
;
1446 instr
(IIsset
(IssetL
(get_local env
id)))
1449 emit_expr ~need_ref
:false env e
;
1454 emit_expr_and_unbox_if_necessary ~need_ref
:false env expr
;
1455 Emit_pos.emit_pos outer_pos
;
1456 instr_istypec OpNull
;
1460 and emit_call_empty_expr env outer_pos
(pos
, expr_
as expr
) =
1462 | A.Array_get
((_, A.Lvar
(_, x
)), Some e
) when x
= SN.Superglobals.globals
->
1464 emit_expr ~need_ref
:false env e
;
1465 Emit_pos.emit_pos outer_pos
;
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 ->
1476 instr_string
@@ SU.Locals.strip_dollar
id;
1477 Emit_pos.emit_pos outer_pos
;
1480 | A.Lvar
id when not
(is_local_this env
(snd
id)) ->
1481 instr_emptyl
(get_local env
id)
1484 emit_expr ~need_ref
:false env e
;
1485 Emit_pos.emit_pos outer_pos
;
1490 emit_expr_and_unbox_if_necessary ~need_ref
:false env expr
;
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
=
1499 | [] -> emit_nyi "isset()"
1500 | [expr
] -> emit_call_isset_expr env pos expr
1502 let n = List.length exprs
in
1503 let its_done = Label.next_regular
() in
1509 emit_call_isset_expr env pos expr
;
1513 instr_jmpz
its_done;
1518 instr_label
its_done
1521 and emit_exit env expr_opt
=
1523 (match expr_opt
with
1524 | None
-> instr_int
0
1525 | Some e
-> emit_expr ~need_ref
:false env e
);
1529 and emit_idx env pos es
=
1530 let default = if List.length es
= 2 then instr_null
else empty
in
1533 Emit_pos.emit_pos pos
;
1538 and emit_define env pos
s e
=
1540 emit_expr ~need_ref
:false env e
;
1541 Emit_pos.emit_pos pos
;
1545 and emit_eval env pos e
=
1547 emit_expr ~need_ref
:false env e
;
1548 Emit_pos.emit_pos pos
;
1552 and emit_xhp_obj_get_raw env e
s nullflavor
=
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
() =
1566 instr_fpushfuncd
0 (Hhbc_id.Function.from_raw_string
"get_class");
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
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
)
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
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
1600 let label = Label.next_fault
() in
1603 instr_try_fault
label body fault
1605 and unset_in_fault temps b
=
1606 try_fault b
@@ fun () ->
1608 gather
@@ List.map temps ~
f:instr_unsetl
;
1612 (* emits iteration over the ~collection where loop body is
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
[
1621 instr_iterinitk
iter loop_end value_local key_local;
1623 let loop_next = Label.next_regular
() in
1625 (* try-fault to release temp locals *)
1626 unset_in_fault
[value_local; key_local] @@ begin fun () ->
1627 (* try-fault to release iterator *)
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;
1641 instr_iterfree
iter;
1646 Iterator.free_iterator
();
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
1658 if hack_arr_dv_arrs () then instr_cast_dict
else instr_cast_darray
;
1659 instr_setl
arr_local;
1662 unset_in_fault
[arr_local] @@ fun () ->
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
;
1673 emit_iter ~collection
:(instr_cgetl
arr_local) @@
1674 begin fun value_local key_local ->
1676 (* generate code for
1677 arr_local[key_local] = WHResult (value_local) *)
1678 instr_cgetl
value_local;
1680 instr_basel
arr_local MemberOpMode.Define
;
1681 instr_setm
0 (MemberKey.EL
key_local);
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 *)
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
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
->
1720 let pairs = combine lhs rhs
in
1721 let lhs, set = generate pairs ~is_ltr
:(php7_ltr_assign ()) in
1726 gather
@@ Core_list.map
pairs
1727 ~
f:(function (_, A.Omitted
), l
-> instr_unsetl l
| _ -> empty
);
1729 match inline_context
with
1730 | GI_list_assignment l
when not
(is_valid_list_assignment l
) ->
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 ->
1736 | GI_expression
when args_count = 0 ->
1737 Some instr_lit_empty_varray
1738 | _ when args_count > max_array_elem_on_stack () ->
1741 Local.scope @@ begin fun () ->
1743 gather
@@ Core_list.map
args ~
f:begin fun arg
->
1744 let label_done = Label.next_regular
() in
1746 emit_expr ~need_ref
:false env arg
;
1748 instr_istypec OpNull
;
1749 instr_jmpz
label_done;
1751 instr_fpushfuncd
0 (Hhbc_id.Function.from_raw_string
"HH\\Asio\\null");
1754 instr_label
label_done;
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
1762 gather
@@ Core_list.map
reserved_locals_reversed ~
f:begin fun l
->
1768 let await_and_process_results =
1769 unset_in_fault
reserved_locals @@ begin fun () ->
1772 instr_awaitall
(List.hd_exn
reserved_locals_reversed) (args_count - 1);
1775 let process_results =
1776 let reify ~pop_result
=
1777 gather
@@ Core_list.map
reserved_locals ~
f:begin fun l
->
1781 if pop_result
then instr_popc
else empty
;
1784 match inline_context
with
1785 | GI_ignore_result
->
1786 reify ~pop_result
:true
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
1805 await_and_process_results;
1810 and emit_await env pos e
=
1811 begin match try_inline_gen_call env e
with
1814 let after_await = Label.next_regular
() in
1816 emit_expr ~need_ref
:false env e
;
1817 Emit_pos.emit_pos pos
;
1819 instr_istypec OpNull
;
1820 instr_jmpnz
after_await;
1822 instr_label
after_await;
1826 and emit_callconv _env kind _e
=
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
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
]
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."
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
@@
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
1858 emit_local ~notice
:Notice ~need_ref env
id
1859 | A.Class_const
(cid
, id) ->
1860 emit_class_const env pos cid
id
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
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
->
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
)
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
1920 emit_box_if_necessary need_ref
@@ emit_collection env expr 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
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
1933 emit_box_if_necessary need_ref
@@ emit_clone env e
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
1939 failwith
"yield break should be in statement position"
1940 | A.Yield_from
_ -> failwith
"complex yield_from expression"
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
1949 check_non_pipe_local e
;
1950 let instr = emit_expr ~need_ref
:false env e
in
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
1970 failwith
"Unsafe expression should be removed during closure conversion"
1972 failwith
"Codegen for 'suspend' operator is not supported"
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
1983 instr (ILitConst
(TypedValue tv
));
1987 and emit_value_only_collection env pos es constructor
=
1988 let limit = max_array_elem_on_stack () in
1991 [gather
@@ List.map exprs
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
=
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
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
]
2024 instr (ILitConst constructor
);
2025 gather
(List.map es ~
f:(expr_and_new env
add_elem_instr instr_add_elemc
));
2029 and emit_struct_array env pos es ctor
=
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")
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
)), _) ->
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
), _) ->
2064 | A.AFkvalue
((_, A.False
), _) ->
2070 let has_references =
2071 (* Reference can only exist as a value *)
2073 ~
f:(function A.AFkvalue
(_, e
)
2074 | A.AFvalue e
-> expr_starts_with_ref e
)
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 *)
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) ->
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),
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
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
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
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
2140 emit_struct_array env pos
es instr_newstructdict
;
2141 Emit_pos.emit_pos pos
;
2142 instr_colfromarray
(collection_type (SU.strip_ns
name));
2145 emit_keyvalue_collection
name env
es (NewDictArray
count)
2148 emit_value_only_collection env pos
es
2149 (fun n -> if hack_arr_dv_arrs () then (NewVecArray
n) else (NewVArray
n))
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
)
2155 emit_keyvalue_collection
"array" env
es
2156 (if hack_arr_dv_arrs () then (NewDictArray
count) else (NewDArray
count))
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)
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
2171 | "dict" | "vec" | "keyset"
2172 -> emit_collection env expr fields
2173 | "Vector" | "ImmVector" ->
2174 let collection_type = collection_type name in
2176 then instr_newcol
collection_type
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
2185 then instr_newcol
collection_type
2188 ~transform_to_collection
:collection_type
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 ())
2207 and emit_collection ?
(transform_to_collection
) env expr
es =
2208 match Ast_constant_folder.expr_to_opt_typed_value
2210 ~restrict_keys
:(not
@@ is_php_array expr
)
2211 (Emit_env.get_namespace env
)
2215 emit_static_collection ~transform_to_collection tv
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
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
2237 let b = Typed_value.to_bool
v in
2239 { instrs = with_pos empty
;
2240 is_fallthrough
= true;
2241 is_label_used
= false; }
2243 { instrs = with_pos @@ instr_jmp
label;
2244 is_fallthrough
= false;
2245 is_label_used
= true; }
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
2256 if r1.is_label_used
then gather
[ r1.instrs; instr_label
skip_label; ]
2258 { instrs = with_pos instrs;
2259 is_fallthrough
= r1.is_label_used
;
2260 is_label_used
= false }
2262 let r2 = emit_jmpz
env e2
label in
2263 let instrs = gather
[
2266 optional
r1.is_label_used
[instr_label
skip_label];
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
2275 { instrs = with_pos r1.instrs;
2276 is_fallthrough
= false;
2277 is_label_used
= r1.is_label_used
}
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
[
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
[
2297 is_fallthrough
= true;
2298 is_label_used
= true; }
2300 { instrs = with_pos @@ gather
[
2301 emit_expr_and_unbox_if_necessary ~need_ref
:false env expr
;
2304 is_fallthrough
= true;
2305 is_label_used
= true; }
2308 (* Emit code that is equivalent to
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
2319 if Typed_value.to_bool
v
2321 { instrs = with_pos @@ instr_jmp
label;
2322 is_fallthrough
= false;
2323 is_label_used
= true }
2325 { instrs = with_pos empty
;
2326 is_fallthrough
= true;
2327 is_label_used
= false }
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
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
2345 { instrs = with_pos @@ gather
[
2347 optional
r1.is_label_used
[instr_label
skip_label]
2349 is_fallthrough
= r1.is_label_used
;
2350 is_label_used
= false }
2352 let r2 = emit_jmpnz
env e2
label in
2353 { instrs = with_pos @@ gather
[
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
[
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
[
2375 is_fallthrough
= true;
2376 is_label_used
= true; }
2378 { instrs = with_pos @@ gather
[
2379 emit_expr_and_unbox_if_necessary ~need_ref
:false env expr
;
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
2391 if r1.is_label_used
then gather
[
2392 instr_label
its_true;
2396 if r1.is_fallthrough
2399 Emit_pos.emit_pos
(fst expr
);
2403 instr_label
its_done ]
2408 and emit_quiet_expr
env pos
(_, expr_
as expr
) =
2410 | A.Lvar
(_, name) when name = SN.Superglobals.globals
->
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)
2419 emit_expr ~need_ref
:false env e
;
2420 Emit_pos.emit_pos pos
;
2423 | A.Array_get
((_, A.Lvar
(_, x
)), Some e
) when x
= SN.Superglobals.globals
->
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
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
,
2443 emit_base ~is_object
:false ~notice
:Notice
env MemberOpMode.Define
2444 elem_stack_size param_num_opt base_expr
in
2446 let mk = MemberKey.EL
local in
2447 if is_base
then instr_dim
MemberOpMode.Define
mk else instr_setm
0 mk in
2449 base_expr_instrs_begin;
2450 base_expr_instrs_end
;
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
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 *)
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
2471 and is_trivial
env (_, e
) =
2473 | A.Int
_ | A.String
_ ->
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
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
=
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
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"
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
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
2522 match param_num_hint_opt with
2525 VGetM
(total_stack_size
, mk)
2527 QueryM
(total_stack_size
, qop
, mk)
2528 | Some
(i
, h
) -> FPassM
(i
, total_stack_size
, mk, h
)
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
[
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
2545 (* load base and indexer, value of indexer will be saved in local *)
2549 ], Some
(local, local_kind
);
2550 (* finish loading the value *)
2554 make_final None
(base
.stack_size
+ elem_stack_size
);
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
@ [
2568 base
.load.instrs_end
;
2569 base
.load.setup_instrs
;
2570 make_final None
(base
.load.stack_size
+ elem_stack_size
);
2573 let store = gather
[
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
2585 base
.load.instrs_begin
@ [
2586 (* load index, value will be saved in local *)
2587 elem_expr_instrs, Some
(local, local_kind
);
2589 base
.load.instrs_end
;
2590 base
.load.setup_instrs
;
2591 make_final None
(base
.load.stack_size
+ elem_stack_size
);
2594 let store = gather
[
2596 instr_setm
0 (MemberKey.EL
local);
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
=
2607 when id = SN.SpecialIdents.this
&& null_flavor
= A.OG_nullsafe
->
2608 Emit_fatal.raise_fatal_parse
2609 pos
"?-> is not allowed with $this"
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
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
,
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
2630 match param_num_hint_opt with
2633 VGetM
(total_stack_size, mk)
2635 QueryM
(total_stack_size, qop
, mk)
2636 | Some
(i
, h
) -> FPassM
(i
, total_stack_size, mk, h
)
2639 base_expr_instrs_begin;
2641 base_expr_instrs_end
;
2642 Emit_pos.emit_pos pos
;
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 *)
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
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
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
=
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 *)
2694 match SU.is_self
cName,
2695 Ast_scope.Scope.get_class
(Emit_env.get_scope
env)
2697 | true, Some cd
-> SU.strip_global_ns
@@ snd cd
.A.c_name
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)
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
=
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 *)
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)
2728 MemberKey.PC stack_index
2730 (* For nullsafe access, insist that property is known *)
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"
2740 mk, emit_expr ~need_ref
:false env prop_expr
, 1
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]
2763 * # Section 1, pushing the value of $ix+2 on the stack
2767 * # Section 2, constructing the base address of $arr[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
2778 | Array_get_base_regular i
->
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) =
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
{
2798 (* run begin part, result will be stored into temp *)
2799 instrs_begin
= [instrs_begin
, Some
(local, local_temp
)];
2803 store = instr_basel
local MemberOpMode.Define
2806 Array_get_base_regular
{
2807 instrs_begin
; instrs_end
; setup_instrs
; stack_size
}
2810 | A.Lvar
(_, x
) when SN.Superglobals.is_superglobal x
->
2812 (instr_string
(SU.Locals.strip_dollar x
))
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
->
2823 (Emit_pos.emit_pos_then thispos
@@ instr (IMisc CheckThis
))
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
2834 (instr_cgetquietl
v)
2836 (instr_basel
v base_mode)
2844 match param_num_opt with
2845 | None
-> BaseL
(v, base_mode)
2846 | Some i
-> FPassBaseL
(i
, v)
2853 (emit_local ~notice ~need_ref
:false env id)
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
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
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
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
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
=
2902 Emit_pos.emit_pos pos
;
2904 match param_num_opt with
2905 | None
-> Dim
(mode, mk)
2906 | Some i
-> FPassDim
(i
, mk)
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
->
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
[
2927 Array_get_base_inout
{
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
{
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
};
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
{
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
};
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 ->
2975 (emit_xhp_obj_get_raw
env base_expr
s null_flavor
)
2977 (gather
[ instr_baser base_offset
])
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
,
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
2992 match param_num_opt with
2993 | None
-> Dim
(mode, mk)
2994 | Some i
-> FPassDim
(i
, mk)
2998 base_expr_instrs_begin;
3001 base_expr_instrs_end
3004 Emit_pos.emit_pos pos
;
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 *)
3015 (emit_load_class_ref
env pos
cexpr)
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
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
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))
3043 let base_expr_instrs = emit_expr ~need_ref
:false env e
in
3047 (Emit_pos.emit_pos_then pos
@@
3048 instr_basenc base_offset
base_mode)
3051 let base_expr_instrs, flavor
= emit_flavored_expr
env expr in
3053 (if binary_assignment_rhs_starts_with_ref
expr
3054 then gather
[base_expr_instrs; instr_unbox
]
3055 else base_expr_instrs)
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
)
3068 | A.Unop
(A.Uref
, e
) -> e
3071 and emit_ignored_expr
env ?
(pop_pos
= Pos.none
) e
=
3073 | A.Expr_list
es -> gather
@@ List.map ~
f:(emit_ignored_expr
env ~pop_pos
) es
3075 let instrs, flavor
= emit_flavored_expr
env e
in
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
3086 if has_inout_args
args
3087 then InoutLocals.collect_written_variables env args
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
3095 if is_splatted && flavor
= Flavor.ReturnVal
3096 then gather
[ instrs; instr_unboxr
] else instrs
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
3107 Emit_pos.emit_pos call_pos
;
3110 let rec aux i
args inout_setters
=
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
3127 (* propagate inout values back *)
3128 if List.is_empty inout_setters
3131 let local = Local.get_unnamed_local
() in
3133 if msrv then empty
else instr_unboxr
;
3134 Emit_inout_helpers.emit_list_set_for_inout_call
local
3135 (List.rev inout_setters
)
3140 let next c
= gather
[ c
; aux (i
+ 1) rest inout_setters
] in
3141 let is_inout, (pos
, _ as expr) =
3143 | A.Callconv
(A.Pinout
, e
) -> true, e
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
3152 | A.Lvar
(_, x
) when SN.Superglobals.is_superglobal x
->
3154 instr_string
(SU.Locals.strip_dollar x
);
3155 instr_fpassg i
hint;
3157 | A.Lvar
((_, s) as id) when is_inout ->
3159 (instr_setl
@@ Local.Named
s) :: inout_setters in
3160 let not_in_try = not
(Emit_env.is_in_try
env) in
3162 if not_in_try && (InoutLocals.should_move_local_value s aliases)
3163 then gather
[ instr_null
; instr_popl
(get_local
env id) ]
3166 emit_expr ~need_ref
:false env expr;
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
3176 next @@ emit_expr ~need_ref
:false env e
3178 check_non_pipe_local e
;
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
->
3185 emit_expr ~need_ref
:false env e
;
3186 instr_fpassg i
hint;
3189 | A.Array_get
(base_expr
, opt_elem_expr
) ->
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 ->
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
3205 instr_setm
0 (get_elem_member_key
env 0 opt_elem_expr
);
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 () ->
3214 instr_fpassc i
hint;
3215 aux (i
+ 1) rest
(store :: inout_setters)
3220 next @@ emit_array_get
3222 { env with Emit_env.env_allows_array_append
= true }
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
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
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) =
3253 | A.Lvar
(_, x
) when is_local_this env x
->
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
3261 emit_local ~notice
:Notice ~need_ref
:false env id;
3263 with_temp_local
temp
3264 begin fun temp _ -> gather
[
3267 instr (IGet
(ClsRefGetL
(temp, 0)));
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
3279 | A.Obj_get
(obj
, (_, A.Id
((_, str
) as id)), null_flavor
)
3280 when str
.[0] = '$'
->
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
3291 then Hhbc_id.Method.add_suffix
name
3292 (Emit_inout_helpers.inout_suffix inout_arg_positions
)
3295 emit_object_expr
env obj
;
3296 instr_fpushobjmethodd
nargs name null_flavor
;
3298 | A.Obj_get
(obj
, method_expr
, null_flavor
) ->
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
3311 then Hhbc_id.Method.add_suffix
method_id
3312 (Emit_inout_helpers.inout_suffix inout_arg_positions
)
3314 begin match cexpr with
3315 (* Statically known *)
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
3326 emit_call_lhs_with_this
env @@ instr_string
method_name;
3327 instr_fpushclsmethod
nargs []
3330 let method_name = Hhbc_id.Method.to_raw_string
method_id in
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
3343 gather
[expr_instrs; instr_fpushclsmethods
nargs SpecialClsRef.Static
]
3345 gather
[expr_instrs; instr_fpushclsmethods
nargs SpecialClsRef.Self
]
3347 gather
[expr_instrs; instr_fpushclsmethods
nargs SpecialClsRef.Parent
]
3348 | Class_expr
(_, A.Lvar
(_, x
)) when x
= SN.SpecialIdents.this
->
3350 emit_call_lhs_with_this
env expr_instrs;
3351 instr_fpushclsmethod
nargs inout_arg_positions
3356 emit_load_class_ref
env pos
cexpr;
3357 instr_fpushclsmethod
nargs inout_arg_positions
3361 | A.Id
(_, s as id)->
3363 Hhbc_id.Function.elaborate_id_with_builtins
(Emit_env.get_namespace
env) id in
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
)
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)
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
))
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 =
3423 (* for warning - adjust the argument id *)
3424 let param_id = Param_unnamed
(i
+ 1) in
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
3431 emit_expr ~need_ref
:false env expr;
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
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
)
3468 let flavor = match id with
3469 | "fb_call_user_func_safe"
3470 | "fb_call_user_func_array_safe" -> Flavor.Cell
3471 | _ -> Flavor.ReturnVal
3474 (* first arg is always emitted as cell *)
3475 emit_expr ~need_ref
:false env (strip_ref arg
);
3477 gather
(List.mapi
args (emit_call_user_func_arg
env id));
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
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
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
->
3499 emit_expr ~need_ref
:false env arg
;
3500 Emit_pos.emit_pos pos
;
3502 if i
= nargs-1 then empty
else instr_popc
3504 Some
(instrs, Flavor.Cell
)
3507 _, A.Call
((_, A.Id
(_, "func_get_args")), _, [], []);
3508 (_, A.Int
_ as count)
3509 ] when not
(jit_enable_rename_function ()) ->
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
)
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"
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
3530 let expr_id = p, A.Id
(p, "\\hh\\invariant_violation") in
3532 (* Could use emit_jmpnz for better code *)
3533 emit_expr ~need_ref
:false env e
;
3535 emit_ignored_expr
env (p, A.Call
(expr_id, [], rest
, uargs
));
3536 Emit_fatal.emit_fatal_runtime
p "invariant_violation";
3542 let l0 = Label.next_regular
() in
3543 let l1 = Label.next_regular
() in
3545 instr_string
"zend.assertions";
3546 instr_fcallbuiltin
1 1 "ini_get";
3559 | ("class_exists" | "interface_exists" | "trait_exists" as id), arg1
::_
3560 when nargs = 1 || nargs = 2 ->
3563 | "class_exists" -> KClass
3564 | "interface_exists" -> KInterface
3565 | "trait_exists" -> KTrait
3566 | _ -> failwith
"class_kind" in
3568 emit_name_string
env arg1
;
3569 instr (IOp CastString
);
3570 if nargs = 1 then instr_true
3572 emit_expr ~need_ref
:false env (List.nth_exn
args 1);
3573 instr (IOp CastBool
)
3575 instr (IMisc
(OODeclExists
class_kind))
3578 | ("exit" | "die"), _ when nargs = 0 || nargs = 1 ->
3579 Some
(emit_exit
env (List.hd
args), Flavor.Cell
)
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
->
3588 emit_expr ~need_ref
:false env arg_expr
;
3589 Emit_pos.emit_pos pos
;
3590 instr (IIsset
(IsTypeC i
))
3593 begin match get_call_builtin_func_info
lower_fq_name with
3594 | Some
(nargs, i
) when nargs = List.length
args ->
3597 emit_exprs
env args;
3598 Emit_pos.emit_pos pos
;
3605 and get_inout_arg_positions
args =
3606 List.filter_mapi
args
3607 ~
f:(fun i
-> function
3608 | _, A.Callconv
(A.Pinout
, _) -> Some i
3611 and emit_call
env pos
(_, expr_
as expr) args uargs
=
3613 | A.Id
(_, s) -> Emit_symbol_refs.add_function
s
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
3620 let flavor = if List.is_empty
inout_arg_positions then
3621 Flavor.ReturnVal
else Flavor.Cell
in
3623 gather
@@ List.init
num_uninit ~
f:(fun _ -> instr_nulluninit
);
3625 env expr nargs (not
(List.is_empty uargs
)) inout_arg_positions;
3626 emit_args_and_call
env pos
args uargs
;
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 ()
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) =
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
3652 if binary_assignment_rhs_starts_with_ref
expr
3656 emit_expr ~need_ref
:false env expr, flavor
3658 and emit_final_member_op stack_index
op mk =
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 =
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 =
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 =
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 =
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)))
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]:
3705 * Dim Warn EI:i_n ...
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
[
3715 else instr_basel
local MemberOpMode.Warn
, 0 in
3717 gather
@@ List.rev_mapi
indices
3719 let mk = MemberKey.EI
(Int64.of_int ix
) in
3721 then instr (IFinal
(QueryM
(stack_count
, QueryOp.CGet
, mk)))
3722 else instr (IBase
(Dim
(MemberOpMode.Warn
, mk)))
3729 and can_use_as_rhs_in_list_assignment
expr =
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
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
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
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 *)
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 *)
3788 |> Core_list.findi ~
f:(fun _ (_, v) -> v <> A.Omitted
)
3789 |> Option.map ~
f:fst
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)
3798 gather
(if not
is_ltr then List.rev set_instrs
else set_instrs
)
3799 | A.Omitted
-> empty
, empty
3801 (* Generate code to access the element from the array *)
3803 match local, indices with
3804 | Some
local, _ :: _ -> emit_array_get_fixed last_usage
local indices
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
3817 if is_ltr then lhs_instrs else empty
;
3825 and expr_starts_with_ref
= function
3826 | _, A.Unop
(A.Uref
, _) -> true
3829 and binary_assignment_rhs_starts_with_ref
= function
3830 | _, A.Binop
(A.Eq None
, _, e
) when expr_starts_with_ref e
-> true
3833 and emit_expr_and_unbox_if_necessary ~need_ref
env e
=
3836 | _, A.Expr_list
es ->
3838 |> Option.value_map ~
default:false ~
f:binary_assignment_rhs_starts_with_ref
3840 binary_assignment_rhs_starts_with_ref e
in
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
=
3849 match op, opt_expr2
with
3850 | LValOp.Set
, Some e
when expr_starts_with_ref e
-> LValOp.SetRef
3853 match op, expr1
, opt_expr2
with
3854 (* Special case for list destructuring, only on assignment *)
3855 | LValOp.Set
, (_, A.List
l), Some expr2
->
3857 List.exists
l ~
f: (function
3858 | _, A.Omitted
-> false
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
->
3866 if can_use_as_rhs_in_list_assignment
(snd expr2
) then
3871 emit_lval_op_list
env local [] expr1
3874 Local.scope @@ fun () ->
3875 let local = Local.get_unnamed_local
() in
3877 emit_expr ~need_ref
:false env expr2
;
3883 Local.scope @@ fun () ->
3884 let rhs_instrs, rhs_stack_size
=
3885 match opt_expr2
with
3887 | Some
(_, A.Yield af
) ->
3888 let temp = Local.get_unnamed_local
() in
3890 emit_yield
env pos af
;
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
3911 Emit_pos.emit_pos pos
;
3915 and emit_lval_op_nonlist_steps
env op (pos
, expr_
) rhs_instrs rhs_stack_size
=
3918 (* Unbelieveably, $test[] += 5; is legal in PHP, but $test[] = $test[] + 5 is not *)
3922 | LValOp.IncDec
_ -> { env with Emit_env.env_allows_array_append
= true }
3924 let handle_dollar e final_op
=
3928 let local = (get_local
env id) in
3930 | LValOp.Unset
| LValOp.IncDec
_ -> instr_cgetl
local
3931 | _ -> instr_cgetl2
local
3941 let instrs = emit_expr ~need_ref
:false env e
in
3947 | A.Lvar
(_, id) when SN.Superglobals.is_superglobal
id ->
3948 instr_string
@@ SU.Locals.strip_dollar
id,
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,
3957 | A.Lvar
id when not
(is_local_this env (snd
id)) || op = LValOp.Unset
->
3960 emit_final_local_op
op (get_local
env id)
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
3969 emit_expr ~need_ref
:false env e
,
3971 final_global_op_instrs
3973 let index_instrs, under_top
= emit_first_expr
env e
in
3981 final_global_op_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
) ->
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
,
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
4006 Emit_pos.emit_pos_then pos
@@
4007 emit_final_member_op
total_stack_size op mk in
4009 base_expr_instrs_begin;
4011 base_expr_instrs_end
;
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";
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
,
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
4039 Emit_pos.emit_pos_then pos
@@
4040 emit_final_member_op
total_stack_size op mk in
4042 base_expr_instrs_begin;
4044 base_expr_instrs_end
;
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
4061 emit_load_class_ref
env pos
cexpr,
4063 gather
[instrs; final_instr]
4065 gather
[instrs; emit_load_class_ref
env pos
cexpr],
4070 Emit_pos.emit_pos_then pos
@@
4071 emit_final_static_op
(snd
cid) prop
op in
4072 emit_class_expr
env cexpr prop
,
4077 | A.Unop
(uop
, e
) ->
4081 emit_lval_op_nonlist
env pos
op e empty rhs_stack_size
;
4086 Emit_fatal.raise_fatal_parse pos
"Can't use return value in write context"
4089 let ints_overflow_to_ints =
4090 Hhbc_options.ints_overflow_to_ints !Hhbc_options.compiler_options
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
4107 emit_box_if_necessary need_ref
@@ gather
[
4108 emit_expr ~need_ref
:false env e
; unop_instr
4111 emit_box_if_necessary need_ref
@@ gather
[
4112 emit_expr ~need_ref
:false env e
; unop_instr
4115 emit_box_if_necessary need_ref
@@ gather
4116 [instr (ILitConst
(Int
(Int64.zero
)));
4117 emit_expr ~need_ref
:false env e
;
4120 emit_box_if_necessary need_ref
@@ gather
4121 [instr (ILitConst
(Int
(Int64.zero
)));
4122 emit_expr ~need_ref
:false env e
;
4124 | A.Uincr
| A.Udecr
| A.Upincr
| A.Updecr
->
4125 begin match unop_to_incdec_op op with
4126 | None
-> emit_nyi "incdec"
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
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
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
4151 and with_temp_local
temp f =
4153 with_temp_local_with_prefix
temp (fun temp label -> empty
, f temp label) in
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
4165 let fault_label = Label.next_fault
() in
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 =
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
4194 instr_label
break_label;
4195 if leave_on_stack
then instr_cgetl
(get_local
env id) else empty
;
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
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
4218 * <code generated by `f temp 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)