Refactor hhbc_from_nast into statement and expression emitters
[hiphop-php.git] / hphp / hack / src / hhbc / emit_expression.ml
blobcba90a7de4095a3ebe1e9c3f9dc2eaf98c9cc59e
1 (**
2 * Copyright (c) 2017, Facebook, Inc.
3 * All rights reserved.
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.
9 *)
11 open Core
12 open Hhbc_ast
13 open Instruction_sequence
15 module A = Ast
16 module H = Hhbc_ast
17 module TC = Hhas_type_constraint
18 module SN = Naming_special_names
19 module CBR = Continue_break_rewriter
21 (* When using the PassX instructions we need to emit the right kind *)
22 module PassByRefKind = struct
23 type t = AllowCell | WarnOnCell | ErrorOnCell
24 end
26 (* Locals, array elements, and properties all support the same range of l-value
27 * operations. *)
28 module LValOp = struct
29 type t =
30 | Set
31 | SetOp of eq_op
32 | IncDec of incdec_op
33 end
35 let self_name = ref (None : string option)
36 let set_self n = self_name := n
38 let compiler_options = ref Hhbc_options.default
39 let set_compiler_options o = compiler_options := o
41 (* Emit a comment in lieu of instructions for not-yet-implemented features *)
42 let emit_nyi description =
43 instr (IComment ("NYI: " ^ description))
45 let strip_dollar id =
46 String.sub id 1 (String.length id - 1)
48 let make_varray p es = p, A.Array (List.map es ~f:(fun e -> A.AFvalue e))
49 let make_kvarray p kvs =
50 p, A.Array (List.map kvs ~f:(fun (k, v) -> A.AFkvalue (k, v)))
52 (* Strict binary operations; assumes that operands are already on stack *)
53 let from_binop op =
54 let ints_overflow_to_ints =
55 Hhbc_options.ints_overflow_to_ints !compiler_options in
56 match op with
57 | A.Plus -> instr (IOp (if ints_overflow_to_ints then Add else AddO))
58 | A.Minus -> instr (IOp (if ints_overflow_to_ints then Sub else SubO))
59 | A.Star -> instr (IOp (if ints_overflow_to_ints then Mul else MulO))
60 | A.Slash -> instr (IOp Div)
61 | A.Eqeq -> instr (IOp Eq)
62 | A.EQeqeq -> instr (IOp Same)
63 | A.Starstar -> instr (IOp Pow)
64 | A.Diff -> instr (IOp Neq)
65 | A.Diff2 -> instr (IOp NSame)
66 | A.Lt -> instr (IOp Lt)
67 | A.Lte -> instr (IOp Lte)
68 | A.Gt -> instr (IOp Gt)
69 | A.Gte -> instr (IOp Gte)
70 | A.Dot -> instr (IOp Concat)
71 | A.Amp -> instr (IOp BitAnd)
72 | A.Bar -> instr (IOp BitOr)
73 | A.Ltlt -> instr (IOp Shl)
74 | A.Gtgt -> instr (IOp Shr)
75 | A.Percent -> instr (IOp Mod)
76 | A.Xor -> instr (IOp BitXor)
77 | A.Eq _ -> emit_nyi "Eq"
78 | A.AMpamp
79 | A.BArbar ->
80 failwith "short-circuiting operator cannot be generated as a simple binop"
82 let binop_to_eqop op =
83 let ints_overflow_to_ints =
84 Hhbc_options.ints_overflow_to_ints !compiler_options in
85 match op with
86 | A.Plus -> Some (if ints_overflow_to_ints then PlusEqual else PlusEqualO)
87 | A.Minus -> Some (if ints_overflow_to_ints then MinusEqual else MinusEqualO)
88 | A.Star -> Some (if ints_overflow_to_ints then MulEqual else MulEqualO)
89 | A.Slash -> Some DivEqual
90 | A.Starstar -> Some PowEqual
91 | A.Amp -> Some AndEqual
92 | A.Bar -> Some OrEqual
93 | A.Xor -> Some XorEqual
94 | A.Ltlt -> Some SlEqual
95 | A.Gtgt -> Some SrEqual
96 | A.Percent -> Some ModEqual
97 | A.Dot -> Some ConcatEqual
98 | _ -> None
100 let unop_to_incdec_op op =
101 let ints_overflow_to_ints =
102 Hhbc_options.ints_overflow_to_ints !compiler_options in
103 match op with
104 | A.Uincr -> Some (if ints_overflow_to_ints then PreInc else PreIncO)
105 | A.Udecr -> Some (if ints_overflow_to_ints then PreDec else PreDecO)
106 | A.Upincr -> Some (if ints_overflow_to_ints then PostInc else PostIncO)
107 | A.Updecr -> Some (if ints_overflow_to_ints then PostDec else PostDecO)
108 | _ -> None
110 let collection_type = function
111 | "Vector" -> 17
112 | "Map" -> 18
113 | "Set" -> 19
114 | "Pair" -> 20
115 | "ImmVector" -> 21
116 | "ImmMap" -> 22
117 | "ImmSet" -> 23
118 | x -> failwith ("unknown collection type '" ^ x ^ "'")
120 let istype_op id =
121 match id with
122 | "is_int" | "is_integer" -> Some OpInt
123 | "is_bool" -> Some OpBool
124 | "is_float" | "is_real" | "is_double" -> Some OpDbl
125 | "is_string" -> Some OpStr
126 | "is_array" -> Some OpArr
127 | "is_object" -> Some OpObj
128 | "is_null" -> Some OpNull
129 | "is_scalar" -> Some OpScalar
130 | _ -> None
132 (* See EmitterVisitor::getPassByRefKind in emitter.cpp *)
133 let get_passByRefKind expr =
134 let open PassByRefKind in
135 let rec from_non_list_assignment permissive_kind expr =
136 match snd expr with
137 | A.New _ | A.Lvar _ | A.Clone _ -> AllowCell
138 | A.Binop(A.Eq None, (_, A.List _), e) ->
139 from_non_list_assignment WarnOnCell e
140 | A.Array_get(_, Some _) -> permissive_kind
141 | A.Binop(A.Eq _, _, _) -> WarnOnCell
142 | A.Unop((A.Uincr | A.Udecr), _) -> WarnOnCell
143 | _ -> ErrorOnCell in
144 from_non_list_assignment AllowCell expr
146 let extract_shape_field_name_pstring = function
147 | A.SFlit p
148 | A.SFclass_const (_, p) -> p
150 let extract_shape_field_name = function
151 | A.SFlit (_, s)
152 | A.SFclass_const (_, (_, s)) -> s
154 let rec expr_and_newc instr_to_add_new instr_to_add = function
155 | A.AFvalue e ->
156 gather [from_expr e; instr_to_add_new]
157 | A.AFkvalue (k, v) ->
158 gather [
159 emit_two_exprs k v;
160 instr_to_add
163 and from_local x =
164 if x = SN.SpecialIdents.this then instr_this
165 else instr_cgetl (Local.Named x)
167 and emit_two_exprs e1 e2 =
168 (* Special case to make use of CGetL2 *)
169 match e1 with
170 | (_, A.Lvar (_, local)) ->
171 gather [
172 from_expr e2;
173 instr_cgetl2 (Local.Named local);
175 | _ ->
176 gather [
177 from_expr e1;
178 from_expr e2;
181 and emit_binop op e1 e2 =
182 match op with
183 | A.AMpamp -> emit_logical_and e1 e2
184 | A.BArbar -> emit_logical_or e1 e2
185 | A.Eq None -> emit_lval_op LValOp.Set e1 (Some e2)
186 | A.Eq (Some obop) ->
187 begin match binop_to_eqop obop with
188 | None -> emit_nyi "illegal eq op"
189 | Some op -> emit_lval_op (LValOp.SetOp op) e1 (Some e2)
191 | _ ->
192 gather [
193 emit_two_exprs e1 e2;
194 from_binop op
197 and emit_instanceof e1 e2 =
198 match (e1, e2) with
199 | (_, (_, A.Id (_, id))) ->
200 gather [
201 from_expr e1;
202 instr_instanceofd id ]
203 | _ ->
204 gather [
205 from_expr e1;
206 from_expr e2;
207 instr_instanceof ]
209 and emit_null_coalesce e1 e2 =
210 let end_label = Label.next_regular () in
211 gather [
212 emit_quiet_expr e1;
213 instr_dup;
214 instr_istypec OpNull;
215 instr_not;
216 instr_jmpnz end_label;
217 instr_popc;
218 from_expr e2;
219 instr_label end_label;
222 and emit_cast hint expr =
223 let op =
224 begin match hint with
225 | A.Happly((_, id), [])
226 when id = SN.Typehints.int
227 || id = SN.Typehints.integer ->
228 instr (IOp CastInt)
229 | A.Happly((_, id), [])
230 when id = SN.Typehints.bool
231 || id = SN.Typehints.boolean ->
232 instr (IOp CastBool)
233 | A.Happly((_, id), [])
234 when id = SN.Typehints.string ->
235 instr (IOp CastString)
236 | A.Happly((_, id), [])
237 when id = SN.Typehints.object_cast ->
238 instr (IOp CastObject)
239 | A.Happly((_, id), [])
240 when id = SN.Typehints.array ->
241 instr (IOp CastArray)
242 | A.Happly((_, id), [])
243 when id = SN.Typehints.real
244 || id = SN.Typehints.double
245 || id = SN.Typehints.float ->
246 instr (IOp CastDouble)
247 (* TODO: unset *)
248 | _ ->
249 emit_nyi "cast type"
250 end in
251 gather [
252 from_expr expr;
256 and emit_conditional_expression etest etrue efalse =
257 match etrue with
258 | Some etrue ->
259 let false_label = Label.next_regular () in
260 let end_label = Label.next_regular () in
261 gather [
262 from_expr etest;
263 instr_jmpz false_label;
264 from_expr etrue;
265 instr_jmp end_label;
266 instr_label false_label;
267 from_expr efalse;
268 instr_label end_label;
270 | None ->
271 let end_label = Label.next_regular () in
272 gather [
273 from_expr etest;
274 instr_dup;
275 instr_jmpnz end_label;
276 instr_popc;
277 from_expr efalse;
278 instr_label end_label;
281 and emit_aget class_expr =
282 match class_expr with
283 | _, A.Lvar (_, id) ->
284 instr (IGet (ClsRefGetL (Local.Named id, 0)))
286 | _ ->
287 gather [
288 from_expr class_expr;
289 instr (IGet (ClsRefGetC 0))
292 and emit_new class_expr args uargs =
293 let nargs = List.length args + List.length uargs in
294 match class_expr with
295 | _, A.Id (_, id) ->
296 gather [
297 instr_fpushctord nargs id;
298 emit_args_and_call args uargs;
299 instr_popr
301 | _ ->
302 gather [
303 emit_aget class_expr;
304 instr_fpushctor nargs 0;
305 emit_args_and_call args uargs;
306 instr_popr
309 and emit_clone expr =
310 gather [
311 from_expr expr;
312 instr_clone;
315 and emit_shape expr fl =
316 let are_values_all_literals =
317 List.for_all fl ~f:(fun (_, e) -> is_literal e)
319 let p = fst expr in
320 if are_values_all_literals then
321 let fl =
322 List.map fl
323 ~f:(fun (fn, e) ->
324 A.AFkvalue ((p,
325 A.String (extract_shape_field_name_pstring fn)), e))
327 from_expr (fst expr, A.Array fl)
328 else
329 let es = List.map fl ~f:(fun (_, e) -> from_expr e) in
330 let keys = List.map fl ~f:(fun (fn, _) -> extract_shape_field_name fn) in
331 gather [
332 gather es;
333 instr_newstructarray keys;
336 and emit_tuple p es =
337 (* Did you know that tuples are functions? *)
338 let af_list = List.map es ~f:(fun e -> A.AFvalue e) in
339 from_expr (p, A.Array af_list)
341 and emit_call_expr expr =
342 let instrs, flavor = emit_flavored_expr expr in
343 gather [
344 instrs;
345 (* If the instruction has produced a ref then unbox it *)
346 if flavor = Flavor.Ref then instr_unboxr else empty
349 and emit_known_class_id cid =
350 gather [
351 instr_string (Utils.strip_ns cid);
352 instr (IGet (ClsRefGetC 0))
355 and emit_class_id cid =
356 if cid = SN.Classes.cStatic
357 then instr (IMisc (LateBoundCls 0))
358 else
359 if cid = SN.Classes.cSelf
360 then match !self_name with
361 | None -> instr (IMisc Self)
362 | Some cid -> emit_known_class_id cid
363 else emit_known_class_id cid
365 and emit_class_get param_num_opt cid id =
366 gather [
367 (* We need to strip off the initial dollar *)
368 instr_string (strip_dollar id);
369 emit_class_id cid;
370 match param_num_opt with
371 | None -> instr (IGet (CGetS 0))
372 | Some i -> instr (ICall (FPassS (i, 0)))
375 and emit_class_const cid id =
376 if id = SN.Members.mClass then instr_string cid
377 else if cid = SN.Classes.cStatic
378 then
379 instrs [
380 IMisc (LateBoundCls 0);
381 ILitConst (ClsCns (id, 0));
383 else if cid = SN.Classes.cSelf
384 then
385 match !self_name with
386 | None ->
387 instrs [
388 IMisc Self;
389 ILitConst (ClsCns (id, 0));
391 | Some cid -> instr (ILitConst (ClsCnsD (id, cid)))
392 else
393 instr (ILitConst (ClsCnsD (id, cid)))
395 and emit_await e =
396 let after_await = Label.next_regular () in
397 gather [
398 from_expr e;
399 instr_dup;
400 instr_istypec OpNull;
401 instr_jmpnz after_await;
402 instr_await;
403 instr_label after_await;
406 and emit_yield = function
407 | A.AFvalue e ->
408 gather [
409 from_expr e;
410 instr_yield;
412 | A.AFkvalue (e1, e2) ->
413 gather [
414 from_expr e1;
415 from_expr e2;
416 instr_yieldk;
419 and emit_yield_break () =
420 gather [
421 instr_null;
422 instr_retc;
425 and emit_string2 exprs =
426 match exprs with
427 | [e] ->
428 gather [
429 from_expr e;
430 instr (IOp CastString)
432 | e1::e2::es ->
433 gather @@ [
434 emit_two_exprs e1 e2;
435 instr (IOp Concat);
436 gather (List.map es (fun e -> gather [from_expr e; instr (IOp Concat)]))
439 | [] -> failwith "String2 with zero arguments is impossible"
441 and emit_lambda fundef ids =
442 (* Closure conversion puts the class number used for CreateCl in the "name"
443 * of the function definition *)
444 let class_num = int_of_string (snd fundef.A.f_name) in
445 gather [
446 (* TODO: deal with explicit use (...) capture variables *)
447 gather @@ List.map ids
448 (fun (x, _isref) -> instr (IGet (CUGetL (Local.Named (snd x)))));
449 instr (IMisc (CreateCl (List.length ids, class_num)))
452 and emit_id (p, s) =
453 match s with
454 | "__FILE__" -> instr (ILitConst File)
455 | "__DIR__" -> instr (ILitConst Dir)
456 | "__LINE__" ->
457 (* If the expression goes on multi lines, we return the last line *)
458 let _, line, _, _ = Pos.info_pos_extended p in
459 instr_int line
460 | _ -> emit_nyi ("emit_id: " ^ s)
462 and rename_xhp (p, s) =
463 (* Translates given :name to xhp_name *)
464 if String_utils.string_starts_with s ":"
465 then (p, "xhp_" ^ (String_utils.lstrip s ":"))
466 else failwith "Incorrectly named xhp element"
468 and emit_xhp p id attributes children =
469 (* Translate into a constructor call. The arguments are:
470 * 1) shape-like array of attributes
471 * 2) vec-like array of children
472 * 3) filename, for debugging
473 * 4) line number, for debugging
475 let convert_xml_attr (name, v) = (A.SFlit name, v) in
476 let attributes = List.map ~f:convert_xml_attr attributes in
477 let attribute_map = p, A.Shape attributes in
478 let children_vec = make_varray p children in
479 let filename = p, A.Id (p, "__FILE__") in
480 let line = p, A.Id (p, "__LINE__") in
481 from_expr @@
482 (p, A.New (
483 (p, A.Id (rename_xhp id)),
484 [attribute_map ; children_vec ; filename ; line],
485 []))
487 and emit_import flavor e =
488 let import_instr = match flavor with
489 | A.Include -> instr @@ IIncludeEvalDefine Incl
490 | A.Require -> instr @@ IIncludeEvalDefine Req
491 | A.IncludeOnce -> instr @@ IIncludeEvalDefine InclOnce
492 | A.RequireOnce -> instr @@ IIncludeEvalDefine ReqOnce
494 gather [
495 from_expr e;
496 import_instr;
499 and emit_lvarvar n (_, id) =
500 gather [
501 instr_cgetl (Local.Named id);
502 gather @@ List.replicate ~num:n instr_cgetn;
505 and from_expr expr =
506 (* Note that this takes an Ast.expr, not a Nast.expr. *)
507 match snd expr with
508 | A.Float (_, litstr) -> instr_double litstr
509 | A.String (_, litstr) -> instr_string litstr
510 (* TODO deal with integer out of range *)
511 | A.Int (_, litstr) -> instr_int_of_string litstr
512 | A.Null -> instr_null
513 | A.False -> instr_false
514 | A.True -> instr_true
515 | A.Lvar (_, x) -> from_local x
516 | A.Class_const ((_, cid), (_, id)) -> emit_class_const cid id
517 | A.Unop (op, e) -> emit_unop op e
518 | A.Binop (op, e1, e2) -> emit_binop op e1 e2
519 | A.Pipe (e1, e2) -> emit_pipe e1 e2
520 | A.Dollardollar -> instr_cgetl2 Local.Pipe
521 | A.InstanceOf (e1, e2) -> emit_instanceof e1 e2
522 | A.NullCoalesce (e1, e2) -> emit_null_coalesce e1 e2
523 | A.Cast((_, hint), e) -> emit_cast hint e
524 | A.Eif (etest, etrue, efalse) ->
525 emit_conditional_expression etest etrue efalse
526 | A.Expr_list es -> gather @@ List.map es ~f:from_expr
527 | A.Call ((p, A.Id (_, "tuple")), es, _) -> emit_tuple p es
528 | A.Call _ -> emit_call_expr expr
529 | A.New (typeexpr, args, uargs) -> emit_new typeexpr args uargs
530 | A.Array es -> emit_collection expr es
531 | A.Darray es ->
533 |> List.map ~f:(fun (e1, e2) -> A.AFkvalue (e1, e2))
534 |> emit_collection expr
535 | A.Varray es ->
537 |> List.map ~f:(fun e -> A.AFvalue e)
538 |> emit_collection expr
539 | A.Collection ((pos, name), fields) ->
540 emit_named_collection expr pos name fields
541 | A.Array_get(base_expr, opt_elem_expr) ->
542 emit_array_get None base_expr opt_elem_expr
543 | A.Clone e -> emit_clone e
544 | A.Shape fl -> emit_shape expr fl
545 | A.Obj_get (expr, prop, nullflavor) -> emit_obj_get None expr prop nullflavor
546 | A.Await e -> emit_await e
547 | A.Yield e -> emit_yield e
548 | A.Yield_break -> emit_yield_break ()
549 | A.Lfun _ ->
550 failwith "expected Lfun to be converted to Efun during closure conversion"
551 | A.Efun (fundef, ids) -> emit_lambda fundef ids
552 | A.Class_get ((_, cid), (_, id)) -> emit_class_get None cid id
553 | A.String2 es -> emit_string2 es
554 | A.Unsafeexpr e -> from_expr e
555 | A.Id id -> emit_id id
556 | A.Xml (id, attributes, children) ->
557 emit_xhp (fst expr) id attributes children
558 | A.Import (flavor, e) -> emit_import flavor e
559 | A.Lvarvar (n, id) -> emit_lvarvar n id
560 (* TODO *)
561 | A.Id_type_arguments (_, _) -> emit_nyi "id_type_arguments"
562 | A.List _ -> emit_nyi "list"
564 and emit_static_collection ~transform_to_collection expr es =
565 let a_label = Label.get_next_data_label () in
566 (* Arrays can either contains values or key/value pairs *)
567 let need_index = match snd expr with
568 | A.Collection ((_, "vec"), _)
569 | A.Collection ((_, "keyset"), _) -> false
570 | _ -> true
572 let _, es =
573 List.fold_left
575 ~init:(0, [])
576 ~f:(fun (index, l) x ->
577 let open Constant_folder in
578 (index + 1, match x with
579 | A.AFvalue e when need_index ->
580 literal_from_expr e :: Int (Int64.of_int index) :: l
581 | A.AFvalue e ->
582 literal_from_expr e :: l
583 | A.AFkvalue (k,v) ->
584 literal_from_expr v :: literal_from_expr k :: l)
587 let es = List.rev es in
588 let lit_constructor = match snd expr with
589 | A.Array _ -> Array (a_label, es)
590 | A.Collection ((_, "dict"), _) -> Dict (a_label, es)
591 | A.Collection ((_, "vec"), _) -> Vec (a_label, es)
592 | A.Collection ((_, "keyset"), _) -> Keyset (a_label, es)
593 | _ -> failwith "impossible"
595 let transform_instr =
596 match transform_to_collection with
597 | Some n -> instr_colfromarray n
598 | None -> empty
600 gather [
601 instr (ILitConst lit_constructor);
602 transform_instr;
605 (* transform_to_collection argument keeps track of
606 * what collection to transform to *)
607 and emit_dynamic_collection ~transform_to_collection expr es =
608 let is_only_values =
609 List.for_all es ~f:(function A.AFkvalue _ -> false | _ -> true)
611 let count = List.length es in
612 if is_only_values && transform_to_collection = None then begin
613 let lit_constructor = match snd expr with
614 | A.Array _ -> NewPackedArray count
615 | A.Collection ((_, "vec"), _) -> NewVecArray count
616 | A.Collection ((_, "keyset"), _) -> NewKeysetArray count
617 | _ -> failwith "impossible"
619 gather [
620 gather @@
621 List.map es
622 ~f:(function A.AFvalue e -> from_expr e | _ -> failwith "impossible");
623 instr @@ ILitConst lit_constructor;
625 end else begin
626 let lit_constructor = match snd expr with
627 | A.Array _ -> NewMixedArray count
628 | A.Collection ((_, "dict"), _) -> NewDictArray count
629 | _ -> failwith "impossible"
631 let transform_instr =
632 match transform_to_collection with
633 | Some n -> instr_colfromarray n
634 | None -> empty
636 let add_elem_instr =
637 if transform_to_collection = None
638 then instr_add_new_elemc
639 else instr_col_add_new_elemc
641 gather @@
642 (instr @@ ILitConst lit_constructor) :: transform_instr ::
643 (List.map es ~f:(expr_and_newc add_elem_instr instr_add_elemc))
646 and emit_named_collection expr pos name fields =
647 match name with
648 | "dict" | "vec" | "keyset" -> emit_collection expr fields
649 | "Vector" | "ImmVector" ->
650 let collection_type = collection_type name in
651 gather [
652 emit_collection (pos, A.Collection ((pos, "vec"), fields)) fields;
653 instr_colfromarray collection_type;
655 | "Set" | "ImmSet" | "Map" | "ImmMap" ->
656 let collection_type = collection_type name in
657 if fields = []
658 then instr_newcol collection_type
659 else
660 emit_collection
661 ~transform_to_collection:collection_type
662 (pos, A.Array fields)
663 fields
664 | "Pair" ->
665 let collection_type = collection_type name in
666 let values = gather @@ List.map
667 fields
668 ~f:(fun x ->
669 expr_and_newc instr_col_add_new_elemc instr_col_add_new_elemc x)
671 gather [
672 instr_newcol collection_type;
673 values;
675 | _ -> failwith @@ "collection: " ^ name ^ " does not exist"
677 and emit_collection ?(transform_to_collection) expr es =
678 if is_literal_afield_list es then
679 emit_static_collection ~transform_to_collection expr es
680 else
681 emit_dynamic_collection ~transform_to_collection expr es
683 and emit_pipe e1 e2 =
684 stash_in_local e1
685 begin fun temp _break_label ->
686 let rewrite_dollardollar e =
687 let rewriter i =
688 match i with
689 | IGet (CGetL2 Local.Pipe) ->
690 IGet (CGetL2 temp)
691 | _ -> i in
692 InstrSeq.map e ~f:rewriter in
693 rewrite_dollardollar (from_expr e2)
696 and emit_logical_and e1 e2 =
697 let left_is_false = Label.next_regular () in
698 let right_is_true = Label.next_regular () in
699 let its_done = Label.next_regular () in
700 gather [
701 from_expr e1;
702 instr_jmpz left_is_false;
703 from_expr e2;
704 instr_jmpnz right_is_true;
705 instr_label left_is_false;
706 instr_false;
707 instr_jmp its_done;
708 instr_label right_is_true;
709 instr_true;
710 instr_label its_done ]
712 and emit_logical_or e1 e2 =
713 let its_true = Label.next_regular () in
714 let its_done = Label.next_regular () in
715 gather [
716 from_expr e1;
717 instr_jmpnz its_true;
718 from_expr e2;
719 instr_jmpnz its_true;
720 instr_false;
721 instr_jmp its_done;
722 instr_label its_true;
723 instr_true;
724 instr_label its_done ]
726 and emit_quiet_expr (_, expr_ as expr) =
727 match expr_ with
728 | A.Lvar (_, x) ->
729 instr_cgetquietl (Local.Named x)
730 | _ ->
731 from_expr expr
733 (* Emit code for e1[e2].
734 * If param_num_opt = Some i
735 * then this is the i'th parameter to a function
737 and emit_array_get param_num_opt base_expr opt_elem_expr =
738 let elem_expr_instrs, elem_stack_size = emit_elem_instrs opt_elem_expr in
739 let base_expr_instrs, base_setup_instrs, base_stack_size =
740 emit_base MemberOpMode.Warn elem_stack_size param_num_opt base_expr in
741 let mk = get_elem_member_key 0 opt_elem_expr in
742 let total_stack_size = elem_stack_size + base_stack_size in
743 let final_instr =
744 instr (IFinal (
745 match param_num_opt with
746 | None -> QueryM (total_stack_size, QueryOp.CGet, mk)
747 | Some i -> FPassM (i, total_stack_size, mk)
748 )) in
749 gather [
750 base_expr_instrs;
751 elem_expr_instrs;
752 base_setup_instrs;
753 final_instr
756 (* Emit code for e1->e2 or e1?->e2.
757 * If param_num_opt = Some i
758 * then this is the i'th parameter to a function
760 and emit_obj_get param_num_opt expr prop null_flavor =
761 let prop_expr_instrs, prop_stack_size = emit_prop_instrs prop in
762 let base_expr_instrs, base_setup_instrs, base_stack_size =
763 emit_base MemberOpMode.Warn prop_stack_size param_num_opt expr in
764 let mk = get_prop_member_key null_flavor 0 prop in
765 let total_stack_size = prop_stack_size + base_stack_size in
766 let final_instr =
767 instr (IFinal (
768 match param_num_opt with
769 | None -> QueryM (total_stack_size, QueryOp.CGet, mk)
770 | Some i -> FPassM (i, total_stack_size, mk)
771 )) in
772 gather [
773 base_expr_instrs;
774 prop_expr_instrs;
775 base_setup_instrs;
776 final_instr
779 and emit_elem_instrs opt_elem_expr =
780 match opt_elem_expr with
781 (* These all have special inline versions of member keys *)
782 | Some (_, (A.Lvar _ | A.Int _ | A.String _)) -> empty, 0
783 | Some expr -> from_expr expr, 1
784 | None -> empty, 0
786 and emit_prop_instrs (_, expr_ as expr) =
787 match expr_ with
788 (* These all have special inline versions of member keys *)
789 | A.Lvar _ | A.Id _ -> empty, 0
790 | _ -> from_expr expr, 1
792 (* Get the member key for an array element expression: the `elem` in
793 * expressions of the form `base[elem]`.
794 * If the array element is missing, use the special key `W`.
796 and get_elem_member_key stack_index opt_expr =
797 match opt_expr with
798 (* Special case for local *)
799 | Some (_, A.Lvar (_, x)) -> MemberKey.EL (Local.Named x)
800 (* Special case for literal integer *)
801 | Some (_, A.Int (_, str)) -> MemberKey.EI (Int64.of_string str)
802 (* Special case for literal string *)
803 | Some (_, A.String (_, str)) -> MemberKey.ET str
804 (* General case *)
805 | Some _ -> MemberKey.EC stack_index
806 (* ELement missing (so it's array append) *)
807 | None -> MemberKey.W
809 (* Get the member key for a property *)
810 and get_prop_member_key null_flavor stack_index prop_expr =
811 match prop_expr with
812 (* Special case for known property name *)
813 | (_, A.Id (_, str)) ->
814 begin match null_flavor with
815 | Ast.OG_nullthrows -> MemberKey.PT str
816 | Ast.OG_nullsafe -> MemberKey.QT str
818 (* Special case for local *)
819 | (_, A.Lvar (_, x)) -> MemberKey.PL (Local.Named x)
820 (* General case *)
821 | _ -> MemberKey.PC stack_index
823 (* Emit code for a base expression `expr` that forms part of
824 * an element access `expr[elem]` or field access `expr->fld`.
825 * The instructions are divided into three sections:
826 * 1. base and element/property expression instructions:
827 * push non-trivial base and key values on the stack
828 * 2. base selector instructions: a sequence of Base/Dim instructions that
829 * actually constructs the base address from "member keys" that are inlined
830 * in the instructions, or pulled from the key values that
831 * were pushed on the stack in section 1.
832 * 3. (constructed by the caller) a final accessor e.g. QueryM or setter
833 * e.g. SetOpM instruction that has the final key inlined in the
834 * instruction, or pulled from the key values that were pushed on the
835 * stack in section 1.
836 * The function returns a triple (base_instrs, base_setup_instrs, stack_size)
837 * where base_instrs is section 1 above, base_setup_instrs is section 2, and
838 * stack_size is the number of values pushed onto the stack by section 1.
840 * For example, the r-value expression $arr[3][$ix+2]
841 * will compile to
842 * # Section 1, pushing the value of $ix+2 on the stack
843 * Int 2
844 * CGetL2 $ix
845 * AddO
846 * # Section 2, constructing the base address of $arr[3]
847 * BaseL $arr Warn
848 * Dim Warn EI:3
849 * # Section 3, indexing the array using the value at stack position 0 (EC:0)
850 * QueryM 1 CGet EC:0
852 and emit_base mode base_offset param_num_opt (_, expr_ as expr) =
853 match expr_ with
854 | A.Lvar (_, x) when x = SN.SpecialIdents.this ->
855 instr (IMisc CheckThis),
856 instr (IBase BaseH),
859 | A.Lvar (_, x) ->
860 empty,
861 instr (IBase (
862 match param_num_opt with
863 | None -> BaseL (Local.Named x, mode)
864 | Some i -> FPassBaseL (i, Local.Named x)
868 | A.Array_get(base_expr, opt_elem_expr) ->
869 let elem_expr_instrs, elem_stack_size = emit_elem_instrs opt_elem_expr in
870 let base_expr_instrs, base_setup_instrs, base_stack_size =
871 emit_base mode (base_offset + elem_stack_size) param_num_opt base_expr in
872 let mk = get_elem_member_key base_offset opt_elem_expr in
873 let total_stack_size = base_stack_size + elem_stack_size in
874 gather [
875 base_expr_instrs;
876 elem_expr_instrs;
878 gather [
879 base_setup_instrs;
880 instr (IBase (
881 match param_num_opt with
882 | None -> Dim (mode, mk)
883 | Some i -> FPassDim (i, mk)
886 total_stack_size
888 | A.Obj_get(base_expr, prop_expr, null_flavor) ->
889 let prop_expr_instrs, prop_stack_size = emit_prop_instrs prop_expr in
890 let base_expr_instrs, base_setup_instrs, base_stack_size =
891 emit_base mode (base_offset + prop_stack_size) param_num_opt base_expr in
892 let mk = get_prop_member_key null_flavor base_offset prop_expr in
893 let total_stack_size = prop_stack_size + base_stack_size in
894 let final_instr =
895 instr (IBase (
896 match param_num_opt with
897 | None -> Dim (mode, mk)
898 | Some i -> FPassDim (i, mk)
899 )) in
900 gather [
901 base_expr_instrs;
902 prop_expr_instrs;
904 gather [
905 base_setup_instrs;
906 final_instr
908 total_stack_size
910 | A.Class_get((_, cid), (_, id)) ->
911 let prop_expr_instrs = instr_string (strip_dollar id) in
912 gather [
913 prop_expr_instrs;
914 emit_class_id cid
916 gather [
917 instr (IBase (BaseSC (base_offset, 0)))
921 | _ ->
922 let base_expr_instrs, flavor = emit_flavored_expr expr in
923 base_expr_instrs,
924 instr (IBase (if flavor = Flavor.Ref
925 then BaseR base_offset else BaseC base_offset)),
928 and instr_fpass kind i =
929 match kind with
930 | PassByRefKind.AllowCell -> instr (ICall (FPassC i))
931 | PassByRefKind.WarnOnCell -> instr (ICall (FPassCW i))
932 | PassByRefKind.ErrorOnCell -> instr (ICall (FPassCE i))
934 and instr_fpassr i = instr (ICall (FPassR i))
936 and emit_arg i ((_, expr_) as e) =
937 match expr_ with
938 | A.Lvar (_, x) -> instr_fpassl i (Local.Named x)
940 | A.Array_get(base_expr, opt_elem_expr) ->
941 emit_array_get (Some i) base_expr opt_elem_expr
943 | A.Obj_get(e1, e2, nullflavor) ->
944 emit_obj_get (Some i) e1 e2 nullflavor
946 | A.Class_get((_, cid), (_, id)) ->
947 emit_class_get (Some i) cid id
949 | _ ->
950 let instrs, flavor = emit_flavored_expr e in
951 gather [
952 instrs;
953 if flavor = Flavor.Ref
954 then instr_fpassr i
955 else instr_fpass (get_passByRefKind e) i
958 and emit_ignored_expr e =
959 let instrs, flavor = emit_flavored_expr e in
960 gather [
961 instrs;
962 instr_pop flavor;
965 (* Emit code to construct the argument frame and then make the call *)
966 and emit_args_and_call args uargs =
967 let all_args = args @ uargs in
968 let nargs = List.length all_args in
969 gather [
970 gather (List.mapi all_args emit_arg);
971 if uargs = []
972 then instr (ICall (FCall nargs))
973 else instr (ICall (FCallUnpack nargs))
976 and emit_call_lhs (_, expr_ as expr) nargs =
977 match expr_ with
978 | A.Obj_get (obj, (_, A.Id (_, id)), null_flavor) ->
979 gather [
980 from_expr obj;
981 instr (ICall (FPushObjMethodD (nargs, id, null_flavor)));
984 | A.Class_const ((_, cid), (_, id)) when cid = SN.Classes.cStatic ->
985 gather [
986 instr_string id;
987 instr (IMisc (LateBoundCls 0));
988 instr (ICall (FPushClsMethod (nargs, 0)));
991 | A.Class_const ((_, cid), (_, id)) when cid.[0] = '$' ->
992 gather [
993 instr_string id;
994 instr (IGet (ClsRefGetL (Local.Named cid, 0)));
995 instr (ICall (FPushClsMethod (nargs, 0)))
998 | A.Class_const ((_, cid), (_, id)) ->
999 instr (ICall (FPushClsMethodD (nargs, id, cid)))
1001 | A.Id (_, id) ->
1002 instr (ICall (FPushFuncD (nargs, id)))
1004 | _ ->
1005 gather [
1006 from_expr expr;
1007 instr (ICall (FPushFunc nargs))
1010 and emit_call (_, expr_ as expr) args uargs =
1011 let nargs = List.length args + List.length uargs in
1012 let default () =
1013 gather [
1014 emit_call_lhs expr nargs;
1015 emit_args_and_call args uargs;
1016 ], Flavor.Ref in
1017 match expr_ with
1018 | A.Id (_, id) when id = SN.SpecialFunctions.echo ->
1019 let instrs = gather @@ List.mapi args begin fun i arg ->
1020 gather [
1021 from_expr arg;
1022 instr (IOp Print);
1023 if i = nargs-1 then empty else instr_popc
1024 ] end in
1025 instrs, Flavor.Cell
1027 | A.Id (_, id) ->
1028 begin match args, istype_op id with
1029 | [(_, A.Lvar (_, arg_id))], Some i ->
1030 instr (IIsset (IsTypeL (Local.Named arg_id, i))),
1031 Flavor.Cell
1032 | [arg_expr], Some i ->
1033 gather [
1034 from_expr arg_expr;
1035 instr (IIsset (IsTypeC i))
1036 ], Flavor.Cell
1037 | _ -> default ()
1039 | _ -> default ()
1042 (* Emit code for an expression that might leave a cell or reference on the
1043 * stack. Return which flavor it left.
1045 and emit_flavored_expr (_, expr_ as expr) =
1046 match expr_ with
1047 | A.Call (e, args, uargs) ->
1048 emit_call e args uargs
1049 | _ ->
1050 from_expr expr, Flavor.Cell
1052 and is_literal expr =
1053 match snd expr with
1054 | A.Array afl
1055 | A.Collection ((_, "vec"), afl)
1056 | A.Collection ((_, "keyset"), afl)
1057 | A.Collection ((_, "dict"), afl) -> is_literal_afield_list afl
1058 | A.Float _
1059 | A.String _
1060 | A.Int _
1061 | A.Null
1062 | A.False
1063 | A.True -> true
1064 | _ -> false
1066 and is_literal_afield_list afl =
1067 List.for_all afl
1068 ~f:(function A.AFvalue e -> is_literal e
1069 | A.AFkvalue (k,v) -> is_literal k && is_literal v)
1071 and emit_final_member_op stack_index op mk =
1072 match op with
1073 | LValOp.Set -> instr (IFinal (SetM (stack_index, mk)))
1074 | LValOp.SetOp op -> instr (IFinal (SetOpM (stack_index, op, mk)))
1075 | LValOp.IncDec op -> instr (IFinal (IncDecM (stack_index, op, mk)))
1077 and emit_final_local_op op lid =
1078 match op with
1079 | LValOp.Set -> instr (IMutator (SetL lid))
1080 | LValOp.SetOp op -> instr (IMutator (SetOpL (lid, op)))
1081 | LValOp.IncDec op -> instr (IMutator (IncDecL (lid, op)))
1083 and emit_final_static_op op =
1084 match op with
1085 | LValOp.Set -> instr (IMutator (SetS 0))
1086 | LValOp.SetOp op -> instr (IMutator (SetOpS (op, 0)))
1087 | LValOp.IncDec op -> instr (IMutator (IncDecS (op, 0)))
1089 (* Given a local $local and a list of integer array indices i_1, ..., i_n,
1090 * generate code to extract the value of $local[i_n]...[i_1]:
1091 * BaseL $local Warn
1092 * Dim Warn EI:i_n ...
1093 * Dim Warn EI:i_2
1094 * QueryM 0 CGet EI:i_1
1096 and emit_array_get_fixed local indices =
1097 gather (
1098 instr (IBase (BaseL (local, MemberOpMode.Warn))) ::
1099 List.rev_mapi indices (fun i ix ->
1100 let mk = MemberKey.EI (Int64.of_int ix) in
1101 if i = 0
1102 then instr (IFinal (QueryM (0, QueryOp.CGet, mk)))
1103 else instr (IBase (Dim (MemberOpMode.Warn, mk))))
1106 (* Generate code for each lvalue assignment in a list destructuring expression.
1107 * Lvalues are assigned right-to-left, regardless of the nesting structure. So
1108 * list($a, list($b, $c)) = $d
1109 * and list(list($a, $b), $c) = $d
1110 * will both assign to $c, $b and $a in that order.
1112 and emit_lval_op_list local indices expr =
1113 match expr with
1114 | (_, A.List exprs) ->
1115 gather @@
1116 List.rev @@
1117 List.mapi exprs (fun i expr -> emit_lval_op_list local (i::indices) expr)
1118 | _ ->
1119 (* Generate code to access the element from the array *)
1120 let access_instrs = emit_array_get_fixed local indices in
1121 (* Generate code to assign to the lvalue *)
1122 let assign_instrs = emit_lval_op_nonlist LValOp.Set expr access_instrs 1 in
1123 gather [
1124 assign_instrs;
1125 instr_popc
1128 (* Emit code for an l-value operation *)
1129 and emit_lval_op op expr1 opt_expr2 =
1130 match op, expr1, opt_expr2 with
1131 (* Special case for list destructuring, only on assignment *)
1132 | LValOp.Set, (_, A.List _), Some expr2 ->
1133 stash_in_local ~leave_on_stack:true expr2
1134 begin fun local _break_label ->
1135 emit_lval_op_list local [] expr1
1137 | _ ->
1138 let rhs_instrs, rhs_stack_size =
1139 match opt_expr2 with
1140 | None -> empty, 0
1141 | Some e -> from_expr e, 1 in
1142 emit_lval_op_nonlist op expr1 rhs_instrs rhs_stack_size
1144 and emit_lval_op_nonlist op expr1 rhs_instrs rhs_stack_size =
1145 match expr1 with
1146 | (_, A.Lvar (_, id)) ->
1147 gather [
1148 rhs_instrs;
1149 emit_final_local_op op (Local.Named id)
1152 | (_, A.Array_get(base_expr, opt_elem_expr)) ->
1153 let elem_expr_instrs, elem_stack_size = emit_elem_instrs opt_elem_expr in
1154 let base_offset = elem_stack_size + rhs_stack_size in
1155 let base_expr_instrs, base_setup_instrs, base_stack_size =
1156 emit_base MemberOpMode.Define base_offset None base_expr in
1157 let mk = get_elem_member_key rhs_stack_size opt_elem_expr in
1158 let total_stack_size = elem_stack_size + base_stack_size in
1159 let final_instr = emit_final_member_op total_stack_size op mk in
1160 gather [
1161 base_expr_instrs;
1162 elem_expr_instrs;
1163 rhs_instrs;
1164 base_setup_instrs;
1165 final_instr
1168 | (_, A.Obj_get(e1, e2, null_flavor)) ->
1169 let prop_expr_instrs, prop_stack_size = emit_prop_instrs e2 in
1170 let base_offset = prop_stack_size + rhs_stack_size in
1171 let base_expr_instrs, base_setup_instrs, base_stack_size =
1172 emit_base MemberOpMode.Define base_offset None e1 in
1173 let mk = get_prop_member_key null_flavor rhs_stack_size e2 in
1174 let total_stack_size = prop_stack_size + base_stack_size in
1175 let final_instr = emit_final_member_op total_stack_size op mk in
1176 gather [
1177 base_expr_instrs;
1178 prop_expr_instrs;
1179 rhs_instrs;
1180 base_setup_instrs;
1181 final_instr
1184 | (_, A.Class_get((_, cid), (_, id))) ->
1185 let prop_expr_instrs = instr_string (strip_dollar id) in
1186 let final_instr = emit_final_static_op op in
1187 gather [
1188 prop_expr_instrs;
1189 emit_class_id cid;
1190 rhs_instrs;
1191 final_instr
1194 | _ ->
1195 gather [
1196 emit_nyi "lval expression";
1197 rhs_instrs;
1200 and emit_unop op e =
1201 let ints_overflow_to_ints =
1202 Hhbc_options.ints_overflow_to_ints !compiler_options in
1203 match op with
1204 | A.Utild -> gather [from_expr e; instr (IOp BitNot)]
1205 | A.Unot -> gather [from_expr e; instr (IOp Not)]
1206 | A.Uplus -> gather
1207 [instr (ILitConst (Int (Int64.zero)));
1208 from_expr e;
1209 instr (IOp (if ints_overflow_to_ints then Add else AddO))]
1210 | A.Uminus -> gather
1211 [instr (ILitConst (Int (Int64.zero)));
1212 from_expr e;
1213 instr (IOp (if ints_overflow_to_ints then Sub else SubO))]
1214 | A.Uincr | A.Udecr | A.Upincr | A.Updecr ->
1215 begin match unop_to_incdec_op op with
1216 | None -> emit_nyi "incdec"
1217 | Some incdec_op ->
1218 emit_lval_op (LValOp.IncDec incdec_op) e None
1220 | A.Uref ->
1221 emit_nyi "references"
1223 and from_exprs exprs =
1224 gather (List.map exprs from_expr)
1226 and stash_in_local ?(leave_on_stack=false) e f =
1227 let break_label = Label.next_regular () in
1228 match e with
1229 | (_, A.Lvar (_, id)) ->
1230 gather [
1231 f (Local.Named id) break_label;
1232 instr_label break_label;
1234 | _ ->
1235 let temp = Local.get_unnamed_local () in
1236 let fault_label = Label.next_fault () in
1237 gather [
1238 from_expr e;
1239 instr_setl temp;
1240 instr_popc;
1241 instr_try_fault
1242 fault_label
1243 (* try block *)
1244 (f temp break_label)
1245 (* fault block *)
1246 (gather [
1247 instr_unsetl temp;
1248 instr_unwind ]);
1249 instr_label break_label;
1250 if leave_on_stack then instr_pushl temp else instr_unsetl temp