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
19 module TC
= Hhas_type_constraint
20 module SN
= Naming_special_names
21 module SU
= Hhbc_string_utils
22 module ULS
= Unique_list_string
23 module Opts
= Hhbc_options
25 let inline_hhas_blocks_: (Hhas_asm.t
SMap.t
) ref = ref SMap.empty
26 let set_inline_hhas_blocks s
= inline_hhas_blocks_ := s
28 let can_inline_gen_functions () =
29 let opts = !Opts.compiler_options
in
30 Emit_env.is_hh_syntax_enabled
() &&
31 (Opts.enable_hiphop_syntax
opts) &&
32 (Opts.can_inline_gen_functions opts) &&
33 not
(Opts.jit_enable_rename_function
opts)
35 let max_array_elem_on_stack () =
36 Hhbc_options.max_array_elem_size_on_the_stack
!Hhbc_options.compiler_options
38 type genva_inline_context
=
41 | GI_list_assignment
of A.expr list
44 | IsExprExpr
of A.expr
45 | IsExprUnnamedLocal
of Local.t
47 type emit_jmp_result
= {
48 (* generated instruction sequence *)
49 instrs
: Instruction_sequence.t
;
50 (* does instruction sequence fall through *)
52 (* was label associated with emit operation used *)
56 (* Locals, array elements, and properties all support the same range of l-value
58 module LValOp
= struct
67 let jit_enable_rename_function () =
68 Hhbc_options.jit_enable_rename_function !Hhbc_options.compiler_options
70 let is_local_this env id
=
71 let scope = Emit_env.get_scope env
in
72 id
= SN.SpecialIdents.this
73 && Ast_scope.Scope.has_this
scope
74 && not
(Ast_scope.Scope.is_toplevel
scope)
76 module InoutLocals
= struct
77 (* for every local that appear as a part of inout argument and also mutated inside
78 argument list this record stores:
79 - position of the first argument when local appears as inout
80 - position of the last argument where local is mutated.
81 Within the this range at every usage of the local must be captured to make sure
82 that later when inout arguments will be written back the same value of the
91 { first_inout
= max_int
; last_write
= min_int
; num_uses
= 0; }
94 if i
< r
.first_inout
then { r
with first_inout
= i
} else r
97 if i
> r
.last_write
then { r
with last_write
= i
} else r
100 { r
with num_uses
= r
.num_uses
+ 1 }
102 let in_range i r
= i
> r
.first_inout
|| i
<= r
.last_write
103 let has_single_ref r
= r
.num_uses
< 2
105 let update name i f m
=
108 |> Option.value ~default
:not_aliased
112 let add_write name i m
= update name i
add_write m
113 let add_inout name i m
= update name i
add_inout m
114 let add_use name i m
= update name i
add_use m
116 let collect_written_variables env args
=
117 (* check value of the argument *)
118 let rec handle_arg ~is_top i acc arg
=
121 | A.Callconv
(A.Pinout
, (_
, A.Lvar
(_
, id
)))
122 when not
(is_local_this env id
) ->
123 let acc = add_use id i
acc in
124 if is_top
then add_inout id i
acc else add_write id i
acc
126 | A.Unop
(A.Uref
, (_
, A.Lvar
(_
, id
))) ->
127 let acc = add_use id i
acc in
131 let acc = add_use id i
acc in
134 (* dive into argument value *)
137 (* collect lvars on the left hand side of '=' operator *)
138 and collect_lvars_lhs i
acc e
=
140 | A.Lvar
(_
, id
) when not
(is_local_this env id
) ->
141 let acc = add_use id i
acc in
144 List.fold_left exprs ~f
:(collect_lvars_lhs i
) ~init
:acc
147 (* descend into expression *)
148 and dive i
acc expr
=
149 let visitor = object(_
)
150 inherit [_
] Ast_visitor.ast_visitor
as super
152 method! on_binop
acc bop l
r =
155 | A.Eq _
-> collect_lvars_lhs i
acc l
157 super#on_binop
acc bop l
r
159 method! on_unop
acc op e
=
162 | A.Uincr
| A.Udecr
-> collect_lvars_lhs i
acc e
164 super#on_unop
acc op e
165 (* f(inout $v) or f(&$v) *)
166 method! on_call
acc _ _ args uargs
=
167 let f = handle_arg ~is_top
:false i
in
168 let acc = List.fold_left args ~init
:acc ~
f in
169 List.fold_left uargs ~init
:acc ~
f
171 visitor#on_expr
acc expr
in
172 List.foldi args ~
f:(handle_arg ~is_top
:true) ~init
:SMap.empty
174 (* determines if value of a local 'name' that appear in parameter 'i'
175 should be saved to local because it might be overwritten later *)
176 let should_save_local_value name i aliases
=
177 Option.value_map ~default
:false ~
f:(in_range i
) (SMap.get name aliases
)
178 let should_move_local_value name aliases
=
179 Option.value_map ~default
:true ~
f:has_single_ref (SMap.get name aliases
)
182 (* Describes what kind of value is intended to be stored in local *)
183 type stored_value_kind
=
185 | Value_kind_expression
187 (* represents sequence of instructions interleaved with temp locals.
188 <i, None :: rest> - is emitted i :: <rest> (commonly used for final instructions in sequence)
189 <i, Some (l, local_kind) :: rest> is emitted as
193 setl/popl l; depending on local_kind
200 type instruction_sequence_with_locals
=
201 (Instruction_sequence.t
* (Local.t
* stored_value_kind
) option) list
203 (* converts instruction_sequence_with_locals to instruction_sequence.t *)
204 let rebuild_sequence s rest
=
205 let rec aux = function
207 | (i
, None
) :: xs
-> gather
[ i
; aux xs
]
208 | (i
, Some
(l
, kind
)) :: xs
->
209 let fault_label = Label.next_fault
() in
210 let unset = instr_unsetl l
in
211 let set = if kind
= Value_kind_expression
then instr_setl l
else instr_popl l
in
212 let try_block = gather
[
216 let fault_block = gather
[ unset; instr_unwind
; ] in
219 instr_try_fault
fault_label try_block fault_block;
223 (* result of emit_array_get *)
224 type array_get_instr
=
225 (* normal $a[..] that does not need to spill anything*)
226 | Array_get_regular
of Instruction_sequence.t
227 (* subscript expression used as inout argument that need to spill intermediate
229 load - instruction_sequence_with_locals to load value
230 store - instruction to set value back (can use locals defined in load part)
232 | Array_get_inout
of {
233 load
: instruction_sequence_with_locals
;
234 store
: Instruction_sequence.t
237 type 'a array_get_base_data
= {
239 instrs_end
: Instruction_sequence.t
;
240 setup_instrs
: Instruction_sequence.t
;
244 (* result of emit_base *)
245 type array_get_base
=
246 (* normal <base> part in <base>[..] that does not need to spill anything *)
247 | Array_get_base_regular
of Instruction_sequence.t array_get_base_data
248 (* base of subscript expression used as inout argument that need to spill
249 intermediate values *)
250 | Array_get_base_inout
of {
251 (* instructions to load base part *)
252 load
: instruction_sequence_with_locals array_get_base_data
;
253 (* instruction to load base part for setting inout argument back *)
254 store
: Instruction_sequence.t
259 | LValOp.IncDec _
-> true
262 let is_global_namespace env
=
263 Namespace_env.is_global_namespace (Emit_env.get_namespace env
)
265 let is_special_function env e args
=
269 let n = List.length args
in
273 | "define" when is_global_namespace env
->
274 begin match args
with
275 | [_
, A.String _
; _
] -> true
279 | "idx" -> not
(jit_enable_rename_function ()) && (n = 2 || n = 3)
280 | "class_alias" when is_global_namespace env
->
283 | [_
, A.String _
; _
, A.String _
]
284 | [_
, A.String _
; _
, A.String _
; _
] -> true
291 let optimize_null_check () =
292 Hhbc_options.optimize_null_check !Hhbc_options.compiler_options
294 let optimize_cuf () =
295 Hhbc_options.optimize_cuf !Hhbc_options.compiler_options
297 let hack_arr_compat_notices () =
298 Hhbc_options.hack_arr_compat_notices !Hhbc_options.compiler_options
300 let hack_arr_dv_arrs () =
301 Hhbc_options.hack_arr_dv_arrs !Hhbc_options.compiler_options
303 let php7_ltr_assign () =
304 Hhbc_options.php7_ltr_assign !Hhbc_options.compiler_options
306 (* Emit a comment in lieu of instructions for not-yet-implemented features *)
307 let emit_nyi description
=
308 instr
(IComment
(H.nyi ^
": " ^ description
))
310 (* Strict binary operations; assumes that operands are already on stack *)
312 let ints_overflow_to_ints =
313 Hhbc_options.ints_overflow_to_ints !Hhbc_options.compiler_options
in
315 | A.Plus
-> instr
(IOp
(if ints_overflow_to_ints then Add
else AddO
))
316 | A.Minus
-> instr
(IOp
(if ints_overflow_to_ints then Sub
else SubO
))
317 | A.Star
-> instr
(IOp
(if ints_overflow_to_ints then Mul
else MulO
))
318 | A.Slash
-> instr
(IOp Div
)
319 | A.Eqeq
-> instr
(IOp Eq
)
320 | A.EQeqeq
-> instr
(IOp Same
)
321 | A.Starstar
-> instr
(IOp Pow
)
322 | A.Diff
-> instr
(IOp Neq
)
323 | A.Diff2
-> instr
(IOp NSame
)
324 | A.Lt
-> instr
(IOp Lt
)
325 | A.Lte
-> instr
(IOp Lte
)
326 | A.Gt
-> instr
(IOp Gt
)
327 | A.Gte
-> instr
(IOp Gte
)
328 | A.Dot
-> instr
(IOp Concat
)
329 | A.Amp
-> instr
(IOp BitAnd
)
330 | A.Bar
-> instr
(IOp BitOr
)
331 | A.Ltlt
-> instr
(IOp Shl
)
332 | A.Gtgt
-> instr
(IOp Shr
)
333 | A.Cmp
-> instr
(IOp Cmp
)
334 | A.Percent
-> instr
(IOp Mod
)
335 | A.Xor
-> instr
(IOp BitXor
)
336 | A.LogXor
-> instr
(IOp Xor
)
337 | A.Eq _
-> emit_nyi "Eq"
340 failwith
"short-circuiting operator cannot be generated as a simple binop"
342 let binop_to_eqop op
=
343 let ints_overflow_to_ints =
344 Hhbc_options.ints_overflow_to_ints !Hhbc_options.compiler_options
in
346 | A.Plus
-> Some
(if ints_overflow_to_ints then PlusEqual
else PlusEqualO
)
347 | A.Minus
-> Some
(if ints_overflow_to_ints then MinusEqual
else MinusEqualO
)
348 | A.Star
-> Some
(if ints_overflow_to_ints then MulEqual
else MulEqualO
)
349 | A.Slash
-> Some DivEqual
350 | A.Starstar
-> Some PowEqual
351 | A.Amp
-> Some AndEqual
352 | A.Bar
-> Some OrEqual
353 | A.Xor
-> Some XorEqual
354 | A.Ltlt
-> Some SlEqual
355 | A.Gtgt
-> Some SrEqual
356 | A.Percent
-> Some ModEqual
357 | A.Dot
-> Some ConcatEqual
360 let unop_to_incdec_op op
=
361 let ints_overflow_to_ints =
362 Hhbc_options.ints_overflow_to_ints !Hhbc_options.compiler_options
in
364 | A.Uincr
-> Some
(if ints_overflow_to_ints then PreInc
else PreIncO
)
365 | A.Udecr
-> Some
(if ints_overflow_to_ints then PreDec
else PreDecO
)
366 | A.Upincr
-> Some
(if ints_overflow_to_ints then PostInc
else PostIncO
)
367 | A.Updecr
-> Some
(if ints_overflow_to_ints then PostDec
else PostDecO
)
370 let collection_type = function
371 | "Vector" -> CollectionType.Vector
372 | "Map" -> CollectionType.Map
373 | "Set" -> CollectionType.Set
374 | "Pair" -> CollectionType.Pair
375 | "ImmVector" -> CollectionType.ImmVector
376 | "ImmMap" -> CollectionType.ImmMap
377 | "ImmSet" -> CollectionType.ImmSet
378 | x
-> failwith
("unknown collection type '" ^ x ^
"'")
380 let istype_op lower_fq_id
=
381 match lower_fq_id
with
382 | "is_int" | "is_integer" | "is_long" -> Some OpInt
383 | "is_bool" -> Some OpBool
384 | "is_float" | "is_real" | "is_double" -> Some OpDbl
385 | "is_string" -> Some OpStr
386 | "is_array" -> Some OpArr
387 | "is_object" -> Some OpObj
388 | "is_null" -> Some OpNull
389 (* We don't use IsType with the resource type because `is_resource()` does
390 validation in addition to a simple type check. We will use it for
391 is-expressions because they only do type checks.
392 | "is_resource" -> Some OpRes *)
393 | "is_scalar" -> Some OpScalar
394 | "hh\\is_keyset" -> Some OpKeyset
395 | "hh\\is_dict" -> Some OpDict
396 | "hh\\is_vec" -> Some OpVec
397 | "hh\\is_varray" -> Some
(if hack_arr_dv_arrs () then OpVec
else OpVArray
)
398 | "hh\\is_darray" -> Some
(if hack_arr_dv_arrs () then OpDict
else OpDArray
)
401 (* Return the IsType op associated with a given primitive typehint. *)
402 let is_expr_primitive_op id
=
404 | "bool" -> Some OpBool
405 | "int" -> Some OpInt
406 | "float" -> Some OpDbl
407 | "string" -> Some OpStr
408 | "resource" -> Some OpRes
409 | "vec" -> Some OpVec
410 | "dict" -> Some OpDict
411 | "keyset" -> Some OpKeyset
412 | "varray" -> Some OpVArray
413 | "darray" -> Some OpDArray
416 (* See EmitterVisitor::getPassByRefKind in emitter.cpp *)
417 let get_passByRefKind is_splatted expr
=
418 let open PassByRefKind
in
419 let rec from_non_list_assignment permissive_kind expr
=
421 | A.New _
| A.Lvar _
| A.Clone _
422 | A.Import
((A.Include
| A.IncludeOnce
| A.Require
), _
) -> AllowCell
423 | A.Binop
(A.Eq None
, (_
, A.List _
), e
) ->
424 from_non_list_assignment WarnOnCell e
425 | A.Array_get
(_
, Some _
) -> permissive_kind
426 | A.Binop
(A.Eq _
, _
, _
) -> WarnOnCell
427 | A.Unop
((A.Uincr
| A.Udecr
| A.Usilence
), _
) -> WarnOnCell
428 | A.Call
((_
, A.Id
(_
, "eval")), _
, [_
], []) ->
430 | A.Call
((_
, A.Id
(_
, "array_key_exists")), _
, [_
; _
], []) ->
432 | A.Call
((_
, A.Id
(_
, ("idx"))), _
, ([_
; _
] | [_
; _
; _
]), []) ->
434 | A.Call
((_
, A.Id
(_
, ("hphp_array_idx"))), _
, [_
; _
; _
], []) ->
438 | A.NewAnonClass _
-> ErrorOnCell
439 | _
-> if is_splatted
then AllowCell
else ErrorOnCell
in
440 from_non_list_assignment AllowCell expr
442 let get_queryMOpMode need_ref op
=
444 | QueryOp.InOut
-> MemberOpMode.InOut
445 | QueryOp.CGet
-> MemberOpMode.Warn
446 | QueryOp.Empty
when need_ref
-> MemberOpMode.Define
447 | _
-> MemberOpMode.ModeNone
449 let extract_shape_field_name_pstring = function
451 Emit_type_constant.check_shape_key s
; A.String s
452 | A.SFclass_const
((pn
, _
) as id
, p
) -> A.Class_const
((pn
, A.Id id
), p
)
454 let rec text_of_expr e_
= match e_
with
455 (* Note we force string literals to become single-quoted, regardless of
456 whether they were single- or double-quoted in the source. Gross. *)
457 | A.String
(p
, s
) -> (p
, "'" ^ s ^
"'")
458 | A.Id id
| A.Lvar id
-> id
459 | A.Array_get
((p
, A.Lvar
(_
, id
)), Some
(_
, e_
)) ->
460 (p
, id ^
"[" ^ snd
(text_of_expr e_
) ^
"]")
461 | _
-> Pos.none
, "unknown" (* TODO: get text of expression *)
463 let parse_include e
=
464 let strip_backslash p
=
465 let len = String.length p
in
466 if len > 0 && p
.[0] = '
/'
then String.sub p
1 (len-1) else p
in
467 let rec split_var_lit = function
468 | _
, A.Binop
(A.Dot
, e1
, e2
) -> begin
469 let v, l
= split_var_lit e2
in
471 then let var, lit
= split_var_lit e1
in var, lit ^ l
474 | _
, A.String
(_
, lit
) -> "", lit
475 | _
, e_
-> snd
(text_of_expr e_
), "" in
476 let var, lit
= split_var_lit e
in
478 if var = "__DIR__" then ("", strip_backslash lit
) else (var, lit
) in
481 if Filename.is_relative lit
482 then Hhas_symbol_refs.SearchPathRelative lit
483 else Hhas_symbol_refs.Absolute lit
485 Hhas_symbol_refs.IncludeRootRelative
(var, strip_backslash lit
)
487 let rec expr_and_new env pos instr_to_add_new instr_to_add
= function
490 if expr_starts_with_ref e
then instr_add_new_elemv
else instr_to_add_new
492 gather
[emit_expr ~need_ref
:false env e
; emit_pos pos
; add_instr]
493 | A.AFkvalue
(k
, v) ->
495 if expr_starts_with_ref
v then instr_add_elemv
else instr_to_add
498 emit_two_exprs env
(fst k
) k
v;
502 and get_local env
(pos
, str
) =
503 if str
= SN.SpecialIdents.dollardollar
505 match Emit_env.get_pipe_var env
with
506 | None
-> Emit_fatal.raise_fatal_runtime pos
507 "Pipe variables must occur only in the RHS of pipe expressions"
511 and check_non_pipe_local e
=
513 | _
, A.Lvar
(pos
, str
) when str
= SN.SpecialIdents.dollardollar
->
514 Emit_fatal.raise_fatal_parse pos
515 "Cannot take indirect reference to a pipe variable"
519 and get_non_pipe_local (pos, str) =
520 if str = SN.SpecialIdents.dollardollar
521 then Emit_fatal.raise_fatal_parse pos
522 "Cannot take indirect reference to a pipe variable"
526 and emit_local ~notice ~need_ref env
((pos
, str
) as id
) =
527 if SN.Superglobals.is_superglobal str
529 instr_string
(SU.Locals.strip_dollar str
);
531 instr
(IGet
(if need_ref
then VGetG
else CGetG
))
534 let local = get_local env id
in
535 if is_local_this env str
&& not
(Emit_env.get_needs_local_this env
) then
539 emit_pos_then pos
@@ instr
(IMisc
(BareThis notice
))
540 else if need_ref
then
545 (* Emit CGetL2 for local variables, and return true to indicate that
546 * the result will be just below the top of the stack *)
547 and emit_first_expr env
(pos
, e
as expr
) =
549 | A.Lvar
((_
, name
) as id
)
550 when not
((is_local_this env name
&& not
(Emit_env.get_needs_local_this env
))
551 || SN.Superglobals.is_superglobal name
) ->
552 instr_cgetl2
(get_local env id
), true
554 emit_expr_and_unbox_if_necessary ~need_ref
:false env pos expr
, false
556 (* Special case for binary operations to make use of CGetL2 *)
557 and emit_two_exprs env outer_pos e1 e2
=
558 let instrs1, is_under_top
= emit_first_expr env e1
in
559 let instrs2 = emit_expr_and_unbox_if_necessary ~need_ref
:false env outer_pos e2
in
562 | _
, A.Lvar _
-> true
568 then [emit_pos outer_pos
; instrs2; instrs1]
569 else [instrs2; emit_pos outer_pos
; instrs1]
572 then [instrs1; emit_pos outer_pos
; instrs2]
573 else [instrs1; instrs2; emit_pos outer_pos
]
575 and emit_is_null env e
=
577 | (_
, A.Lvar
((_
, str
) as id
)) when not
(is_local_this env str
) ->
578 instr_istypel
(get_local env id
) OpNull
581 emit_expr_and_unbox_if_necessary ~need_ref
:false env
(fst e
) e
;
585 and emit_binop env expr op e1 e2
=
588 emit_two_exprs env
(fst expr
) e1 e2
;
592 | A.AMpamp
| A.BArbar
-> emit_short_circuit_op env expr
594 emit_lval_op env
(fst expr
) LValOp.Set e1
(Some e2
)
595 | A.Eq
(Some obop
) ->
596 begin match binop_to_eqop obop
with
597 | None
-> emit_nyi "illegal eq op"
598 | Some op
-> emit_lval_op env
(fst expr
) (LValOp.SetOp op
) e1
(Some e2
)
601 if not
(optimize_null_check ())
605 | A.EQeqeq
when snd e2
= A.Null
->
607 | A.EQeqeq
when snd e1
= A.Null
->
609 | A.Diff2
when snd e2
= A.Null
->
614 | A.Diff2
when snd e1
= A.Null
->
622 and emit_box_if_necessary pos need_ref instr
=
632 and emit_instanceof env pos e1 e2
=
634 | (_
, (_
, A.Id _
)) ->
635 let lhs = emit_expr ~need_ref
:false env e1
in
636 let from_class_ref instrs
=
643 let scope = Emit_env.get_scope env
in
644 begin match expr_to_class_expr ~resolve_self
:true scope e2
with
646 from_class_ref @@ gather
[
647 instr_fcallbuiltin
0 0 "get_called_class";
651 from_class_ref @@ gather
[
656 from_class_ref @@ gather
[
662 Hhbc_id.Class.elaborate_id
(Emit_env.get_namespace env
) name
in
668 | Class_unnamed_local _
->
669 failwith
"cannot get this shape from from A.Id"
673 emit_expr ~need_ref
:false env e1
;
674 emit_expr ~need_ref
:false env e2
;
677 and emit_as env pos e h is_nullable
=
678 if not
@@ Hhbc_options.enable_hackc_only_feature
!Hhbc_options.compiler_options
679 then Emit_fatal.raise_fatal_runtime pos
"As expression is not allowed"
681 if is_nullable
then begin
682 Local.scope @@ fun () ->
683 let local = Local.get_unnamed_local
() in
684 let true_label = Label.next_regular
() in
685 let done_label = Label.next_regular
() in
687 emit_expr ~need_ref
:false env e
;
689 (* (e is h) ? e : null *)
690 emit_is env pos
(IsExprUnnamedLocal
local) h
;
691 instr_jmpnz
true_label;
694 instr_jmp
done_label;
695 instr_label
true_label;
697 instr_label
done_label;
700 let namespace = Emit_env.get_namespace env
in
701 let tv = Emit_type_constant.hint_to_type_constant
702 ~is_typedef
:true ~tparams
:[] ~
namespace h
in
704 emit_expr ~need_ref
:false env e
;
705 instr_astypestruct
@@ Emit_adata.get_array_identifier
tv
708 and emit_is env pos
lhs h
=
709 if not
@@ Hhbc_options.enable_hackc_only_feature
!Hhbc_options.compiler_options
710 then Emit_fatal.raise_fatal_runtime pos
"Is expression is not allowed"
713 | A.Happly
((_
, id
), tyl
) when (SU.strip_global_ns id
) = "classname" ->
715 | IsExprExpr e
-> emit_is_create_local env pos e h
716 | IsExprUnnamedLocal
local ->
717 let true_label = Label.next_regular
() in
718 let done_label = Label.next_regular
() in
719 let skip_label = Label.next_regular
() in
721 instr_istypel
local OpStr
;
722 instr_jmpz
skip_label;
723 begin match List.hd tyl
with
725 emit_is_classname_exists
local true_label
728 | A.Happly
((_
, id
), _
)
729 when id
= SN.Typehints.mixed
730 || id
= SN.Typehints.nonnull
731 || id
= SN.Typehints.dynamic
->
732 emit_is_classname_exists
local true_label
733 | A.Happly
((_
, id
), _
) ->
738 instr_fcallbuiltin
3 3 "is_a";
740 instr_jmpnz
true_label;
743 emit_nyi "Type constants"
745 failwith
"Invalid classname: nullable typehint"
747 failwith
"Invalid classname: callable typehint"
749 failwith
"Invalid classname: tuple typehint"
751 failwith
"Invalid classname: shape typehint"
753 failwith
"Invalid classname: soft typehint"
756 instr_label
skip_label;
758 instr_jmp
done_label;
759 instr_label
true_label;
761 instr_label
done_label;
764 | A.Happly
((_
, id
), _
)
765 when id
= SN.Typehints.arraykey
|| id
= SN.Typehints.num
->
767 | IsExprExpr e
-> emit_is_create_local env pos e h
768 | IsExprUnnamedLocal
local ->
769 let op2 = if id
= SN.Typehints.arraykey
then OpStr
else OpDbl
in
770 let its_true = Label.next_regular
() in
771 let its_done = Label.next_regular
() in
773 instr_istypel
local OpInt
;
774 instr_jmpnz
its_true;
775 instr_istypel
local op2;
776 instr_jmpnz
its_true;
779 instr_label
its_true;
781 instr_label
its_done;
784 | A.Happly
((_
, id
), _
) when id
= SN.Typehints.nonnull
->
787 instr_istypec OpNull
;
790 | A.Happly
((_
, id
), _
)
791 when id
= SN.Typehints.mixed
792 || id
= SN.Typehints.dynamic
->
794 | A.Happly
((_
, id
), _
) when id
= SN.Typehints.this
->
796 | IsExprExpr e
-> emit_is_create_local env pos e h
797 | IsExprUnnamedLocal
local ->
798 let true_label = Label.next_regular
() in
799 let done_label = Label.next_regular
() in
800 let skip_label = Label.next_regular
() in
802 instr_istypel
local OpObj
;
803 instr_jmpz
skip_label;
805 instr_fcallbuiltin
1 1 "get_class";
807 instr_fcallbuiltin
0 0 "get_called_class";
810 instr_jmpnz
true_label;
811 instr_label
skip_label;
813 instr_jmp
done_label;
814 instr_label
true_label;
816 instr_label
done_label;
819 | A.Happly
((_
, id
), _
) ->
820 begin match is_expr_primitive_op id
with
829 instr_isnamed
(Hhbc_id.Class.from_raw_string id
);
834 | IsExprExpr e
-> emit_is_create_local env pos e h
835 | IsExprUnnamedLocal
local ->
836 let its_true = Label.next_regular
() in
837 let its_done = Label.next_regular
() in
839 instr_istypel
local OpNull
;
840 instr_jmpnz
its_true;
841 emit_is env pos
lhs h2
;
842 instr_jmpnz
its_true;
845 instr_label
its_true;
851 A.si_allows_unknown_fields
= shape_is_open
;
852 si_shape_field_list
= field_list
;
855 | IsExprExpr e
-> emit_is_create_local env pos e h
856 | IsExprUnnamedLocal
local ->
857 let pass_label = Label.next_regular
() in
858 let done_label = Label.next_regular
() in
859 let fail_label = Label.next_regular
() in
860 let count_local = Local.get_unnamed_local
() in
864 else Some
(Local.get_unnamed_local
())
866 let verify_field = emit_is_shape_verify_field
867 env pos
local fail_label expected_count
870 instr_istypel
local OpDArray
;
871 instr_jmpz
fail_label;
873 instr_fpushfuncd
1 (Hhbc_id.Function.from_raw_string
"count");
874 instr_fpassl
0 local H.Cell
;
877 instr_setl
count_local;
879 emit_is_shape_verify_count
count_local fail_label shape_is_open
882 begin match expected_count with
883 | Some expected_count_local
->
886 instr_popl expected_count_local
;
887 gather
(List.map ~
f:verify_field field_list
);
888 instr_cgetl
count_local;
889 instr_cgetl expected_count_local
;
891 instr_jmpnz
fail_label;
894 gather
(List.map ~
f:verify_field field_list
)
897 instr_jmp
pass_label;
898 instr_label
fail_label;
900 instr_jmp
done_label;
901 instr_label
pass_label;
903 instr_label
done_label;
908 | IsExprExpr e
-> emit_is_create_local env pos e h
909 | IsExprUnnamedLocal
local ->
910 let its_true = Label.next_regular
() in
911 let its_done = Label.next_regular
() in
912 let skip_label = Label.next_regular
() in
914 instr_istypel
local OpVArray
;
915 instr_jmpz
skip_label;
917 instr_int
(List.length hl
);
918 instr_fpushfuncd
1 (Hhbc_id.Function.from_raw_string
"count");
919 instr_fpassl
0 local H.Cell
;
923 instr_jmpz
skip_label;
925 gather
(List.mapi ~
f:(emit_is_tuple_elem env pos
local skip_label) hl
);
928 instr_label
skip_label;
931 instr_label
its_true;
937 emit_nyi "is expression: unsupported type const access"
939 failwith
"Soft typehints cannot be used with `is` expressions"
941 failwith
"Function typehints cannot be used with `is` expressions"
943 and emit_is_create_local env pos e h
=
944 Local.scope @@ fun () ->
945 let local = Local.get_unnamed_local
() in
947 emit_expr ~need_ref
:false env e
;
949 emit_is env pos
(IsExprUnnamedLocal
local) h
;
953 and emit_is_lhs env
lhs =
955 | IsExprExpr e
-> emit_expr ~need_ref
:false env e
956 | IsExprUnnamedLocal
local -> instr_cgetl
local
958 and emit_is_classname_exists
local true_label =
960 instr_fpushfuncd
1 (Hhbc_id.Function.from_raw_string
"class_exists");
961 instr_fpassl
0 local H.Cell
;
964 instr_jmpnz
true_label;
965 instr_fpushfuncd
1 (Hhbc_id.Function.from_raw_string
"interface_exists");
966 instr_fpassl
0 local H.Cell
;
969 instr_jmpnz
true_label;
972 and emit_is_shape_verify_count
count_local fail_label shape_is_open field_list
=
973 let num_fields = List.length field_list
in
974 let num_required_fields = List.count field_list
975 ~
f:(fun field
-> not field
.A.sf_optional
) in
979 instr_int
num_required_fields;
981 instr_jmpnz
fail_label;
983 else if num_required_fields = num_fields
986 instr_int
num_required_fields;
988 instr_jmpz
fail_label;
992 instr_int
num_required_fields;
994 instr_jmpnz
fail_label;
995 instr_cgetl
count_local;
996 instr_int
num_fields;
998 instr_jmpnz
fail_label;
1001 and emit_is_shape_verify_field env pos
local fail_label expected_count field
=
1002 let key_instrs = match field
.A.sf_name
with
1003 | Ast_defs.SFlit
(_
, field_name
) ->
1004 instr_string field_name
1005 | Ast_defs.SFclass_const
(cid
, (_
, const
)) ->
1006 emit_class_const_impl env cid const
1008 let local_fname = Local.get_unnamed_local
() in
1009 let local_f = Local.get_unnamed_local
() in
1010 let skip_label = Label.next_regular
() in
1013 instr_setl
local_fname;
1015 instr
(IMisc AKExists
);
1016 instr_jmpz
(if field
.A.sf_optional
then skip_label else fail_label);
1018 instr_basel
local MemberOpMode.Warn
;
1019 instr_querym
0 H.QueryOp.CGet
(MemberKey.EL
local_fname);
1021 emit_is env pos
(IsExprUnnamedLocal
local_f) field
.A.sf_hint
;
1022 instr_unsetl
local_f;
1023 instr_jmpz
fail_label;
1025 begin match expected_count with
1026 | Some expected_count_local
->
1028 instr
(IMutator
(IncDecL
(expected_count_local
, PreInc
)));
1034 instr_label
skip_label;
1037 and emit_is_tuple_elem env pos
local skip_label i h
=
1038 let local_i = Local.get_unnamed_local
() in
1040 instr_basel
local MemberOpMode.Warn
;
1041 instr_querym
0 H.QueryOp.CGet
(MemberKey.EI
(Int64.of_int i
));
1043 emit_is env pos
(IsExprUnnamedLocal
local_i) h
;
1044 instr_unsetl
local_i;
1045 instr_jmpz
skip_label;
1048 and emit_null_coalesce env pos e1 e2
=
1049 let end_label = Label.next_regular
() in
1051 emit_quiet_expr env pos e1
;
1053 instr_istypec OpNull
;
1055 instr_jmpnz
end_label;
1057 emit_expr ~need_ref
:false env e2
;
1058 instr_label
end_label;
1061 and emit_cast env pos hint expr
=
1063 begin match hint
with
1064 | A.Happly
((_
, id
), []) ->
1065 let id = String.lowercase_ascii
id in
1067 | _
when id = SN.Typehints.int
1068 || id = SN.Typehints.integer
-> instr
(IOp CastInt
)
1069 | _
when id = SN.Typehints.bool
1070 || id = SN.Typehints.boolean
-> instr
(IOp CastBool
)
1071 | _
when id = SN.Typehints.string ||
1072 id = "binary" -> instr
(IOp CastString
)
1073 | _
when id = SN.Typehints.object_cast
-> instr
(IOp CastObject
)
1074 | _
when id = SN.Typehints.array
-> instr
(IOp CastArray
)
1075 | _
when id = SN.Typehints.real
1076 || id = SN.Typehints.double
1077 || id = SN.Typehints.float -> instr
(IOp CastDouble
)
1078 | _
when id = "unset" -> gather
[ instr_popc
; instr_null
]
1079 | _
-> emit_nyi "cast type"
1082 emit_nyi "cast type"
1085 emit_expr ~last_pos
:pos ~need_ref
:false env expr
;
1090 and emit_conditional_expression env pos etest etrue efalse
=
1093 let false_label = Label.next_regular
() in
1094 let end_label = Label.next_regular
() in
1095 let r = emit_jmpz env etest
false_label in
1098 (* only emit true branch if there is fallthrough from condition *)
1099 begin if r.is_fallthrough
1101 emit_expr ~need_ref
:false env etrue
;
1107 (* only emit false branch if false_label is used *)
1108 begin if r.is_label_used
1110 instr_label
false_label;
1111 emit_expr ~need_ref
:false env efalse
;
1115 (* end_label is used to jump out of true branch so they should be emitted
1117 begin if r.is_fallthrough
1118 then instr_label
end_label
1123 let end_label = Label.next_regular
() in
1125 emit_expr ~last_pos
:pos ~need_ref
:false env etest
;
1127 instr_jmpnz
end_label;
1129 emit_expr ~need_ref
:false env efalse
;
1130 instr_label
end_label;
1133 and emit_new env pos expr args uargs
=
1134 let nargs = List.length args
+ List.length uargs
in
1135 let cexpr = expr_to_class_expr ~resolve_self
:true
1136 (Emit_env.get_scope env
) expr
in
1138 (* Special case for statically-known class *)
1140 let fq_id, _id_opt
=
1141 Hhbc_id.Class.elaborate_id
(Emit_env.get_namespace env
) id in
1142 Emit_symbol_refs.add_class
(Hhbc_id.Class.to_raw_string
fq_id);
1145 instr_fpushctord
nargs fq_id;
1146 emit_args_and_call env pos args uargs
;
1152 instr_fpushctors
nargs SpecialClsRef.Static
;
1153 emit_args_and_call env pos args uargs
;
1159 instr_fpushctors
nargs SpecialClsRef.Self
;
1160 emit_args_and_call env pos args uargs
;
1166 instr_fpushctors
nargs SpecialClsRef.Parent
;
1167 emit_args_and_call env pos args uargs
;
1172 emit_load_class_ref env pos
cexpr;
1173 instr_fpushctor
nargs 0;
1174 emit_args_and_call env pos args uargs
;
1178 and emit_new_anon env pos cls_idx args uargs
=
1179 let nargs = List.length args
+ List.length uargs
in
1181 instr_defcls cls_idx
;
1182 instr_fpushctori
nargs cls_idx
;
1183 emit_args_and_call env pos args uargs
;
1187 and emit_clone env expr
=
1189 emit_expr ~need_ref
:false env expr
;
1193 and emit_shape env expr fl
=
1198 ((p, extract_shape_field_name_pstring fn
), e
))
1200 emit_expr ~need_ref
:false env
(p, A.Darray
fl)
1202 and emit_call_expr ?last_pos ~need_ref env expr
=
1203 let instrs, flavor
= emit_flavored_expr env expr
in
1206 (* If the instruction has produced a ref then unbox it *)
1207 if flavor
= Flavor.ReturnVal
then
1208 emit_pos_then
(Option.value ~
default:(fst expr
) last_pos
) @@
1217 and emit_known_class_id env
id =
1218 let fq_id, _
= Hhbc_id.Class.elaborate_id
(Emit_env.get_namespace env
) id in
1219 Emit_symbol_refs.add_class
(Hhbc_id.Class.to_raw_string
fq_id);
1221 instr_string
(Hhbc_id.Class.to_raw_string
fq_id);
1225 and emit_load_class_ref env pos
cexpr =
1226 emit_pos_then pos
@@
1228 | Class_static
-> instr
(IMisc
(LateBoundCls
0))
1229 | Class_parent
-> instr
(IMisc
(Parent
0))
1230 | Class_self
-> instr
(IMisc
(Self
0))
1231 | Class_id
id -> emit_known_class_id env
id
1232 | Class_unnamed_local l
-> instr
(IGet
(ClsRefGetL
(l
, 0)))
1233 | Class_expr expr
->
1234 begin match snd expr
with
1235 | A.Lvar
((_
, id) as pos_id
)
1236 when id <> SN.SpecialIdents.this
|| (Emit_env.get_needs_local_this env
) ->
1237 let local = get_local env pos_id
in
1238 instr
(IGet
(ClsRefGetL
(local, 0)))
1242 emit_expr ~need_ref
:false env expr
;
1247 and emit_load_class_const env pos
cexpr id =
1248 (* TODO(T21932293): HHVM does not match Zend here.
1249 * Eventually remove this to match PHP7 *)
1250 match Ast_scope.Scope.get_class
(Emit_env.get_scope env
) with
1251 | Some cd
when cd
.A.c_kind
= A.Ctrait
1252 && cexpr = Class_self
1253 && SU.is_class
id ->
1254 emit_pos_then pos
@@
1255 instr_string
@@ SU.strip_global_ns
@@ snd cd
.A.c_name
1259 then instr
(IMisc
(ClsRefName
0))
1260 else instr
(ILitConst
(ClsCns
(Hhbc_id.Const.from_ast_name
id, 0)))
1263 emit_load_class_ref env pos
cexpr;
1267 and emit_class_expr env
cexpr prop
=
1269 | Class_expr
((pos
, (A.BracedExpr _
|
1272 A.Lvar
(_
, "$this") |
1274 A.Class_get _
)) as e
) ->
1275 (* if class is stored as dollar or braced expression (computed dynamically)
1276 it needs to be stored in unnamed local and eventually cleaned.
1277 Here we don't use stash_in_local because shape of the code generated
1278 for class case is different (PopC / UnsetL is the part of try block) *)
1280 Local.scope @@ fun () -> emit_expr ~need_ref
:false env e
in
1282 Local.scope @@ fun () ->
1283 let temp = Local.get_unnamed_local
() in
1284 let instrs = emit_class_expr env
(Class_unnamed_local
temp) prop
in
1285 let fault_label = Label.next_fault
() in
1306 let load_prop, load_prop_first
=
1308 | pos
, A.Id
(_
, id) ->
1309 emit_pos_then pos
@@
1310 instr_string
id, true
1311 | pos
, A.Lvar
(_
, id) ->
1312 emit_pos_then pos
@@
1313 instr_string
(SU.Locals.strip_dollar
id), true
1314 | _
, A.Dollar
(_
, A.Lvar _
as e
) ->
1315 emit_expr ~need_ref
:false env e
, false
1316 (* The outer dollar just says "class property" *)
1317 | _
, A.Dollar e
| e
->
1318 emit_expr ~need_ref
:false env e
, true
1320 let load_cls_ref = emit_load_class_ref env
(fst prop
) cexpr in
1321 if load_prop_first
then load_prop, load_cls_ref
1322 else load_cls_ref, load_prop
1324 and emit_class_get env param_num_opt qop need_ref cid prop
=
1325 let cexpr = expr_to_class_expr ~resolve_self
:false
1326 (Emit_env.get_scope env
) cid
1329 of_pair
@@ emit_class_expr env
cexpr prop
;
1330 match (param_num_opt
, qop
) with
1331 | (None
, QueryOp.CGet
) -> if need_ref
then instr_vgets
else instr_cgets
1332 | (None
, QueryOp.CGetQuiet
) -> failwith
"emit_class_get: CGetQuiet"
1333 | (None
, QueryOp.Isset
) -> instr_issets
1334 | (None
, QueryOp.Empty
) -> instr_emptys
1335 | (None
, QueryOp.InOut
) -> failwith
"emit_class_get: InOut"
1336 | (Some
(i
, h
), _
) -> instr
(ICall
(FPassS
(i
, 0, h
)))
1339 (* Class constant <cid>::<id>.
1340 * We follow the logic for the Construct::KindOfClassConstantExpression
1341 * case in emitter.cpp
1343 and emit_class_const env pos cid
(_
, id) =
1344 let cexpr = expr_to_class_expr ~resolve_self
:true
1345 (Emit_env.get_scope env
) cid
in
1348 emit_class_const_impl env cid
id
1350 emit_load_class_const env pos
cexpr id
1352 and emit_class_const_impl env cid
id =
1353 let fq_id, _id_opt
=
1354 Hhbc_id.Class.elaborate_id
(Emit_env.get_namespace env
) cid
in
1355 let fq_id_str = Hhbc_id.Class.to_raw_string
fq_id in
1356 Emit_symbol_refs.add_class
fq_id_str;
1357 emit_pos_then
(fst cid
) @@
1359 then instr_string
fq_id_str
1360 else instr
(ILitConst
(ClsCnsD
(Hhbc_id.Const.from_ast_name
id, fq_id)))
1362 and emit_yield env pos
= function
1365 emit_expr ~last_pos
:pos ~need_ref
:false env e
;
1369 | A.AFkvalue
(e1
, e2
) ->
1371 emit_expr ~last_pos
:pos ~need_ref
:false env e1
;
1372 emit_expr ~need_ref
:false env e2
;
1377 and emit_execution_operator env pos exprs
=
1380 (* special handling of ``*)
1381 | [_
, A.String
(_
, "") as e
] -> emit_expr ~need_ref
:false env e
1382 | _
-> emit_string2 env pos exprs
in
1384 instr_fpushfuncd
1 (Hhbc_id.Function.from_raw_string
"shell_exec");
1386 instr_fpass
PassByRefKind.AllowCell
0 Cell
;
1390 and emit_string2 env pos exprs
=
1394 emit_expr ~last_pos
:pos ~need_ref
:false env e
;
1396 instr
(IOp CastString
)
1400 emit_two_exprs env
(fst e1
) e1 e2
;
1403 gather
(List.map es
(fun e
->
1404 gather
[emit_expr ~need_ref
:false env e
;
1405 emit_pos pos
; instr
(IOp Concat
)]))
1408 | [] -> failwith
"String2 with zero arguments is impossible"
1411 and emit_lambda env fundef ids
=
1412 (* Closure conversion puts the class number used for CreateCl in the "name"
1413 * of the function definition *)
1414 let fundef_name = snd fundef
.A.f_name
in
1415 let class_num = int_of_string
fundef_name in
1416 let explicit_use = SSet.mem
fundef_name (Emit_env.get_explicit_use_set
()) in
1418 gather
@@ List.map ids
1421 let lid = get_local env x
in
1424 if isref
then VGetL
lid else CGetL
lid
1426 instr
(IMisc
(CreateCl
(List.length ids
, class_num)))
1429 and emit_id env
(p, s
as id) =
1430 let s = String.uppercase_ascii
s in
1432 | "__FILE__" -> instr
(ILitConst File
)
1433 | "__DIR__" -> instr
(ILitConst Dir
)
1434 | "__METHOD__" -> instr
(ILitConst Method
)
1436 (* If the expression goes on multi lines, we return the last line *)
1437 let _, line
, _, _ = Pos.info_pos_extended
p in
1439 | "__NAMESPACE__" ->
1440 let ns = Emit_env.get_namespace env
in
1441 instr_string
(Option.value ~
default:"" ns.Namespace_env.ns_name
)
1442 | "__COMPILER_FRONTEND__" -> instr_string
"hackc"
1443 | ("EXIT" | "DIE") ->
1446 let fq_id, id_opt
, contains_backslash
=
1447 Hhbc_id.Const.elaborate_id
(Emit_env.get_namespace env
) id in
1448 begin match id_opt
with
1450 Emit_symbol_refs.add_constant
(Hhbc_id.Const.to_raw_string
fq_id);
1451 Emit_symbol_refs.add_constant
id;
1453 instr
(ILitConst
(CnsU
(fq_id, id)))
1455 Emit_symbol_refs.add_constant
(snd
id);
1458 (if contains_backslash
then CnsE
fq_id else Cns
fq_id))
1461 and rename_xhp
(p, s) = (p, SU.Xhp.mangle
s)
1463 and emit_xhp env
p id attributes children
=
1464 (* Translate into a constructor call. The arguments are:
1465 * 1) struct-like array of attributes
1466 * 2) vec-like array of children
1467 * 3) filename, for debugging
1468 * 4) line number, for debugging
1470 * Spread operators are injected into the attributes array with placeholder
1471 * keys that the runtime will interpret as a spread. These keys are not
1472 * parseable as user-specified attributes, so they will never collide.
1474 let create_spread p id = (p, "...$" ^ string_of_int
(id)) in
1475 let convert_attr (spread_id
, attrs
) = function
1476 | A.Xhp_simple
(name
, v) ->
1477 let attr = (A.SFlit name
, v) in
1478 (spread_id
, attr::attrs
)
1481 let attr = (A.SFlit
(create_spread p spread_id
), e
) in
1482 (spread_id
+ 1, attr::attrs
) in
1483 let (_, attributes
) = List.fold_left ~
f:convert_attr ~init
:(0, []) attributes
in
1484 let attribute_map = p, A.Shape
(List.rev attributes
) in
1485 let children_vec = p, A.Varray children
in
1486 let filename = p, A.Id
(p, "__FILE__") in
1487 let line = p, A.Id
(p, "__LINE__") in
1488 let renamed_id = rename_xhp
id in
1489 Emit_symbol_refs.add_class
(snd
renamed_id);
1490 emit_expr ~need_ref
:false env
@@
1492 (p, A.Id
renamed_id),
1493 [attribute_map ; children_vec ; filename ; line],
1496 and emit_import env pos flavor e
=
1497 let inc = parse_include e
in
1498 Emit_symbol_refs.add_include
inc;
1499 let e, import_op
= match flavor
with
1500 | A.Include
-> e, IIncludeEvalDefine Incl
1501 | A.Require
-> e, IIncludeEvalDefine Req
1502 | A.IncludeOnce
-> e, IIncludeEvalDefine InclOnce
1504 let include_roots = Hhbc_options.include_roots !Hhbc_options.compiler_options
in
1505 match Hhas_symbol_refs.resolve_to_doc_root_relative
inc ~
include_roots with
1506 | Hhas_symbol_refs.DocRootRelative path
->
1507 (pos
, A.String
(fst
e, path
)), IIncludeEvalDefine ReqDoc
1508 | _ -> e, IIncludeEvalDefine ReqOnce
1511 emit_expr ~need_ref
:false env
e;
1516 and emit_call_isset_expr env outer_pos
(pos
, expr_
as expr
) =
1518 | A.Array_get
((_, A.Lvar
(_, x
)), Some
e) when x
= SN.Superglobals.globals
->
1520 emit_expr ~need_ref
:false env
e;
1522 instr
(IIsset IssetG
)
1524 | A.Array_get
(base_expr
, opt_elem_expr
) ->
1525 emit_array_get ~need_ref
:false env pos None
QueryOp.Isset base_expr opt_elem_expr
1526 | A.Class_get
(cid
, id) ->
1527 emit_class_get env None
QueryOp.Isset
false cid
id
1528 | A.Obj_get
(expr
, prop
, nullflavor
) ->
1529 emit_obj_get ~need_ref
:false env pos None
QueryOp.Isset expr prop nullflavor
1530 | A.Lvar
((_, name
) as id)
1531 when is_local_this env name
&& not
(Emit_env.get_needs_local_this env
) ->
1534 emit_local ~notice
:NoNotice ~need_ref
:false env
id;
1536 instr_istypec OpNull
;
1540 emit_pos_then outer_pos
@@
1541 instr
(IIsset
(IssetL
(get_local env
id)))
1544 emit_expr ~need_ref
:false env
e;
1549 emit_expr_and_unbox_if_necessary ~need_ref
:false env outer_pos expr
;
1550 instr_istypec OpNull
;
1554 and emit_call_empty_expr env outer_pos
(pos
, expr_
as expr
) =
1556 | A.Array_get
((_, A.Lvar
(_, x
)), Some
e) when x
= SN.Superglobals.globals
->
1558 emit_expr ~need_ref
:false env
e;
1562 | A.Array_get
(base_expr
, opt_elem_expr
) ->
1563 emit_array_get ~need_ref
:false env pos None
QueryOp.Empty base_expr opt_elem_expr
1564 | A.Class_get
(cid
, id) ->
1565 emit_class_get env None
QueryOp.Empty
false cid
id
1566 | A.Obj_get
(expr
, prop
, nullflavor
) ->
1567 emit_obj_get ~need_ref
:false env pos None
QueryOp.Empty expr prop nullflavor
1568 | A.Lvar
(_, id) when SN.Superglobals.is_superglobal
id ->
1570 instr_string
@@ SU.Locals.strip_dollar
id;
1575 if not
(is_local_this env
(snd
id)) ||
1576 Emit_env.get_needs_local_this env
1578 emit_pos_then outer_pos
@@
1579 instr_emptyl
(get_local env
id)
1583 instr
(IMisc
(BareThis NoNotice
));
1589 emit_expr ~last_pos
:outer_pos ~need_ref
:false env
e;
1595 emit_expr_and_unbox_if_necessary ~need_ref
:false env pos expr
;
1599 and emit_unset_expr env expr
=
1600 emit_lval_op_nonlist env
(fst expr
) LValOp.Unset expr empty
0
1602 and emit_call_isset_exprs env pos exprs
=
1604 | [] -> emit_nyi "isset()"
1605 | [expr
] -> emit_call_isset_expr env pos expr
1607 let n = List.length exprs
in
1608 let its_done = Label.next_regular
() in
1614 emit_call_isset_expr env pos expr
;
1618 instr_jmpz
its_done;
1623 instr_label
its_done
1626 and emit_exit env expr_opt
=
1628 (match expr_opt
with
1629 | None
-> instr_int
0
1630 | Some
e -> emit_expr ~need_ref
:false env
e);
1634 and emit_idx env pos es
=
1635 let default = if List.length es
= 2 then instr_null
else empty
in
1637 emit_exprs env pos es
;
1643 and emit_define env pos
s e =
1645 emit_expr ~need_ref
:false env
e;
1650 and emit_eval env pos
e =
1652 emit_expr ~need_ref
:false env
e;
1657 and emit_xhp_obj_get_raw env pos
e s nullflavor
=
1658 let fn_name = pos
, A.Obj_get
(e, (pos
, A.Id
(pos
, "getAttribute")), nullflavor
) in
1659 let args = [pos
, A.String
(pos
, SU.Xhp.clean
s)] in
1660 fst
(emit_call env pos
fn_name args [])
1662 and emit_xhp_obj_get ~need_ref env pos param_num_opt
e s nullflavor
=
1663 let call = emit_xhp_obj_get_raw env pos
e s nullflavor
in
1664 match param_num_opt
with
1665 | Some
(i
, h
) -> gather
[ call; instr_fpassr i h
]
1666 | None
-> gather
[ call;
1667 emit_pos pos
; if need_ref
then instr_boxr
else instr_unboxr
]
1669 and emit_get_class_no_args
() =
1671 instr_fpushfuncd
0 (Hhbc_id.Function.from_raw_string
"get_class");
1676 and emit_class_alias es
=
1677 let c1, c2
= match es
with
1678 | (_, A.String
(_, c1)) :: (_, A.String
(_, c2
)) :: _ -> c1, c2
1679 | _ -> failwith
"emit_class_alias: impossible"
1681 let default = if List.length es
= 2 then instr_true
else instr_string c2
in
1684 instr_alias_cls
c1 c2
1687 and try_inline_gen_call env
e =
1688 if not
(can_inline_gen_functions ()) then None
1689 else match snd
e with
1690 | A.Call
((_, A.Id
(_, s)), _, [arg
], [])
1691 when String.lowercase_ascii
(SU.strip_global_ns
s) = "gena"->
1692 Some
(inline_gena_call env arg
)
1694 try_inline_genva_call env
e GI_expression
1696 and try_inline_genva_call env
e inline_context
=
1697 if not
(can_inline_gen_functions ()) then None
1699 | pos
, A.Call
((_, A.Id
(_, s)), _, args, uargs
)
1700 when String.lowercase_ascii
(SU.strip_global_ns
s) = "genva"->
1701 try_inline_genva_call_ env pos
args uargs inline_context
1705 let label = Label.next_fault
() in
1708 instr_try_fault
label body fault
1710 and unset_in_fault temps b
=
1711 try_fault b
@@ fun () ->
1713 gather
@@ List.map temps ~
f:instr_unsetl
;
1717 (* emits iteration over the ~collection where loop body is
1719 and emit_iter ~collection
f = Local.scope @@ fun () ->
1720 let loop_end = Label.next_regular
() in
1721 let key_local = Local.get_unnamed_local
() in
1722 let value_local = Local.get_unnamed_local
() in
1723 let iter = Iterator.get_iterator
() in
1724 let iter_init = gather
[
1726 instr_iterinitk
iter loop_end value_local key_local;
1728 let loop_next = Label.next_regular
() in
1730 (* try-fault to release temp locals *)
1731 unset_in_fault
[value_local; key_local] @@ begin fun () ->
1732 (* try-fault to release iterator *)
1736 instr_label
loop_next;
1737 f value_local key_local;
1738 instr_iternextk
iter loop_next value_local key_local;
1739 instr_label
loop_end;
1740 instr_unsetl
value_local;
1741 instr_unsetl
key_local;
1746 instr_iterfree
iter;
1751 Iterator.free_iterator
();
1757 and inline_gena_call env arg
= Local.scope @@ fun () ->
1758 (* convert input to array *)
1759 let load_array = emit_expr ~need_ref
:false env arg
in
1760 let arr_local = Local.get_unnamed_local
() in
1763 if hack_arr_dv_arrs () then instr_cast_dict
else instr_cast_darray
;
1764 instr_setl
arr_local;
1767 unset_in_fault
[arr_local] @@ fun () ->
1769 instr_fpushclsmethodd
1
1770 (Hhbc_id.Method.from_raw_string
1771 (if hack_arr_dv_arrs () then "fromDict" else "fromDArray"))
1772 (Hhbc_id.Class.from_raw_string
"HH\\AwaitAllWaitHandle");
1773 instr_fpassl
0 arr_local Cell
;
1778 emit_iter ~collection
:(instr_cgetl
arr_local) @@
1779 begin fun value_local key_local ->
1781 (* generate code for
1782 arr_local[key_local] = WHResult (value_local) *)
1783 instr_cgetl
value_local;
1785 instr_basel
arr_local MemberOpMode.Define
;
1786 instr_setm
0 (MemberKey.EL
key_local);
1792 instr_pushl
arr_local;
1795 and try_inline_genva_call_ env pos
args uargs inline_context
=
1796 let args_count = List.length
args in
1797 let is_valid_list_assignment l
=
1798 Core_list.findi l ~
f:(fun i
(_, x
) -> i
>= args_count && x
<> A.Omitted
)
1799 |> Option.is_none
in
1800 let emit_list_assignment lhs rhs
=
1801 let rec combine lhs rhs
=
1802 (* ensure that list of values on left hand side and right hand size
1803 has the same length *)
1805 | l
:: lhs, r :: rhs
-> (l
, r) :: combine lhs rhs
1806 (* left hand size is smaller - pad with omitted expression *)
1807 | [], r :: rhs
-> ((Pos.none
, A.Omitted
), r) :: combine [] rhs
1809 let generate values ~is_ltr
=
1810 let rec aux lhs_acc set_acc
= function
1811 | [] -> (if is_ltr
then List.rev lhs_acc
else lhs_acc
), List.rev set_acc
1812 | ((_, A.Omitted
), _) :: tail
-> aux lhs_acc set_acc tail
1813 | (lhs, rhs
) :: tail
->
1814 let lhs_instrs, set_instrs
=
1815 emit_lval_op_list ~last_usage
:true env pos
(Some rhs
) [] lhs in
1816 aux (lhs_instrs::lhs_acc
) (set_instrs
::set_acc
) tail
in
1817 aux [] [] (if is_ltr
then values
else List.rev values
) in
1818 let reify = gather
@@ Core_list.map rhs ~
f:begin fun l
->
1825 let pairs = combine lhs rhs
in
1826 let lhs, set = generate pairs ~is_ltr
:(php7_ltr_assign ()) in
1831 gather
@@ Core_list.map
pairs
1832 ~
f:(function (_, A.Omitted
), l
-> instr_unsetl l
| _ -> empty
);
1834 match inline_context
with
1835 | GI_list_assignment l
when not
(is_valid_list_assignment l
) ->
1837 | _ when not
(List.is_empty uargs
) ->
1838 Emit_fatal.raise_fatal_runtime pos
"do not use ...$args with genva()"
1839 | GI_ignore_result
| GI_list_assignment
_ when args_count = 0 ->
1841 | GI_expression
when args_count = 0 ->
1842 Some instr_lit_empty_varray
1843 | _ when args_count > max_array_elem_on_stack () ->
1846 Local.scope @@ begin fun () ->
1848 gather
@@ Core_list.map
args ~
f:begin fun arg
->
1849 let label_done = Label.next_regular
() in
1851 emit_expr ~need_ref
:false env arg
;
1853 instr_istypec OpNull
;
1854 instr_jmpz
label_done;
1856 instr_fpushfuncd
0 (Hhbc_id.Function.from_raw_string
"HH\\Asio\\null");
1859 instr_label
label_done;
1862 let reserved_locals =
1863 List.init
args_count (fun _ -> Local.get_unnamed_local
()) in
1864 let reserved_locals_reversed =
1865 List.rev
reserved_locals in
1867 gather
@@ Core_list.map
reserved_locals_reversed ~
f:begin fun l
->
1873 let await_and_process_results =
1874 unset_in_fault
reserved_locals @@ begin fun () ->
1877 instr_awaitall
(List.hd_exn
reserved_locals_reversed) (args_count - 1);
1880 let process_results =
1881 let reify ~pop_result
=
1882 gather
@@ Core_list.map
reserved_locals ~
f:begin fun l
->
1886 if pop_result
then instr_popc
else empty
;
1889 match inline_context
with
1890 | GI_ignore_result
->
1891 reify ~pop_result
:true
1894 reify ~pop_result
:false;
1895 instr_lit_const
(if hack_arr_dv_arrs ()
1896 then (NewVecArray
args_count)
1897 else (NewVArray
args_count));
1899 | GI_list_assignment l
->
1900 emit_list_assignment l
reserved_locals in
1910 await_and_process_results;
1915 and emit_await env pos
e =
1916 begin match try_inline_gen_call env
e with
1919 let after_await = Label.next_regular
() in
1921 emit_expr ~need_ref
:false env
e;
1924 instr_istypec OpNull
;
1925 instr_jmpnz
after_await;
1927 instr_label
after_await;
1931 and emit_callconv _env kind _e
=
1934 failwith
"emit_callconv: This should have been caught at emit_arg"
1936 and emit_inline_hhas
s =
1937 match SMap.get
s !inline_hhas_blocks_ with
1940 Label_rewriter.clone_with_fresh_regular_labels
@@ Hhas_asm.instrs asm
in
1941 (* TODO: handle case when code after inline hhas is unreachable
1942 i.e. fallthrough return should not be emitted *)
1943 begin match get_estimated_stack_depth
instrs with
1944 | 0 -> gather
[ instrs; instr_null
]
1947 Emit_fatal.raise_fatal_runtime
Pos.none
1948 "Inline assembly expressions should leave the stack unchanged, \
1949 or push exactly one cell onto the stack."
1952 failwith
@@ "impossible: cannot find parsed inline hhas for '" ^
s ^
"'"
1954 and emit_expr env ?last_pos ?
(need_ref
=false) (pos
, expr_
as expr
) =
1956 | A.Float
_ | A.String
_ | A.Int
_ | A.Null
| A.False
| A.True
->
1957 let v = Ast_constant_folder.expr_to_typed_value
(Emit_env.get_namespace env
) expr
in
1958 emit_pos_then pos
@@
1959 emit_box_if_necessary pos need_ref
@@
1960 instr
(ILitConst
(TypedValue
v))
1961 | A.ParenthesizedExpr
e ->
1962 emit_expr ~need_ref env
e
1965 emit_pos
(Option.value ~
default:pos last_pos
);
1966 emit_local ~notice
:Notice ~need_ref env
id
1968 | A.Class_const
(cid
, id) ->
1969 emit_class_const env pos cid
id
1971 emit_unop ~need_ref env pos
op e
1972 | A.Binop
(op, e1
, e2
) ->
1973 emit_box_if_necessary pos need_ref
@@ emit_binop env expr
op e1 e2
1974 | A.Pipe
(e1
, e2
) ->
1975 emit_box_if_necessary pos need_ref
@@ emit_pipe env pos e1 e2
1976 | A.InstanceOf
(e1
, e2
) ->
1977 emit_box_if_necessary pos need_ref
@@ emit_instanceof env pos e1 e2
1979 emit_box_if_necessary pos need_ref
@@ emit_is env pos
(IsExprExpr
e) h
1980 | A.As
(e, h
, is_nullable
) ->
1981 emit_box_if_necessary pos need_ref
@@ emit_as env pos
e h is_nullable
1982 | A.NullCoalesce
(e1
, e2
) ->
1983 emit_box_if_necessary pos need_ref
@@ emit_null_coalesce env pos e1 e2
1984 | A.Cast
((_, hint
), e) ->
1985 emit_box_if_necessary pos need_ref
@@ emit_cast env pos hint
e
1986 | A.Eif
(etest
, etrue
, efalse
) ->
1987 emit_box_if_necessary pos need_ref
@@
1988 emit_conditional_expression env pos etest etrue efalse
1989 | A.Expr_list es
-> gather
@@ List.map es ~
f:(emit_expr ~need_ref
:false env
)
1990 | A.Array_get
((_, A.Lvar
(_, x
)), Some
e) when x
= SN.Superglobals.globals
->
1992 emit_expr ~need_ref
:false env
e;
1994 instr
(IGet
(if need_ref
then VGetG
else CGetG
))
1996 | A.Array_get
(base_expr
, opt_elem_expr
) ->
1997 let query_op = if need_ref
then QueryOp.Empty
else QueryOp.CGet
in
1998 emit_array_get ~need_ref env pos None
query_op base_expr opt_elem_expr
1999 | A.Obj_get
(expr
, prop
, nullflavor
) ->
2000 let query_op = if need_ref
then QueryOp.Empty
else QueryOp.CGet
in
2001 emit_obj_get ~need_ref env pos None
query_op expr prop nullflavor
2002 | A.Call
((_, A.Id
(_, "isset")), _, exprs
, []) ->
2003 emit_box_if_necessary pos need_ref
@@ emit_call_isset_exprs env pos exprs
2004 | A.Call
((_, A.Id
(_, "empty")), _, [expr
], []) ->
2005 emit_box_if_necessary pos need_ref
@@ emit_call_empty_expr env pos expr
2006 | A.Call
((_, A.Id
(_, "idx")), _, ([_; _] | [_; _; _] as es
), _)
2007 when not
(jit_enable_rename_function ()) ->
2008 emit_box_if_necessary pos need_ref
@@ emit_idx env pos es
2009 | A.Call
((_, A.Id
(_, "define")), _, [(_, A.String
(_, s)); e], _)
2010 when is_global_namespace env
->
2011 emit_box_if_necessary pos need_ref
@@ emit_define env pos
s e
2012 | A.Call
((_, A.Id
(_, "eval")), _, [expr
], _) ->
2013 emit_box_if_necessary pos need_ref
@@ emit_eval env pos expr
2014 | A.Call
((_, A.Id
(_, "class_alias")), _, es
, _)
2015 when is_global_namespace env
->
2016 emit_pos_then pos
@@
2017 emit_box_if_necessary pos need_ref
@@ emit_class_alias es
2018 | A.Call
((_, A.Id
(_, "get_class")), _, [], _) ->
2019 emit_box_if_necessary pos need_ref
@@ emit_get_class_no_args
()
2020 | A.Call
((_, A.Id
(_, ("exit" | "die"))), _, es
, _) ->
2021 emit_pos_then pos
@@
2022 emit_exit env
(List.hd es
)
2024 (* execution operator is compiled as call to `shell_exec` and should
2025 be handled in the same way *)
2026 | A.Execution_operator
_ ->
2027 emit_call_expr ?last_pos ~need_ref env expr
2028 | A.New
(typeexpr
, args, uargs
) ->
2029 emit_box_if_necessary pos need_ref
@@ emit_new env pos typeexpr
args uargs
2030 | A.NewAnonClass
(args, uargs
, { A.c_name
= (_, cls_name
); _ }) ->
2031 let cls_idx = int_of_string cls_name
in
2032 emit_box_if_necessary pos need_ref
@@ emit_new_anon env pos
cls_idx args uargs
2034 emit_pos_then pos
@@
2035 emit_box_if_necessary pos need_ref
@@ emit_collection env expr es
2037 emit_pos_then pos
@@
2038 let es2 = List.map ~
f:(fun (e1
, e2
) -> A.AFkvalue
(e1
, e2
)) es
in
2039 let darray_e = fst expr
, A.Darray es
in
2040 emit_box_if_necessary pos need_ref
@@ emit_collection env
darray_e es2
2042 emit_pos_then pos
@@
2043 let es2 = List.map ~
f:(fun e -> A.AFvalue
e) es
in
2044 let varray_e = fst expr
, A.Varray es
in
2045 emit_box_if_necessary pos need_ref
@@ emit_collection env
varray_e es2
2046 | A.Collection
((pos
, name
), fields
) ->
2047 emit_box_if_necessary pos need_ref
2048 @@ emit_named_collection env expr pos name fields
2050 emit_pos_then pos
@@
2051 emit_box_if_necessary pos need_ref
@@ emit_clone env
e
2053 emit_pos_then pos
@@
2054 emit_box_if_necessary pos need_ref
@@ emit_shape env expr
fl
2055 | A.Await
e -> emit_await env pos
e
2056 | A.Yield
e -> emit_yield env pos
e
2058 failwith
"yield break should be in statement position"
2059 | A.Yield_from
_ -> failwith
"complex yield_from expression"
2061 failwith
"expected Lfun to be converted to Efun during closure conversion"
2062 | A.Efun
(fundef
, ids
) ->
2063 emit_pos_then pos
@@
2064 emit_lambda env fundef ids
2065 | A.Class_get
(cid
, id) ->
2066 emit_class_get env None
QueryOp.CGet need_ref cid
id
2067 | A.String2 es
-> emit_string2 env pos es
2068 | A.BracedExpr
e -> emit_expr ~need_ref
:false env
e
2070 check_non_pipe_local
e;
2071 let instr = emit_expr ?last_pos ~need_ref
:false env
e in
2074 emit_pos
(Option.value ~
default:pos last_pos
);
2075 if need_ref
then instr_vgetn
else instr_cgetn
2078 emit_pos_then pos
@@
2080 | A.Xml
(id, attributes
, children
) ->
2081 emit_xhp env
(fst expr
) id attributes children
2082 | A.Callconv
(kind
, e) ->
2083 emit_box_if_necessary pos need_ref
@@ emit_callconv env kind
e
2084 | A.Import
(flavor
, e) -> emit_import env pos flavor
e
2085 | A.Id_type_arguments
(id, _) ->
2086 emit_pos_then pos
@@
2088 | A.Omitted
-> empty
2090 failwith
"Unsafe expression should be removed during closure conversion"
2092 failwith
"Codegen for 'suspend' operator is not supported"
2094 failwith
"List destructor can only be used as an lvar"
2096 and emit_static_collection ~transform_to_collection pos
tv =
2097 let transform_instr =
2098 match transform_to_collection
with
2099 | Some
collection_type ->
2100 instr_colfromarray
collection_type
2105 instr (ILitConst
(TypedValue
tv));
2109 and emit_value_only_collection env pos es constructor
=
2110 let limit = max_array_elem_on_stack () in
2113 [gather
@@ List.map exprs
2117 | A.AFvalue
e -> emit_expr ~need_ref
:false env
e);
2119 instr @@ ILitConst
(constructor
@@ List.length exprs
)]
2121 let outofline exprs
=
2127 | A.AFvalue
e -> gather
[emit_expr ~need_ref
:false env
e; instr_add_new_elemc
])
2129 match (List.groupi ~break
:(fun i
_ _ -> i
= limit) es
) with
2131 | x1
:: [] -> inline x1
2132 | x1
:: x2
:: _ -> gather
[inline x1
; outofline x2
]
2134 and emit_keyvalue_collection name env pos es constructor
=
2135 let name = SU.strip_ns
name in
2136 let transform_instr =
2137 if name = "dict" || name = "array" then empty
else
2138 let collection_type = collection_type name in
2139 instr_colfromarray
collection_type
2141 let add_elem_instr =
2142 if name = "array" then instr_add_new_elemc
2143 else gather
[instr_dup
; instr_add_elemc
]
2147 instr (ILitConst constructor
);
2148 gather
(List.map es ~
f:(expr_and_new env pos
add_elem_instr instr_add_elemc
));
2153 and emit_struct_array env pos es ctor
=
2156 ~
f:(function A.AFkvalue
(k
, v) ->
2157 let ns = Emit_env.get_namespace env
in
2158 (* TODO: Consider reusing folded keys from is_struct_init *)
2159 begin match snd
@@ Ast_constant_folder.fold_expr
ns k
with
2160 | A.String
(_, s) -> s, emit_expr ~need_ref
:false env
v
2161 | _ -> failwith
"impossible"
2163 | _ -> failwith
"impossible")
2166 gather
@@ List.map
es ~
f:snd
;
2168 ctor
@@ List.map
es ~
f:fst
;
2171 (* isPackedInit() returns true if this expression list looks like an
2172 * array with no keys and no ref values *)
2173 and is_packed_init ?
(hack_arr_compat
=true) es =
2174 let is_only_values =
2175 List.for_all
es ~
f:(function A.AFkvalue
_ -> false | _ -> true)
2177 let keys_are_zero_indexed_properly_formed =
2178 List.foldi
es ~init
:true ~
f:(fun i b
f -> b
&& match f with
2179 | A.AFkvalue
((_, A.Int
(_, k
)), _) ->
2181 (* arrays with int-like string keys are still considered packed
2182 and should be emitted via NewArray *)
2183 | A.AFkvalue
((_, A.String
(_, k
)), _) when not hack_arr_compat
->
2184 (try int_of_string k
= i
with Failure
_ -> false)
2185 (* True and False are considered 1 and 0, respectively *)
2186 | A.AFkvalue
((_, A.True
), _) ->
2188 | A.AFkvalue
((_, A.False
), _) ->
2194 let has_references =
2195 (* Reference can only exist as a value *)
2197 ~
f:(function A.AFkvalue
(_, e)
2198 | A.AFvalue
e -> expr_starts_with_ref
e)
2202 ~
f:(function A.AFkvalue
((_, (A.True
| A.False
)), _) -> true | _ -> false)
2204 (is_only_values || keys_are_zero_indexed_properly_formed)
2205 && not
(has_bool_keys && (hack_arr_compat
&& hack_arr_compat_notices()))
2206 && not
has_references
2207 && (List.length
es) > 0
2209 and is_struct_init env
es allow_numerics
=
2210 let has_references =
2211 (* Reference can only exist as a value *)
2213 ~
f:(function A.AFkvalue
(_, e)
2214 | A.AFvalue
e -> expr_starts_with_ref
e)
2216 let keys = ULS.empty
in
2217 let are_all_keys_non_numeric_strings, keys =
2218 List.fold_right
es ~init
:(true, keys) ~
f:(fun field
(b
, keys) ->
2220 | A.AFkvalue
(key
, _) ->
2221 let ns = Emit_env.get_namespace env
in
2222 begin match snd
@@ Ast_constant_folder.fold_expr
ns key
with
2223 | A.String
(_, s) ->
2224 b
&& (Option.is_none
2225 @@ Typed_value.string_to_int_opt
2226 ~allow_following
:false ~allow_inf
:false s),
2232 let num_keys = List.length
es in
2233 let has_duplicate_keys =
2234 ULS.cardinal
keys <> num_keys
2236 let limit = max_array_elem_on_stack () in
2237 (allow_numerics
|| are_all_keys_non_numeric_strings)
2238 && not
has_duplicate_keys
2239 && not
has_references
2240 && num_keys <= limit
2243 (* transform_to_collection argument keeps track of
2244 * what collection to transform to *)
2245 and emit_dynamic_collection env
(pos
, expr_
) es =
2246 let count = List.length
es in
2248 | A.Collection
((_, "vec"), _) ->
2249 emit_value_only_collection env pos
es (fun n -> NewVecArray
n)
2250 | A.Collection
((_, "keyset"), _) ->
2251 emit_value_only_collection env pos
es (fun n -> NewKeysetArray
n)
2252 | A.Collection
((_, "dict"), _) ->
2253 if is_struct_init env
es true then
2254 emit_struct_array env pos
es instr_newstructdict
2256 emit_keyvalue_collection
"dict" env pos
es (NewDictArray
count)
2257 | A.Collection
((_, name), _)
2258 when SU.strip_ns
name = "Set"
2259 || SU.strip_ns
name = "ImmSet"
2260 || SU.strip_ns
name = "Map"
2261 || SU.strip_ns
name = "ImmMap" ->
2262 if is_struct_init env
es true then
2264 emit_struct_array env pos
es instr_newstructdict
;
2266 instr_colfromarray
(collection_type (SU.strip_ns
name));
2269 emit_keyvalue_collection
name env pos
es (NewDictArray
count)
2272 emit_value_only_collection env pos
es
2273 (fun n -> if hack_arr_dv_arrs () then (NewVecArray
n) else (NewVArray
n))
2275 if is_struct_init env
es false then
2276 emit_struct_array env pos
es
2277 (fun arg
-> emit_pos_then pos
@@
2278 if hack_arr_dv_arrs () then instr_newstructdict arg
else instr_newstructdarray arg
)
2280 emit_keyvalue_collection
"array" env pos
es
2281 (if hack_arr_dv_arrs () then (NewDictArray
count) else (NewDArray
count))
2283 (* From here on, we're only dealing with PHP arrays *)
2284 if is_packed_init
es then
2285 emit_value_only_collection env pos
es (fun n -> NewPackedArray
n)
2286 else if is_struct_init env
es false then
2287 emit_struct_array env pos
es instr_newstructarray
2288 else if is_packed_init ~hack_arr_compat
:false es then
2289 emit_keyvalue_collection
"array" env pos
es (NewArray
count)
2291 emit_keyvalue_collection
"array" env pos
es (NewMixedArray
count)
2293 and emit_named_collection env expr pos
name fields
=
2294 let name = SU.Types.fix_casing
@@ SU.strip_ns
name in
2296 | "dict" | "vec" | "keyset" ->
2297 emit_pos_then pos
@@
2298 emit_collection env expr fields
2299 | "Vector" | "ImmVector" ->
2300 let collection_type = collection_type name in
2303 emit_pos_then pos
@@
2304 instr_newcol
collection_type
2307 emit_collection env
(pos
, A.Collection
((pos
, "vec"), fields
)) fields
;
2308 instr_colfromarray
collection_type;
2310 | "Map" | "ImmMap" | "Set" | "ImmSet" ->
2311 let collection_type = collection_type name in
2314 emit_pos_then pos
@@
2315 instr_newcol
collection_type
2318 ~transform_to_collection
:collection_type
2324 gather
(List.map fields
(function
2325 | A.AFvalue
e -> emit_expr ~need_ref
:false env
e
2326 | _ -> failwith
"impossible Pair argument"));
2327 instr (ILitConst NewPair
);
2329 | _ -> failwith
@@ "collection: " ^
name ^
" does not exist"
2331 and is_php_array
= function
2332 | _, A.Array
_ -> true
2333 | _, A.Varray
_ -> not
(hack_arr_dv_arrs ())
2334 | _, A.Darray
_ -> not
(hack_arr_dv_arrs ())
2337 and emit_collection ?
(transform_to_collection
) env expr
es =
2338 match Ast_constant_folder.expr_to_opt_typed_value
2340 ~restrict_keys
:(not
@@ is_php_array expr
)
2341 (Emit_env.get_namespace env
)
2345 emit_static_collection ~transform_to_collection
(fst expr
) tv
2347 emit_dynamic_collection env expr
es
2349 and emit_pipe env pos e1 e2
=
2350 stash_in_local ~always_stash
:true env pos e1
2351 begin fun temp _break_label
->
2352 let env = Emit_env.with_pipe_var
temp env in
2353 emit_expr ~need_ref
:false env e2
2356 (* Emit code that is equivalent to
2359 * Generate specialized code in case expr is statically known, and for
2360 * !, && and || expressions
2362 and emit_jmpz
env (pos
, expr_
as expr
) label: emit_jmp_result
=
2363 let with_pos i
= emit_pos_then pos i
in
2364 let opt = optimize_null_check () in
2365 match Ast_constant_folder.expr_to_opt_typed_value
(Emit_env.get_namespace
env) expr
with
2367 let b = Typed_value.to_bool
v in
2369 { instrs = with_pos empty
;
2370 is_fallthrough
= true;
2371 is_label_used
= false; }
2373 { instrs = with_pos @@ instr_jmp
label;
2374 is_fallthrough
= false;
2375 is_label_used
= true; }
2377 begin match expr_
with
2378 | A.Unop
(A.Unot
, e) ->
2379 emit_jmpnz
env e label
2380 | A.Binop
(A.BArbar
, e1
, e2
) ->
2381 let skip_label = Label.next_regular
() in
2382 let r1 = emit_jmpnz
env e1
skip_label in
2383 if not
r1.is_fallthrough
2386 if r1.is_label_used
then gather
[ r1.instrs; instr_label
skip_label; ]
2388 { instrs = with_pos instrs;
2389 is_fallthrough
= r1.is_label_used
;
2390 is_label_used
= false }
2392 let r2 = emit_jmpz
env e2
label in
2393 let instrs = gather
[
2396 optional
r1.is_label_used
[instr_label
skip_label];
2398 { instrs = with_pos instrs;
2399 is_fallthrough
= r2.is_fallthrough
|| r1.is_label_used
;
2400 is_label_used
= r2.is_label_used
}
2401 | A.Binop
(A.AMpamp
, e1
, e2
) ->
2402 let r1 = emit_jmpz
env e1
label in
2403 if not
r1.is_fallthrough
2405 { instrs = with_pos r1.instrs;
2406 is_fallthrough
= false;
2407 is_label_used
= r1.is_label_used
}
2409 let r2 = emit_jmpz
env e2
label in
2410 { instrs = with_pos @@ gather
[ r1.instrs; r2.instrs; ];
2411 is_fallthrough
= r2.is_fallthrough
;
2412 is_label_used
= r1.is_label_used
|| r2.is_label_used
}
2413 | A.Binop
(A.EQeqeq
, e, (_, A.Null
))
2414 | A.Binop
(A.EQeqeq
, (_, A.Null
), e) when opt ->
2415 { instrs = with_pos @@ gather
[
2419 is_fallthrough
= true;
2420 is_label_used
= true; }
2421 | A.Binop
(A.Diff2
, e, (_, A.Null
))
2422 | A.Binop
(A.Diff2
, (_, A.Null
), e) when opt ->
2423 { instrs = with_pos @@ gather
[
2427 is_fallthrough
= true;
2428 is_label_used
= true; }
2430 { instrs = with_pos @@ gather
[
2431 emit_expr_and_unbox_if_necessary ~need_ref
:false env pos expr
;
2434 is_fallthrough
= true;
2435 is_label_used
= true; }
2438 (* Emit code that is equivalent to
2441 * Generate specialized code in case expr is statically known, and for
2442 * !, && and || expressions
2444 and emit_jmpnz
env (pos
, expr_
as expr
) label: emit_jmp_result
=
2445 let with_pos i
= emit_pos_then pos i
in
2446 let opt = optimize_null_check () in
2447 match Ast_constant_folder.expr_to_opt_typed_value
(Emit_env.get_namespace
env) expr
with
2449 if Typed_value.to_bool
v
2451 { instrs = with_pos @@ instr_jmp
label;
2452 is_fallthrough
= false;
2453 is_label_used
= true }
2455 { instrs = with_pos empty
;
2456 is_fallthrough
= true;
2457 is_label_used
= false }
2459 begin match expr_
with
2460 | A.Unop
(A.Unot
, e) ->
2461 emit_jmpz
env e label
2462 | A.Binop
(A.BArbar
, e1
, e2
) ->
2463 let r1 = emit_jmpnz
env e1
label in
2464 if not
r1.is_fallthrough
then r1
2466 let r2 = emit_jmpnz
env e2
label in
2467 { instrs = with_pos @@ gather
[ r1.instrs; r2.instrs ];
2468 is_fallthrough
= r2.is_fallthrough
;
2469 is_label_used
= r1.is_label_used
|| r2.is_label_used
}
2470 | A.Binop
(A.AMpamp
, e1
, e2
) ->
2471 let skip_label = Label.next_regular
() in
2472 let r1 = emit_jmpz
env e1
skip_label in
2473 if not
r1.is_fallthrough
2475 { instrs = with_pos @@ gather
[
2477 optional
r1.is_label_used
[instr_label
skip_label]
2479 is_fallthrough
= r1.is_label_used
;
2480 is_label_used
= false }
2482 let r2 = emit_jmpnz
env e2
label in
2483 { instrs = with_pos @@ gather
[
2486 optional
r1.is_label_used
[instr_label
skip_label]
2488 is_fallthrough
= r2.is_fallthrough
|| r1.is_label_used
;
2489 is_label_used
= r2.is_label_used
}
2491 | A.Binop
(A.EQeqeq
, e, (_, A.Null
))
2492 | A.Binop
(A.EQeqeq
, (_, A.Null
), e) when opt ->
2493 { instrs = with_pos @@ gather
[
2497 is_fallthrough
= true;
2498 is_label_used
= true; }
2499 | A.Binop
(A.Diff2
, e, (_, A.Null
))
2500 | A.Binop
(A.Diff2
, (_, A.Null
), e) when opt ->
2501 { instrs = with_pos @@ gather
[
2505 is_fallthrough
= true;
2506 is_label_used
= true; }
2508 { instrs = with_pos @@ gather
[
2509 emit_expr_and_unbox_if_necessary ~need_ref
:false env pos expr
;
2512 is_fallthrough
= true;
2513 is_label_used
= true; }
2516 and emit_short_circuit_op
env expr
=
2517 let its_true = Label.next_regular
() in
2518 let its_done = Label.next_regular
() in
2519 let r1 = emit_jmpnz
env expr
its_true in
2521 if r1.is_label_used
then gather
[
2522 instr_label
its_true;
2523 emit_pos
(fst expr
);
2527 if r1.is_fallthrough
2530 emit_pos
(fst expr
);
2534 instr_label
its_done ]
2539 and emit_quiet_expr
env pos
(_, expr_
as expr
) =
2541 | A.Lvar
(name_pos
, name) when name = SN.Superglobals.globals
->
2544 instr_string
(SU.Locals.strip_dollar
name);
2546 instr (IGet CGetQuietG
)
2548 | A.Lvar
((_, name) as id) when not
(is_local_this env name) ->
2549 instr_cgetquietl
(get_local
env id)
2552 emit_expr ~need_ref
:false env e;
2556 | A.Array_get
((_, A.Lvar
(_, x
)), Some
e) when x
= SN.Superglobals.globals
->
2558 emit_expr ~need_ref
:false env e;
2560 instr (IGet CGetQuietG
)
2562 | A.Array_get
(base_expr
, opt_elem_expr
) ->
2563 emit_array_get ~need_ref
:false env pos None
QueryOp.CGetQuiet base_expr opt_elem_expr
2564 | A.Obj_get
(expr
, prop
, nullflavor
) ->
2565 emit_obj_get ~need_ref
:false env pos None
QueryOp.CGetQuiet expr prop nullflavor
2567 emit_expr ~need_ref
:false env expr
2569 (* returns instruction that will represent setter for $base[local] where
2570 is_base is true when result cell is base for another subscript operator and
2571 false when it is final left hand side of the assignment *)
2572 and emit_store_for_simple_base ~is_base
env pos elem_stack_size param_num_opt base_expr
local =
2573 let base_expr_instrs_begin,
2574 base_expr_instrs_end
,
2577 emit_base ~is_object
:false ~notice
:Notice
env MemberOpMode.Define
2578 elem_stack_size param_num_opt base_expr
in
2580 let mk = MemberKey.EL
local in
2581 if is_base
then instr_dim
MemberOpMode.Define
mk else instr_setm
0 mk in
2583 base_expr_instrs_begin;
2584 base_expr_instrs_end
;
2590 (* get LocalTempKind option for a given expression
2591 - None - expression can be emitted as is
2592 - Some Value_kind_local - expression represents local that will be
2594 - Some Value_kind_expression - spilled non-trivial expression *)
2595 and get_local_temp_kind inout_param_info
env e_opt
=
2596 match e_opt
, inout_param_info
with
2597 (* not inout case - no need to save *)
2599 (* local that will later be overwritten *)
2600 | Some
(_, A.Lvar
(_, id)), Some
(i
, aliases
)
2601 when InoutLocals.should_save_local_value id i aliases
-> Some Value_kind_local
2602 (* non-trivial expression *)
2603 | Some
e, _ -> if is_trivial
env e then None
else Some Value_kind_expression
2606 and is_trivial
env (_, e) =
2608 | A.Int
_ | A.String
_ ->
2611 not
(is_local_this env s) || Emit_env.get_needs_local_this
env
2612 | A.Array_get
(b, None
) -> is_trivial
env b
2613 | A.Array_get
(b, Some
e) -> is_trivial
env b && is_trivial
env e
2617 (* Emit code for e1[e2] or isset(e1[e2]).
2618 * If param_num_opt = Some i
2619 * then this is the i'th parameter to a function
2622 and emit_array_get ?
(no_final
=false) ?mode ~need_ref
2623 env outer_pos param_num_hint_opt qop base_expr opt_elem_expr
=
2625 emit_array_get_worker ~no_final ?mode ~need_ref ~inout_param_info
:None
2626 env outer_pos param_num_hint_opt qop base_expr opt_elem_expr
in
2628 | Array_get_regular i
-> i
2629 | Array_get_inout
_ -> failwith
"unexpected inout"
2631 and emit_array_get_worker ?
(no_final
=false) ?mode
2632 ~need_ref ~inout_param_info
2633 env outer_pos param_num_hint_opt qop base_expr opt_elem_expr
=
2634 (* Disallow use of array(..)[] *)
2635 match base_expr
, opt_elem_expr
with
2636 | (pos
, A.Array
_), None
->
2637 Emit_fatal.raise_fatal_parse pos
"Can't use array() as base in write context"
2638 | (pos
, _), None
when not
(Emit_env.does_env_allow_array_append
env)->
2639 Emit_fatal.raise_fatal_runtime pos
"Can't use [] for reading"
2641 let local_temp_kind =
2642 get_local_temp_kind inout_param_info
env opt_elem_expr
in
2643 let param_num_hint_opt =
2644 if qop
= QueryOp.InOut
then None
else param_num_hint_opt in
2645 let mode = Option.value mode ~
default:(get_queryMOpMode need_ref qop
) in
2646 let elem_expr_instrs, elem_stack_size
=
2647 emit_elem_instrs ~
local_temp_kind env opt_elem_expr
in
2648 let param_num_opt = Option.map ~
f:(fun (n, _h
) -> n) param_num_hint_opt in
2649 let mk = get_elem_member_key
env 0 opt_elem_expr
in
2651 emit_base_worker ~is_object
:false ~inout_param_info
2652 ~notice
:(match qop
with QueryOp.Isset
-> NoNotice
| _ -> Notice
)
2653 env mode elem_stack_size
param_num_opt base_expr
in
2654 let make_final param_num_hint_opt total_stack_size
=
2655 if no_final
then empty
else
2657 match param_num_hint_opt with
2660 VGetM
(total_stack_size
, mk)
2662 QueryM
(total_stack_size
, qop
, mk)
2663 | Some
(i
, h
) -> FPassM
(i
, total_stack_size
, mk, h
)
2665 match base_result, local_temp_kind with
2666 | Array_get_base_regular base
, None
->
2667 (* both base and expression don't need to store anything *)
2668 Array_get_regular
(gather
[
2674 make_final param_num_hint_opt (base
.stack_size
+ elem_stack_size
);
2676 | Array_get_base_regular base
, Some local_kind
->
2677 (* base does not need temp locals but index expression does *)
2678 let local = Local.get_unnamed_local
() in
2681 (* load base and indexer, value of indexer will be saved in local *)
2685 ], Some
(local, local_kind
);
2686 (* finish loading the value *)
2691 make_final None
(base
.stack_size
+ elem_stack_size
);
2695 emit_store_for_simple_base ~is_base
:false env outer_pos elem_stack_size
2696 param_num_opt base_expr
local in
2697 Array_get_inout
{ load; store }
2699 | Array_get_base_inout base
, None
->
2700 (* base needs temp locals, indexer - does not,
2701 simply concat two instruction sequences *)
2702 let load = base
.load.instrs_begin
@ [
2705 base
.load.instrs_end
;
2707 base
.load.setup_instrs
;
2708 make_final None
(base
.load.stack_size
+ elem_stack_size
);
2711 let store = gather
[
2715 Array_get_inout
{ load; store }
2717 | Array_get_base_inout base
, Some local_kind
->
2718 (* both base and index need temp locals,
2719 create local for index value *)
2720 let local = Local.get_unnamed_local
() in
2723 base
.load.instrs_begin
@ [
2724 (* load index, value will be saved in local *)
2725 elem_expr_instrs, Some
(local, local_kind
);
2727 base
.load.instrs_end
;
2729 base
.load.setup_instrs
;
2730 make_final None
(base
.load.stack_size
+ elem_stack_size
);
2733 let store = gather
[
2735 instr_setm
0 (MemberKey.EL
local);
2737 Array_get_inout
{ load; store }
2739 (* Emit code for e1->e2 or e1?->e2 or isset(e1->e2).
2740 * If param_num_opt = Some i
2741 * then this is the i'th parameter to a function
2743 and emit_obj_get ~need_ref
env pos
param_num_hint_opt qop
expr prop null_flavor
=
2746 when id = SN.SpecialIdents.this
&& null_flavor
= A.OG_nullsafe
->
2747 Emit_fatal.raise_fatal_parse
2748 pos
"?-> is not allowed with $this"
2750 begin match snd prop
with
2751 | A.Id
(_, s) when SU.Xhp.is_xhp
s ->
2752 emit_xhp_obj_get ~need_ref
env pos
param_num_hint_opt expr s null_flavor
2754 let param_num_opt = Option.map ~
f:(fun (n, _h
) -> n) param_num_hint_opt in
2755 let mode = get_queryMOpMode need_ref qop
in
2756 let mk, prop_expr_instrs
, prop_stack_size
=
2757 emit_prop_expr
env null_flavor
0 prop
in
2758 let base_expr_instrs_begin,
2759 base_expr_instrs_end
,
2763 ~is_object
:true ~notice
:Notice
2764 env mode prop_stack_size
param_num_opt expr
2766 let total_stack_size = prop_stack_size
+ base_stack_size
in
2769 match param_num_hint_opt with
2772 VGetM
(total_stack_size, mk)
2774 QueryM
(total_stack_size, qop
, mk)
2775 | Some
(i
, h
) -> FPassM
(i
, total_stack_size, mk, h
)
2778 base_expr_instrs_begin;
2780 base_expr_instrs_end
;
2787 and is_special_class_constant_accessed_with_class_id
env (_, cName
) id =
2788 (* TODO(T21932293): HHVM does not match Zend here.
2789 * Eventually remove this to match PHP7 *)
2791 (not
(SU.is_self cName
|| SU.is_parent cName
|| SU.is_static cName
)
2792 || (Ast_scope.Scope.is_in_trait
(Emit_env.get_scope
env)) && SU.is_self cName
)
2794 and emit_elem_instrs
env ~
local_temp_kind opt_elem_expr
=
2795 match opt_elem_expr
with
2796 (* These all have special inline versions of member keys *)
2797 | Some
(_, (A.Int
_ | A.String
_)) -> empty
, 0
2798 | Some
(_, (A.Lvar
((_, id) as pid
))) when not
(is_local_this env id) ->
2799 if Option.is_some
local_temp_kind
2800 then instr_cgetquietl
(get_local
env pid
), 0
2802 | Some
(_, (A.Class_const
((_, A.Id cid
), (_, id))))
2803 when is_special_class_constant_accessed_with_class_id
env cid
id -> empty
, 0
2804 | Some
expr -> emit_expr ~need_ref
:false env expr, 1
2807 (* Get the member key for an array element expression: the `elem` in
2808 * expressions of the form `base[elem]`.
2809 * If the array element is missing, use the special key `W`.
2811 and get_elem_member_key
env stack_index opt_expr
=
2813 (* Special case for local *)
2814 | Some
(_, A.Lvar
id) when not
(is_local_this env (snd
id)) ->
2815 MemberKey.EL
(get_local
env id)
2816 (* Special case for literal integer *)
2817 | Some
(_, A.Int
(_, str
) as int_expr
)->
2818 let open Ast_constant_folder
in
2819 let namespace = Emit_env.get_namespace
env in
2820 begin match expr_to_typed_value
namespace int_expr
with
2821 | TV.Int i
-> MemberKey.EI i
2822 | _ -> failwith
(str ^
" is not a valid integer index")
2824 (* Special case for literal string *)
2825 | Some
(_, A.String
(_, str
)) -> MemberKey.ET str
2826 (* Special case for class name *)
2827 | Some
(_, (A.Class_const
((_, A.Id
(p, cName
as cid
)), (_, id))))
2828 when is_special_class_constant_accessed_with_class_id
env cid
id ->
2829 (* Special case for self::class in traits *)
2830 (* TODO(T21932293): HHVM does not match Zend here.
2831 * Eventually remove this to match PHP7 *)
2833 match SU.is_self
cName,
2834 Ast_scope.Scope.get_class
(Emit_env.get_scope
env)
2836 | true, Some cd
-> SU.strip_global_ns
@@ snd cd
.A.c_name
2840 Hhbc_id.Class.elaborate_id
(Emit_env.get_namespace
env) (p, cName) in
2841 MemberKey.ET
(Hhbc_id.Class.to_raw_string
fq_id)
2843 | Some
_ -> MemberKey.EC stack_index
2844 (* ELement missing (so it's array append) *)
2845 | None
-> MemberKey.W
2847 (* Get the member key for a property, and return any instructions and
2848 * the size of the stack in the case that the property cannot be
2849 * placed inline in the instruction. *)
2850 and emit_prop_expr
env null_flavor stack_index prop_expr
=
2852 match snd prop_expr
with
2853 | A.Id
((_, name) as id) when String_utils.string_starts_with
name "$" ->
2854 MemberKey.PL
(get_local
env id)
2855 (* Special case for known property name *)
2857 | A.String
(_, id) ->
2858 let pid = Hhbc_id.Prop.from_ast_name
id in
2859 begin match null_flavor
with
2860 | Ast.OG_nullthrows
-> MemberKey.PT
pid
2861 | Ast.OG_nullsafe
-> MemberKey.QT
pid
2863 | A.Lvar
((_, name) as id) when not
(is_local_this env name) ->
2864 MemberKey.PL
(get_local
env id)
2867 MemberKey.PC stack_index
2869 (* For nullsafe access, insist that property is known *)
2871 | MemberKey.PL
_ | MemberKey.PC
_ ->
2872 if null_flavor
= A.OG_nullsafe
then
2873 Emit_fatal.raise_fatal_parse
(fst prop_expr
)
2874 "?-> can only be used with scalar property names"
2879 mk, emit_expr ~need_ref
:false env prop_expr
, 1
2883 (* Emit code for a base expression `expr` that forms part of
2884 * an element access `expr[elem]` or field access `expr->fld`.
2885 * The instructions are divided into three sections:
2886 * 1. base and element/property expression instructions:
2887 * push non-trivial base and key values on the stack
2888 * 2. base selector instructions: a sequence of Base/Dim instructions that
2889 * actually constructs the base address from "member keys" that are inlined
2890 * in the instructions, or pulled from the key values that
2891 * were pushed on the stack in section 1.
2892 * 3. (constructed by the caller) a final accessor e.g. QueryM or setter
2893 * e.g. SetOpM instruction that has the final key inlined in the
2894 * instruction, or pulled from the key values that were pushed on the
2895 * stack in section 1.
2896 * The function returns a triple (base_instrs, base_setup_instrs, stack_size)
2897 * where base_instrs is section 1 above, base_setup_instrs is section 2, and
2898 * stack_size is the number of values pushed onto the stack by section 1.
2900 * For example, the r-value expression $arr[3][$ix+2]
2902 * # Section 1, pushing the value of $ix+2 on the stack
2906 * # Section 2, constructing the base address of $arr[3]
2909 * # Section 3, indexing the array using the value at stack position 0 (EC:0)
2910 * QueryM 1 CGet EC:0
2913 and emit_base ~is_object ~notice
env mode base_offset
param_num_opt e =
2914 let result = emit_base_worker ~is_object ~notice ~inout_param_info
:None
2915 env mode base_offset
param_num_opt e in
2917 | Array_get_base_regular i
->
2922 | Array_get_base_inout
_ -> failwith
"unexpected inout"
2924 and emit_base_worker ~is_object ~notice ~inout_param_info
env mode base_offset
2925 param_num_opt (pos
, expr_
as expr) =
2927 if mode = MemberOpMode.InOut
then MemberOpMode.Warn
else mode in
2928 let local_temp_kind =
2929 get_local_temp_kind inout_param_info
env (Some
expr) in
2930 (* generic handler that will try to save local into temp if this is necessary *)
2931 let emit_default instrs_begin instrs_end setup_instrs stack_size
=
2932 match local_temp_kind with
2933 | Some local_temp
->
2934 let local = Local.get_unnamed_local
() in
2935 Array_get_base_inout
{
2937 (* run begin part, result will be stored into temp *)
2938 instrs_begin
= [instrs_begin
, Some
(local, local_temp
)];
2942 store = instr_basel
local MemberOpMode.Define
2945 Array_get_base_regular
{
2946 instrs_begin
; instrs_end
; setup_instrs
; stack_size
}
2949 | A.Lvar
(name_pos
, x
) when SN.Superglobals.is_superglobal x
->
2951 (emit_pos_then name_pos
@@ instr_string
(SU.Locals.strip_dollar x
))
2954 match param_num_opt with
2955 | None
-> BaseGC
(base_offset
, base_mode)
2956 | Some i
-> FPassBaseGC
(i
, base_offset
)
2960 | A.Lvar
(thispos
, x
) when is_object
&& x
= SN.SpecialIdents.this
->
2962 (emit_pos_then thispos
@@ instr (IMisc CheckThis
))
2964 (instr (IBase BaseH
))
2967 | A.Lvar
((_, str
) as id)
2968 when not
(is_local_this env str
) || Emit_env.get_needs_local_this
env ->
2969 let v = get_local
env id in
2970 if Option.is_some
local_temp_kind
2973 (instr_cgetquietl
v)
2975 (instr_basel
v base_mode)
2983 match param_num_opt with
2984 | None
-> BaseL
(v, base_mode)
2985 | Some i
-> FPassBaseL
(i
, v)
2992 (emit_local ~notice ~need_ref
:false env id)
2994 (instr (IBase
(BaseC base_offset
)))
2997 | A.Array_get
((_, A.Lvar
(_, x
)), Some
(_, A.Lvar y
))
2998 when x
= SN.Superglobals.globals
->
2999 let v = get_local
env y
in
3004 match param_num_opt with
3005 | None
-> BaseGL
(v, base_mode)
3006 | Some i
-> FPassBaseGL
(i
, v)
3010 | A.Array_get
((_, A.Lvar
(_, x
)), Some
e) when x
= SN.Superglobals.globals
->
3011 let elem_expr_instrs = emit_expr ~need_ref
:false env e in
3016 match param_num_opt with
3017 | None
-> BaseGC
(base_offset
, base_mode)
3018 | Some i
-> FPassBaseGC
(i
, base_offset
)
3021 (* $a[] can not be used as the base of an array get unless as an lval *)
3022 | A.Array_get
(_, None
) when not
(Emit_env.does_env_allow_array_append
env) ->
3023 Emit_fatal.raise_fatal_runtime pos
"Can't use [] for reading"
3024 (* base is in turn array_get - do a specific handling for inout params
3026 | A.Array_get
(base_expr
, opt_elem_expr
) ->
3028 let local_temp_kind =
3029 get_local_temp_kind inout_param_info
env opt_elem_expr
in
3030 let elem_expr_instrs, elem_stack_size
=
3031 emit_elem_instrs ~
local_temp_kind env opt_elem_expr
in
3034 ~notice ~is_object
:false ~inout_param_info
3035 env mode (base_offset
+ elem_stack_size
) param_num_opt base_expr
3037 let mk = get_elem_member_key
env base_offset opt_elem_expr
in
3038 let make_setup_instrs base_setup_instrs
=
3042 match param_num_opt with
3043 | None
-> Dim
(mode, mk)
3044 | Some i
-> FPassDim
(i
, mk)
3047 begin match base_result, local_temp_kind with
3048 (* both base and index don't use temps - fallback to default handler *)
3049 | Array_get_base_regular base
, None
->
3056 (make_setup_instrs base
.setup_instrs
)
3057 (base
.stack_size
+ elem_stack_size
)
3058 | Array_get_base_regular base
, Some local_temp
->
3059 (* base does not need temps but index does *)
3060 let local = Local.get_unnamed_local
() in
3061 let instrs_begin = gather
[
3065 Array_get_base_inout
{
3067 (* store result of instr_begin to temp *)
3068 instrs_begin = [instrs_begin, Some
(local, local_temp
)];
3069 instrs_end
= base
.instrs_end
;
3070 setup_instrs
= make_setup_instrs base
.setup_instrs
;
3071 stack_size
= base
.stack_size
+ elem_stack_size
};
3072 store = emit_store_for_simple_base ~is_base
:true env pos elem_stack_size
3073 param_num_opt base_expr
local
3075 | Array_get_base_inout base
, None
->
3076 (* base needs temps, index - does not *)
3077 Array_get_base_inout
{
3079 (* concat index evaluation to base *)
3080 instrs_begin = base
.load.instrs_begin @ [elem_expr_instrs, None
];
3081 instrs_end
= base
.load.instrs_end
;
3082 setup_instrs
= make_setup_instrs base
.load.setup_instrs
;
3083 stack_size
= base
.load.stack_size
+ elem_stack_size
};
3086 instr_dim
MemberOpMode.Define
mk;
3089 | Array_get_base_inout base
, Some local_kind
->
3090 (* both base and index needs locals *)
3091 let local = Local.get_unnamed_local
() in
3092 Array_get_base_inout
{
3095 base
.load.instrs_begin @ [
3096 (* evaluate index, result will be stored in local *)
3097 elem_expr_instrs, Some
(local, local_kind
)
3099 instrs_end
= base
.load.instrs_end
;
3100 setup_instrs
= make_setup_instrs base
.load.setup_instrs
;
3101 stack_size
= base
.load.stack_size
+ elem_stack_size
};
3104 instr_dim
MemberOpMode.Define
(MemberKey.EL
local);
3109 | A.Obj_get
(base_expr
, prop_expr
, null_flavor
) ->
3110 begin match snd prop_expr
with
3111 | A.Id
(_, s) when SU.Xhp.is_xhp
s ->
3113 (emit_xhp_obj_get_raw
env pos base_expr
s null_flavor
)
3115 (gather
[ instr_baser base_offset
])
3118 let mk, prop_expr_instrs
, prop_stack_size
=
3119 emit_prop_expr
env null_flavor base_offset prop_expr
in
3120 let base_expr_instrs_begin,
3121 base_expr_instrs_end
,
3124 emit_base ~notice
:Notice ~is_object
:true
3125 env mode (base_offset
+ prop_stack_size
) param_num_opt base_expr
3127 let total_stack_size = prop_stack_size
+ base_stack_size
in
3130 match param_num_opt with
3131 | None
-> Dim
(mode, mk)
3132 | Some i
-> FPassDim
(i
, mk)
3136 base_expr_instrs_begin;
3139 base_expr_instrs_end
3147 | A.Class_get
(cid
, (_, A.Dollar
(_, A.Lvar
id))) ->
3148 let cexpr = expr_to_class_expr ~resolve_self
:false
3149 (Emit_env.get_scope
env) cid
in
3150 (* special case for $x->$$y: use BaseSL *)
3152 (emit_load_class_ref
env pos
cexpr)
3154 (emit_pos_then pos
@@
3155 instr_basesl
(get_local
env id))
3157 | A.Class_get
(cid
, prop
) ->
3158 let cexpr = expr_to_class_expr ~resolve_self
:false
3159 (Emit_env.get_scope
env) cid
in
3160 let cexpr_begin, cexpr_end
= emit_class_expr
env cexpr prop
in
3164 (instr_basesc base_offset
)
3166 | A.Dollar
(_, A.Lvar
id as e) ->
3167 check_non_pipe_local
e;
3168 let local = get_local
env id in
3172 (emit_pos_then pos
@@
3173 match param_num_opt with
3174 | None
-> instr_basenl
local base_mode
3175 | Some i
-> instr (IBase
(FPassBaseNL
(i
, local))
3179 let base_expr_instrs = emit_expr ~need_ref
:false env e in
3183 (emit_pos_then pos
@@
3184 instr_basenc base_offset
base_mode)
3187 let base_expr_instrs, flavor
= emit_flavored_expr
env expr in
3189 (if binary_assignment_rhs_starts_with_ref
expr
3190 then gather
[base_expr_instrs; instr_unbox
]
3191 else base_expr_instrs)
3193 (emit_pos_then pos
@@
3194 instr (IBase
(if flavor
= Flavor.ReturnVal
3195 then BaseR base_offset
else BaseC base_offset
)))
3198 and get_pass_by_ref_hint
expr =
3199 if Emit_env.is_systemlib
() || not
(Emit_env.is_hh_syntax_enabled
())
3200 then Any
else (if expr_starts_with_ref
expr then Ref
else Cell
)
3204 | A.Unop
(A.Uref
, e) -> e
3207 and emit_ignored_expr
env ?
(pop_pos
= Pos.none
) e =
3209 | A.Expr_list
es -> gather
@@ List.map ~
f:(emit_ignored_expr
env ~pop_pos
) es
3211 let instrs, flavor
= emit_flavored_expr ~last_pos
:pop_pos
env e in
3214 emit_pos_then pop_pos
@@ instr_pop flavor
;
3217 (* Emit code to construct the argument frame and then make the call *)
3218 and emit_args_and_call
env call_pos
args uargs
=
3219 let args_count = List.length
args in
3220 let all_args = args @ uargs
in
3222 if has_inout_args
args
3223 then InoutLocals.collect_written_variables env args
3226 (* generic emit function *)
3227 let default_emit i
expr hint
=
3228 let instrs, flavor
= emit_flavored_expr
env ~last_pos
:call_pos
expr in
3229 let is_splatted = i
>= args_count in
3231 if is_splatted && flavor
= Flavor.ReturnVal
3232 then gather
[ instrs; instr_unboxr
] else instrs
3235 match is_splatted, flavor
with
3236 | false, Flavor.Ref
-> instr_fpassv i hint
3237 | false, Flavor.ReturnVal
-> instr_fpassr i hint
3238 | false, Flavor.Cell
3239 | true, _ -> instr_fpass
(get_passByRefKind is_splatted expr) i hint
3246 let rec aux i
args inout_setters
=
3250 Hhbc_options.use_msrv_for_inout
!Hhbc_options.compiler_options
in
3251 let use_unpack = (uargs
!= []) in
3252 let num_inout = List.length inout_setters
in
3253 let use_callm = msrv && (num_inout > 0) in
3254 let nargs = List.length
all_args in
3255 let instr_call = match (use_callm, use_unpack) with
3256 | (false, false) -> instr (ICall
(FCall
nargs))
3257 | (false, true) -> instr (ICall
(FCallUnpack
nargs))
3258 | (true, false) -> instr (ICall
(FCallM
(nargs, num_inout + 1)))
3259 | (true, true) -> instr (ICall
(FCallUnpackM
(nargs, num_inout + 1))) in
3263 (* propagate inout values back *)
3264 if List.is_empty inout_setters
3267 let local = Local.get_unnamed_local
() in
3269 if msrv then empty
else instr_unboxr
;
3270 Emit_inout_helpers.emit_list_set_for_inout_call
local
3271 (List.rev inout_setters
)
3276 let next c
= gather
[ c
; aux (i
+ 1) rest inout_setters
] in
3277 let is_inout, expr =
3279 | A.Callconv
(A.Pinout
, e) -> true, e
3282 let hint = get_pass_by_ref_hint
expr in
3283 let pos, expr_
= strip_ref
expr in
3284 if i
>= args_count then
3285 next @@ default_emit i
expr hint
3288 | A.Lvar
(name_pos
, x
) when SN.Superglobals.is_superglobal x
->
3291 instr_string
(SU.Locals.strip_dollar x
);
3292 instr_fpassg i
hint;
3294 | A.Lvar
((_, s) as id) when is_inout ->
3296 (instr_setl
@@ Local.Named
s) :: inout_setters in
3297 let not_in_try = not
(Emit_env.is_in_try
env) in
3299 if not_in_try && (InoutLocals.should_move_local_value s aliases)
3300 then gather
[ instr_null
; instr_popl
(get_local
env id) ]
3303 emit_expr ~need_ref
:false env expr;
3306 instr_fpassc i
hint;
3307 aux (i
+ 1) rest
inout_setters
3309 | A.Lvar
((_, str
) as id)
3310 when not
(is_local_this env str
) || Emit_env.get_needs_local_this
env ->
3311 next @@ instr_fpassl i
(get_local
env id) hint
3313 next @@ emit_expr ~need_ref
:false env e
3315 check_non_pipe_local
e;
3317 emit_expr ~need_ref
:false env e;
3318 instr_fpassn i
hint;
3320 | A.Array_get
((_, A.Lvar
(_, x
)), Some
e) when x
= SN.Superglobals.globals
->
3322 emit_expr ~need_ref
:false env e;
3323 instr_fpassg i
hint;
3326 | A.Array_get
(base_expr
, opt_elem_expr
) ->
3329 let array_get_result =
3330 emit_array_get_worker ~need_ref
:false
3331 ~inout_param_info
:(Some
(i
, aliases)) env pos (Some
(i
, hint))
3332 QueryOp.InOut base_expr opt_elem_expr
in
3333 match array_get_result with
3334 | Array_get_regular
instrs ->
3337 emit_array_get ~no_final
:true ~need_ref
:false
3338 ~
mode:MemberOpMode.Define
3339 env pos None
QueryOp.InOut base_expr opt_elem_expr
in
3342 instr_setm
0 (get_elem_member_key
env 0 opt_elem_expr
);
3346 instr_fpassc i
hint;
3347 aux (i
+ 1) rest
(setter :: inout_setters) ]
3348 | Array_get_inout
{ load; store } ->
3349 rebuild_sequence load @@ begin fun () ->
3351 instr_fpassc i
hint;
3352 aux (i
+ 1) rest
(store :: inout_setters)
3357 next @@ emit_array_get
3359 { env with Emit_env.env_allows_array_append
= true }
3360 pos (Some
(i
, hint))
3361 QueryOp.CGet base_expr opt_elem_expr
3363 | A.Obj_get
(e1
, e2
, nullflavor
) ->
3364 next @@ emit_obj_get ~need_ref
:false env pos (Some
(i
, hint)) QueryOp.CGet e1 e2 nullflavor
3366 | A.Class_get
(cid
, e) ->
3367 next @@ emit_class_get
env (Some
(i
, hint)) QueryOp.CGet
false cid
e
3369 | A.Binop
(A.Eq None
, (_, A.List
_ as e), (_, A.Lvar
id)) ->
3370 let local = get_local
env id in
3371 let lhs_instrs, set_instrs
=
3372 emit_lval_op_list
env pos (Some
local) [] e in
3376 instr_fpassl i
local hint;
3378 | _ when expr_starts_with_ref
expr ->
3379 (* pass expression with a stripped reference but
3380 use hint from the original expression *)
3381 next @@ default_emit i
(pos, expr_
) hint
3383 next @@ default_emit i
expr hint
3385 Local.scope @@ fun () -> aux 0 all_args []
3387 (* Expression that appears in an object context, such as expr->meth(...) *)
3388 and emit_object_expr
env ?last_pos
(_, expr_
as expr) =
3390 | A.Lvar
(_, x
) when is_local_this env x
->
3392 | _ -> emit_expr ?last_pos ~need_ref
:false env expr
3394 and emit_call_lhs_with_this
env instrs = Local.scope @@ fun () ->
3395 let id = Pos.none
, SN.SpecialIdents.this
in
3396 let temp = Local.get_unnamed_local
() in
3398 emit_local ~notice
:Notice ~need_ref
:false env id;
3400 with_temp_local
temp
3401 begin fun temp _ -> gather
[
3404 instr (IGet
(ClsRefGetL
(temp, 0)));
3410 and has_inout_args
es =
3411 List.exists
es ~
f:(function _, A.Callconv
(A.Pinout
, _) -> true | _ -> false)
3413 and emit_call_lhs
env outer_pos
(pos, expr_
as expr) nargs has_splat inout_arg_positions
=
3414 let has_inout_args = List.length inout_arg_positions
<> 0 in
3416 | A.Obj_get
(obj
, (_, A.Id
((_, str
) as id)), null_flavor
)
3417 when str
.[0] = '$'
->
3419 emit_object_expr ~last_pos
:outer_pos
env obj
;
3420 instr_cgetl
(get_local
env id);
3421 instr_fpushobjmethod
nargs null_flavor inout_arg_positions
;
3423 | A.Obj_get
(obj
, (_, A.String
(_, id)), null_flavor
)
3424 | A.Obj_get
(obj
, (_, A.Id
(_, id)), null_flavor
) ->
3425 let name = Hhbc_id.Method.from_ast_name
id in
3428 then Hhbc_id.Method.add_suffix
name
3429 (Emit_inout_helpers.inout_suffix inout_arg_positions
)
3432 emit_object_expr
env ~last_pos
:outer_pos obj
;
3434 instr_fpushobjmethodd
nargs name null_flavor
;
3436 | A.Obj_get
(obj
, method_expr
, null_flavor
) ->
3439 emit_object_expr
env ~last_pos
:outer_pos obj
;
3440 emit_expr ~need_ref
:false env method_expr
;
3441 instr_fpushobjmethod
nargs null_flavor inout_arg_positions
;
3444 | A.Class_const
(cid
, (_, id)) ->
3445 let cexpr = expr_to_class_expr ~resolve_self
:false
3446 (Emit_env.get_scope
env) cid
in
3447 let method_id = Hhbc_id.Method.from_ast_name
id in
3450 then Hhbc_id.Method.add_suffix
method_id
3451 (Emit_inout_helpers.inout_suffix inout_arg_positions
)
3453 begin match cexpr with
3454 (* Statically known *)
3456 let fq_cid, _ = Hhbc_id.Class.elaborate_id
(Emit_env.get_namespace
env) cid
in
3457 instr_fpushclsmethodd
nargs method_id fq_cid
3459 instr_fpushclsmethodsd
nargs SpecialClsRef.Static
method_id
3461 instr_fpushclsmethodsd
nargs SpecialClsRef.Self
method_id
3463 instr_fpushclsmethodsd
nargs SpecialClsRef.Parent
method_id
3464 | Class_expr
(_, A.Lvar
(_, x
)) when x
= SN.SpecialIdents.this
->
3465 let method_name = Hhbc_id.Method.to_raw_string
method_id in
3467 emit_call_lhs_with_this
env @@ instr_string
method_name;
3468 instr_fpushclsmethod
nargs []
3471 let method_name = Hhbc_id.Method.to_raw_string
method_id in
3473 of_pair
@@ emit_class_expr
env cexpr (Pos.none
, A.Id
(Pos.none
, method_name));
3474 instr_fpushclsmethod
nargs []
3478 | A.Class_get
(cid
, e) ->
3479 let cexpr = expr_to_class_expr ~resolve_self
:false
3480 (Emit_env.get_scope
env) cid
in
3481 let expr_instrs = emit_expr ~need_ref
:false env e in
3482 begin match cexpr with
3484 gather
[expr_instrs;
3485 emit_pos outer_pos
; instr_fpushclsmethods
nargs SpecialClsRef.Static
]
3487 gather
[expr_instrs;
3488 emit_pos outer_pos
; instr_fpushclsmethods
nargs SpecialClsRef.Self
]
3490 gather
[expr_instrs;
3491 emit_pos outer_pos
; instr_fpushclsmethods
nargs SpecialClsRef.Parent
]
3492 | Class_expr
(_, A.Lvar
(_, x
)) when x
= SN.SpecialIdents.this
->
3494 emit_call_lhs_with_this
env expr_instrs;
3496 instr_fpushclsmethod
nargs inout_arg_positions
3501 emit_load_class_ref
env pos cexpr;
3503 instr_fpushclsmethod
nargs inout_arg_positions
3507 | A.Id
(_, s as id)->
3509 Hhbc_id.Function.elaborate_id_with_builtins
(Emit_env.get_namespace
env) id in
3511 match id_opt
, SU.strip_global_ns
s with
3512 | None
, "min" when nargs = 2 && not has_splat
->
3513 Hhbc_id.Function.from_raw_string
"__SystemLib\\min2", None
3514 | None
, "max" when nargs = 2 && not has_splat
->
3515 Hhbc_id.Function.from_raw_string
"__SystemLib\\max2", None
3516 | _ -> fq_id, id_opt
in
3517 let fq_id = if has_inout_args
3518 then Hhbc_id.Function.add_suffix
3519 fq_id (Emit_inout_helpers.inout_suffix inout_arg_positions
)
3521 emit_pos_then outer_pos
@@
3522 begin match id_opt
with
3523 | Some
id -> instr (ICall
(FPushFuncU
(nargs, fq_id, id)))
3524 | None
-> instr (ICall
(FPushFuncD
(nargs, fq_id)))
3526 | A.String
(_, s) ->
3527 emit_pos_then outer_pos
@@
3528 instr_fpushfuncd
nargs (Hhbc_id.Function.from_raw_string
s)
3531 emit_expr ~need_ref
:false env expr;
3533 instr_fpushfunc
nargs inout_arg_positions
3536 (* Retuns whether the function is a call_user_func function,
3537 min args, max args *)
3538 and get_call_user_func_info
= function
3539 | "call_user_func" -> (true, 1, max_int
)
3540 | "call_user_func_array" -> (true, 2, 2)
3541 | "forward_static_call" -> (true, 1, max_int
)
3542 | "forward_static_call_array" -> (true, 2, 2)
3543 | "fb_call_user_func_safe" -> (true, 1, max_int
)
3544 | "fb_call_user_func_array_safe" -> (true, 2, 2)
3545 | "fb_call_user_func_safe_return" -> (true, 2, max_int
)
3546 | _ -> (false, 0, 0)
3548 and is_call_user_func
id num_args
=
3549 let (is_fn
, min_args
, max_args
) = get_call_user_func_info
id in
3550 is_fn
&& num_args
>= min_args
&& num_args
<= max_args
3552 and get_call_builtin_func_info lower_fq_id
=
3553 match lower_fq_id
with
3554 | "array_key_exists" -> Some
(2, IMisc AKExists
)
3555 | "hphp_array_idx" -> Some
(3, IMisc ArrayIdx
)
3556 | "intval" -> Some
(1, IOp CastInt
)
3557 | "boolval" -> Some
(1, IOp CastBool
)
3558 | "strval" -> Some
(1, IOp CastString
)
3559 | "floatval" | "doubleval" -> Some
(1, IOp CastDouble
)
3560 | "hh\\vec" -> Some
(1, IOp CastVec
)
3561 | "hh\\keyset" -> Some
(1, IOp CastKeyset
)
3562 | "hh\\dict" -> Some
(1, IOp CastDict
)
3563 | "hh\\varray" -> Some
(1, IOp
(if hack_arr_dv_arrs () then CastVec
else CastVArray
))
3564 | "hh\\darray" -> Some
(1, IOp
(if hack_arr_dv_arrs () then CastDict
else CastDArray
))
3567 and emit_call_user_func_arg
env f i
expr =
3568 let hint = get_pass_by_ref_hint
expr in
3569 let hint, warning
, expr =
3572 (* for warning - adjust the argument id *)
3573 let param_id = Param_unnamed
(i
+ 1) in
3575 The passthrough type of call_user_func is always cell or any, so
3576 any call to a function taking a ref will result in a warning *)
3577 Cell
, instr_raise_fpass_warning
hint f param_id, strip_ref
expr
3578 else hint, empty
, expr in
3580 emit_expr ~need_ref
:false env expr;
3582 instr_fpass
PassByRefKind.AllowCell i
hint;
3585 and emit_call_user_func
env id arg
args =
3586 let return_default, args = match id with
3587 | "fb_call_user_func_safe_return" ->
3588 begin match args with
3589 | [] -> failwith
"fb_call_user_func_safe_return - requires default arg"
3590 | a
:: args -> emit_expr ~need_ref
:false env a
, args
3594 let num_params = List.length
args in
3595 let begin_instr = match id with
3596 | "forward_static_call"
3597 | "forward_static_call_array" -> instr_fpushcuff
num_params
3598 | "fb_call_user_func_safe"
3599 | "fb_call_user_func_array_safe" ->
3600 gather
[instr_null
; instr_fpushcuf_safe
num_params]
3601 | "fb_call_user_func_safe_return" ->
3602 gather
[return_default; instr_fpushcuf_safe
num_params]
3603 | _ -> instr_fpushcuf
num_params
3605 let call_instr = match id with
3606 | "call_user_func_array"
3607 | "forward_static_call_array"
3608 | "fb_call_user_func_array_safe" -> instr (ICall FCallArray
)
3609 | _ -> instr (ICall
(FCall
num_params))
3611 let end_instr = match id with
3612 | "fb_call_user_func_safe_return" -> instr (ICall CufSafeReturn
)
3613 | "fb_call_user_func_safe"
3614 | "fb_call_user_func_array_safe" -> instr (ICall CufSafeArray
)
3617 let flavor = match id with
3618 | "fb_call_user_func_safe"
3619 | "fb_call_user_func_array_safe" -> Flavor.Cell
3620 | _ -> Flavor.ReturnVal
3623 (* first arg is always emitted as cell *)
3624 emit_expr ~need_ref
:false env (strip_ref arg
);
3626 gather
(List.mapi
args (emit_call_user_func_arg
env id));
3631 (* TODO: work out what HHVM does special here *)
3632 and emit_name_string
env e =
3633 emit_expr ~need_ref
:false env e
3635 and emit_special_function
env pos id args uargs
default =
3636 let nargs = List.length
args + List.length uargs
in
3638 Hhbc_id.Function.elaborate_id_with_builtins
(Emit_env.get_namespace
env) (Pos.none
, id) in
3639 (* Make sure that we do not treat a special function that is aliased as not
3642 String.lowercase_ascii
(Hhbc_id.Function.to_raw_string
fq_id) in
3643 let hh_enabled = Emit_env.is_hh_syntax_enabled
() in
3644 match lower_fq_name, args with
3645 | id, _ when id = SN.SpecialFunctions.echo
->
3646 let instrs = gather
@@ List.mapi
args begin fun i arg
->
3648 emit_expr ~need_ref
:false env arg
;
3651 if i
= nargs-1 then empty
else instr_popc
3653 Some
(instrs, Flavor.Cell
)
3656 _, A.Call
((_, A.Id
(_, s)), _, [], []); (_, A.Int
_ as count)
3657 ] when not
(jit_enable_rename_function ())
3658 && String.lowercase_ascii
@@ SU.strip_ns
s = "func_get_args"->
3660 Some
(emit_call
env pos (p,
3661 A.Id
(p, "\\__SystemLib\\func_slice_args")) [count] [])
3663 | "hh\\asm", [_, A.String
(_, s)] ->
3664 Some
(emit_inline_hhas
s, Flavor.Cell
)
3667 (optimize_cuf ()) && (is_call_user_func
id (List.length
args)) ->
3668 if List.length uargs
!= 0 then
3669 failwith
"Using argument unpacking for a call_user_func is not supported";
3670 begin match args with
3671 | [] -> failwith
"call_user_func - needs a name"
3673 Some
(emit_call_user_func
env id arg
args)
3676 | "hh\\invariant", e::rest
when hh_enabled ->
3677 let l = Label.next_regular
() in
3679 let expr_id = p, A.Id
(p, "\\hh\\invariant_violation") in
3681 (* Could use emit_jmpnz for better code *)
3682 emit_expr ~need_ref
:false env e;
3684 emit_ignored_expr
env (p, A.Call
(expr_id, [], rest
, uargs
));
3685 Emit_fatal.emit_fatal_runtime
p "invariant_violation";
3691 let l0 = Label.next_regular
() in
3692 let l1 = Label.next_regular
() in
3694 instr_string
"zend.assertions";
3695 instr_fcallbuiltin
1 1 "ini_get";
3708 | ("class_exists" | "interface_exists" | "trait_exists" as id), arg1
::_
3709 when nargs = 1 || nargs = 2 ->
3712 | "class_exists" -> KClass
3713 | "interface_exists" -> KInterface
3714 | "trait_exists" -> KTrait
3715 | _ -> failwith
"class_kind" in
3717 emit_name_string
env arg1
;
3718 instr (IOp CastString
);
3719 if nargs = 1 then instr_true
3721 emit_expr ~need_ref
:false env (List.nth_exn
args 1);
3722 instr (IOp CastBool
)
3724 instr (IMisc
(OODeclExists
class_kind))
3727 | ("exit" | "die"), _ when nargs = 0 || nargs = 1 ->
3728 Some
(emit_exit
env (List.hd
args), Flavor.Cell
)
3731 begin match args, istype_op lower_fq_name with
3732 | [(_, A.Lvar
(_, arg_str
as arg_id
))], Some i
3733 when not
(is_local_this env arg_str
) ->
3734 Some
(instr (IIsset
(IsTypeL
(get_local
env arg_id
, i
))), Flavor.Cell
)
3735 | [arg_expr
], Some i
->
3737 emit_expr ~need_ref
:false env arg_expr
;
3739 instr (IIsset
(IsTypeC i
))
3742 begin match get_call_builtin_func_info
lower_fq_name with
3743 | Some
(nargs, i
) when nargs = List.length
args ->
3746 emit_exprs
env pos args;
3754 and get_inout_arg_positions
args =
3755 List.filter_mapi
args
3756 ~
f:(fun i
-> function
3757 | _, A.Callconv
(A.Pinout
, _) -> Some i
3760 and emit_call
env pos (_, expr_
as expr) args uargs
=
3762 | A.Id
(_, s) -> Emit_symbol_refs.add_function
s
3764 let nargs = List.length
args + List.length uargs
in
3765 let inout_arg_positions = get_inout_arg_positions
args in
3766 let msrv = Hhbc_options.use_msrv_for_inout
!Hhbc_options.compiler_options
in
3767 let num_uninit = if msrv then List.length
inout_arg_positions else 0 in
3769 let flavor = if List.is_empty
inout_arg_positions then
3770 Flavor.ReturnVal
else Flavor.Cell
in
3772 gather
@@ List.init
num_uninit ~
f:(fun _ -> instr_nulluninit
);
3774 env pos expr nargs (not
(List.is_empty uargs
)) inout_arg_positions;
3775 emit_args_and_call
env pos args uargs
;
3778 match expr_
, args with
3779 | A.Id
(_, id), _ ->
3780 let special_fn_opt = emit_special_function
env pos id args uargs
default in
3781 begin match special_fn_opt with
3782 | Some
(instrs, flavor) -> instrs, flavor
3783 | None
-> default ()
3788 (* Emit code for an expression that might leave a cell or reference on the
3789 * stack. Return which flavor it left.
3791 and emit_flavored_expr
env ?last_pos
(pos, expr_
as expr) =
3793 | A.Call
(e, _, args, uargs
)
3794 when not
(is_special_function env e args) ->
3795 let instrs, flavor = emit_call
env pos e args uargs
in
3796 emit_pos_then
pos instrs, flavor
3797 | A.Execution_operator
es ->
3798 emit_execution_operator
env pos es, Flavor.ReturnVal
3801 if binary_assignment_rhs_starts_with_ref
expr
3805 emit_expr ?last_pos
env expr, flavor
3807 and emit_final_member_op stack_index
op mk =
3809 | LValOp.Set
-> instr (IFinal
(SetM
(stack_index
, mk)))
3810 | LValOp.SetRef
-> instr (IFinal
(BindM
(stack_index
, mk)))
3811 | LValOp.SetOp
op -> instr (IFinal
(SetOpM
(stack_index
, op, mk)))
3812 | LValOp.IncDec
op -> instr (IFinal
(IncDecM
(stack_index
, op, mk)))
3813 | LValOp.Unset
-> instr (IFinal
(UnsetM
(stack_index
, mk)))
3815 and emit_final_local_op
pos op lid =
3816 emit_pos_then
pos @@
3818 | LValOp.Set
-> instr (IMutator
(SetL
lid))
3819 | LValOp.SetRef
-> instr (IMutator
(BindL
lid))
3820 | LValOp.SetOp
op -> instr (IMutator
(SetOpL
(lid, op)))
3821 | LValOp.IncDec
op -> instr (IMutator
(IncDecL
(lid, op)))
3822 | LValOp.Unset
-> instr (IMutator
(UnsetL
lid))
3824 and emit_final_named_local_op
pos op =
3826 | LValOp.Set
-> emit_pos_then
pos @@ instr (IMutator SetN
)
3827 | LValOp.SetRef
-> instr (IMutator BindN
)
3828 | LValOp.SetOp
op -> instr (IMutator
(SetOpN
op))
3829 | LValOp.IncDec
op -> instr (IMutator
(IncDecN
op))
3830 | LValOp.Unset
-> instr (IMutator UnsetN
)
3832 and emit_final_global_op
pos op =
3834 | LValOp.Set
-> emit_pos_then
pos @@ instr (IMutator SetG
)
3835 | LValOp.SetRef
-> instr (IMutator BindG
)
3836 | LValOp.SetOp
op -> instr (IMutator
(SetOpG
op))
3837 | LValOp.IncDec
op -> instr (IMutator
(IncDecG
op))
3838 | LValOp.Unset
-> emit_pos_then
pos @@ instr (IMutator UnsetG
)
3840 and emit_final_static_op cid prop
op =
3842 | LValOp.Set
-> instr (IMutator
(SetS
0))
3843 | LValOp.SetRef
-> instr (IMutator
(BindS
0))
3844 | LValOp.SetOp
op -> instr (IMutator
(SetOpS
(op, 0)))
3845 | LValOp.IncDec
op -> instr (IMutator
(IncDecS
(op, 0)))
3847 let cid = text_of_expr cid in
3848 let id = text_of_expr (snd prop
) in
3849 Emit_fatal.emit_fatal_runtime
(fst
id)
3850 ("Attempt to unset static property " ^ snd
cid ^
"::" ^ snd
id)
3852 (* Given a local $local and a list of integer array indices i_1, ..., i_n,
3853 * generate code to extract the value of $local[i_n]...[i_1]:
3855 * Dim Warn EI:i_n ...
3857 * QueryM 0 CGet EI:i_1
3859 and emit_array_get_fixed last_usage
local indices
=
3860 let base, stack_count
=
3861 if last_usage
then gather
[
3865 else instr_basel
local MemberOpMode.Warn
, 0 in
3867 gather
@@ List.rev_mapi
indices
3869 let mk = MemberKey.EI
(Int64.of_int ix
) in
3871 then instr (IFinal
(QueryM
(stack_count
, QueryOp.CGet
, mk)))
3872 else instr (IBase
(Dim
(MemberOpMode.Warn
, mk)))
3879 and can_use_as_rhs_in_list_assignment
expr =
3881 | A.Call
((_, A.Id
(_, s)), _, _, _) when String.lowercase_ascii
s = "echo" ->
3902 | A.Pipe
(_, (_, r))
3903 | A.Binop
((A.Eq None
), (_, A.List
_), (_, r)) ->
3904 can_use_as_rhs_in_list_assignment
r
3905 | A.Binop
(A.Plus
, _, _)
3906 | A.Binop
(A.Eq
_, _, _) -> true
3910 (* Generate code for each lvalue assignment in a list destructuring expression.
3911 * Lvalues are assigned right-to-left, regardless of the nesting structure. So
3912 * list($a, list($b, $c)) = $d
3913 * and list(list($a, $b), $c) = $d
3914 * will both assign to $c, $b and $a in that order.
3915 * Returns a pair of instructions:
3916 * 1. initialization part of the left hand side
3918 * this is necessary to handle cases like:
3919 * list($a[$f()]) = b();
3920 * here f() should be invoked before b()
3922 and emit_lval_op_list ?
(last_usage
=false) env outer_pos
local indices expr =
3923 let is_ltr = php7_ltr_assign () in
3926 let last_non_omitted =
3927 (* last usage of the local will happen when processing last non-omitted
3928 element in the list - find it *)
3934 |> Core_list.foldi ~init
:None
3935 ~
f:(fun i
acc (_, v) -> if v = A.Omitted
then acc else Some i
)
3936 (* in right-to-left case result list will be reversed
3937 so we need to find first non-omitted expression *)
3940 |> Core_list.findi ~
f:(fun _ (_, v) -> v <> A.Omitted
)
3941 |> Option.map ~
f:fst
3944 let lhs_instrs, set_instrs
=
3945 List.mapi exprs
(fun i
expr ->
3946 emit_lval_op_list ~last_usage
:(Some i
= last_non_omitted)
3947 env outer_pos
local (i
::indices) expr)
3950 gather
(if not
is_ltr then List.rev set_instrs
else set_instrs
)
3951 | A.Omitted
-> empty
, empty
3953 (* Generate code to access the element from the array *)
3955 match local, indices with
3956 | Some
local, _ :: _ -> emit_array_get_fixed last_usage
local indices
3958 if last_usage
then instr_pushl
local
3959 else instr_cgetl
local
3960 | None
, _ -> instr_null
3962 (* Generate code to assign to the lvalue *)
3963 (* Return pair: side effects to initialize lhs + assignment *)
3964 let lhs_instrs, rhs_instrs
, set_op
=
3965 emit_lval_op_nonlist_steps
env outer_pos
LValOp.Set
expr access_instrs 1 in
3966 let lhs = if is_ltr then empty
else lhs_instrs in
3969 if is_ltr then lhs_instrs else empty
;
3977 and expr_starts_with_ref
= function
3978 | _, A.Unop
(A.Uref
, _) -> true
3981 and binary_assignment_rhs_starts_with_ref
= function
3982 | _, A.Binop
(A.Eq None
, _, e) when expr_starts_with_ref
e -> true
3985 and emit_expr_and_unbox_if_necessary ?last_pos ~need_ref
env pos e =
3988 | _, A.Expr_list
es ->
3990 |> Option.value_map ~
default:false ~
f:binary_assignment_rhs_starts_with_ref
3992 binary_assignment_rhs_starts_with_ref
e in
3994 emit_expr ?last_pos ~need_ref
env e;
3995 if need_unboxing then emit_pos_then
pos @@ instr_unbox
else empty
;
3998 (* Emit code for an l-value operation *)
3999 and emit_lval_op
env pos op expr1 opt_expr2
=
4001 match op, opt_expr2
with
4002 | LValOp.Set
, Some
e when expr_starts_with_ref
e -> LValOp.SetRef
4005 match op, expr1
, opt_expr2
with
4006 (* Special case for list destructuring, only on assignment *)
4007 | LValOp.Set
, (_, A.List
l), Some expr2
->
4009 List.exists
l ~
f: (function
4010 | _, A.Omitted
-> false
4013 if has_elements then
4014 stash_in_local_with_prefix
4015 ~always_stash
:(php7_ltr_assign ()) ~leave_on_stack
:true env pos expr2
4016 begin fun local _break_label
->
4018 if can_use_as_rhs_in_list_assignment
(snd expr2
) then
4023 emit_lval_op_list
env pos local [] expr1
4026 Local.scope @@ fun () ->
4027 let local = Local.get_unnamed_local
() in
4029 emit_expr ~need_ref
:false env expr2
;
4035 Local.scope @@ fun () ->
4036 let rhs_instrs, rhs_stack_size
=
4037 match opt_expr2
with
4039 | Some
(_, A.Yield af
) ->
4040 let temp = Local.get_unnamed_local
() in
4042 emit_yield
env pos af
;
4047 | Some
(pos, A.Unop
(A.Uref
, (_, A.Obj_get
(_, _, A.OG_nullsafe
)
4048 | _, A.Array_get
((_,
4049 A.Obj_get
(_, _, A.OG_nullsafe
)), _)))) ->
4050 Emit_fatal.raise_fatal_runtime
4051 pos "?-> is not allowed in write context"
4052 | Some
e -> emit_expr_and_unbox_if_necessary ~need_ref
:false env pos e, 1
4054 emit_lval_op_nonlist
env pos op expr1
rhs_instrs rhs_stack_size
4056 and emit_lval_op_nonlist
env pos op e rhs_instrs rhs_stack_size
=
4057 let (lhs, rhs
, setop
) =
4058 emit_lval_op_nonlist_steps
env pos op e rhs_instrs rhs_stack_size
4066 and emit_lval_op_nonlist_steps
env outer_pos
op (pos, expr_
) rhs_instrs rhs_stack_size
=
4069 (* Unbelieveably, $test[] += 5; is legal in PHP, but $test[] = $test[] + 5 is not *)
4073 | LValOp.IncDec
_ -> { env with Emit_env.env_allows_array_append
= true }
4075 let handle_dollar e final_op
=
4079 let local = (get_local
env id) in
4081 | LValOp.Unset
| LValOp.IncDec
_ -> instr_cgetl
local
4082 | _ -> instr_cgetl2
local
4093 let instrs = emit_expr ~need_ref
:false env e in
4099 | A.Lvar
(name_pos
, id) when SN.Superglobals.is_superglobal
id ->
4100 emit_pos_then name_pos
@@ instr_string
@@ SU.Locals.strip_dollar
id,
4102 emit_final_global_op outer_pos
op
4104 | A.Lvar
((_, str
) as id) when is_local_this env str
&& is_incdec op ->
4105 emit_local ~notice
:Notice ~need_ref
:false env id,
4109 | A.Lvar
id when not
(is_local_this env (snd
id)) || op = LValOp.Unset
->
4112 emit_final_local_op outer_pos
op (get_local
env id)
4115 handle_dollar e (emit_final_named_local_op
pos)
4117 | A.Array_get
((_, A.Lvar
(_, x
)), Some
e) when x
= SN.Superglobals.globals
->
4118 let final_global_op_instrs = emit_final_global_op
pos op in
4119 if rhs_stack_size
= 0
4121 emit_expr ~need_ref
:false env e,
4123 final_global_op_instrs
4125 let index_instrs, under_top
= emit_first_expr
env e in
4133 final_global_op_instrs
4137 final_global_op_instrs
4138 | A.Array_get
(_, None
) when not
(Emit_env.does_env_allow_array_append
env) ->
4139 Emit_fatal.raise_fatal_runtime
pos "Can't use [] for reading"
4140 | A.Array_get
(base_expr
, opt_elem_expr
) ->
4143 | LValOp.Unset
-> MemberOpMode.Unset
4144 | _ -> MemberOpMode.Define
in
4145 let elem_expr_instrs, elem_stack_size
=
4146 emit_elem_instrs ~
local_temp_kind:None
env opt_elem_expr
in
4147 let base_offset = elem_stack_size
+ rhs_stack_size
in
4148 let base_expr_instrs_begin,
4149 base_expr_instrs_end
,
4152 emit_base ~notice
:Notice ~is_object
:false env
4153 mode base_offset None base_expr
4155 let mk = get_elem_member_key
env rhs_stack_size opt_elem_expr
in
4156 let total_stack_size = elem_stack_size
+ base_stack_size
in
4158 emit_pos_then
pos @@
4159 emit_final_member_op
total_stack_size op mk in
4161 base_expr_instrs_begin;
4163 base_expr_instrs_end
;
4172 | A.Obj_get
(e1
, e2
, null_flavor
) ->
4173 if null_flavor
= A.OG_nullsafe
then
4174 Emit_fatal.raise_fatal_parse
pos "?-> is not allowed in write context";
4177 | LValOp.Unset
-> MemberOpMode.Unset
4178 | _ -> MemberOpMode.Define
in
4179 let mk, prop_expr_instrs
, prop_stack_size
=
4180 emit_prop_expr
env null_flavor rhs_stack_size e2
in
4181 let base_offset = prop_stack_size
+ rhs_stack_size
in
4182 let base_expr_instrs_begin,
4183 base_expr_instrs_end
,
4187 ~notice
:Notice ~is_object
:true
4188 env mode base_offset None e1
4190 let total_stack_size = prop_stack_size
+ base_stack_size
in
4192 emit_pos_then
pos @@
4193 emit_final_member_op
total_stack_size op mk in
4195 base_expr_instrs_begin;
4197 base_expr_instrs_end
;
4205 | A.Class_get
(cid, prop
) ->
4206 let cexpr = expr_to_class_expr ~resolve_self
:false
4207 (Emit_env.get_scope
env) cid in
4208 begin match snd prop
with
4209 | A.Dollar
(_, A.Lvar
_ as e) ->
4210 let final_instr = emit_final_static_op
(snd
cid) prop
op in
4211 let instrs, under_top
= emit_first_expr
env e in
4214 emit_load_class_ref
env pos cexpr,
4216 gather
[instrs; final_instr]
4218 gather
[instrs; emit_load_class_ref
env pos cexpr],
4223 emit_pos_then
pos @@
4224 emit_final_static_op
(snd
cid) prop
op in
4225 of_pair
@@ emit_class_expr
env cexpr prop
,
4230 | A.Unop
(uop
, e) ->
4234 emit_lval_op_nonlist
env pos op e empty rhs_stack_size
;
4239 Emit_fatal.raise_fatal_parse
pos "Can't use return value in write context"
4242 let ints_overflow_to_ints =
4243 Hhbc_options.ints_overflow_to_ints !Hhbc_options.compiler_options
4246 | A.Utild
-> instr (IOp BitNot
)
4247 | A.Unot
-> instr (IOp Not
)
4248 | A.Uplus
-> instr (IOp
(if ints_overflow_to_ints then Add
else AddO
))
4249 | A.Uminus
-> instr (IOp
(if ints_overflow_to_ints then Sub
else SubO
))
4250 | A.Uincr
| A.Udecr
| A.Upincr
| A.Updecr
| A.Uref
| A.Usilence
->
4251 emit_nyi "unop - probably does not need translation"
4253 and emit_expr_as_ref
env e =
4254 emit_expr ~need_ref
:true { env with Emit_env.env_allows_array_append
= true} e
4256 and emit_unop ~need_ref
env pos op e =
4257 let unop_instr = emit_pos_then
pos @@ from_unop
op in
4260 emit_box_if_necessary
pos need_ref
@@ gather
[
4261 emit_expr ~last_pos
:pos ~need_ref
:false env e; unop_instr
4264 emit_box_if_necessary
pos need_ref
@@ gather
[
4265 emit_expr ~last_pos
:pos ~need_ref
:false env e; unop_instr
4268 emit_box_if_necessary
pos need_ref
@@ gather
4270 instr (ILitConst
(Int
(Int64.zero
)));
4271 emit_expr ~last_pos
:pos ~need_ref
:false env e;
4274 emit_box_if_necessary
pos need_ref
@@ gather
4276 instr (ILitConst
(Int
(Int64.zero
)));
4277 emit_expr ~last_pos
:pos ~need_ref
:false env e;
4279 | A.Uincr
| A.Udecr
| A.Upincr
| A.Updecr
->
4280 begin match unop_to_incdec_op op with
4281 | None
-> emit_nyi "incdec"
4283 let instr = emit_lval_op
env pos (LValOp.IncDec incdec_op
) e None
in
4284 emit_box_if_necessary
pos need_ref
instr
4286 | A.Uref
-> emit_expr_as_ref
env e
4288 Local.scope @@ fun () ->
4289 let enclosing_span = Ast_scope.Scope.get_span
env.Emit_env.env_scope
in
4290 let fault_label = Label.next_fault
() in
4291 let temp_local = Local.get_unnamed_local
() in
4292 let cleanup = instr_silence_end
temp_local in
4294 gather
[emit_expr ~need_ref
:false env e; emit_pos
pos; cleanup] in
4295 let fault = gather
[emit_pos
enclosing_span; cleanup; instr_unwind
] in
4298 instr_silence_start
temp_local;
4299 instr_try_fault
fault_label body fault
4302 and emit_exprs
env pos exprs
=
4306 gather
(emit_expr_and_unbox_if_necessary ~last_pos
:pos ~need_ref
:false env pos expr ::
4307 List.map exprs
(emit_expr_and_unbox_if_necessary ~last_pos
:pos ~need_ref
:false env pos))
4309 (* allows to create a block of code that will
4310 - get a fresh temporary local
4311 - be wrapped in a try/fault where fault will clean temporary from the previous
4313 and with_temp_local
temp f =
4315 with_temp_local_with_prefix
temp (fun temp label -> empty
, f temp label) in
4318 (* similar to with_temp_local with addition that
4319 function 'f' that creates result block of code can generate an
4320 additional prefix instruction sequence that should be
4321 executed before the result block *)
4322 and with_temp_local_with_prefix
temp f =
4323 let break_label = Label.next_regular
() in
4324 let prefix, block = f temp break_label in
4325 if is_empty
block then prefix, block
4327 let fault_label = Label.next_fault
() in
4338 instr_label
break_label;
4341 (* Similar to stash_in_local with addition that function that
4342 creates a block of code can yield a prefix instrution
4343 that will be executed as the first instruction in the result instruction set *)
4344 and stash_in_local_with_prefix ?
(always_stash
=false) ?
(leave_on_stack
=false)
4345 ?
(always_stash_this
=false) env pos e f =
4347 | (_, A.Lvar
id) when not always_stash
4348 && not
(is_local_this env (snd
id) &&
4349 ((Emit_env.get_needs_local_this
env) || always_stash_this
)) ->
4350 let break_label = Label.next_regular
() in
4351 let prefix_instr, result_instr
=
4352 f (get_local
env id) break_label in
4356 instr_label
break_label;
4357 if leave_on_stack
then instr_cgetl
(get_local
env id) else empty
;
4360 let generate_value =
4361 Local.scope @@ fun () ->
4362 emit_expr_and_unbox_if_necessary ~need_ref
:false env pos e in
4363 Local.scope @@ fun () ->
4364 let temp = Local.get_unnamed_local
() in
4365 let prefix_instr, result_instr
=
4366 with_temp_local_with_prefix
temp f in
4374 if leave_on_stack
then instr_pushl
temp else instr_unsetl
temp
4376 (* Generate code to evaluate `e`, and, if necessary, store its value in a
4377 * temporary local `temp` (unless it is itself a local). Then use `f` to
4378 * generate code that uses this local and branches or drops through to
4381 * <code generated by `f temp break_label`>
4383 * push `temp` on stack if `leave_on_stack` is true.
4385 and stash_in_local ?
(always_stash
=false) ?
(leave_on_stack
=false)
4386 ?
(always_stash_this
=false) env pos e f =
4387 stash_in_local_with_prefix ~always_stash ~leave_on_stack ~always_stash_this
4388 env pos e (fun temp label -> empty
, f temp label)