2 * Copyright (c) 2017, Facebook, Inc.
5 * This source code is licensed under the MIT license found in the
6 * LICENSE file in the "hack" directory of this source tree.
12 open Instruction_sequence
16 module TC
= Hhas_type_constraint
17 module SN
= Naming_special_names
18 module TFR
= Try_finally_rewriter
19 module JT
= Jump_targets
20 module Opts
= Hhbc_options
22 (* Context for code generation. It would be more elegant to pass this
23 * around in an environment parameter. *)
24 let verify_return : Aast.hint
option ref = ref None
26 let default_return_value = ref instr_null
28 let default_dropthrough = ref None
30 let verify_out = ref empty
34 let function_pos = ref Pos.none
36 let set_num_out c
= num_out := c
38 let set_verify_return b
= verify_return := b
40 let set_default_return_value i
= default_return_value := i
42 let set_default_dropthrough i
= default_dropthrough := i
44 let set_verify_out i
= verify_out := i
46 let set_function_pos p
= function_pos := p
48 let emit_return (env
: Emit_env.t
) =
50 ~
verify_return:!verify_return
51 ~
verify_out:!verify_out
53 ~in_finally_epilogue
:false
56 let emit_def_inline def
=
60 if Emit_env.is_systemlib
() then
65 Emit_pos.emit_pos_then
(fst cd
.A.c_name
)
66 @@ defcls_fn (int_of_string
(snd cd
.A.c_name
))
68 Emit_pos.emit_pos_then
(fst td
.A.t_name
)
69 @@ instr_deftypealias
(int_of_string
(snd td
.A.t_name
))
71 Emit_pos.emit_pos_then
(fst rd
.A.rd_name
)
72 @@ instr_defrecord
(int_of_string
(snd rd
.A.rd_name
))
73 | _
-> failwith
"Define inline: Invalid inline definition"
75 let emit_markup env s echo_expr_opt ~check_for_hashbang
=
76 let emit_ignored_call_expr f e
=
80 (A.Call
(Aast.Cnormal
, Tast_annotate.make
(A.Id
(p, f
)), [], [e
], None
))
82 emit_ignored_expr env
call_expr
84 let emit_ignored_call_for_non_empty_string f s
=
85 if String.length s
= 0 then
88 emit_ignored_call_expr f
(Tast_annotate.make
(A.String s
))
91 if String.length s
= 0 then
95 if check_for_hashbang
then
96 (* if markup text starts with #!
97 - extract a line with hashbang - it will be emitted as a call
98 to print_hashbang function
99 - emit remaining part of text as regular markup *)
100 let r = Str.regexp
"^#!.*\n" in
101 if Str.string_match
r s
0 then
102 let cmd = Str.matched_string s
in
103 String_utils.lstrip s
cmd
109 emit_ignored_call_for_non_empty_string SN.SpecialFunctions.echo
tail
112 match echo_expr_opt
with
113 | Some e
-> emit_ignored_call_expr SN.SpecialFunctions.echo e
116 gather
[markup; echo]
118 let get_level p op e
=
119 match A.get_break_continue_level e
with
120 | A.Level_ok
(Some i
) -> i
121 | A.Level_ok None
-> 1
122 | A.Level_non_positive
->
123 Emit_fatal.raise_fatal_parse
125 ("'" ^ op ^
"' operator accepts only positive numbers")
126 | A.Level_non_literal
->
127 Emit_fatal.raise_fatal_parse
129 ("'" ^ op ^
"' with non-constant operand is not supported")
131 let rec set_bytes_kind name
=
134 "^hh\\\\set_bytes\\(_rev\\|\\)_\\([a-z0-9]+\\)\\(_vec\\|\\)$"
136 if Str.string_match
re name
0 then
138 if Str.matched_group
1 name
= "_rev" then
143 let size = Str.matched_group
2 name
in
144 let is_vec = Str.matched_group
3 name
= "_vec" in
145 match (size, is_vec) with
146 | ("string", false) -> Some
(op, 1, true)
150 | ("int16", _
) -> Some
(op, 2, is_vec)
161 and emit_stmt env
(pos
, stmt
) =
163 | A.Expr
(_
, A.Yield_break
) -> gather
[instr_null
; emit_return env
]
164 | A.Expr
(((pos
, _
), A.Call
(_
, (_
, A.Id
(_
, s
)), _
, exprl
, None
)) as expr
) ->
165 let s = Hhbc_id.Function.(from_ast_name
s |> to_raw_string
) in
166 if String.lowercase
s = "unset" then
167 gather
(List.map exprl
(emit_unset_expr env
))
169 match set_bytes_kind s with
173 | (_
, A.Callconv
(Ast_defs.Pinout
, e
)) :: t
-> e
:: t
176 emit_set_range_expr env pos
s kind
exprl
177 | None
-> emit_ignored_expr ~pop_pos
:pos env expr
179 | A.Return
(Some
((inner_pos
, _
), A.Await e
)) ->
180 gather
[emit_await env inner_pos e
; Emit_pos.emit_pos pos
; emit_return env
]
181 | A.Return
(Some
(_
, A.Yield_from e
)) ->
184 emit_yield_from_delegates env pos e
;
185 Emit_pos.emit_pos pos
;
188 | A.Expr
(ann
, A.Await e
) -> gather
[emit_await env
(fst ann
) e
; instr_popc
]
193 ((_
, A.List l
) as e1
),
194 ((await_pos
, _
), A.Await e_await
) ) ) ->
195 let awaited_instrs = emit_await env await_pos e_await
in
197 List.exists l ~f
:(function
198 | (_
, A.Omitted
) -> false
202 Scope.with_unnamed_local
@@ fun temp
->
204 ( gather
[awaited_instrs; instr_popl temp
],
206 of_pair
@@ emit_lval_op_list env pos
(Some temp
) [] e1
,
210 gather
[awaited_instrs; instr_popc
]
212 (_
, A.Binop
(Ast_defs.Eq None
, e_lhs
, ((await_pos
, _
), A.Await e_await
)))
214 emit_await_assignment env await_pos e_lhs e_await
215 | A.Expr
(_
, A.Yield_from e
) ->
216 gather
[emit_yield_from_delegates env pos e
; emit_pos pos
; instr_popc
]
217 | A.Expr
((pos
, _
), A.Binop
(Ast_defs.Eq None
, e_lhs
, (_
, A.Yield_from e
))) ->
218 Local.scope
@@ fun () ->
219 let temp = Local.get_unnamed_local
() in
220 let rhs_instrs = instr_pushl
temp in
223 emit_yield_from_delegates env pos e
;
226 emit_lval_op_nonlist env pos
LValOp.Set e_lhs
rhs_instrs 1;
229 | A.Expr expr
-> emit_ignored_expr ~pop_pos
:pos env expr
230 | A.Return None
-> gather
[instr_null
; Emit_pos.emit_pos pos
; emit_return env
]
231 | A.Return
(Some expr
) ->
232 gather
[emit_expr env expr
; Emit_pos.emit_pos pos
; emit_return env
]
233 | A.GotoLabel
(_
, label
) -> instr_label
(Label.named label
)
234 | A.Goto
(_
, label
) -> TFR.emit_goto ~in_finally_epilogue
:false env label
235 | A.Block b
-> emit_stmts env b
236 | A.If
(condition
, consequence
, alternative
) ->
237 emit_if env pos condition consequence alternative
238 | A.While
(e
, b
) -> emit_while env e
(pos
, A.Block b
)
242 us_has_await
= has_await
;
245 us_is_block_scoped
= is_block_scoped
;
247 emit_using env pos is_block_scoped has_await e
(block_pos b
, A.Block b
)
248 | A.Break
-> emit_break env pos
249 | A.Continue
-> emit_continue env pos
250 | A.Do
(b
, e
) -> emit_do env
(pos
, A.Block b
) e
251 | A.For
(e1
, e2
, e3
, b
) -> emit_for env pos e1 e2 e3
(pos
, A.Block b
)
252 | A.Throw
((_
, _
) as expr
) ->
253 gather
[emit_expr env expr
; Emit_pos.emit_pos pos
; instr
(IContFlow Throw
)]
254 | A.Try
(try_block
, catch_list
, finally_block
) ->
255 if JT.get_function_has_goto
() then
256 TFR.fail_if_goto_from_try_to_finally try_block finally_block
;
258 (* TFR.fail_if_goto_from_try_to_finally try_block finally_block;*)
259 if catch_list
<> [] && finally_block
<> [] then
263 A.Try
([(pos
, A.Try
(try_block
, catch_list
, []))], [], finally_block
)
265 else if catch_list
<> [] then
266 emit_try_catch env
(pos
, A.Block try_block
) catch_list
271 (pos
, A.Block try_block
)
272 (pos
, A.Block finally_block
)
273 | A.Switch
(e
, cl
) -> emit_switch env pos e cl
274 | A.Foreach
(collection
, iterator
, block
) ->
275 emit_foreach env pos collection iterator
(pos
, A.Block block
)
276 | A.Def_inline def
-> emit_def_inline def
277 | A.Awaitall
(el
, b
) -> emit_awaitall env pos el b
278 | A.Markup
((_
, s), echo_expr_opt
) ->
279 emit_markup env
s echo_expr_opt ~check_for_hashbang
:false
284 and emit_break env pos
=
285 TFR.emit_break_or_continue ~is_break
:true ~in_finally_epilogue
:false env pos
1
287 and emit_continue env pos
=
288 TFR.emit_break_or_continue
290 ~in_finally_epilogue
:false
295 and emit_temp_break env pos level
=
296 TFR.emit_break_or_continue
298 ~in_finally_epilogue
:false
303 and emit_temp_continue env pos level
=
304 TFR.emit_break_or_continue
306 ~in_finally_epilogue
:false
311 and get_instrs
r = r.Emit_expression.instrs
313 and emit_if env pos condition consequence alternative
=
314 match alternative
with
317 let done_label = Label.next_regular
() in
320 get_instrs
@@ emit_jmpz env condition
done_label;
321 emit_stmts env consequence
;
322 instr_label
done_label;
325 let alternative_label = Label.next_regular
() in
326 let done_label = Label.next_regular
() in
327 let consequence_instr = emit_stmts env consequence
in
328 let alternative_instr = emit_stmts env alternative
in
331 get_instrs
@@ emit_jmpz env condition
alternative_label;
334 instr_jmp
done_label;
335 instr_label
alternative_label;
337 instr_label
done_label;
340 and emit_await_assignment env pos lval e
=
342 | A.Lvar id
when not
(is_local_this env
(snd id
)) ->
345 emit_await env pos e
;
347 instr_popl
(get_local env
(pos
, Local_id.get_name
(snd id
)));
350 let awaited_instrs = emit_await env pos e
in
351 Scope.with_unnamed_local
@@ fun temp ->
352 let rhs_instrs = instr_pushl
temp in
353 let (lhs
, rhs
, setop
) =
354 emit_lval_op_nonlist_steps env pos
LValOp.Set lval
rhs_instrs 1
357 ( gather
[awaited_instrs; instr_popl
temp],
361 gather
[rhs
; setop
; instr_popc
] )
363 and emit_awaitall env pos el b
=
366 | [(lvar
, e
)] -> emit_awaitall_single env pos lvar e b
367 | _
-> emit_awaitall_multi env el b
369 and emit_awaitall_single env pos lval e b
=
370 Scope.with_unnamed_locals
@@ fun () ->
371 let load_arg = emit_await env pos e
in
375 let l = Local.init_unnamed_local_for_tempname
(Local_id.get_name str
) in
376 (instr_popl
l, instr_unsetl
l)
377 | None
-> (instr_popc
, empty
)
380 (gather
[load_arg; load
], (* inner *)
381 emit_stmts env b
, (* after *)
384 and emit_awaitall_multi env el b
=
385 Scope.with_unnamed_locals
@@ fun () ->
387 gather
@@ List.map el ~f
:(fun (_
, arg
) -> emit_expr env arg
)
390 List.map el ~f
:(fun (lvar
, _
) ->
392 | None
-> Local.get_unnamed_local
()
394 Local.init_unnamed_local_for_tempname
(Local_id.get_name str
))
396 let init_locals = gather
@@ List.rev_map
locals ~f
:instr_popl
in
397 let await_all = gather
[instr_awaitall_list
locals; instr_popc
] in
400 @@ List.map
locals ~f
:(fun l ->
401 let label_done = Label.next_regular
() in
406 instr_istypec OpNull
;
407 instr_jmpnz
label_done;
409 instr_label
label_done;
413 let block = emit_stmts env b
in
414 let unset_locals = gather
@@ List.map
locals ~f
:instr_unsetl
in
416 ( gather
[load_args; init_locals],
418 gather
[await_all; unpack; block],
422 and emit_while env e b
=
423 let break_label = Label.next_regular
() in
424 let cont_label = Label.next_regular
() in
425 let start_label = Label.next_regular
() in
426 (* TODO: This is *bizarre* codegen for a while loop.
427 It would be better to generate this as
428 instr_label continue_label;
430 instr_jmpz break_label;
432 instr_jmp continue_label;
433 instr_label break_label;
437 get_instrs
@@ emit_jmpz env e
break_label;
438 instr_label
start_label;
439 Emit_env.do_in_loop_body
break_label cont_label env b emit_stmt
;
440 instr_label
cont_label;
441 get_instrs
@@ emit_jmpnz env
(fst e
) (snd e
) start_label;
442 instr_label
break_label;
446 (env
: Emit_env.t
) pos is_block_scoped has_await
(e
: Tast.expr
) b
=
453 let ((p, _
), _
) = e
in
457 A.us_has_await
= has_await
;
458 A.us_is_block_scoped
= is_block_scoped
;
464 Local.scope
@@ fun () ->
465 let (local
, preamble
) =
467 | A.Binop
(Ast_defs.Eq None
, (_
, A.Lvar
(_
, id
)), _
)
469 ( Local.Named
(Local_id.get_name id
),
470 gather
[emit_expr env e
; Emit_pos.emit_pos
(fst b
); instr_popc
] )
472 let l = Local.get_unnamed_local
() in
473 (l, gather
[emit_expr env e
; instr_setl
l; instr_popc
])
475 let finally_start = Label.next_regular
() in
476 let finally_end = Label.next_regular
() in
477 let body = Emit_env.do_in_using_body
finally_start env b emit_stmt
in
478 let jump_instructions = TFR.collect_jump_instructions
body env
in
480 if IMap.is_empty
jump_instructions then
483 TFR.cleanup_try_body
body
486 Hhbc_id.Method.from_raw_string
493 let emit_finally () =
494 let (epilogue
, async_eager_label
) =
496 let after_await = Label.next_regular
() in
497 ( gather
[instr_await
; instr_label
after_await; instr_popc
],
507 instr_fcallobjmethodd
508 (make_fcall_args ?async_eager_label
0)
512 ( if is_block_scoped
then
518 let finally_epilogue =
519 TFR.emit_finally_epilogue
522 ~
verify_return:!verify_return
523 ~
verify_out:!verify_out
528 let exn_local = Local.get_unnamed_local
() in
530 if is_empty_block b
then
539 make_finally_catch
exn_local (emit_finally ());
547 instr_label
finally_start;
550 instr_label
finally_end;
553 and emit_do env b e
=
554 let cont_label = Label.next_regular
() in
555 let break_label = Label.next_regular
() in
556 let start_label = Label.next_regular
() in
559 instr_label
start_label;
560 Emit_env.do_in_loop_body
break_label cont_label env b emit_stmt
;
561 instr_label
cont_label;
562 get_instrs
@@ emit_jmpnz env
(fst e
) (snd e
) start_label;
563 instr_label
break_label;
566 and emit_for
(env
: Emit_env.t
) p (e1
: Tast.expr
) e2 e3 b
=
567 let break_label = Label.next_regular
() in
568 let cont_label = Label.next_regular
() in
569 let start_label = Label.next_regular
() in
570 (* TODO: this is bizarre codegen for a "for" loop.
571 This should be codegen'd as
572 emit_ignored_expr initializer;
573 instr_label start_label;
575 instr_jmpz break_label;
577 instr_label continue_label;
578 emit_ignored_expr increment;
579 instr_jmp start_label;
580 instr_label break_label;
582 let emit_cond ~jmpz label
=
586 emit_jmpz env cond label
588 emit_jmpnz env
(fst cond
) (snd cond
) label
)
590 let rec expr_list h tl
=
596 Typing_defs.mk
(Typing_reason.none
, Typing_defs.make_tany
())
600 | h1
:: t1
-> emit_ignored_expr env ~pop_pos
:p h
:: expr_list h1 t1
603 | (_
, A.Expr_list
[]) ->
608 | (_
, A.Expr_list
(h
:: t
)) -> gather
@@ expr_list h t
613 emit_ignored_expr env e1
;
614 emit_cond ~jmpz
:true break_label;
615 instr_label
start_label;
616 Emit_env.do_in_loop_body
break_label cont_label env b emit_stmt
;
617 instr_label
cont_label;
618 emit_ignored_expr env e3
;
619 emit_cond ~jmpz
:false start_label;
620 instr_label
break_label;
623 and emit_switch
(env
: Emit_env.t
) pos scrutinee_expr cl
=
624 let (instr_init
, instr_free
, emit_check_case
) =
625 match snd scrutinee_expr
with
627 (* Special case for simple scrutinee *)
630 fun case_expr case_handler_label
->
631 let ((pos
, _
), _
) = case_expr
in
634 emit_two_exprs env pos scrutinee_expr case_expr
;
636 instr_jmpnz case_handler_label
;
639 ( emit_expr env scrutinee_expr
,
641 fun case_expr case_handler_label
->
642 let next_case_label = Label.next_regular
() in
643 let ((pos
, _
), _
) = case_expr
in
647 emit_expr env case_expr
;
650 instr_jmpz
next_case_label;
652 instr_jmp case_handler_label
;
653 instr_label
next_case_label;
656 (* "continue" in a switch in PHP has the same semantics as break! *)
657 let break_label = Label.next_regular
() in
661 | A.Default _
-> true
672 failwith
"impossible - switch statements must have at least one case"
673 | [A.Default _
] -> failwith
"impossible - there shouldn't be a default"
674 | [A.Case
(e
, b
)] -> [A.Case
(e
, b
@ [(Pos.none
, A.Break
)])]
675 | c
:: cl -> c
:: aux cl
680 Emit_env.do_in_switch_body
break_label env
cl @@ fun env _
->
681 List.map
cl ~f
:(emit_case env
)
683 let instr_bodies = gather
@@ List.map
cl ~f
:snd
in
686 List.filter_map
cl ~f
:(fun ((e
, l), _
) ->
687 if Option.is_none e
then
692 match default_labels with
696 Emit_fatal.raise_fatal_runtime
698 "Switch statements may only contain one 'default' clause."
700 let instr_check_cases =
702 @@ List.map
cl ~f
:(function
703 (* jmp to default case should be emitted as the very last 'else' case *)
704 | ((None
, _
), _
) -> empty
705 | ((Some e
, l), _
) -> emit_check_case e
l)
712 instr_jmp
default_label;
714 instr_label
break_label;
718 let bpos = List.map b fst
in
719 let valid_pos = List.filter
bpos (fun e
-> e
<> Pos.none
) in
720 if valid_pos = [] then
723 Pos.btw
(List.hd_exn
valid_pos) (List.last_exn
valid_pos)
726 (env
: Emit_env.t
) pos end_label
((_
, catch_type
), (_
, catch_local
), b
) =
727 (* Note that this is a "regular" label; we're not going to branch to
728 it directly in the event of an exception. *)
729 let next_catch = Label.next_regular
() in
730 let id = Hhbc_id.Class.from_ast_name catch_type
in
734 instr_instanceofd
id;
735 instr_jmpz
next_catch;
736 instr_setl
(Local.Named
(Local_id.get_name catch_local
));
738 emit_stmt env
(Pos.none
, A.Block b
);
739 Emit_pos.emit_pos pos
;
741 instr_label
next_catch;
744 and emit_catches
(env
: Emit_env.t
) pos catch_list end_label
=
745 gather
(List.map catch_list ~f
:(emit_catch env pos end_label
))
747 and is_empty_block
(_
, b
) =
749 | A.Block
l -> List.for_all ~f
:is_empty_block
l
753 and emit_try_catch
(env
: Emit_env.t
) try_block catch_list
=
754 Local.scope
@@ fun () -> emit_try_catch_ env try_block catch_list
756 and emit_try_catch_
(env
: Emit_env.t
) try_block catch_list
=
757 if is_empty_block try_block
then
760 let end_label = Label.next_regular
() in
761 let (pos
, _
) = try_block
in
762 let try_env = Emit_env.with_try env
in
764 ~opt_done_label
:end_label
765 (gather
[emit_stmt
try_env try_block
; Emit_pos.emit_pos pos
])
766 (emit_catches env pos catch_list
end_label)
768 and emit_try_finally env pos try_block finally_block
=
769 Local.scope
@@ fun () -> emit_try_finally_ env pos try_block finally_block
771 and emit_try_finally_ env pos try_block finally_block
=
772 let make_finally_body () =
773 Emit_env.do_in_finally_body env finally_block emit_stmt
775 if is_empty_block try_block
then
779 We need to generate four things:
780 (1) the try-body, which will be followed by
781 (2) the normal-continuation finally body, and
782 (3) an epilogue to the finally body that deals with finally-blocked
784 (4) the exceptional-continuation catch body.
789 The try body might have un-rewritten continues and breaks which
790 branch to a label outside of the try. This means that we must
791 first run the normal-continuation finally, and then branch to the
794 We do this by running a rewriter which turns continues and breaks
795 inside the try body into setting temp_local to an integer which indicates
796 what action the finally must perform when it is finished, followed by a
797 jump directly to the finally.
799 let finally_start = Label.next_regular
() in
800 let finally_end = Label.next_regular
() in
801 let enclosing_span = Ast_scope.Scope.get_span env
.Emit_env.env_scope
in
802 let try_env = Emit_env.with_try env
in
804 Emit_env.do_in_try_body
finally_start try_env try_block emit_stmt
806 let jump_instructions = TFR.collect_jump_instructions
try_body env
in
808 if IMap.is_empty
jump_instructions then
811 TFR.cleanup_try_body
try_body
816 Note that this is used both in the normal-continuation and
817 exceptional-continuation cases; we generate the same code twice.
819 TODO: We might consider changing the codegen so that the finally block
820 is only generated once. We could do this by making the catch block set a
821 temp local to -1, and then branch to the finally block. In the finally block
822 epilogue it can check to see if the local is -1, and if so, issue an unwind
825 It is illegal to have a continue or break which branches out of a finally.
826 Unfortunately we at present do not detect this at parse time; rather, we
827 generate an exception at run-time by rewriting continue and break
828 instructions found inside finally blocks.
830 TODO: If we make this illegal at parse time then we can remove this pass.
832 let exn_local = Local.get_unnamed_local
() in
833 let finally_body = make_finally_body () in
834 let finally_body_for_catch =
835 finally_body |> Label_rewriter.clone_with_fresh_regular_labels
837 (* (3) Finally epilogue *)
838 let finally_epilogue =
839 TFR.emit_finally_epilogue
842 ~
verify_return:!verify_return
843 ~
verify_out:!verify_out
851 We now emit the catch body; it is just cleanup code for the temp_local,
852 a copy of the finally body (without the branching epilogue, since we are
853 going to unwind rather than branch), and an unwind instruction.
855 TODO: The HHVM emitter sometimes emits seemingly spurious
856 unset-unnamed-local instructions into the catch block. These look
857 like bugs in the emitter. Investigate; if they are bugs in the HHVM
858 emitter, get them fixed there. If not, get a clear explanation of
859 what they are for and why they are required.
867 emit_pos
enclosing_span;
868 make_finally_catch
exn_local finally_body_for_catch;
871 (* Put it all together. *)
875 instr_label
finally_start;
876 Emit_pos.emit_pos
(fst finally_block
);
879 instr_label
finally_end;
882 and make_finally_catch
exn_local finally_body =
885 instr_popl
exn_local;
886 instr_unsetl
(Local.get_label_id_local
());
887 instr_unsetl
(Local.get_retval_local
());
890 (gather
[instr_pushl
exn_local; instr_chain_faults
]);
891 instr_pushl
exn_local;
895 and get_id_of_simple_lvar_opt v
=
897 | A.Lvar
(pos
, id) when Local_id.get_name
id = SN.SpecialIdents.this
->
898 Emit_fatal.raise_fatal_parse pos
"Cannot re-assign $this"
901 ( SN.Superglobals.is_superglobal
(Local_id.get_name
id)
902 || Local_id.get_name
id = SN.Superglobals.globals
) ->
903 Some
(Local_id.get_name
id)
906 and emit_load_list_elements env path vs
=
907 let (preamble
, load_value
) =
908 List.mapi ~f
:(emit_load_list_element env path
) vs
|> List.unzip
910 (List.concat preamble
, List.concat load_value
)
912 and emit_load_list_element env path i v
=
916 gather
@@ List.rev path
;
917 instr_querym
0 QueryOp.CGet
(MemberKey.EI
(Int64.of_int i
));
921 | (_
, A.Lvar
(_
, id)) ->
926 instr_setl
(Local.Named
(Local_id.get_name
id));
931 | (_
, A.List exprs
) ->
933 instr_dim
MemberOpMode.Warn
(MemberKey.EI
(Int64.of_int i
))
935 emit_load_list_elements env
(dim_instr :: path
) exprs
937 let set_instrs = emit_lval_op_nonlist env pos
LValOp.Set v
query_value 1 in
938 let load_value = [set_instrs; instr_popc
] in
939 ([], [gather
load_value])
941 (* Assigns a location to store values for foreach-key and foreach-value and
942 creates a code to populate them.
943 NOT suitable for foreach (... await ...) which uses different code-gen
944 Returns: key_local_opt * value_local * key_preamble * value_preamble
946 - key_local_opt - local variable to store a foreach-key value if it is
948 - value_local - local variable to store a foreach-value
949 - key_preamble - list of instructions to populate foreach-key
950 - value_preamble - list of instructions to populate foreach-value
952 and emit_iterator_key_value_storage env iterator
:
953 Hhbc_ast.local_id
option * Hhbc_ast.local_id
* Instruction_sequence.t
=
955 | A.As_kv
(((_
, k
) as expr_k
), ((_
, v
) as expr_v
)) ->
957 match (get_id_of_simple_lvar_opt k
, get_id_of_simple_lvar_opt v
) with
958 | (Some key_id
, Some value_id
) ->
959 let key_local = Local.Named key_id
in
960 let value_local = Local.Named value_id
in
961 (Some
key_local, value_local, empty
)
963 let key_local = Local.get_unnamed_local
() in
964 let value_local = Local.get_unnamed_local
() in
965 let (key_preamble
, key_load
) =
966 emit_iterator_lvalue_storage env expr_k
key_local
968 let (value_preamble
, value_load
) =
969 emit_iterator_lvalue_storage env expr_v
value_local
971 (* HHVM prepends code to initialize non-plain, non-list foreach-key
972 to the value preamble - do the same to minimize diffs *)
973 let (key_preamble
, value_preamble
) =
975 | A.List _
-> (key_preamble
, value_preamble
)
976 | _
-> ([], gather key_preamble
:: value_preamble
)
982 gather value_preamble
;
988 | A.As_v
((_
, v
) as expr_v
) ->
990 match get_id_of_simple_lvar_opt v
with
992 let value_local = Local.Named value_id
in
993 (None
, value_local, empty
)
995 let value_local = Local.get_unnamed_local
() in
996 let (value_preamble
, value_load
) =
997 emit_iterator_lvalue_storage env expr_v
value_local
999 (None
, value_local, gather
[gather value_preamble
; gather value_load
])
1001 | _
-> failwith
"emit_iterator_key_value_storage with iterator using await"
1003 (* Emit code for either the key or value l-value operation in foreach await.
1004 * `indices` is the initial prefix of the array indices ([0] for key or [1] for
1005 * value) that is prepended onto the indices needed for list destructuring
1007 * TODO: we don't need unnamed local if the target is a local
1009 and emit_foreach_await_lvalue_storage
1010 (env
: Emit_env.t
) (expr1
: Tast.expr
) indices keep_on_stack
=
1011 let ((pos
, _
), _
) = expr1
in
1012 Scope.with_unnamed_local
@@ fun local
->
1016 of_pair
@@ emit_lval_op_list env pos
(Some local
) indices expr1
,
1018 if keep_on_stack
then
1021 instr_unsetl local
)
1023 (* Emit code for the value and possibly key l-value operation in a foreach
1024 * await statement. The result of invocation of the `next` method has been
1025 * stored on top of the stack. For example:
1026 * foreach (foo() await as $a->f => list($b[0], $c->g)) { ... }
1027 * Here, we need to construct l-value operations that access the [0] (for $a->f)
1028 * and [1;0] (for $b[0]) and [1;1] (for $c->g) indices of the array returned
1029 * from the `next` method.
1031 and emit_foreach_await_key_value_storage
(env
: Emit_env.t
) iterator
=
1033 | A.Await_as_kv
(_
, expr_k
, expr_v
)
1034 | A.As_kv
(expr_k
, expr_v
) ->
1035 let key_instrs = emit_foreach_await_lvalue_storage env expr_k
[0] true in
1036 let value_instrs = emit_foreach_await_lvalue_storage env expr_v
[1] false in
1037 gather
[key_instrs; value_instrs]
1038 | A.Await_as_v
(_
, expr_v
)
1040 emit_foreach_await_lvalue_storage env expr_v
[1] false
1042 (*Generates a code to initialize a given foreach-* value.
1043 Returns: preamble * load_code
1045 - preamble - preparation part that should be executed before the loading
1046 - load_code - instructions to actually populate the value.
1047 This split is necessary to reflect the way how HHVM loads values.
1048 as an example for the code
1049 list($$$a, $$b, $$$c)
1050 preamble part will include code that pushes cells for $$a and $$c on the stack
1051 load_code will be executed assuming that stack is prepopulated:
1054 and emit_iterator_lvalue_storage env v local
=
1056 | ((pos
, _
), A.Call _
) ->
1057 Emit_fatal.raise_fatal_parse pos
"Can't use return value in write context"
1058 | (_
, A.List exprs
) ->
1059 let (preamble
, load_values
) =
1060 emit_load_list_elements env
[instr_basel local
MemberOpMode.Warn
] exprs
1062 let load_values = [gather
@@ List.rev
load_values; instr_unsetl local
] in
1063 (preamble
, load_values)
1065 let (lhs
, rhs
, set_op
) =
1066 emit_lval_op_nonlist_steps env pos
LValOp.Set v
(instr_cgetl local
) 1
1068 ([lhs
], [rhs
; set_op
; instr_popc
; instr_unsetl local
])
1070 and emit_foreach env pos collection iterator
block =
1071 Local.scope
@@ fun () ->
1075 emit_foreach_ env pos collection iterator
block
1076 | A.Await_as_kv
(pos
, _
, _
)
1077 | A.Await_as_v
(pos
, _
) ->
1078 emit_foreach_await env pos collection iterator
block
1080 and emit_foreach_await env pos collection iterator
block =
1081 let instr_collection = emit_expr env collection
in
1082 Scope.with_unnamed_local
@@ fun iter_temp_local
->
1083 let input_is_async_iterator_label = Label.next_regular
() in
1084 let next_label = Label.next_regular
() in
1085 let exit_label = Label.next_regular
() in
1086 let pop_and_exit_label = Label.next_regular
() in
1087 let async_eager_label = Label.next_regular
() in
1088 let next_meth = Hhbc_id.Method.from_raw_string
"next" in
1094 instr_instanceofd
(Hhbc_id.Class.from_raw_string
"HH\\AsyncIterator");
1095 instr_jmpnz
input_is_async_iterator_label;
1096 Emit_fatal.emit_fatal_runtime
1098 "Unable to iterate non-AsyncIterator asynchronously";
1099 instr_label
input_is_async_iterator_label;
1100 instr_popl iter_temp_local
;
1105 instr_label
next_label;
1106 instr_cgetl iter_temp_local
;
1109 instr_fcallobjmethodd
1110 (make_fcall_args ~
async_eager_label 0)
1114 instr_label
async_eager_label;
1116 instr_istypec OpNull
;
1117 instr_jmpnz
pop_and_exit_label;
1118 emit_foreach_await_key_value_storage env iterator
;
1119 Emit_env.do_in_loop_body
exit_label next_label env
block emit_stmt
;
1121 instr_jmp
next_label;
1122 instr_label
pop_and_exit_label;
1124 instr_label
exit_label;
1127 instr_unsetl iter_temp_local
)
1129 and emit_foreach_ env pos collection iterator
block =
1130 let instr_collection = emit_expr env collection
in
1131 Scope.with_unnamed_locals_and_iterators
@@ fun () ->
1132 let iter_id = Iterator.get_iterator
() in
1133 let loop_break_label = Label.next_regular
() in
1134 let loop_continue_label = Label.next_regular
() in
1135 let loop_head_label = Label.next_regular
() in
1136 let (key_id
, val_id
, preamble
) =
1137 emit_iterator_key_value_storage env iterator
1139 let iter_args = { iter_id; key_id
; val_id
} in
1140 let init = instr_iterinit
iter_args loop_break_label in
1141 let next = instr_iternext
iter_args loop_head_label in
1143 Emit_env.do_in_loop_body
1151 ( gather
[instr_collection; emit_pos
(Tast_annotate.get_pos collection
); init],
1154 instr_label
loop_head_label;
1157 instr_label
loop_continue_label;
1161 gather
[instr_label
loop_break_label] )
1163 and emit_yield_from_delegates env pos e
=
1164 let iterator_number = Iterator.get_iterator
() in
1165 let loop_label = Label.next_regular
() in
1170 instr_contAssignDelegate
iterator_number;
1175 instr_label
loop_label;
1176 instr_contEnterDelegate
;
1177 instr_yieldFromDelegate
iterator_number loop_label;
1179 (instr_contUnsetDelegate_free
iterator_number);
1180 instr_contUnsetDelegate_ignore
iterator_number;
1183 and emit_stmts env stl
=
1184 let results = List.map stl
(emit_stmt env
) in
1187 and emit_case
(env
: Emit_env.t
) c
=
1188 let l = Label.next_regular
() in
1191 | A.Default
(_
, b
) -> (b
, None
)
1192 | A.Case
(e
, b
) -> (b
, Some e
)
1194 let b = emit_stmt env
(Pos.none
, A.Block
b) in
1195 ((e
, l), gather
[instr_label
l; b])
1197 let emit_dropthrough_return env
=
1198 match !default_dropthrough with
1199 | Some instrs
-> instrs
1201 Emit_pos.emit_pos_then
(Pos.last_char
!function_pos)
1202 @@ gather
[!default_return_value; emit_return env
]
1204 let rec emit_final_statement env
s =
1209 | A.Expr
(_
, A.Yield_break
) ->
1211 | A.Block
b -> emit_final_statements env
b
1212 | _
-> gather
[emit_stmt env
s; emit_dropthrough_return env
]
1214 and emit_final_statements env
b =
1216 | [] -> emit_dropthrough_return env
1217 | [s] -> emit_final_statement env
s
1219 let i1 = emit_stmt env
s in
1220 let i2 = emit_final_statements env
b in