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.
11 open Instruction_sequence
14 module JT
= Jump_targets
16 (* Collect list of Ret* and non rewritten Break/Continue instructions inside
18 let collect_jump_instructions instrseq env
=
19 let jump_targets = Emit_env.get_jump_targets env
in
20 let get_label_id ~is_break level
=
21 match JT.get_target_for_level is_break level
jump_targets with
22 | JT.ResolvedRegular
(target_label
, _
)
23 | JT.ResolvedTryFinally
{ JT.target_label
= target_label
; _
} ->
24 JT.get_id_for_label target_label
25 | _
-> failwith
"impossible"
29 | ISpecialFlow Break l
->
30 IMap.add
(get_label_id ~is_break
:true l
) i map
31 | ISpecialFlow Continue l
->
32 IMap.add
(get_label_id ~is_break
:false l
) i map
33 | IContFlow
(RetC
| RetV
| RetM _
) ->
34 IMap.add
(JT.get_id_for_return
()) i map
35 | ISpecialFlow Goto l
->
36 IMap.add
(JT.get_id_for_label
(Label.named l
)) i map
39 InstrSeq.fold_left instrseq ~init
:IMap.empty ~f
:folder
41 (* Delete Ret*, Break/Continue/Jmp(Named)/IterBreak(Named)
42 instructions from the try body *)
43 let cleanup_try_body instrseq
=
46 | ISpecialFlow _
| IContFlow
(RetC
| RetV
| RetM _
) -> None
49 InstrSeq.filter_map instrseq ~f
:rewriter
51 let emit_jump_to_label l iters
=
54 | iters
-> instr_iter_break l iters
56 let emit_save_label_id id
=
59 instr_setl
(Local.get_label_id_local
());
63 let get_pos_for_error env
=
67 | Some p
-> Pos.first_char_of_line p
69 let rec aux_scope = function
70 | Ast_scope.ScopeItem.Function fd
:: _
->
71 aux_pos @@ Some fd
.Ast.f_span
72 (* For methods, it points to class not the method.. weird *)
73 | Ast_scope.ScopeItem.Class cd
:: _
->
74 aux_pos @@ Some cd
.Ast.c_span
75 | Ast_scope.ScopeItem.Method _
:: scope
76 | Ast_scope.ScopeItem.Lambda
:: scope
77 | Ast_scope.ScopeItem.LongLambda _
:: scope
-> aux_scope scope
80 aux_scope @@ Emit_env.get_scope env
82 let fail_if_goto_from_try_to_finally try_block finally_block
=
83 let find_gotos_in block
=
85 object inherit [_
] Ast_visitor.ast_visitor
86 method! on_goto goto_labels label
= label
:: goto_labels
88 visitor#on_block
[] block
in
89 let goto_labels = find_gotos_in try_block
in
90 let fail_if_find_any_label_in block
goto_labels =
92 object inherit [_
] A.iter
93 method! on_GotoLabel
goto_labels (_
, label
) =
94 let label_opt = List.find
goto_labels
95 (fun (_
, label_in_list
) -> label_in_list
= label
) in
97 | Some
(p
, _label
) -> Emit_fatal.raise_fatal_parse p
98 "'goto' into finally statement is disallowed"
101 visitor#on_block
goto_labels block
in
102 fail_if_find_any_label_in finally_block
goto_labels
104 let emit_goto ~in_finally_epilogue env label
=
105 let err_pos = get_pos_for_error env
in
106 match SMap.get label
@@ JT.get_labels_in_function
() with
108 Emit_fatal.raise_fatal_parse
109 err_pos @@ "'goto' to undefined label '" ^ label ^
"'"
111 let named_label = Label.named label
in
112 (* CONSIDER: we don't need to assign state id for label
113 for cases when it is not necessary, i.e. when jump target is in the same
114 scope. HHVM does not do this today, do the same for compatibility reasons *)
115 let label_id = JT.get_id_for_label
named_label in
116 let jump_targets = Emit_env.get_jump_targets env
in
117 begin match JT.find_goto_target
jump_targets label
with
118 | JT.ResolvedGoto_label iters
->
120 if not in_finally_epilogue
then empty
121 else instr_unsetl
@@ Local.get_label_id_local
() in
124 emit_jump_to_label named_label iters
126 | JT.ResolvedGoto_finally
{
127 JT.rgf_finally_start_label
;
128 JT.rgf_iterators_to_release
;
131 if in_finally_epilogue
then empty
132 else emit_save_label_id label_id in
135 emit_jump_to_label rgf_finally_start_label rgf_iterators_to_release
;
136 (* emit goto as an indicator for try/finally rewriter to generate
137 finally epilogue, try/finally rewriter will remove it. *)
140 | JT.ResolvedGoto_goto_from_finally
->
141 Emit_fatal.raise_fatal_runtime
143 "Goto to a label outside a finally block is not supported"
144 | JT.ResolvedGoto_goto_invalid_label
->
147 then "'goto' into or across using statement is disallowed"
148 else "'goto' into loop or switch statement is disallowed" in
149 Emit_fatal.raise_fatal_parse
err_pos message
153 ~need_ref ~verify_return ~verify_out ~num_out ~in_finally_epilogue env
=
154 let ret_instr = if need_ref
then instr_retv
else instr_retc
in
155 (* check if there are try/finally region *)
156 let jump_targets = Emit_env.get_jump_targets env
in
157 begin match JT.get_closest_enclosing_finally_label
jump_targets with
158 (* no finally blocks, but there might be some iterators that should be
159 released before exit - do it *)
161 let verify_return_instr =
163 then if need_ref
then instr_verifyRetTypeV
else instr_verifyRetTypeC
166 let release_iterators_instr =
167 let iterators_to_release = JT.collect_iterators
jump_targets in
168 gather
@@ List.map
iterators_to_release ~f
:(fun (is_mutable
, it
) ->
169 let iter_free = if is_mutable
then MIterFree it
else IterFree it
in
170 instr
(IIterator
iter_free)
173 if in_finally_epilogue
175 let load_retval_instr =
176 if need_ref
then instr_vgetl
(Local.get_retval_local
())
177 else instr_cgetl
(Local.get_retval_local
())
183 release_iterators_instr;
184 if num_out
<> 0 then instr_retm
(num_out
+ 1) else ret_instr
189 release_iterators_instr;
190 if num_out
<> 0 then instr_retm
(num_out
+ 1) else ret_instr
192 (* ret is in finally block and there might be iterators to release -
193 jump to finally block via Jmp/IterBreak *)
194 | Some
(target_label
, iterators_to_release) ->
196 if in_finally_epilogue
then empty
198 let save_state = emit_save_label_id (JT.get_id_for_return
()) in
200 if need_ref
then gather
[
201 instr_bindl
(Local.get_retval_local
());
205 instr_setl
(Local.get_retval_local
());
216 emit_jump_to_label target_label
iterators_to_release;
217 (* emit ret instr as an indicator for try/finally rewriter to generate
218 finally epilogue, try/finally rewriter will remove it. *)
223 and emit_break_or_continue ~is_break ~in_finally_epilogue env pos level
=
224 let jump_targets = Emit_env.get_jump_targets env
in
225 match JT.get_target_for_level ~is_break level
jump_targets with
226 | JT.NotFound
-> Emit_fatal.emit_fatal_for_break_continue pos level
227 | JT.ResolvedRegular
(target_label
, iterators_to_release) ->
229 if in_finally_epilogue
&& level
= 1 then instr_unsetl
@@ Local.get_label_id_local
()
234 Emit_pos.emit_pos pos
;
235 emit_jump_to_label target_label
iterators_to_release;
237 | JT.ResolvedTryFinally
{
240 JT.iterators_to_release;
241 JT.adjusted_level
; } ->
243 if not in_finally_epilogue
244 then emit_save_label_id (JT.get_id_for_label target_label
)
249 emit_jump_to_label finally_label
iterators_to_release;
250 Emit_pos.emit_pos pos
;
251 (* emit break/continue instr as an indicator for try/finally rewriter
252 to generate finally epilogue - try/finally rewriter will remove it. *)
253 if is_break
then instr_break adjusted_level
else instr_continue adjusted_level
256 let emit_finally_epilogue
257 ~verify_return ~verify_out ~num_out env pos jump_instructions finally_end
=
263 ~need_ref
:false ~verify_return ~verify_out ~num_out ~in_finally_epilogue
:true env
266 ~need_ref
:true ~verify_return ~verify_out ~num_out ~in_finally_epilogue
:true env
267 | ISpecialFlow
(Break l
) ->
268 emit_break_or_continue ~is_break
:true ~in_finally_epilogue
:true env pos l
269 | ISpecialFlow
(Continue l
) ->
270 emit_break_or_continue ~is_break
:false ~in_finally_epilogue
:true env pos l
271 | ISpecialFlow
(Goto l
) ->
272 emit_goto ~in_finally_epilogue
:true env l
274 "unexpected instruction: " ^
275 "only Ret* or Break/Continue/Jmp(Named)/IterBreak(Named) are expected"
277 match IMap.elements jump_instructions
with
281 Emit_pos.emit_pos pos
;
282 instr_issetl
(Local.get_label_id_local
());
283 instr_jmpz finally_end
;
285 | (max_id
, _
) :: _
as lst
->
286 (* mimic HHVM behavior:
287 in some cases ids can be non-consequtive - this might happen i.e. return statement
288 appear in the block and it was assigned a high id before.
289 ((3, Return), (1, Break), (0, Continue))
290 In thid case generate switch as
292 L0 -> handler for continue
293 L1 -> handler for break
295 L3 -> handler for return
298 (* This function builds a list of labels and jump targets for switch.
299 It is possible that cases ids are not consequtive
300 [L1,L2,L4]. Vector of labels in switch should be dense so we need to
301 fill holes with a label that points to the end of finally block
302 [End, L1, L2, End, L4]
304 let rec aux n instructions labels bodies
=
305 match instructions
with
307 aux (n
- 1) instructions
(finally_end
:: labels
) (empty
:: bodies
)
308 | [] -> (labels
, bodies
)
309 | (id
, instruction
) :: t
->
311 let label = Label.next_regular
() in
314 emit_instr instruction
;]
316 aux (n
- 1) t
(label :: labels
) (body :: bodies
)
318 aux (n
- 1) instructions
(finally_end
:: labels
) (empty
:: bodies
)
320 (* lst is already sorted - IMap.elements took care of it *)
321 (* TODO: add is_sorted assert to make sure this behavior is preserved *)
322 let (labels
, bodies
) = aux max_id lst
[] [] in
323 let labels = labels in
325 Emit_pos.emit_pos pos
;
326 instr_issetl
(Local.get_label_id_local
());
327 instr_jmpz finally_end
;
328 instr_cgetl
(Local.get_label_id_local
());
332 TODO: This codegen is unnecessarily complex. Basically we are generating
337 Switch Unbounded 0 <L4 L5>
345 Two problems immediately come to mind. First, why is the unset in every case,
346 instead of after the CGetL? Surely the unset doesn't modify the stack.
347 Second, now we have a jump-to-jump situation.
349 Would it not make more sense to instead say
355 Switch Unbounded 0 <LBreak LContinue>