/
emit_statement.ml
1195 lines (1127 loc) · 37.6 KB
/
emit_statement.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(**
* Copyright (c) 2017, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the "hack" directory of this source tree.
*
*)
open Core_kernel
open Hhbc_ast
open Instruction_sequence
open Emit_expression
open Emit_pos
module A = Ast
module H = Hhbc_ast
module TC = Hhas_type_constraint
module SN = Naming_special_names
module TFR = Try_finally_rewriter
module JT = Jump_targets
module Opts = Hhbc_options
(* Context for code generation. It would be more elegant to pass this
* around in an environment parameter. *)
let verify_return = ref None
let default_return_value = ref instr_null
let default_dropthrough = ref None
let verify_out = ref empty
let num_out = ref 0
let function_pos = ref Pos.none
let set_num_out c = num_out := c
let set_verify_return b = verify_return := b
let set_default_return_value i = default_return_value := i
let set_default_dropthrough i = default_dropthrough := i
let set_verify_out i = verify_out := i
let set_function_pos p = function_pos := p
let emit_return env =
TFR.emit_return
~verify_return:!verify_return
~verify_out:!verify_out
~num_out:!num_out
~in_finally_epilogue:false
env
let emit_def_inline = function
| A.Class cd ->
let defcls_fn =
if Emit_env.is_systemlib () then instr_defclsnop else instr_defcls in
Emit_pos.emit_pos_then (fst cd.Ast.c_name) @@
defcls_fn (int_of_string (snd cd.Ast.c_name))
| A.Typedef td ->
Emit_pos.emit_pos_then (fst td.Ast.t_id) @@
instr_deftypealias (int_of_string (snd td.Ast.t_id))
| _ ->
failwith "Define inline: Invalid inline definition"
let emit_markup env s echo_expr_opt ~check_for_hashbang =
let emit_ignored_call_expr f e =
let p = Pos.none in
let call_expr = p, A.Call ((p, A.Id (p, f)), [], [e], []) in
emit_ignored_expr env call_expr
in
let emit_ignored_call_for_non_empty_string f s =
if String.length s = 0 then empty
else emit_ignored_call_expr f (Pos.none, A.String s)
in
let markup =
if String.length s = 0
then empty
else
let hashbang, tail =
if check_for_hashbang
then
(* if markup text starts with #!
- extract a line with hashbang - it will be emitted as a call
to print_hashbang function
- emit remaining part of text as regular markup *)
let r = Str.regexp "^#!.*\n" in
if Str.string_match r s 0
then
let cmd = Str.matched_string s in
let tail = String_utils.lstrip s cmd in
cmd, tail
else "", s
else "", s
in
gather [
emit_ignored_call_for_non_empty_string
"__SystemLib\\print_hashbang" hashbang;
emit_ignored_call_for_non_empty_string SN.SpecialFunctions.echo tail
]
in
let echo =
match echo_expr_opt with
| Some e -> emit_ignored_call_expr SN.SpecialFunctions.echo e
| None -> empty
in
gather [
markup;
echo
]
let get_level p op e =
match Ast_utils.get_break_continue_level e with
| Ast_utils.Level_ok (Some i) -> i
| Ast_utils.Level_ok None -> 1
| Ast_utils.Level_non_positive ->
Emit_fatal.raise_fatal_parse
p ("'" ^ op ^ "' operator accepts only positive numbers")
| Ast_utils.Level_non_literal ->
Emit_fatal.raise_fatal_parse
p ("'" ^ op ^ "' with non-constant operand is not supported")
let set_bytes_kind name =
let re = Str.regexp_case_fold
"^hh\\\\set_bytes\\(_rev\\|\\)_\\([a-z0-9]+\\)\\(_vec\\|\\)$"
in
if Str.string_match re name 0 then
let op =
if Str.matched_group 1 name = "_rev" then Reverse else Forward
in
let size = Str.matched_group 2 name in
let is_vec = Str.matched_group 3 name = "_vec" in
match size, is_vec with
| "string", false -> Some (op, 1, true)
| "bool", _ | "int8", _ -> Some (op, 1, is_vec)
| "int16", _ -> Some (op, 2, is_vec)
| "int32", _ | "float32", _ -> Some (op, 4, is_vec)
| "int64", _ | "float64", _ -> Some (op, 8, is_vec)
| _ -> None
else None
let rec emit_stmt env (pos, st_) =
match st_ with
| A.Let _ -> assert false (* Let statement is converted to assignment in closure convert *)
| A.Expr (_, A.Yield_break) ->
gather [
instr_null;
emit_return env;
]
| A.Expr ((pos, A.Call ((_, A.Id (_, s)), _, exprl, [])) as expr) ->
if String.lowercase s = "unset" then
gather (List.map exprl (emit_unset_expr env))
else
begin match set_bytes_kind s with
| Some kind -> emit_set_range_expr env pos s kind exprl
| None -> emit_ignored_expr ~pop_pos:pos env expr
end
| A.Return (Some (inner_pos, A.Await e)) ->
gather [
emit_await env inner_pos e;
Emit_pos.emit_pos pos;
emit_return env;
]
| A.Return (Some (_, A.Yield_from e)) ->
gather [
emit_yield_from_delegates env pos e;
Emit_pos.emit_pos pos;
emit_return env;
]
| A.Expr (pos, A.Await e) ->
begin match try_inline_genva_call env e GI_ignore_result with
| Some r -> r
| None -> emit_awaitall_single_no_assign env pos e
end
| A.Expr
(_, A.Binop ((A.Eq None), ((_, A.List l) as e1), (await_pos, A.Await e_await))) ->
begin match try_inline_genva_call env e_await (GI_list_assignment l) with
| Some r -> r
| None ->
let has_elements =
List.exists l ~f: (function
| _, A.Omitted -> false
| _ -> true)
in
if has_elements then
Local.scope @@ fun () ->
let awaited = emit_await env await_pos e_await in
let temp = Local.get_unnamed_local () in
gather [
awaited;
instr_setl temp;
instr_popc;
with_temp_local temp
begin fun temp _ ->
let prefix, block =
emit_lval_op_list env pos (Some temp) [] e1 in
gather [
prefix;
block
]
end;
instr_pushl temp;
instr_popc;
]
else
Local.scope @@ fun () ->
let temp = Local.get_unnamed_local () in
gather [
emit_await env await_pos e_await;
instr_setl temp;
instr_popc;
instr_pushl temp;
instr_popc;
]
end
| A.Expr (_, A.Binop (A.Eq None, e_lhs, (await_pos, A.Await e_await))) ->
emit_awaitall_single env await_pos e_lhs e_await
| A.Expr (_, A.Yield_from e) ->
gather [
emit_yield_from_delegates env pos e;
emit_pos pos;
instr_popc;
]
| A.Expr (pos, A.Binop (A.Eq None, e_lhs, (_, A.Yield_from e))) ->
Local.scope @@ fun () ->
let temp = Local.get_unnamed_local () in
let rhs_instrs = instr_pushl temp in
gather [
emit_yield_from_delegates env pos e;
instr_setl temp;
instr_popc;
emit_lval_op_nonlist env pos LValOp.Set e_lhs rhs_instrs 1;
instr_popc;
]
| A.Expr expr ->
emit_ignored_expr ~pop_pos:pos env expr
| A.Return None ->
gather [
instr_null;
Emit_pos.emit_pos pos;
emit_return env;
]
| A.Return (Some expr) ->
gather [
emit_expr ~need_ref:false env expr;
Emit_pos.emit_pos pos;
emit_return env;
]
| A.GotoLabel (_, label) ->
instr_label (Label.named label)
| A.Goto (_, label) ->
TFR.emit_goto ~in_finally_epilogue:false env label
| A.Block b -> emit_stmts env b
| A.If (condition, consequence, alternative) ->
emit_if env pos condition consequence alternative
| A.While (e, b) ->
emit_while env e (pos, A.Block b)
| A.Declare (is_block, e, b) ->
emit_declare env is_block e b
| A.Using {
Ast.us_has_await = has_await;
Ast.us_expr = e; Ast.us_block = b;
Ast.us_is_block_scoped = is_block_scoped
} ->
emit_using env pos is_block_scoped has_await e (block_pos b, A.Block b)
| A.Break level_opt ->
emit_break env pos (get_level pos "break" level_opt)
| A.Continue level_opt ->
emit_continue env pos (get_level pos "continue" level_opt)
| A.Do (b, e) ->
emit_do env (pos, A.Block b) e
| A.For (e1, e2, e3, b) ->
emit_for env pos e1 e2 e3 (pos, A.Block b)
| A.Throw e ->
gather [
emit_expr ~need_ref:false env e;
Emit_pos.emit_pos pos;
instr (IContFlow Throw);
]
| A.Try (try_block, catch_list, finally_block) ->
if (JT.get_function_has_goto ()) then
TFR.fail_if_goto_from_try_to_finally try_block finally_block;
if catch_list <> [] && finally_block <> [] then
emit_stmt env (pos, A.Try([pos, A.Try (try_block, catch_list, [])], [], finally_block))
else if catch_list <> [] then
emit_try_catch env (pos, A.Block try_block) catch_list
else
emit_try_finally env pos (pos, A.Block try_block) (pos, A.Block finally_block)
| A.Switch (e, cl) ->
emit_switch env pos e cl
| A.Foreach (collection, await_pos, iterator, block) ->
emit_foreach env pos collection await_pos iterator (pos, A.Block block)
| A.Def_inline def ->
emit_def_inline def
| A.Static_var es ->
emit_static_var pos es
| A.Global_var es ->
emit_global_vars pos es
| A.Awaitall el ->
emit_awaitall env pos el
| A.Markup ((_, s), echo_expr_opt) ->
emit_markup env s echo_expr_opt ~check_for_hashbang:false
(* TODO: What do we do with unsafe? *)
| A.Unsafe
| A.Fallthrough
| A.Noop -> empty
and emit_break env pos level =
TFR.emit_break_or_continue ~is_break:true ~in_finally_epilogue:false env pos level
and emit_continue env pos level =
TFR.emit_break_or_continue ~is_break:false ~in_finally_epilogue:false env pos level
and get_instrs r = r.Emit_expression.instrs
and emit_if env pos condition consequence alternative =
match alternative with
| []
| [_, A.Noop] ->
let done_label = Label.next_regular () in
gather [
get_instrs @@ emit_jmpz env condition done_label;
emit_stmts env consequence;
instr_label done_label;
]
| _ ->
let alternative_label = Label.next_regular () in
let done_label = Label.next_regular () in
let consequence_instr = emit_stmts env consequence in
let alternative_instr = emit_stmts env alternative in
gather [
get_instrs @@ emit_jmpz env condition alternative_label;
consequence_instr;
emit_pos pos;
instr_jmp done_label;
instr_label alternative_label;
alternative_instr;
instr_label done_label;
]
and emit_global_vars p es =
let emit_global_var (_, e) =
match e with
| A.Id (_, name) when name.[0] = '$' ->
if SN.Superglobals.is_superglobal name
then empty
else
gather [
instr_string (SU.Locals.strip_dollar name);
instr_vgetg;
instr_bindl @@ Local.Named name;
instr_popv;
]
| _ ->
failwith "Global var - impossible"
in
(* Deduplicate global variable declarations *)
let _, instrs = List.fold es ~init:([], [])
~f:begin fun (seen, instrs) e ->
match snd e with
| A.Id (_, name) when List.mem ~equal:(=) seen name ->
seen, instrs
| A.Id (_, name) ->
name::seen, (emit_global_var e)::instrs
| _ ->
seen, (emit_global_var e)::instrs
end in
Emit_pos.emit_pos_then p @@ gather (List.rev instrs)
and emit_static_var pos es =
let emit_static_var_single e =
match snd e with
| A.Lvar (_, name)
| A.Binop (A.Eq _, (_, A.Lvar (_, name)), _) ->
gather [
Emit_pos.emit_pos pos;
instr_static_loc_init name
]
| _ -> failwith "Static var - impossible"
in
gather @@ List.map es ~f:emit_static_var_single
and emit_awaitall env pos el =
match el with
| [] -> empty
| (Some lvar, e) :: [] -> emit_awaitall_single env pos (Pos.none, A.Lvar lvar) e
| (None, e) :: [] -> emit_awaitall_single_no_assign env pos e
| _ -> emit_awaitall_ env pos el
and emit_awaitall_single env pos lval e =
let result = Local.scope @@ fun () -> emit_await env pos e in
Local.scope @@ fun () ->
let temp = Local.get_unnamed_local () in
let rhs_instrs = instr_pushl temp in
let lhs, rhs, setop =
emit_lval_op_nonlist_steps env pos LValOp.Set lval rhs_instrs 1 in
gather [
result;
instr_setl temp;
instr_popc;
with_temp_local temp (fun _ _ -> lhs);
rhs;
setop;
instr_popc;
]
and emit_awaitall_single_no_assign env pos e =
gather [
emit_await env pos e;
instr_popc;
]
and emit_awaitall_ env _pos el =
let concurrent_items = List.map el
~f:(function (x, y) -> x, y, Local.get_unnamed_local ()) in
let emit_list_assignment =
let reify = gather @@ List.map concurrent_items ~f:begin fun (_, _, l) ->
let label_done = Label.next_regular () in
gather [
instr_istypel l OpNull;
instr_jmpnz label_done;
instr_pushl l;
instr_whresult;
instr_popl l;
instr_label label_done;
]
end in
let set = gather @@ List.filter_map concurrent_items (function
| (None, _, _) -> None
| (Some lhs, _, rhs) ->
Some (gather [
instr_pushl rhs;
instr_setl (get_local env lhs);
instr_popc;
])) in
gather [ reify; set ] in
Local.scope @@ begin fun () ->
let load_args =
gather @@ List.map concurrent_items ~f:begin fun (_, arg, _) ->
emit_expr ~need_ref:false env arg
end in
let init_locals =
gather @@ List.map (List.rev concurrent_items) ~f:begin fun (_, _, l) ->
gather [
instr_setl l;
instr_popc;
]
end in
let await_and_process_results =
let rhs_local = List.map concurrent_items
~f:(function (_, _, x) -> x) in
unset_in_fault rhs_local @@ begin fun () ->
gather [
instr_awaitall
(Some ((List.hd_exn rhs_local), (List.length rhs_local)));
instr_popc;
emit_list_assignment;
]
end in
gather [
load_args;
init_locals;
await_and_process_results;
]
end
and emit_while env e b =
let break_label = Label.next_regular () in
let cont_label = Label.next_regular () in
let start_label = Label.next_regular () in
(* TODO: This is *bizarre* codegen for a while loop.
It would be better to generate this as
instr_label continue_label;
emit_expr e;
instr_jmpz break_label;
body;
instr_jmp continue_label;
instr_label break_label;
*)
gather [
get_instrs @@ emit_jmpz env e break_label;
instr_label start_label;
(Emit_env.do_in_loop_body break_label cont_label env b emit_stmt);
instr_label cont_label;
get_instrs @@ emit_jmpnz env e start_label;
instr_label break_label;
]
and emit_declare env is_block (p, e) b =
(* TODO: We are ignoring the directive (e) here?? *)
let errors =
match e with
| A.Binop (A.Eq None, (_, A.Id (_, "strict_types")), _) when is_block ->
Emit_fatal.emit_fatal_runtime
p "strict_types declaration must not use block mode"
| _ -> empty
in
gather [ errors; emit_stmts env b ]
and emit_using env pos is_block_scoped has_await e b =
match snd e with
| A.Expr_list es ->
emit_stmt env @@ List.fold_right es
~f:(fun e acc ->
fst e, A.Using {
Ast.us_has_await = has_await;
Ast.us_is_block_scoped = is_block_scoped;
Ast.us_expr = e;
Ast.us_block = [acc];
})
~init:b
| _ ->
Local.scope @@ begin fun () ->
let local, preamble = match snd e with
| A.Binop (A.Eq None, (_, A.Lvar (_, id)), _)
| A.Lvar (_, id) ->
Local.Named id, gather [
emit_expr ~need_ref:false env e;
Emit_pos.emit_pos (fst b);
instr_popc;
]
| _ ->
let l = Local.get_unnamed_local () in
l, gather [
emit_expr ~need_ref:false env e;
instr_setl l;
instr_popc
]
in
let finally_start = Label.next_regular () in
let finally_end = Label.next_regular () in
let body = Emit_env.do_in_using_body finally_start env b emit_stmt in
let jump_instructions = TFR.collect_jump_instructions body env in
let body =
if IMap.is_empty jump_instructions then body
else TFR.cleanup_try_body body
in
let fn_name = Hhbc_id.Method.from_raw_string @@
if has_await then "__disposeAsync" else "__dispose"
in
let emit_finally () =
let epilogue, async_eager_label =
if has_await then
let after_await = Label.next_regular() in
gather [
instr_await;
instr_label after_await;
instr_popc
], Some after_await
else
instr_popc, None
in gather [
instr_cgetl local;
instr_fpushobjmethodd 0 fn_name A.OG_nullthrows;
instr_fcall (make_fcall_args ?async_eager_label 0);
epilogue;
if is_block_scoped then instr_unsetl local else empty;
]
in
let finally_epilogue =
TFR.emit_finally_epilogue
env pos ~verify_return:!verify_return ~verify_out:!verify_out ~num_out:!num_out
jump_instructions finally_end
in
let exn_local = Local.get_unnamed_local () in
let after_catch = Label.next_regular() in
let middle =
if is_empty_block b then empty
else gather [
instr_try_catch_begin;
body;
instr_jmp after_catch;
instr_try_catch_middle;
emit_pos (fst b);
make_finally_catch exn_local (emit_finally ());
emit_pos pos;
instr_try_catch_end;
instr_label after_catch;
]
in
gather [
preamble;
middle;
instr_label finally_start;
emit_finally ();
finally_epilogue;
instr_label finally_end;
]
end
and emit_do env b e =
let cont_label = Label.next_regular () in
let break_label = Label.next_regular () in
let start_label = Label.next_regular () in
gather [
instr_label start_label;
(Emit_env.do_in_loop_body break_label cont_label env b emit_stmt);
instr_label cont_label;
get_instrs @@ emit_jmpnz env e start_label;
instr_label break_label;
]
and emit_for env p e1 e2 e3 b =
let break_label = Label.next_regular () in
let cont_label = Label.next_regular () in
let start_label = Label.next_regular () in
(* TODO: this is bizarre codegen for a "for" loop.
This should be codegen'd as
emit_ignored_expr initializer;
instr_label start_label;
from_expr condition;
instr_jmpz break_label;
body;
instr_label continue_label;
emit_ignored_expr increment;
instr_jmp start_label;
instr_label break_label;
*)
let emit_cond ~jmpz label =
let final cond =
get_instrs (if jmpz then emit_jmpz env cond label else emit_jmpnz env cond label)
in
let rec expr_list h tl =
match tl with
| [] -> [final @@ (Pos.none, A.Expr_list [h])]
| h1 :: t1 -> emit_ignored_expr env ~pop_pos:p h :: expr_list h1 t1
in
match e2 with
| _, A.Expr_list [] -> if jmpz then empty else instr_jmp label
| _, A.Expr_list (h::t) -> gather @@ expr_list h t
| cond -> final cond
in
gather [
emit_ignored_expr env ~pop_pos:p e1;
emit_cond ~jmpz:true break_label;
instr_label start_label;
(Emit_env.do_in_loop_body break_label cont_label env b emit_stmt);
instr_label cont_label;
emit_ignored_expr env ~pop_pos:p e3;
emit_cond ~jmpz:false start_label;
instr_label break_label;
]
and emit_switch env pos scrutinee_expr cl =
if List.is_empty cl
then emit_ignored_expr env scrutinee_expr
else
stash_in_local env pos scrutinee_expr
begin fun local break_label ->
(* If there is no default clause, add an empty one at the end *)
let is_default c = match c with A.Default _ -> true | _ -> false in
let cl, has_default =
match List.count cl is_default with
| 0 -> cl @ [A.Default []], false
| 1 -> cl, true
| _ -> Emit_fatal.raise_fatal_runtime
pos "Switch statements may only contain one 'default' clause." in
(* "continue" in a switch in PHP has the same semantics as break! *)
let cl =
Emit_env.do_in_switch_body break_label env cl @@
fun env _ -> List.map cl ~f:(emit_case env)
in
let bodies = gather @@ List.map cl ~f:snd in
let default_label_to_shift =
if has_default
then List.find_map cl ~f: (fun ((e, l), _) ->
if Option.is_none e then Some l else None)
else None in
let init = gather @@ List.map cl
~f: begin fun x ->
let (e_opt, l) = fst x in
match e_opt with
| None ->
(* jmp to default case should be emitted as the
very last 'else' case so do not emit it if it appear in the
middle of emitted if/elseif clauses *)
if Option.is_none default_label_to_shift
then instr_jmp l
else empty
| Some e ->
(* Special case for simple scrutinee *)
match scrutinee_expr with
| _, A.Lvar _ ->
let eq_expr = pos, A.Binop (A.Eqeq, scrutinee_expr, e) in
gather [
emit_expr ~need_ref:false env eq_expr;
instr_jmpnz l
]
| _ ->
gather [
instr_cgetl local;
emit_expr ~need_ref:false env e;
instr_eq;
instr_jmpnz l]
end
in
gather [
init;
Option.value_map default_label_to_shift ~default:empty ~f:instr_jmp;
bodies;
]
end
and block_pos b =
let bpos = List.map b fst in
let valid_pos = List.filter bpos (fun e -> e <> Pos.none) in
if valid_pos = [] then Pos.none
else Pos.btw (List.hd_exn valid_pos) (List.last_exn valid_pos)
and emit_catch env pos end_label (catch_type, (_, catch_local), b) =
(* Note that this is a "regular" label; we're not going to branch to
it directly in the event of an exception. *)
let next_catch = Label.next_regular () in
let id, _ = Hhbc_id.Class.elaborate_id
(Emit_env.get_namespace env) catch_type in
gather [
instr_dup;
instr_instanceofd id;
instr_jmpz next_catch;
instr_setl (Local.Named catch_local);
instr_popc;
emit_stmt env (Pos.none, A.Block b);
Emit_pos.emit_pos pos;
instr_jmp end_label;
instr_label next_catch;
]
and emit_catches env pos catch_list end_label =
gather (List.map catch_list ~f:(emit_catch env pos end_label))
and is_empty_block b =
match b with
| _, A.Block l -> List.for_all ~f:is_empty_block l
| _, A.Noop -> true
| _ -> false
and emit_try_catch env try_block catch_list =
Local.scope @@ fun () ->
emit_try_catch_ env try_block catch_list
and emit_try_catch_ env try_block catch_list =
if is_empty_block try_block then empty
else
let end_label = Label.next_regular () in
let (pos, _) = try_block in
let try_env = Emit_env.with_try env in
gather [
instr_try_catch_begin;
emit_stmt try_env try_block;
Emit_pos.emit_pos pos;
instr_jmp end_label;
instr_try_catch_middle;
emit_catches env pos catch_list end_label;
instr_throw;
instr_try_catch_end;
instr_label end_label;
]
and emit_try_finally env pos try_block finally_block =
Local.scope @@ fun () ->
emit_try_finally_ env pos try_block finally_block
and emit_try_finally_ env pos try_block finally_block =
let make_finally_body () =
Emit_env.do_in_finally_body env finally_block emit_stmt
in
if is_empty_block try_block then make_finally_body ()
else
(*
We need to generate four things:
(1) the try-body, which will be followed by
(2) the normal-continuation finally body, and
(3) an epilogue to the finally body that deals with finally-blocked
break and continue
(4) the exceptional-continuation catch body.
*)
(* (1) Try body
The try body might have un-rewritten continues and breaks which
branch to a label outside of the try. This means that we must
first run the normal-continuation finally, and then branch to the
appropriate label.
We do this by running a rewriter which turns continues and breaks
inside the try body into setting temp_local to an integer which indicates
what action the finally must perform when it is finished, followed by a
jump directly to the finally.
*)
let finally_start = Label.next_regular () in
let finally_end = Label.next_regular () in
let enclosing_span = Ast_scope.Scope.get_span env.Emit_env.env_scope in
let try_env = Emit_env.with_try env in
let try_body =
Emit_env.do_in_try_body finally_start try_env try_block emit_stmt in
let jump_instructions =
TFR.collect_jump_instructions try_body env
in
let try_body =
if IMap.is_empty jump_instructions then try_body
else TFR.cleanup_try_body try_body
in
(* (2) Finally body
Note that this is used both in the normal-continuation and
exceptional-continuation cases; we generate the same code twice.
TODO: We might consider changing the codegen so that the finally block
is only generated once. We could do this by making the catch block set a
temp local to -1, and then branch to the finally block. In the finally block
epilogue it can check to see if the local is -1, and if so, issue an unwind
instruction.
It is illegal to have a continue or break which branches out of a finally.
Unfortunately we at present do not detect this at parse time; rather, we
generate an exception at run-time by rewriting continue and break
instructions found inside finally blocks.
TODO: If we make this illegal at parse time then we can remove this pass.
*)
let exn_local = Local.get_unnamed_local () in
let finally_body = make_finally_body () in
let finally_body_for_catch =
finally_body
|> Label_rewriter.clone_with_fresh_regular_labels
|> strip_fault_bodies
in
(* (3) Finally epilogue *)
let finally_epilogue =
TFR.emit_finally_epilogue
env pos ~verify_return:!verify_return ~verify_out:!verify_out ~num_out:!num_out
jump_instructions finally_end
in
(* (4) Catch body
We now emit the catch body; it is just cleanup code for the temp_local,
a copy of the finally body (without the branching epilogue, since we are
going to unwind rather than branch), and an unwind instruction.
TODO: The HHVM emitter sometimes emits seemingly spurious
unset-unnamed-local instructions into the catch block. These look
like bugs in the emitter. Investigate; if they are bugs in the HHVM
emitter, get them fixed there. If not, get a clear explanation of
what they are for and why they are required.
*)
let after_catch = Label.next_regular() in
let middle = gather [
instr_try_catch_begin;
try_body;
instr_jmp after_catch;
instr_try_catch_middle;
emit_pos enclosing_span;
make_finally_catch exn_local finally_body_for_catch;
instr_try_catch_end;
instr_label after_catch;
]
in
(* Put it all together. *)
gather [
middle;
instr_label finally_start;
Emit_pos.emit_pos (fst finally_block);
finally_body;
finally_epilogue;
instr_label finally_end;
]
and make_finally_catch exn_local finally_body =
let after_catch = Label.next_regular() in
gather [
instr_popl exn_local;
instr_unsetl (Local.get_label_id_local ());
instr_unsetl (Local.get_retval_local ());
instr_try_catch_begin;
finally_body;
instr_jmp after_catch;
instr_try_catch_middle;
instr_pushl exn_local;
instr_chain_faults;
instr_throw;
instr_try_catch_end;
instr_label after_catch;
instr_pushl exn_local;
instr_throw;
]
and get_id_of_simple_lvar_opt v =
match v with
| A.Lvar (pos, str) when str = SN.SpecialIdents.this ->
Emit_fatal.raise_fatal_parse pos "Cannot re-assign $this"
| A.Lvar (_, id) | A.Unop (A.Uref, (_, A.Lvar (_, id)))
when not (SN.Superglobals.is_superglobal id) -> Some id
| _ -> None
and emit_load_list_elements env path vs =
let preamble, load_value =
List.mapi ~f:(emit_load_list_element env path) vs
|> List.unzip
in
List.concat preamble, List.concat load_value
and emit_load_list_element env path i v =
let query_value = gather [
gather @@ List.rev path;
instr_querym 0 QueryOp.CGet (MemberKey.EI (Int64.of_int i));
]
in
match v with
| _, A.Lvar (_, id) ->
let load_value = gather [
query_value;
instr_setl (Local.Named id);
instr_popc
]
in
[], [load_value]
| _, A.List exprs ->
let dim_instr =
instr_dim MemberOpMode.Warn (MemberKey.EI (Int64.of_int i))
in
emit_load_list_elements env (dim_instr::path) exprs
| pos, _ ->
let set_instrs = emit_lval_op_nonlist env pos LValOp.Set v query_value 1 in
let load_value = [set_instrs; instr_popc] in
[], [gather load_value]
(* Assigns a location to store values for foreach-key and foreach-value and
creates a code to populate them.
NOT suitable for foreach (... await ...) which uses different code-gen
Returns: key_local_opt * value_local * key_preamble * value_preamble
where:
- key_local_opt - local variable to store a foreach-key value if it is
declared
- value_local - local variable to store a foreach-value
- key_preamble - list of instructions to populate foreach-key
- value_preamble - list of instructions to populate foreach-value
*)
and emit_iterator_key_value_storage env iterator =
match iterator with
| A.As_kv (((_, k) as expr_k), ((_, v) as expr_v)) ->
begin match get_id_of_simple_lvar_opt k,
get_id_of_simple_lvar_opt v with
| Some key_id, Some value_id ->
let key_local = Local.Named key_id in
let value_local = Local.Named value_id in
Some key_local, value_local, empty
| _ ->
let key_local = Local.get_unnamed_local () in
let value_local = Local.get_unnamed_local () in
let key_preamble, key_load =
emit_iterator_lvalue_storage env expr_k key_local in
let value_preamble, value_load =
emit_iterator_lvalue_storage env expr_v value_local
in
(* HHVM prepends code to initialize non-plain, non-list foreach-key
to the value preamble - do the same to minimize diffs *)
let key_preamble, value_preamble =
match k with
| A.List _ -> key_preamble, value_preamble
| _ -> [], (gather key_preamble) :: value_preamble
in
Some key_local, value_local,
gather [
gather value_preamble;
gather value_load;
gather key_preamble;
gather key_load;
]
end
| A.As_v ((_, v) as expr_v) ->
begin match get_id_of_simple_lvar_opt v with
| Some value_id ->
let value_local = Local.Named value_id in
None, value_local, empty
| None ->
let value_local = Local.get_unnamed_local () in
let value_preamble, value_load =
emit_iterator_lvalue_storage env expr_v value_local in
None, value_local, gather [ gather value_preamble; gather value_load ]
end
(* Emit code for either the key or value l-value operation in foreach await.
* `indices` is the initial prefix of the array indices ([0] for key or [1] for
* value) that is prepended onto the indices needed for list destructuring
*)
and emit_foreach_await_lvalue_storage env expr1 indices local =
let instrs1, instrs2 = emit_lval_op_list env (fst expr1) (Some local) indices expr1 in
gather [
instrs1;
instrs2;
]
(* Emit code for the value and possibly key l-value operation in a foreach
* await statement. `local` is the temporary into which the result of invoking
* the `next` method has been stored. For example:
* foreach (foo() await as $a->f => list($b[0], $c->g)) { ... }
* Here, we need to construct l-value operations that access the [0] (for $a->f)
* and [1;0] (for $b[0]) and [1;1] (for $c->g) indices of the array returned
* from the `next` method.