Class get
[hiphop-php.git] / hphp / hack / src / hhbc / hhbc_by_ref / emit_expression.rs
blob75f817a975ba9b4a5fc6b41194b7ed84c8b5c61e
1 // Copyright (c) Facebook, Inc. and its affiliates.
2 //
3 // This source code is licensed under the MIT license found in the
4 // LICENSE file in the "hack" directory of this source tree.
6 use decl_provider::DeclProvider;
7 use ffi::{Maybe::Just, Slice, Str};
8 use hhbc_by_ref_ast_class_expr::ClassExpr;
9 use hhbc_by_ref_ast_constant_folder as ast_constant_folder;
10 use hhbc_by_ref_emit_adata as emit_adata;
11 use hhbc_by_ref_emit_fatal as emit_fatal;
12 use hhbc_by_ref_emit_pos::{emit_pos, emit_pos_then};
13 use hhbc_by_ref_emit_symbol_refs as emit_symbol_refs;
14 use hhbc_by_ref_emit_type_constant as emit_type_constant;
15 use hhbc_by_ref_env::{emitter::Emitter, Env, Flags as EnvFlags};
16 use hhbc_by_ref_hhbc_ast::*;
17 use hhbc_by_ref_hhbc_id::{class, r#const, function, method, prop, Id};
18 use hhbc_by_ref_hhbc_string_utils as string_utils;
19 use hhbc_by_ref_instruction_sequence::{
20     instr, unrecoverable,
21     Error::{self, Unrecoverable},
22     InstrSeq, Result,
24 use hhbc_by_ref_label::Label;
25 use hhbc_by_ref_local::Local;
26 use hhbc_by_ref_options::{CompilerFlags, HhvmFlags, LangFlags, Options};
27 use hhbc_by_ref_runtime::TypedValue;
28 use hhbc_by_ref_scope::scope;
29 use hhbc_by_ref_symbol_refs_state::IncludePath;
30 use itertools::Either;
31 use lazy_static::lazy_static;
32 use naming_special_names_rust::{
33     emitter_special_functions, fb, pseudo_consts, pseudo_functions, special_functions,
34     special_idents, superglobals, typehints, user_attributes,
36 use oxidized::{
37     aast, aast_defs,
38     aast_visitor::{visit, visit_mut, AstParams, Node, NodeMut, Visitor, VisitorMut},
39     ast, ast_defs, local_id,
40     pos::Pos,
42 use regex::Regex;
44 use hash::HashSet;
45 use indexmap::IndexSet;
46 use std::{
47     collections::BTreeMap, convert::TryInto, iter, result::Result as StdResult, str::FromStr,
50 #[derive(Debug)]
51 pub struct EmitJmpResult<'arena> {
52     // generated instruction sequence
53     pub instrs: InstrSeq<'arena>,
54     // does instruction sequence fall through
55     is_fallthrough: bool,
56     // was label associated with emit operation used
57     is_label_used: bool,
60 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
61 pub enum LValOp {
62     Set,
63     SetOp(EqOp),
64     IncDec(IncdecOp),
65     Unset,
68 impl LValOp {
69     fn is_incdec(&self) -> bool {
70         if let Self::IncDec(_) = self {
71             return true;
72         };
73         false
74     }
77 pub fn is_local_this<'a, 'arena>(env: &Env<'a, 'arena>, lid: &local_id::LocalId) -> bool {
78     local_id::get_name(lid) == special_idents::THIS
79         && env.scope.has_this()
80         && !env.scope.is_toplevel()
83 mod inout_locals {
84     use crate::*;
85     use hash::HashMap;
86     use oxidized::{aast_defs::Lid, aast_visitor, aast_visitor::Node, ast, ast_defs};
87     use std::marker::PhantomData;
89     pub(super) struct AliasInfo {
90         first_inout: isize,
91         last_write: isize,
92         num_uses: usize,
93     }
95     impl Default for AliasInfo {
96         fn default() -> Self {
97             AliasInfo {
98                 first_inout: std::isize::MAX,
99                 last_write: std::isize::MIN,
100                 num_uses: 0,
101             }
102         }
103     }
105     impl AliasInfo {
106         pub(super) fn add_inout(&mut self, i: isize) {
107             if i < self.first_inout {
108                 self.first_inout = i;
109             }
110         }
112         pub(super) fn add_write(&mut self, i: isize) {
113             if i > self.last_write {
114                 self.last_write = i;
115             }
116         }
118         pub(super) fn add_use(&mut self) {
119             self.num_uses += 1
120         }
122         pub(super) fn in_range(&self, i: isize) -> bool {
123             i > self.first_inout || i <= self.last_write
124         }
126         pub(super) fn has_single_ref(&self) -> bool {
127             self.num_uses < 2
128         }
129     }
131     pub(super) type AliasInfoMap<'ast> = HashMap<&'ast str, AliasInfo>;
133     pub(super) fn new_alias_info_map<'ast>() -> AliasInfoMap<'ast> {
134         HashMap::default()
135     }
137     fn add_write<'ast>(name: &'ast str, i: usize, map: &mut AliasInfoMap<'ast>) {
138         map.entry(name.as_ref()).or_default().add_write(i as isize);
139     }
141     fn add_inout<'ast>(name: &'ast str, i: usize, map: &mut AliasInfoMap<'ast>) {
142         map.entry(name.as_ref()).or_default().add_inout(i as isize);
143     }
145     fn add_use<'ast>(name: &'ast str, map: &mut AliasInfoMap<'ast>) {
146         map.entry(name.as_ref()).or_default().add_use();
147     }
149     // determines if value of a local 'name' that appear in parameter 'i'
150     // should be saved to local because it might be overwritten later
151     pub(super) fn should_save_local_value(name: &str, i: usize, aliases: &AliasInfoMap) -> bool {
152         aliases
153             .get(name)
154             .map_or(false, |alias| alias.in_range(i as isize))
155     }
157     pub(super) fn should_move_local_value<'arena>(
158         local: &Local<'arena>,
159         aliases: &AliasInfoMap,
160     ) -> bool {
161         match local {
162             Local::Named(name) => aliases
163                 .get(name.as_str())
164                 .map_or(true, |alias| alias.has_single_ref()),
165             Local::Unnamed(_) => false,
166         }
167     }
169     pub(super) fn collect_written_variables<'ast, 'arena>(
170         env: &Env<'ast, 'arena>,
171         args: &'ast [ast::Expr],
172     ) -> AliasInfoMap<'ast> {
173         let mut acc = HashMap::default();
174         args.iter()
175             .enumerate()
176             .for_each(|(i, arg)| handle_arg(env, true, i, arg, &mut acc));
177         acc
178     }
180     fn handle_arg<'ast, 'arena>(
181         env: &Env<'ast, 'arena>,
182         is_top: bool,
183         i: usize,
184         arg: &'ast ast::Expr,
185         acc: &mut AliasInfoMap<'ast>,
186     ) {
187         use ast::{Expr, Expr_};
188         let Expr(_, _, e) = arg;
189         // inout $v
190         if let Some((ast_defs::ParamKind::Pinout, Expr(_, _, Expr_::Lvar(lid)))) = e.as_callconv() {
191             let Lid(_, lid) = &**lid;
192             if !is_local_this(env, &lid) {
193                 add_use(&lid.1, acc);
194                 return if is_top {
195                     add_inout(lid.1.as_str(), i, acc);
196                 } else {
197                     add_write(lid.1.as_str(), i, acc);
198                 };
199             }
200         }
201         // $v
202         if let Some(Lid(_, (_, id))) = e.as_lvar() {
203             return add_use(id.as_str(), acc);
204         }
205         // dive into argument value
206         aast_visitor::visit(
207             &mut Visitor(PhantomData),
208             &mut Ctx { state: acc, env, i },
209             arg,
210         )
211         .unwrap();
212     }
214     struct Visitor<'r, 'arena>(PhantomData<(&'arena (), &'r ())>);
216     pub struct Ctx<'r, 'ast: 'r, 'arena: 'r> {
217         state: &'r mut AliasInfoMap<'ast>,
218         env: &'r Env<'ast, 'arena>,
219         i: usize,
220     }
222     impl<'r, 'ast: 'r, 'arena: 'r> aast_visitor::Visitor<'ast> for Visitor<'r, 'arena> {
223         type P = aast_visitor::AstParams<Ctx<'r, 'ast, 'arena>, ()>;
225         fn object(&mut self) -> &mut dyn aast_visitor::Visitor<'ast, P = Self::P> {
226             self
227         }
229         fn visit_expr_(
230             &mut self,
231             c: &mut Ctx<'r, 'ast, 'arena>,
232             p: &'ast ast::Expr_,
233         ) -> std::result::Result<(), ()> {
234             // f(inout $v) or f(&$v)
235             if let ast::Expr_::Call(expr) = p {
236                 let (_, _, args, uarg) = &**expr;
237                 args.iter()
238                     .for_each(|arg| handle_arg(&c.env, false, c.i, arg, &mut c.state));
239                 if let Some(arg) = uarg.as_ref() {
240                     handle_arg(&c.env, false, c.i, arg, &mut c.state)
241                 }
242                 Ok(())
243             } else {
244                 p.recurse(c, self.object())?;
245                 Ok(match p {
246                     // lhs op= _
247                     ast::Expr_::Binop(expr) => {
248                         let (bop, left, _) = &**expr;
249                         if let ast_defs::Bop::Eq(_) = bop {
250                             collect_lvars_hs(c, left)
251                         }
252                     }
253                     // $i++ or $i--
254                     ast::Expr_::Unop(expr) => {
255                         let (uop, e) = &**expr;
256                         match uop {
257                             ast_defs::Uop::Uincr | ast_defs::Uop::Udecr => collect_lvars_hs(c, e),
258                             _ => {}
259                         }
260                     }
261                     // $v
262                     ast::Expr_::Lvar(expr) => {
263                         let Lid(_, (_, id)) = &**expr;
264                         add_use(id, &mut c.state);
265                     }
266                     _ => {}
267                 })
268             }
269         }
270     } // impl<'ast, 'a, 'arena> aast_visitor::Visitor<'ast> for Visitor<'a, 'arena>
272     // collect lvars on the left hand side of '=' operator
273     fn collect_lvars_hs<'r, 'ast, 'arena>(ctx: &mut Ctx<'r, 'ast, 'arena>, expr: &'ast ast::Expr) {
274         let ast::Expr(_, _, e) = expr;
275         match &*e {
276             ast::Expr_::Lvar(lid) => {
277                 let Lid(_, lid) = &**lid;
278                 if !is_local_this(&ctx.env, &lid) {
279                     add_use(lid.1.as_str(), &mut ctx.state);
280                     add_write(lid.1.as_str(), ctx.i, &mut ctx.state);
281                 }
282             }
283             ast::Expr_::List(exprs) => exprs.iter().for_each(|expr| collect_lvars_hs(ctx, expr)),
284             _ => {}
285         }
286     }
287 } //mod inout_locals
289 pub fn get_type_structure_for_hint<'arena, 'decl, D: DeclProvider<'decl>>(
290     alloc: &'arena bumpalo::Bump,
291     e: &mut Emitter<'arena, 'decl, D>,
292     tparams: &[&str],
293     targ_map: &IndexSet<&str>,
294     hint: &aast::Hint,
295 ) -> std::result::Result<InstrSeq<'arena>, hhbc_by_ref_instruction_sequence::Error> {
296     let targ_map: BTreeMap<&str, i64> = targ_map
297         .iter()
298         .enumerate()
299         .map(|(i, n)| (*n, i as i64))
300         .collect();
301     let tv = emit_type_constant::hint_to_type_constant(
302         alloc,
303         e.options(),
304         tparams,
305         &targ_map,
306         &hint,
307         false,
308         false,
309     )?;
310     let i = Str::from(emit_adata::get_array_identifier(alloc, e, &tv));
311     Ok(instr::lit_const(alloc, InstructLitConst::Dict(i)))
314 pub struct Setrange {
315     pub op: SetrangeOp,
316     pub size: usize,
317     pub vec: bool,
320 /// kind of value stored in local
321 #[derive(Debug, Clone, Copy)]
322 pub enum StoredValueKind {
323     Local,
324     Expr,
327 /// represents sequence of instructions interleaved with temp locals.
328 ///    <(i, None) :: rest> - is emitted i :: <rest> (commonly used for final instructions in sequence)
329 ///    <(i, Some(l, local_kind)) :: rest> is emitted as
331 ///    i
332 ///    .try {
333 ///      setl/popl l; depending on local_kind
334 ///      <rest>
335 ///    } .catch {
336 ///      unset l
337 ///      throw
338 ///    }
339 ///    unsetl l
340 type InstrSeqWithLocals<'arena> = Vec<(InstrSeq<'arena>, Option<(Local<'arena>, StoredValueKind)>)>;
342 /// result of emit_array_get
343 enum ArrayGetInstr<'arena> {
344     /// regular $a[..] that does not need to spill anything
345     Regular(InstrSeq<'arena>),
346     /// subscript expression used as inout argument that need to spill intermediate values:
347     Inout {
348         /// instruction sequence with locals to load value
349         load: InstrSeqWithLocals<'arena>,
350         /// instruction to set value back (can use locals defined in load part)
351         store: InstrSeq<'arena>,
352     },
355 struct ArrayGetBaseData<'arena, T> {
356     base_instrs: T,
357     cls_instrs: InstrSeq<'arena>,
358     setup_instrs: InstrSeq<'arena>,
359     base_stack_size: StackIndex,
360     cls_stack_size: StackIndex,
363 /// result of emit_base
364 enum ArrayGetBase<'arena> {
365     /// regular <base> part in <base>[..] that does not need to spill anything
366     Regular(ArrayGetBaseData<'arena, InstrSeq<'arena>>),
367     /// base of subscript expression used as inout argument that need to spill
368     /// intermediate values
369     Inout {
370         /// instructions to load base part
371         load: ArrayGetBaseData<'arena, InstrSeqWithLocals<'arena>>,
372         /// instruction to load base part for setting inout argument back
373         store: InstrSeq<'arena>,
374     },
377 pub fn emit_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
378     emitter: &mut Emitter<'arena, 'decl, D>,
379     env: &Env<'a, 'arena>,
380     expression: &ast::Expr,
381 ) -> Result<InstrSeq<'arena>> {
382     use aast_defs::Lid;
383     use ast::Expr_;
384     let alloc = env.arena;
385     let ast::Expr(_, pos, expr) = expression;
386     match expr {
387         Expr_::Float(_)
388         | Expr_::EnumClassLabel(_)
389         | Expr_::String(_)
390         | Expr_::Int(_)
391         | Expr_::Null
392         | Expr_::False
393         | Expr_::True => {
394             let v = ast_constant_folder::expr_to_typed_value(alloc, emitter, expression)
395                 .map_err(|_| unrecoverable("expr_to_typed_value failed"))?;
396             Ok(emit_pos_then(alloc, pos, instr::typedvalue(alloc, v)))
397         }
398         Expr_::PrefixedString(e) => emit_expr(emitter, env, &e.1),
399         Expr_::Lvar(e) => {
400             let Lid(pos, _) = &**e;
401             Ok(InstrSeq::gather(
402                 alloc,
403                 vec![
404                     emit_pos(alloc, pos),
405                     emit_local(emitter, env, BareThisOp::Notice, e)?,
406                 ],
407             ))
408         }
409         Expr_::ClassConst(e) => emit_class_const(emitter, env, pos, &e.0, &e.1),
410         Expr_::Unop(e) => emit_unop(emitter, env, pos, e),
411         Expr_::Binop(_) => emit_binop(emitter, env, pos, expression),
412         Expr_::Pipe(e) => emit_pipe(emitter, env, e),
413         Expr_::Is(is_expr) => {
414             let (e, h) = &**is_expr;
415             let is = emit_is(emitter, env, pos, h)?;
416             Ok(InstrSeq::gather(
417                 alloc,
418                 vec![emit_expr(emitter, env, e)?, is],
419             ))
420         }
421         Expr_::As(e) => emit_as(emitter, env, pos, e),
422         Expr_::Cast(e) => emit_cast(emitter, env, pos, &(e.0).1, &e.1),
423         Expr_::Eif(e) => emit_conditional_expr(emitter, env, pos, &e.0, &e.1, &e.2),
424         Expr_::ArrayGet(e) => {
425             let (base_expr, opt_elem_expr) = &**e;
426             Ok(emit_array_get(
427                 emitter,
428                 env,
429                 pos,
430                 None,
431                 QueryOp::CGet,
432                 base_expr,
433                 opt_elem_expr.as_ref(),
434                 false,
435                 false,
436             )?
437             .0)
438         }
439         Expr_::ObjGet(e) => Ok(emit_obj_get(
440             emitter,
441             env,
442             pos,
443             QueryOp::CGet,
444             &e.0,
445             &e.1,
446             &e.2,
447             false,
448             false,
449         )?
450         .0),
451         Expr_::Call(c) => emit_call_expr(emitter, env, pos, None, false, c),
452         Expr_::New(e) => emit_new(emitter, env, pos, e, false),
453         Expr_::FunctionPointer(fp) => emit_function_pointer(emitter, env, pos, &fp.0, &fp.1),
454         Expr_::Record(e) => emit_record(emitter, env, pos, e),
455         Expr_::Darray(e) => Ok(emit_pos_then(
456             alloc,
457             pos,
458             emit_collection(emitter, env, expression, &mk_afkvalues(&e.1), None)?,
459         )),
460         Expr_::Varray(e) => Ok(emit_pos_then(
461             alloc,
462             pos,
463             emit_collection(emitter, env, expression, &mk_afvalues(&e.1), None)?,
464         )),
465         Expr_::Collection(e) => emit_named_collection_str(emitter, env, expression, e),
466         Expr_::ValCollection(e) => {
467             let (kind, _, es) = &**e;
468             let fields = mk_afvalues(es);
469             let collection_typ = match kind {
470                 aast_defs::VcKind::Vector => CollectionType::Vector,
471                 aast_defs::VcKind::ImmVector => CollectionType::ImmVector,
472                 aast_defs::VcKind::Set => CollectionType::Set,
473                 aast_defs::VcKind::ImmSet => CollectionType::ImmSet,
474                 _ => return emit_collection(emitter, env, expression, &fields, None),
475             };
476             emit_named_collection(emitter, env, pos, expression, &fields, collection_typ)
477         }
478         Expr_::Pair(e) => {
479             let (_, e1, e2) = (**e).to_owned();
480             let fields = mk_afvalues(&[e1, e2]);
481             emit_named_collection(emitter, env, pos, expression, &fields, CollectionType::Pair)
482         }
483         Expr_::KeyValCollection(e) => {
484             let (kind, _, fields) = &**e;
485             let fields = mk_afkvalues(
486                 &fields
487                     .to_owned()
488                     .into_iter()
489                     .map(|ast::Field(e1, e2)| (e1, e2))
490                     .collect::<Vec<_>>()
491                     .as_slice(),
492             );
493             let collection_typ = match kind {
494                 aast_defs::KvcKind::Map => CollectionType::Map,
495                 aast_defs::KvcKind::ImmMap => CollectionType::ImmMap,
496                 _ => return emit_collection(emitter, env, expression, &fields, None),
497             };
498             emit_named_collection(emitter, env, pos, expression, &fields, collection_typ)
499         }
500         Expr_::Clone(e) => Ok(emit_pos_then(alloc, pos, emit_clone(emitter, env, e)?)),
501         Expr_::Shape(e) => Ok(emit_pos_then(
502             alloc,
503             pos,
504             emit_shape(emitter, env, expression, e)?,
505         )),
506         Expr_::Await(e) => emit_await(emitter, env, pos, e),
507         Expr_::ReadonlyExpr(e) => emit_readonly_expr(emitter, env, pos, e),
508         Expr_::Yield(e) => emit_yield(emitter, env, pos, e),
509         Expr_::Efun(e) => Ok(emit_pos_then(
510             alloc,
511             pos,
512             emit_lambda(emitter, env, &e.0, &e.1)?,
513         )),
514         Expr_::ClassGet(e) => {
515             // class gets without a readonly expression must be mutable
516             emit_class_get(emitter, env, QueryOp::CGet, &e.0, &e.1, ReadonlyOp::Mutable)
517         }
519         Expr_::String2(es) => emit_string2(emitter, env, pos, es),
520         Expr_::Id(e) => Ok(emit_pos_then(alloc, pos, emit_id(emitter, env, e)?)),
521         Expr_::Xml(_) => Err(unrecoverable(
522             "emit_xhp: syntax should have been converted during rewriting",
523         )),
524         Expr_::Callconv(_) => Err(unrecoverable(
525             "emit_callconv: This should have been caught at emit_arg",
526         )),
527         Expr_::Import(e) => emit_import(emitter, env, pos, &e.0, &e.1),
528         Expr_::Omitted => Ok(instr::empty(alloc)),
529         Expr_::Lfun(_) => Err(unrecoverable(
530             "expected Lfun to be converted to Efun during closure conversion emit_expr",
531         )),
532         Expr_::List(_) => Err(emit_fatal::raise_fatal_parse(
533             pos,
534             "list() can only be used as an lvar. Did you mean to use tuple()?",
535         )),
536         Expr_::Tuple(e) => Ok(emit_pos_then(
537             alloc,
538             pos,
539             emit_collection(emitter, env, expression, &mk_afvalues(&e), None)?,
540         )),
542         Expr_::This | Expr_::Lplaceholder(_) | Expr_::Dollardollar(_) => {
543             unimplemented!("TODO(hrust) Codegen after naming pass on AAST")
544         }
545         Expr_::ExpressionTree(et) => emit_expr(emitter, env, &et.runtime_expr),
546         Expr_::ETSplice(_) => Err(unrecoverable(
547             "expression trees: splice should be erased during rewriting",
548         )),
549         Expr_::FunId(_)
550         | Expr_::MethodId(_)
551         | Expr_::MethodCaller(_)
552         | Expr_::SmethodId(_)
553         | Expr_::Hole(_) => {
554             unimplemented!("TODO(hrust)")
555         }
556     }
559 fn emit_exprs<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
560     e: &mut Emitter<'arena, 'decl, D>,
561     env: &Env<'a, 'arena>,
562     exprs: &[ast::Expr],
563 ) -> Result<InstrSeq<'arena>> {
564     let alloc = env.arena;
565     if exprs.is_empty() {
566         Ok(instr::empty(alloc))
567     } else {
568         Ok(InstrSeq::gather(
569             alloc,
570             exprs
571                 .iter()
572                 .map(|expr| emit_expr(e, env, expr))
573                 .collect::<Result<Vec<_>>>()?,
574         ))
575     }
578 fn emit_id<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
579     emitter: &mut Emitter<'arena, 'decl, D>,
580     env: &Env<'a, 'arena>,
581     id: &ast::Sid,
582 ) -> Result<InstrSeq<'arena>> {
583     use pseudo_consts::*;
584     use InstructLitConst::*;
586     let alloc = env.arena;
587     let ast_defs::Id(p, s) = id;
588     let res = match s.as_str() {
589         G__FILE__ => instr::lit_const(alloc, File),
590         G__DIR__ => instr::lit_const(alloc, Dir),
591         G__METHOD__ => instr::lit_const(alloc, Method),
592         G__FUNCTION_CREDENTIAL__ => instr::lit_const(alloc, FuncCred),
593         G__CLASS__ => InstrSeq::gather(alloc, vec![instr::self_(alloc), instr::classname(alloc)]),
594         G__COMPILER_FRONTEND__ => instr::string(alloc, "hackc"),
595         G__LINE__ => instr::int(
596             alloc,
597             p.info_pos_extended().1.try_into().map_err(|_| {
598                 emit_fatal::raise_fatal_parse(p, "error converting end of line from usize to isize")
599             })?,
600         ),
601         G__NAMESPACE__ => instr::string(alloc, env.namespace.name.as_ref().map_or("", |s| &s[..])),
602         EXIT | DIE => return emit_exit(emitter, env, None),
603         _ => {
604             // panic!("TODO: uncomment after D19350786 lands")
605             // let cid: ConstId = r#const::ConstType::from_ast_name(&s);
606             let cid: ConstId = (alloc, string_utils::strip_global_ns(&s)).into();
607             emit_symbol_refs::add_constant(alloc, emitter, cid.clone());
608             return Ok(emit_pos_then(alloc, p, instr::lit_const(alloc, CnsE(cid))));
609         }
610     };
611     Ok(res)
614 fn emit_exit<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
615     emitter: &mut Emitter<'arena, 'decl, D>,
616     env: &Env<'a, 'arena>,
617     expr_opt: Option<&ast::Expr>,
618 ) -> Result<InstrSeq<'arena>> {
619     let alloc = env.arena;
620     Ok(InstrSeq::gather(
621         alloc,
622         vec![
623             expr_opt.map_or_else(|| Ok(instr::int(alloc, 0)), |e| emit_expr(emitter, env, e))?,
624             instr::exit(alloc),
625         ],
626     ))
629 fn emit_yield<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
630     e: &mut Emitter<'arena, 'decl, D>,
631     env: &Env<'a, 'arena>,
632     pos: &Pos,
633     af: &ast::Afield,
634 ) -> Result<InstrSeq<'arena>> {
635     let alloc = env.arena;
636     Ok(match af {
637         ast::Afield::AFvalue(v) => InstrSeq::gather(
638             alloc,
639             vec![
640                 emit_expr(e, env, v)?,
641                 emit_pos(alloc, pos),
642                 instr::yield_(alloc),
643             ],
644         ),
645         ast::Afield::AFkvalue(k, v) => InstrSeq::gather(
646             alloc,
647             vec![
648                 emit_expr(e, env, k)?,
649                 emit_expr(e, env, v)?,
650                 emit_pos(alloc, pos),
651                 instr::yieldk(alloc),
652             ],
653         ),
654     })
657 fn parse_include<'arena>(alloc: &'arena bumpalo::Bump, e: &ast::Expr) -> IncludePath<'arena> {
658     fn strip_backslash(s: &mut String) {
659         if s.starts_with('/') {
660             *s = s[1..].into()
661         }
662     }
663     fn split_var_lit(e: &ast::Expr) -> (String, String) {
664         match &e.2 {
665             ast::Expr_::Binop(x) if x.0.is_dot() => {
666                 let (v, l) = split_var_lit(&x.2);
667                 if v.is_empty() {
668                     let (var, lit) = split_var_lit(&x.1);
669                     (var, format!("{}{}", lit, l))
670                 } else {
671                     (v, String::new())
672                 }
673             }
674             ast::Expr_::String(lit) => (String::new(), lit.to_string()),
675             _ => (text_of_expr(e), String::new()),
676         }
677     }
678     let (mut var, mut lit) = split_var_lit(e);
679     if var == pseudo_consts::G__DIR__ {
680         var = String::new();
681         strip_backslash(&mut lit);
682     }
683     if var.is_empty() {
684         if std::path::Path::new(lit.as_str()).is_relative() {
685             IncludePath::SearchPathRelative(Str::new_str(alloc, lit))
686         } else {
687             IncludePath::Absolute(Str::new_str(alloc, lit))
688         }
689     } else {
690         strip_backslash(&mut lit);
691         IncludePath::IncludeRootRelative(Str::new_str(alloc, var), Str::new_str(alloc, lit))
692     }
695 fn text_of_expr(e: &ast::Expr) -> String {
696     match &e.2 {
697         ast::Expr_::String(s) => format!("\'{}\'", s),
698         ast::Expr_::Id(id) => id.1.to_string(),
699         ast::Expr_::Lvar(lid) => local_id::get_name(&lid.1).to_string(),
700         ast::Expr_::ArrayGet(x) => match ((x.0).2.as_lvar(), x.1.as_ref()) {
701             (Some(ast::Lid(_, id)), Some(e_)) => {
702                 format!("{}[{}]", local_id::get_name(&id), text_of_expr(e_))
703             }
704             _ => "unknown".into(),
705         },
706         _ => "unknown".into(),
707     }
710 fn text_of_class_id(cid: &ast::ClassId) -> String {
711     match &cid.2 {
712         ast::ClassId_::CIparent => "parent".into(),
713         ast::ClassId_::CIself => "self".into(),
714         ast::ClassId_::CIstatic => "static".into(),
715         ast::ClassId_::CIexpr(e) => text_of_expr(e),
716         ast::ClassId_::CI(ast_defs::Id(_, id)) => id.into(),
717     }
720 fn text_of_prop(prop: &ast::ClassGetExpr) -> String {
721     match prop {
722         ast::ClassGetExpr::CGstring((_, s)) => s.into(),
723         ast::ClassGetExpr::CGexpr(e) => text_of_expr(e),
724     }
727 fn emit_import<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
728     e: &mut Emitter<'arena, 'decl, D>,
729     env: &Env<'a, 'arena>,
730     pos: &Pos,
731     flavor: &ast::ImportFlavor,
732     expr: &ast::Expr,
733 ) -> Result<InstrSeq<'arena>> {
734     use ast::ImportFlavor;
735     let alloc = env.arena;
736     let inc = parse_include(alloc, expr);
737     emit_symbol_refs::add_include(alloc, e, inc.clone());
738     let (expr_instrs, import_op_instr) = match flavor {
739         ImportFlavor::Include => (emit_expr(e, env, expr)?, instr::incl(alloc)),
740         ImportFlavor::Require => (emit_expr(e, env, expr)?, instr::req(alloc)),
741         ImportFlavor::IncludeOnce => (emit_expr(e, env, expr)?, instr::inclonce(alloc)),
742         ImportFlavor::RequireOnce => {
743             match inc.into_doc_root_relative(alloc, e.options().hhvm.include_roots.get()) {
744                 IncludePath::DocRootRelative(path) => {
745                     let expr = ast::Expr((), pos.clone(), ast::Expr_::String(path.as_str().into()));
746                     (emit_expr(e, env, &expr)?, instr::reqdoc(alloc))
747                 }
748                 _ => (emit_expr(e, env, expr)?, instr::reqonce(alloc)),
749             }
750         }
751     };
752     Ok(InstrSeq::gather(
753         alloc,
754         vec![expr_instrs, emit_pos(alloc, pos), import_op_instr],
755     ))
758 fn emit_string2<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
759     e: &mut Emitter<'arena, 'decl, D>,
760     env: &Env<'a, 'arena>,
761     pos: &Pos,
762     es: &[ast::Expr],
763 ) -> Result<InstrSeq<'arena>> {
764     let alloc = env.arena;
765     if es.is_empty() {
766         Err(unrecoverable("String2 with zero araguments is impossible"))
767     } else if es.len() == 1 {
768         Ok(InstrSeq::gather(
769             alloc,
770             vec![
771                 emit_expr(e, env, &es[0])?,
772                 emit_pos(alloc, pos),
773                 instr::cast_string(alloc),
774             ],
775         ))
776     } else {
777         Ok(InstrSeq::gather(
778             alloc,
779             vec![
780                 emit_two_exprs(e, env, &es[0].1, &es[0], &es[1])?,
781                 emit_pos(alloc, pos),
782                 instr::concat(alloc),
783                 InstrSeq::gather(
784                     alloc,
785                     (&es[2..])
786                         .iter()
787                         .map(|expr| {
788                             Ok(InstrSeq::gather(
789                                 alloc,
790                                 vec![
791                                     emit_expr(e, env, expr)?,
792                                     emit_pos(alloc, pos),
793                                     instr::concat(alloc),
794                                 ],
795                             ))
796                         })
797                         .collect::<Result<_>>()?,
798                 ),
799             ],
800         ))
801     }
804 fn emit_clone<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
805     e: &mut Emitter<'arena, 'decl, D>,
806     env: &Env<'a, 'arena>,
807     expr: &ast::Expr,
808 ) -> Result<InstrSeq<'arena>> {
809     let alloc = env.arena;
810     Ok(InstrSeq::gather(
811         alloc,
812         vec![emit_expr(e, env, expr)?, instr::clone(alloc)],
813     ))
816 fn emit_lambda<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
817     e: &mut Emitter<'arena, 'decl, D>,
818     env: &Env<'a, 'arena>,
819     fndef: &ast::Fun_,
820     ids: &[aast_defs::Lid],
821 ) -> Result<InstrSeq<'arena>> {
822     let alloc = env.arena;
823     // Closure conversion puts the class number used for CreateCl in the "name"
824     // of the function definition
825     let fndef_name = &(fndef.name).1;
826     let cls_num = fndef_name
827         .parse::<isize>()
828         .map_err(|err| Unrecoverable(err.to_string()))?;
829     let explicit_use = e.emit_global_state().explicit_use_set.contains(fndef_name);
830     let is_in_lambda = env.scope.is_in_lambda();
831     Ok(InstrSeq::gather(
832         alloc,
833         vec![
834             InstrSeq::gather(
835                 alloc,
836                 ids.iter()
837                     .map(|ast::Lid(pos, id)| {
838                         match string_utils::reified::is_captured_generic(local_id::get_name(id)) {
839                             Some((is_fun, i)) => {
840                                 if is_in_lambda {
841                                     Ok(instr::cgetl(
842                                         alloc,
843                                         Local::Named(Str::new_str(
844                                             alloc,
845                                             string_utils::reified::reified_generic_captured_name(
846                                                 is_fun, i as usize,
847                                             )
848                                             .as_str(),
849                                         )),
850                                     ))
851                                 } else {
852                                     emit_reified_generic_instrs(
853                                         alloc,
854                                         &Pos::make_none(),
855                                         is_fun,
856                                         i as usize,
857                                     )
858                                 }
859                             }
860                             None => Ok({
861                                 let lid = get_local(e, env, pos, local_id::get_name(id))?;
862                                 if explicit_use {
863                                     instr::cgetl(alloc, lid)
864                                 } else {
865                                     instr::cugetl(alloc, lid)
866                                 }
867                             }),
868                         }
869                     })
870                     .collect::<Result<Vec<_>>>()?,
871             ),
872             instr::createcl(alloc, ids.len(), cls_num),
873         ],
874     ))
877 pub fn emit_await<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
878     emitter: &mut Emitter<'arena, 'decl, D>,
879     env: &Env<'a, 'arena>,
880     pos: &Pos,
881     expr: &ast::Expr,
882 ) -> Result<InstrSeq<'arena>> {
883     let ast::Expr(_, _, e) = expr;
884     let alloc = env.arena;
885     let cant_inline_gen_functions = !emitter
886         .options()
887         .hhvm
888         .flags
889         .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION);
890     match e.as_call() {
891         Some((ast::Expr(_, _, ast::Expr_::Id(id)), _, args, None))
892             if (cant_inline_gen_functions
893                 && args.len() == 1
894                 && string_utils::strip_global_ns(&(*id.1)) == "gena") =>
895         {
896             inline_gena_call(emitter, env, &args[0])
897         }
898         _ => {
899             let after_await = emitter.label_gen_mut().next_regular();
900             let instrs = match e {
901                 ast::Expr_::Call(c) => {
902                     emit_call_expr(emitter, env, pos, Some(after_await.clone()), false, &*c)?
903                 }
904                 _ => emit_expr(emitter, env, expr)?,
905             };
906             Ok(InstrSeq::gather(
907                 alloc,
908                 vec![
909                     instrs,
910                     emit_pos(alloc, pos),
911                     instr::dup(alloc),
912                     instr::istypec(alloc, IstypeOp::OpNull),
913                     instr::jmpnz(alloc, after_await.clone()),
914                     instr::await_(alloc),
915                     instr::label(alloc, after_await),
916                 ],
917             ))
918         }
919     }
922 fn inline_gena_call<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
923     emitter: &mut Emitter<'arena, 'decl, D>,
924     env: &Env<'a, 'arena>,
925     arg: &ast::Expr,
926 ) -> Result<InstrSeq<'arena>> {
927     let alloc = env.arena;
928     let load_arr = emit_expr(emitter, env, arg)?;
929     let async_eager_label = emitter.label_gen_mut().next_regular();
931     scope::with_unnamed_local(alloc, emitter, |alloc, e, arr_local| {
932         let before = InstrSeq::gather(
933             alloc,
934             vec![
935                 load_arr,
936                 instr::cast_dict(alloc),
937                 instr::popl(alloc, arr_local),
938             ],
939         );
941         let inner = InstrSeq::gather(
942             alloc,
943             vec![
944                 instr::nulluninit(alloc),
945                 instr::nulluninit(alloc),
946                 instr::cgetl(alloc, arr_local),
947                 instr::fcallclsmethodd(
948                     alloc,
949                     FcallArgs::new(
950                         FcallFlags::default(),
951                         1,
952                         Slice::empty(),
953                         Slice::empty(),
954                         Some(async_eager_label),
955                         1,
956                         None,
957                     ),
958                     method::from_raw_string(alloc, "fromDict"),
959                     class::from_raw_string(alloc, "HH\\AwaitAllWaitHandle"),
960                 ),
961                 instr::await_(alloc),
962                 instr::label(alloc, async_eager_label),
963                 instr::popc(alloc),
964                 emit_iter(
965                     alloc,
966                     e,
967                     instr::cgetl(alloc, arr_local),
968                     |alloc, val_local, key_local| {
969                         InstrSeq::gather(
970                             alloc,
971                             vec![
972                                 instr::cgetl(alloc, val_local),
973                                 instr::whresult(alloc),
974                                 instr::basel(alloc, arr_local, MemberOpMode::Define),
975                                 instr::setm(alloc, 0, MemberKey::EL(key_local, ReadonlyOp::Any)),
976                                 instr::popc(alloc),
977                             ],
978                         )
979                     },
980                 )?,
981             ],
982         );
984         let after = instr::pushl(alloc, arr_local);
986         Ok((before, inner, after))
987     })
990 fn emit_iter<
991     'arena,
992     'decl,
993     D: DeclProvider<'decl>,
994     F: FnOnce(&'arena bumpalo::Bump, Local<'arena>, Local<'arena>) -> InstrSeq<'arena>,
996     alloc: &'arena bumpalo::Bump,
997     e: &mut Emitter<'arena, 'decl, D>,
998     collection: InstrSeq<'arena>,
999     f: F,
1000 ) -> Result<InstrSeq<'arena>> {
1001     scope::with_unnamed_locals_and_iterators(alloc, e, |alloc, e| {
1002         let iter_id = e.iterator_mut().get();
1003         let val_id = e.local_gen_mut().get_unnamed();
1004         let key_id = e.local_gen_mut().get_unnamed();
1005         let loop_end = e.label_gen_mut().next_regular();
1006         let loop_next = e.label_gen_mut().next_regular();
1007         let iter_args = IterArgs {
1008             iter_id,
1009             key_id: Just(key_id),
1010             val_id,
1011         };
1012         let iter_init = InstrSeq::gather(
1013             alloc,
1014             vec![
1015                 collection,
1016                 instr::iterinit(alloc, iter_args.clone(), loop_end),
1017             ],
1018         );
1019         let iterate = InstrSeq::gather(
1020             alloc,
1021             vec![
1022                 instr::label(alloc, loop_next),
1023                 f(alloc, val_id, key_id),
1024                 instr::iternext(alloc, iter_args, loop_next),
1025             ],
1026         );
1027         let iter_done = InstrSeq::gather(
1028             alloc,
1029             vec![
1030                 instr::unsetl(alloc, val_id),
1031                 instr::unsetl(alloc, key_id),
1032                 instr::label(alloc, loop_end),
1033             ],
1034         );
1035         Ok((iter_init, iterate, iter_done))
1036     })
1039 fn emit_shape<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1040     emitter: &mut Emitter<'arena, 'decl, D>,
1041     env: &Env<'a, 'arena>,
1042     expr: &ast::Expr,
1043     fl: &[(ast_defs::ShapeFieldName, ast::Expr)],
1044 ) -> Result<InstrSeq<'arena>> {
1045     fn extract_shape_field_name_pstring<'a, 'arena>(
1046         env: &Env<'a, 'arena>,
1047         pos: &Pos,
1048         field: &ast_defs::ShapeFieldName,
1049     ) -> Result<ast::Expr_> {
1050         use ast_defs::ShapeFieldName as SF;
1051         Ok(match field {
1052             SF::SFlitInt(s) => ast::Expr_::mk_int(s.1.clone()),
1053             SF::SFlitStr(s) => ast::Expr_::mk_string(s.1.clone()),
1054             SF::SFclassConst(id, p) => {
1055                 if is_reified_tparam(env, true, &id.1).is_some()
1056                     || is_reified_tparam(env, false, &id.1).is_some()
1057                 {
1058                     return Err(emit_fatal::raise_fatal_parse(
1059                         &id.0,
1060                         "Reified generics cannot be used in shape keys",
1061                     ));
1062                 } else {
1063                     ast::Expr_::mk_class_const(
1064                         ast::ClassId((), pos.clone(), ast::ClassId_::CI(id.clone())),
1065                         p.clone(),
1066                     )
1067                 }
1068             }
1069         })
1070     }
1071     let pos = &expr.1;
1072     // TODO(hrust): avoid clone
1073     let fl = fl
1074         .iter()
1075         .map(|(f, e)| {
1076             Ok((
1077                 ast::Expr(
1078                     (),
1079                     pos.clone(),
1080                     extract_shape_field_name_pstring(env, pos, f)?,
1081                 ),
1082                 e.clone(),
1083             ))
1084         })
1085         .collect::<Result<Vec<_>>>()?;
1086     emit_expr(
1087         emitter,
1088         env,
1089         &ast::Expr((), pos.clone(), ast::Expr_::mk_darray(None, fl)),
1090     )
1093 fn emit_vec_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1094     e: &mut Emitter<'arena, 'decl, D>,
1095     env: &Env<'a, 'arena>,
1096     pos: &Pos,
1097     fields: &[ast::Afield],
1098 ) -> Result<InstrSeq<'arena>> {
1099     let alloc = env.arena;
1100     match ast_constant_folder::vec_to_typed_value(alloc, e, fields) {
1101         Ok(tv) => emit_static_collection::<D>(env, None, pos, tv),
1102         Err(_) => emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewVec),
1103     }
1106 fn emit_named_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1107     e: &mut Emitter<'arena, 'decl, D>,
1108     env: &Env<'a, 'arena>,
1109     pos: &Pos,
1110     expr: &ast::Expr,
1111     fields: &[ast::Afield],
1112     collection_type: CollectionType,
1113 ) -> Result<InstrSeq<'arena>> {
1114     let alloc = env.arena;
1115     let emit_vector_like = |e: &mut Emitter<'arena, 'decl, D>, collection_type| {
1116         Ok(if fields.is_empty() {
1117             emit_pos_then(alloc, pos, instr::newcol(alloc, collection_type))
1118         } else {
1119             InstrSeq::gather(
1120                 alloc,
1121                 vec![
1122                     emit_vec_collection(e, env, pos, fields)?,
1123                     instr::colfromarray(alloc, collection_type),
1124                 ],
1125             )
1126         })
1127     };
1128     let emit_map_or_set = |e: &mut Emitter<'arena, 'decl, D>, collection_type| {
1129         if fields.is_empty() {
1130             Ok(emit_pos_then(
1131                 alloc,
1132                 pos,
1133                 instr::newcol(alloc, collection_type),
1134             ))
1135         } else {
1136             emit_collection(e, env, expr, fields, Some(collection_type))
1137         }
1138     };
1139     use CollectionType as C;
1140     match collection_type {
1141         C::Dict | C::Vec | C::Keyset => {
1142             let instr = emit_collection(e, env, expr, fields, None)?;
1143             Ok(emit_pos_then(alloc, pos, instr))
1144         }
1145         C::Vector | C::ImmVector => emit_vector_like(e, collection_type),
1146         C::Map | C::ImmMap | C::Set | C::ImmSet => emit_map_or_set(e, collection_type),
1147         C::Pair => Ok(InstrSeq::gather(
1148             alloc,
1149             vec![
1150                 InstrSeq::gather(
1151                     alloc,
1152                     fields
1153                         .iter()
1154                         .map(|f| match f {
1155                             ast::Afield::AFvalue(v) => emit_expr(e, env, v),
1156                             _ => Err(unrecoverable("impossible Pair argument")),
1157                         })
1158                         .collect::<Result<_>>()?,
1159                 ),
1160                 instr::new_pair(alloc),
1161             ],
1162         )),
1163         _ => Err(unrecoverable("Unexpected named collection type")),
1164     }
1167 fn emit_named_collection_str<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1168     e: &mut Emitter<'arena, 'decl, D>,
1169     env: &Env<'a, 'arena>,
1170     expr: &ast::Expr,
1171     (ast_defs::Id(pos, name), _, fields): &(
1172         ast::Sid,
1173         Option<ast::CollectionTarg>,
1174         Vec<ast::Afield>,
1175     ),
1176 ) -> Result<InstrSeq<'arena>> {
1177     let name = string_utils::strip_ns(name);
1178     let name = string_utils::types::fix_casing(name);
1179     use CollectionType::*;
1180     let ctype = match name {
1181         "dict" => Dict,
1182         "vec" => Vec,
1183         "keyset" => Keyset,
1184         "Vector" => Vector,
1185         "ImmVector" => ImmVector,
1186         "Map" => Map,
1187         "ImmMap" => ImmMap,
1188         "Set" => Set,
1189         "ImmSet" => ImmSet,
1190         "Pair" => Pair,
1191         _ => {
1192             return Err(unrecoverable(format!(
1193                 "collection: {} does not exist",
1194                 name
1195             )));
1196         }
1197     };
1198     emit_named_collection(e, env, pos, expr, fields, ctype)
1201 fn mk_afkvalues(es: &[(ast::Expr, ast::Expr)]) -> Vec<ast::Afield> {
1202     es.to_owned()
1203         .into_iter()
1204         .map(|(e1, e2)| ast::Afield::mk_afkvalue(e1, e2))
1205         .collect()
1208 fn mk_afvalues(es: &[ast::Expr]) -> Vec<ast::Afield> {
1209     es.to_owned()
1210         .into_iter()
1211         .map(ast::Afield::mk_afvalue)
1212         .collect()
1215 fn emit_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1216     e: &mut Emitter<'arena, 'decl, D>,
1217     env: &Env<'a, 'arena>,
1218     expr: &ast::Expr,
1219     fields: &[ast::Afield],
1220     transform_to_collection: Option<CollectionType>,
1221 ) -> Result<InstrSeq<'arena>> {
1222     let alloc = env.arena;
1223     let pos = &expr.1;
1224     match ast_constant_folder::expr_to_typed_value_(
1225         alloc, e, expr, true,  /*allow_map*/
1226         false, /*force_class_const*/
1227     ) {
1228         Ok(tv) => emit_static_collection::<D>(env, transform_to_collection, pos, tv),
1229         Err(_) => emit_dynamic_collection(e, env, expr, fields),
1230     }
1233 fn emit_static_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1234     env: &Env<'a, 'arena>,
1235     transform_to_collection: Option<CollectionType>,
1236     pos: &Pos,
1237     tv: TypedValue<'arena>,
1238 ) -> Result<InstrSeq<'arena>> {
1239     let alloc = env.arena;
1240     let transform_instr = match transform_to_collection {
1241         Some(collection_type) => instr::colfromarray(alloc, collection_type),
1242         _ => instr::empty(alloc),
1243     };
1244     Ok(InstrSeq::gather(
1245         alloc,
1246         vec![
1247             emit_pos(alloc, pos),
1248             instr::typedvalue(alloc, tv),
1249             transform_instr,
1250         ],
1251     ))
1254 fn expr_and_new<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1255     e: &mut Emitter<'arena, 'decl, D>,
1256     env: &Env<'a, 'arena>,
1257     pos: &Pos,
1258     instr_to_add_new: InstrSeq<'arena>,
1259     instr_to_add: InstrSeq<'arena>,
1260     field: &ast::Afield,
1261 ) -> Result<InstrSeq<'arena>> {
1262     let alloc = env.arena;
1263     match field {
1264         ast::Afield::AFvalue(v) => Ok(InstrSeq::gather(
1265             alloc,
1266             vec![
1267                 emit_expr(e, env, v)?,
1268                 emit_pos(alloc, pos),
1269                 instr_to_add_new,
1270             ],
1271         )),
1272         ast::Afield::AFkvalue(k, v) => Ok(InstrSeq::gather(
1273             alloc,
1274             vec![emit_two_exprs(e, env, &k.1, k, v)?, instr_to_add],
1275         )),
1276     }
1279 fn emit_keyvalue_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1280     e: &mut Emitter<'arena, 'decl, D>,
1281     env: &Env<'a, 'arena>,
1282     pos: &Pos,
1283     fields: &[ast::Afield],
1284     ctype: CollectionType,
1285     constructor: InstructLitConst<'arena>,
1286 ) -> Result<InstrSeq<'arena>> {
1287     let alloc = env.arena;
1288     let (transform_instr, add_elem_instr) = match ctype {
1289         CollectionType::Dict | CollectionType::Array => {
1290             (instr::empty(alloc), instr::add_new_elemc(alloc))
1291         }
1292         _ => (
1293             instr::colfromarray(alloc, ctype),
1294             InstrSeq::gather(alloc, vec![instr::dup(alloc), instr::add_elemc(alloc)]),
1295         ),
1296     };
1297     let emitted_pos = emit_pos(alloc, pos);
1298     Ok(InstrSeq::gather(
1299         alloc,
1300         vec![
1301             InstrSeq::<'arena>::clone(alloc, &emitted_pos),
1302             instr::lit_const(alloc, constructor),
1303             fields
1304                 .iter()
1305                 .map(|f| {
1306                     expr_and_new(
1307                         e,
1308                         env,
1309                         pos,
1310                         InstrSeq::<'arena>::clone(alloc, &add_elem_instr),
1311                         instr::add_elemc(alloc),
1312                         f,
1313                     )
1314                 })
1315                 .collect::<Result<_>>()
1316                 .map(|s| InstrSeq::gather(alloc, s))?,
1317             emitted_pos,
1318             transform_instr,
1319         ],
1320     ))
1323 fn non_numeric(s: &str) -> bool {
1324     // Note(hrust): OCaml Int64.of_string and float_of_string ignore underscores
1325     let s = s.replace("_", "");
1326     lazy_static! {
1327         static ref HEX: Regex = Regex::new(r"(?P<sign>^-?)0[xX](?P<digits>.*)").unwrap();
1328         static ref OCTAL: Regex = Regex::new(r"(?P<sign>^-?)0[oO](?P<digits>.*)").unwrap();
1329         static ref BINARY: Regex = Regex::new(r"(?P<sign>^-?)0[bB](?P<digits>.*)").unwrap();
1330         static ref FLOAT: Regex =
1331             Regex::new(r"(?P<int>\d*)\.(?P<dec>[0-9--0]*)(?P<zeros>0*)").unwrap();
1332         static ref NEG_FLOAT: Regex =
1333             Regex::new(r"(?P<int>-\d*)\.(?P<dec>[0-9--0]*)(?P<zeros>0*)").unwrap();
1334         static ref HEX_RADIX: u32 = 16;
1335         static ref OCTAL_RADIX: u32 = 8;
1336         static ref BINARY_RADIX: u32 = 2;
1337     }
1338     fn int_from_str(s: &str) -> std::result::Result<i64, ()> {
1339         // Note(hrust): OCaml Int64.of_string reads decimal, hexadecimal, octal, and binary
1340         (if HEX.is_match(s) {
1341             u64::from_str_radix(&HEX.replace(s, "${sign}${digits}"), *HEX_RADIX).map(|x| x as i64)
1342         } else if OCTAL.is_match(s) {
1343             u64::from_str_radix(&OCTAL.replace(s, "${sign}${digits}"), *OCTAL_RADIX)
1344                 .map(|x| x as i64)
1345         } else if BINARY.is_match(s) {
1346             u64::from_str_radix(&BINARY.replace(s, "${sign}${digits}"), *BINARY_RADIX)
1347                 .map(|x| x as i64)
1348         } else {
1349             i64::from_str(&s)
1350         })
1351         .map_err(|_| ())
1352     }
1353     fn float_from_str_radix(s: &str, radix: u32) -> std::result::Result<f64, ()> {
1354         let i = i64::from_str_radix(&s.replace(".", ""), radix).map_err(|_| ())?;
1355         Ok(match s.matches('.').count() {
1356             0 => i as f64,
1357             1 => {
1358                 let pow = s.split('.').last().unwrap().len();
1359                 (i as f64) / f64::from(radix).powi(pow as i32)
1360             }
1361             _ => return Err(()),
1362         })
1363     }
1364     fn out_of_bounds(s: &str) -> bool {
1365         // compare strings instead of floats to avoid rounding imprecision
1366         if FLOAT.is_match(s) {
1367             FLOAT.replace(s, "${int}.${dec}").trim_end_matches('.') > i64::MAX.to_string().as_str()
1368         } else if NEG_FLOAT.is_match(s) {
1369             NEG_FLOAT.replace(s, "${int}.${dec}").trim_end_matches('.')
1370                 > i64::MIN.to_string().as_str()
1371         } else {
1372             false
1373         }
1374     }
1375     fn float_from_str(s: &str) -> std::result::Result<f64, ()> {
1376         // Note(hrust): OCaml float_of_string ignores leading whitespace, reads decimal and hexadecimal
1377         let s = s.trim_start();
1378         if HEX.is_match(s) {
1379             float_from_str_radix(&HEX.replace(s, "${sign}${digits}"), *HEX_RADIX)
1380         } else {
1381             let out_of_bounds =
1382                 |f: f64| out_of_bounds(s) && (f > i64::MAX as f64 || f < i64::MIN as f64);
1383             let validate_float = |f: f64| {
1384                 if out_of_bounds(f) || f.is_infinite() || f.is_nan() {
1385                     Err(())
1386                 } else {
1387                     Ok(f)
1388                 }
1389             };
1390             f64::from_str(s).map_err(|_| ()).and_then(validate_float)
1391         }
1392     }
1393     int_from_str(&s).is_err() && float_from_str(&s).is_err()
1396 fn is_struct_init<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1397     e: &mut Emitter<'arena, 'decl, D>,
1398     env: &Env<'a, 'arena>,
1399     fields: &[ast::Afield],
1400     allow_numerics: bool,
1401 ) -> Result<bool> {
1402     let alloc = env.arena;
1403     let mut are_all_keys_non_numeric_strings = true;
1404     let mut uniq_keys = HashSet::<bstr::BString>::default();
1405     for f in fields.iter() {
1406         if let ast::Afield::AFkvalue(key, _) = f {
1407             // TODO(hrust): if key is String, don't clone and call fold_expr
1408             let mut key = key.clone();
1409             ast_constant_folder::fold_expr(&mut key, alloc, e)
1410                 .map_err(|e| unrecoverable(format!("{}", e)))?;
1411             if let ast::Expr(_, _, ast::Expr_::String(s)) = key {
1412                 are_all_keys_non_numeric_strings = are_all_keys_non_numeric_strings
1413                     && non_numeric(
1414                         // FIXME: This is not safe--string literals are binary strings.
1415                         // There's no guarantee that they're valid UTF-8.
1416                         unsafe { std::str::from_utf8_unchecked(s.as_slice()) },
1417                     );
1418                 uniq_keys.insert(s);
1419             } else {
1420                 are_all_keys_non_numeric_strings = false;
1421             }
1422             continue;
1423         }
1424         are_all_keys_non_numeric_strings = false;
1425     }
1426     let num_keys = fields.len();
1427     let limit = *(e.options().max_array_elem_size_on_the_stack.get()) as usize;
1428     Ok((allow_numerics || are_all_keys_non_numeric_strings)
1429         && uniq_keys.len() == num_keys
1430         && num_keys <= limit
1431         && num_keys != 0)
1434 fn emit_struct_array<
1435     'a,
1436     'arena,
1437     'decl,
1438     D: DeclProvider<'decl>,
1439     C: FnOnce(
1440         &'arena bumpalo::Bump,
1441         &mut Emitter<'arena, 'decl, D>,
1442         &'arena [&'arena str],
1443     ) -> Result<InstrSeq<'arena>>,
1445     e: &mut Emitter<'arena, 'decl, D>,
1446     env: &Env<'a, 'arena>,
1447     pos: &Pos,
1448     fields: &[ast::Afield],
1449     ctor: C,
1450 ) -> Result<InstrSeq<'arena>> {
1451     use ast::{Expr as E, Expr_ as E_};
1452     let alloc = env.arena;
1453     let (keys, value_instrs): (Vec<String>, _) = fields
1454         .iter()
1455         .map(|f| match f {
1456             ast::Afield::AFkvalue(k, v) => match k {
1457                 E(_, _, E_::String(s)) => Ok((
1458                     // FIXME: This is not safe--string literals are binary strings.
1459                     // There's no guarantee that they're valid UTF-8.
1460                     unsafe { String::from_utf8_unchecked(s.clone().into()) },
1461                     emit_expr(e, env, v)?,
1462                 )),
1463                 _ => {
1464                     let mut k = k.clone();
1465                     ast_constant_folder::fold_expr(&mut k, alloc, e)
1466                         .map_err(|e| unrecoverable(format!("{}", e)))?;
1467                     match k {
1468                         E(_, _, E_::String(s)) => Ok((
1469                             // FIXME: This is not safe--string literals are binary strings.
1470                             // There's no guarantee that they're valid UTF-8.
1471                             unsafe { String::from_utf8_unchecked(s.into()) },
1472                             emit_expr(e, env, v)?,
1473                         )),
1474                         _ => Err(unrecoverable("Key must be a string")),
1475                     }
1476                 }
1477             },
1478             _ => Err(unrecoverable("impossible")),
1479         })
1480         .collect::<Result<Vec<(String, InstrSeq<'arena>)>>>()?
1481         .into_iter()
1482         .unzip();
1483     let keys_ = bumpalo::collections::Vec::from_iter_in(
1484         keys.into_iter()
1485             .map(|x| bumpalo::collections::String::from_str_in(x.as_str(), alloc).into_bump_str()),
1486         alloc,
1487     )
1488     .into_bump_slice();
1489     Ok(InstrSeq::gather(
1490         alloc,
1491         vec![
1492             InstrSeq::gather(alloc, value_instrs),
1493             emit_pos(alloc, pos),
1494             ctor(alloc, e, keys_)?,
1495         ],
1496     ))
1499 fn emit_dynamic_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1500     e: &mut Emitter<'arena, 'decl, D>,
1501     env: &Env<'a, 'arena>,
1502     expr: &ast::Expr,
1503     fields: &[ast::Afield],
1504 ) -> Result<InstrSeq<'arena>> {
1505     let alloc = env.arena;
1506     let pos = &expr.1;
1507     let count = fields.len();
1508     let emit_dict = |e: &mut Emitter<'arena, 'decl, D>| {
1509         if is_struct_init(e, env, fields, true)? {
1510             emit_struct_array(e, env, pos, fields, |alloc, _, x| {
1511                 Ok(instr::newstructdict(alloc, x))
1512             })
1513         } else {
1514             let ctor = InstructLitConst::NewDictArray(count as isize);
1515             emit_keyvalue_collection(e, env, pos, fields, CollectionType::Dict, ctor)
1516         }
1517     };
1518     let emit_collection_helper = |e: &mut Emitter<'arena, 'decl, D>, ctype| {
1519         if is_struct_init(e, env, fields, true)? {
1520             Ok(InstrSeq::gather(
1521                 alloc,
1522                 vec![
1523                     emit_struct_array(e, env, pos, fields, |alloc, _, x| {
1524                         Ok(instr::newstructdict(alloc, x))
1525                     })?,
1526                     emit_pos(alloc, pos),
1527                     instr::colfromarray(alloc, ctype),
1528                 ],
1529             ))
1530         } else {
1531             let ctor = InstructLitConst::NewDictArray(count as isize);
1532             emit_keyvalue_collection(e, env, pos, fields, ctype, ctor)
1533         }
1534     };
1535     use ast::Expr_ as E_;
1536     match &expr.2 {
1537         E_::ValCollection(v) if v.0 == ast::VcKind::Vec => {
1538             emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewVec)
1539         }
1540         E_::Collection(v) if (v.0).1 == "vec" => {
1541             emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewVec)
1542         }
1543         E_::Tuple(_) => emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewVec),
1544         E_::ValCollection(v) if v.0 == ast::VcKind::Keyset => {
1545             emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewKeysetArray)
1546         }
1547         E_::Collection(v) if (v.0).1 == "keyset" => {
1548             emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewKeysetArray)
1549         }
1550         E_::Collection(v) if (v.0).1 == "dict" => emit_dict(e),
1551         E_::KeyValCollection(v) if v.0 == ast::KvcKind::Dict => emit_dict(e),
1552         E_::Collection(v) if string_utils::strip_ns(&(v.0).1) == "Set" => {
1553             emit_collection_helper(e, CollectionType::Set)
1554         }
1555         E_::ValCollection(v) if v.0 == ast::VcKind::Set => {
1556             emit_collection_helper(e, CollectionType::Set)
1557         }
1558         E_::Collection(v) if string_utils::strip_ns(&(v.0).1) == "ImmSet" => {
1559             emit_collection_helper(e, CollectionType::ImmSet)
1560         }
1561         E_::ValCollection(v) if v.0 == ast::VcKind::ImmSet => {
1562             emit_collection_helper(e, CollectionType::ImmSet)
1563         }
1564         E_::Collection(v) if string_utils::strip_ns(&(v.0).1) == "Map" => {
1565             emit_collection_helper(e, CollectionType::Map)
1566         }
1567         E_::KeyValCollection(v) if v.0 == ast::KvcKind::Map => {
1568             emit_collection_helper(e, CollectionType::Map)
1569         }
1570         E_::Collection(v) if string_utils::strip_ns(&(v.0).1) == "ImmMap" => {
1571             emit_collection_helper(e, CollectionType::ImmMap)
1572         }
1573         E_::KeyValCollection(v) if v.0 == ast::KvcKind::ImmMap => {
1574             emit_collection_helper(e, CollectionType::ImmMap)
1575         }
1576         E_::Varray(_) => {
1577             let instrs = emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewVec);
1578             Ok(instrs?)
1579         }
1580         E_::Darray(_) => {
1581             if is_struct_init(e, env, fields, false /* allow_numerics */)? {
1582                 let instrs = emit_struct_array(e, env, pos, fields, |alloc, _, arg| {
1583                     let instr = instr::newstructdict(alloc, arg);
1584                     Ok(emit_pos_then(alloc, pos, instr))
1585                 });
1586                 Ok(instrs?)
1587             } else {
1588                 let constr = InstructLitConst::NewDictArray(count as isize);
1589                 let instrs =
1590                     emit_keyvalue_collection(e, env, pos, fields, CollectionType::Array, constr);
1591                 Ok(instrs?)
1592             }
1593         }
1594         _ => Err(unrecoverable("plain PHP arrays cannot be constructed")),
1595     }
1598 fn emit_value_only_collection<
1599     'a,
1600     'arena,
1601     'decl,
1602     D: DeclProvider<'decl>,
1603     F: FnOnce(isize) -> InstructLitConst<'arena>,
1605     e: &mut Emitter<'arena, 'decl, D>,
1606     env: &Env<'a, 'arena>,
1607     pos: &Pos,
1608     fields: &[ast::Afield],
1609     constructor: F,
1610 ) -> Result<InstrSeq<'arena>> {
1611     let alloc = env.arena;
1612     let limit = *(e.options().max_array_elem_size_on_the_stack.get()) as usize;
1613     let inline = |
1614         alloc: &'arena bumpalo::Bump,
1615         e: &mut Emitter<'arena, 'decl, D>,
1616         exprs: &[ast::Afield],
1617     | -> Result<InstrSeq<'arena>> {
1618         let mut instrs = vec![];
1619         for expr in exprs.iter() {
1620             instrs.push(emit_expr(e, env, expr.value())?)
1621         }
1623         Ok(InstrSeq::gather(
1624             alloc,
1625             vec![
1626                 InstrSeq::gather(alloc, instrs),
1627                 emit_pos(alloc, pos),
1628                 instr::lit_const(alloc, constructor(exprs.len() as isize)),
1629             ],
1630         ))
1631     };
1632     let outofline = |
1633         alloc: &'arena bumpalo::Bump,
1634         e: &mut Emitter<'arena, 'decl, D>,
1635         exprs: &[ast::Afield],
1636     | -> Result<InstrSeq<'arena>> {
1637         let mut instrs = vec![];
1638         for expr in exprs.iter() {
1639             instrs.push(emit_expr(e, env, expr.value())?);
1640             instrs.push(instr::add_new_elemc(alloc));
1641         }
1642         Ok(InstrSeq::gather(alloc, instrs))
1643     };
1644     let (x1, x2) = fields.split_at(std::cmp::min(fields.len(), limit));
1645     Ok(match (x1, x2) {
1646         ([], []) => instr::empty(alloc),
1647         (_, []) => inline(alloc, e, x1)?,
1648         _ => {
1649             let outofline_instrs = outofline(alloc, e, x2)?;
1650             let inline_instrs = inline(alloc, e, x1)?;
1651             InstrSeq::gather(alloc, vec![inline_instrs, outofline_instrs])
1652         }
1653     })
1656 fn emit_record<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1657     e: &mut Emitter<'arena, 'decl, D>,
1658     env: &Env<'a, 'arena>,
1659     pos: &Pos,
1660     (cid, es): &(ast::Sid, Vec<(ast::Expr, ast::Expr)>),
1661 ) -> Result<InstrSeq<'arena>> {
1662     let alloc = env.arena;
1663     let es = mk_afkvalues(es);
1664     let id = class::ClassType::from_ast_name_and_mangle(alloc, &cid.1);
1665     emit_symbol_refs::add_class(alloc, e, id);
1666     emit_struct_array(e, env, pos, &es, |alloc, _, keys| {
1667         Ok(instr::new_record(alloc, id, keys))
1668     })
1671 fn emit_call_isset_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1672     e: &mut Emitter<'arena, 'decl, D>,
1673     env: &Env<'a, 'arena>,
1674     outer_pos: &Pos,
1675     expr: &ast::Expr,
1676 ) -> Result<InstrSeq<'arena>> {
1677     let alloc = env.arena;
1678     let pos = &expr.1;
1679     if let Some((base_expr, opt_elem_expr)) = expr.2.as_array_get() {
1680         return Ok(emit_array_get(
1681             e,
1682             env,
1683             pos,
1684             None,
1685             QueryOp::Isset,
1686             base_expr,
1687             opt_elem_expr.as_ref(),
1688             false,
1689             false,
1690         )?
1691         .0);
1692     }
1693     if let Some((cid, id, _)) = expr.2.as_class_get() {
1694         return emit_class_get(e, env, QueryOp::Isset, cid, id, ReadonlyOp::Any);
1695     }
1696     if let Some((expr_, prop, nullflavor, _)) = expr.2.as_obj_get() {
1697         return Ok(emit_obj_get(
1698             e,
1699             env,
1700             pos,
1701             QueryOp::Isset,
1702             expr_,
1703             prop,
1704             nullflavor,
1705             false,
1706             false,
1707         )?
1708         .0);
1709     }
1710     if let Some(lid) = expr.2.as_lvar() {
1711         let name = local_id::get_name(&lid.1);
1712         return Ok(if superglobals::is_any_global(&name) {
1713             InstrSeq::gather(
1714                 alloc,
1715                 vec![
1716                     emit_pos(alloc, outer_pos),
1717                     instr::string(alloc, string_utils::locals::strip_dollar(&name)),
1718                     emit_pos(alloc, outer_pos),
1719                     instr::issetg(alloc),
1720                 ],
1721             )
1722         } else if is_local_this(env, &lid.1)
1723             && !env.flags.contains(hhbc_by_ref_env::Flags::NEEDS_LOCAL_THIS)
1724         {
1725             InstrSeq::gather(
1726                 alloc,
1727                 vec![
1728                     emit_pos(alloc, outer_pos),
1729                     emit_local(e, env, BareThisOp::NoNotice, lid)?,
1730                     emit_pos(alloc, outer_pos),
1731                     instr::istypec(alloc, IstypeOp::OpNull),
1732                     instr::not(alloc),
1733                 ],
1734             )
1735         } else {
1736             emit_pos_then(
1737                 alloc,
1738                 outer_pos,
1739                 instr::issetl(alloc, get_local(e, env, &lid.0, name)?),
1740             )
1741         });
1742     }
1743     Ok(InstrSeq::gather(
1744         alloc,
1745         vec![
1746             emit_expr(e, env, expr)?,
1747             instr::istypec(alloc, IstypeOp::OpNull),
1748             instr::not(alloc),
1749         ],
1750     ))
1753 fn emit_call_isset_exprs<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1754     e: &mut Emitter<'arena, 'decl, D>,
1755     env: &Env<'a, 'arena>,
1756     pos: &Pos,
1757     exprs: &[ast::Expr],
1758 ) -> Result<InstrSeq<'arena>> {
1759     let alloc = env.arena;
1760     match exprs {
1761         [] => Err(emit_fatal::raise_fatal_parse(
1762             pos,
1763             "Cannot use isset() without any arguments",
1764         )),
1765         [expr] => emit_call_isset_expr(e, env, pos, expr),
1766         _ => {
1767             let its_done = e.label_gen_mut().next_regular();
1768             Ok(InstrSeq::gather(
1769                 alloc,
1770                 vec![
1771                     InstrSeq::gather(
1772                         alloc,
1773                         exprs
1774                             .iter()
1775                             .enumerate()
1776                             .map(|(i, expr)| {
1777                                 Ok(InstrSeq::gather(
1778                                     alloc,
1779                                     vec![
1780                                         emit_call_isset_expr(e, env, pos, expr)?,
1781                                         if i < exprs.len() - 1 {
1782                                             InstrSeq::gather(
1783                                                 alloc,
1784                                                 vec![
1785                                                     instr::dup(alloc),
1786                                                     instr::jmpz(alloc, its_done),
1787                                                     instr::popc(alloc),
1788                                                 ],
1789                                             )
1790                                         } else {
1791                                             instr::empty(alloc)
1792                                         },
1793                                     ],
1794                                 ))
1795                             })
1796                             .collect::<Result<Vec<_>>>()?,
1797                     ),
1798                     instr::label(alloc, its_done),
1799                 ],
1800             ))
1801         }
1802     }
1805 fn emit_tag_provenance_here<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1806     e: &mut Emitter<'arena, 'decl, D>,
1807     env: &Env<'a, 'arena>,
1808     pos: &Pos,
1809     es: &[ast::Expr],
1810 ) -> Result<InstrSeq<'arena>> {
1811     let alloc = env.arena;
1812     let pop = if es.len() == 1 {
1813         instr::empty(alloc)
1814     } else {
1815         instr::popc(alloc)
1816     };
1817     Ok(InstrSeq::gather(
1818         alloc,
1819         vec![emit_exprs(e, env, es)?, emit_pos(alloc, pos), pop],
1820     ))
1823 fn emit_array_mark_legacy<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1824     e: &mut Emitter<'arena, 'decl, D>,
1825     env: &Env<'a, 'arena>,
1826     pos: &Pos,
1827     es: &[ast::Expr],
1828     legacy: bool,
1829 ) -> Result<InstrSeq<'arena>> {
1830     let alloc = env.arena;
1831     let default = if es.len() == 1 {
1832         instr::false_(alloc)
1833     } else {
1834         instr::empty(alloc)
1835     };
1836     let mark = if legacy {
1837         instr::instr(alloc, Instruct::IMisc(InstructMisc::ArrayMarkLegacy))
1838     } else {
1839         instr::instr(alloc, Instruct::IMisc(InstructMisc::ArrayUnmarkLegacy))
1840     };
1841     Ok(InstrSeq::gather(
1842         alloc,
1843         vec![emit_exprs(e, env, es)?, emit_pos(alloc, pos), default, mark],
1844     ))
1847 fn emit_idx<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1848     e: &mut Emitter<'arena, 'decl, D>,
1849     env: &Env<'a, 'arena>,
1850     pos: &Pos,
1851     es: &[ast::Expr],
1852 ) -> Result<InstrSeq<'arena>> {
1853     let alloc = env.arena;
1854     let default = if es.len() == 2 {
1855         instr::null(alloc)
1856     } else {
1857         instr::empty(alloc)
1858     };
1859     Ok(InstrSeq::gather(
1860         alloc,
1861         vec![
1862             emit_exprs(e, env, es)?,
1863             emit_pos(alloc, pos),
1864             default,
1865             instr::idx(alloc),
1866         ],
1867     ))
1870 fn emit_call<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1871     e: &mut Emitter<'arena, 'decl, D>,
1872     env: &Env<'a, 'arena>,
1873     pos: &Pos,
1874     expr: &ast::Expr,
1875     targs: &[ast::Targ],
1876     args: &[ast::Expr],
1877     uarg: Option<&ast::Expr>,
1878     async_eager_label: Option<Label>,
1879     readonly_return: bool,
1880 ) -> Result<InstrSeq<'arena>> {
1881     let alloc = env.arena;
1882     if let Some(ast_defs::Id(_, s)) = expr.as_id() {
1883         let fid = function::FunctionType::<'arena>::from_ast_name(alloc, s);
1884         emit_symbol_refs::add_function(alloc, e, fid);
1885     }
1886     let fcall_args = get_fcall_args(
1887         e,
1888         alloc,
1889         args,
1890         uarg,
1891         async_eager_label,
1892         env.call_context.clone(),
1893         false,
1894         readonly_return,
1895     );
1896     match expr.2.as_id() {
1897         None => emit_call_default(e, env, pos, expr, targs, args, uarg, fcall_args),
1898         Some(ast_defs::Id(_, id)) => {
1899             let fq = function::FunctionType::<'arena>::from_ast_name(alloc, id);
1900             let lower_fq_name = fq.to_raw_string();
1901             emit_special_function(e, env, pos, args, uarg, lower_fq_name)
1902                 .transpose()
1903                 .unwrap_or_else(|| {
1904                     emit_call_default(e, env, pos, expr, targs, args, uarg, fcall_args)
1905                 })
1906         }
1907     }
1910 fn emit_call_default<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1911     e: &mut Emitter<'arena, 'decl, D>,
1912     env: &Env<'a, 'arena>,
1913     pos: &Pos,
1914     expr: &ast::Expr,
1915     targs: &[ast::Targ],
1916     args: &[ast::Expr],
1917     uarg: Option<&ast::Expr>,
1918     fcall_args: FcallArgs<'arena>,
1919 ) -> Result<InstrSeq<'arena>> {
1920     let alloc = env.arena;
1921     scope::with_unnamed_locals(alloc, e, |alloc, em| {
1922         let FcallArgs(_, _, num_ret, _, _, _, _) = &fcall_args;
1923         let num_uninit = num_ret - 1;
1924         let (lhs, fcall) = emit_call_lhs_and_fcall(em, env, expr, fcall_args, targs, None)?;
1925         let (args, inout_setters) = emit_args_inout_setters(em, env, args)?;
1926         let uargs = match uarg {
1927             Some(uarg) => emit_expr(em, env, uarg)?,
1928             None => instr::empty(alloc),
1929         };
1930         Ok((
1931             instr::empty(alloc),
1932             InstrSeq::gather(
1933                 alloc,
1934                 vec![
1935                     InstrSeq::gather(
1936                         alloc,
1937                         iter::repeat_with(|| instr::nulluninit(alloc))
1938                             .take(num_uninit)
1939                             .collect::<Vec<_>>(),
1940                     ),
1941                     lhs,
1942                     args,
1943                     uargs,
1944                     emit_pos(alloc, pos),
1945                     fcall,
1946                     inout_setters,
1947                 ],
1948             ),
1949             instr::empty(alloc),
1950         ))
1951     })
1954 pub fn emit_reified_targs<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1955     e: &mut Emitter<'arena, 'decl, D>,
1956     env: &Env<'a, 'arena>,
1957     pos: &Pos,
1958     targs: &[&ast::Hint],
1959 ) -> Result<InstrSeq<'arena>> {
1960     let alloc = env.arena;
1961     let current_fun_tparams = env.scope.get_fun_tparams();
1962     let current_cls_tparams = env.scope.get_class_tparams();
1963     let is_in_lambda = env.scope.is_in_lambda();
1964     let is_soft =
1965         |ual: &Vec<ast::UserAttribute>| ual.iter().any(|ua| user_attributes::is_soft(&ua.name.1));
1966     let same_as_targs = |tparams: &[ast::Tparam]| {
1967         tparams.len() == targs.len()
1968             && tparams.iter().zip(targs).all(|(tp, ta)| {
1969                 ta.1.as_happly().map_or(false, |(id, hs)| {
1970                     id.1 == tp.name.1
1971                         && hs.is_empty()
1972                         && !is_soft(&tp.user_attributes)
1973                         && tp.reified.is_reified()
1974                 })
1975             })
1976     };
1977     Ok(if !is_in_lambda && same_as_targs(&current_fun_tparams) {
1978         instr::cgetl(
1979             alloc,
1980             Local::Named(Str::new_str(
1981                 alloc,
1982                 string_utils::reified::GENERICS_LOCAL_NAME,
1983             )),
1984         )
1985     } else if !is_in_lambda && same_as_targs(&current_cls_tparams[..]) {
1986         InstrSeq::gather(
1987             alloc,
1988             vec![
1989                 instr::checkthis(alloc),
1990                 instr::baseh(alloc),
1991                 instr::querym(
1992                     alloc,
1993                     0,
1994                     QueryOp::CGet,
1995                     MemberKey::PT(
1996                         prop::from_raw_string(alloc, string_utils::reified::PROP_NAME),
1997                         ReadonlyOp::Any,
1998                     ),
1999                 ),
2000             ],
2001         )
2002     } else {
2003         InstrSeq::gather(
2004             alloc,
2005             vec![
2006                 InstrSeq::gather(
2007                     alloc,
2008                     targs
2009                         .iter()
2010                         .map(|h| Ok(emit_reified_arg(e, env, pos, false, h)?.0))
2011                         .collect::<Result<Vec<_>>>()?,
2012                 ),
2013                 instr::new_vec_array(alloc, targs.len() as isize),
2014             ],
2015         )
2016     })
2019 fn get_erased_tparams<'a, 'arena>(env: &'a Env<'a, 'arena>) -> Vec<&'a str> {
2020     env.scope
2021         .get_tparams()
2022         .iter()
2023         .filter_map(|tparam| match tparam.reified {
2024             ast::ReifyKind::Erased => Some(tparam.name.1.as_str()),
2025             _ => None,
2026         })
2027         .collect()
2030 pub fn has_non_tparam_generics(env: &Env, hints: &[ast::Hint]) -> bool {
2031     let erased_tparams = get_erased_tparams(env);
2032     hints.iter().any(|hint| {
2033         hint.1
2034             .as_happly()
2035             .map_or(true, |(id, _)| !erased_tparams.contains(&id.1.as_str()))
2036     })
2039 fn has_non_tparam_generics_targs(env: &Env, targs: &[ast::Targ]) -> bool {
2040     let erased_tparams = get_erased_tparams(env);
2041     targs.iter().any(|targ| {
2042         (targ.1)
2043             .1
2044             .as_happly()
2045             .map_or(true, |(id, _)| !erased_tparams.contains(&id.1.as_str()))
2046     })
2049 fn from_ast_null_flavor(nullflavor: ast::OgNullFlavor) -> ObjNullFlavor {
2050     match nullflavor {
2051         ast::OgNullFlavor::OGNullsafe => ObjNullFlavor::NullSafe,
2052         ast::OgNullFlavor::OGNullthrows => ObjNullFlavor::NullThrows,
2053     }
2056 fn emit_object_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
2057     e: &mut Emitter<'arena, 'decl, D>,
2058     env: &Env<'a, 'arena>,
2059     expr: &ast::Expr,
2060 ) -> Result<InstrSeq<'arena>> {
2061     let alloc = env.arena;
2062     match &expr.2 {
2063         ast::Expr_::Lvar(x) if is_local_this(env, &x.1) => Ok(instr::this(alloc)),
2064         _ => emit_expr(e, env, expr),
2065     }
2068 fn emit_call_lhs_and_fcall<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
2069     e: &mut Emitter<'arena, 'decl, D>,
2070     env: &Env<'a, 'arena>,
2071     expr: &ast::Expr,
2072     mut fcall_args: FcallArgs<'arena>,
2073     targs: &[ast::Targ],
2074     caller_readonly_opt: Option<&Pos>,
2075 ) -> Result<(InstrSeq<'arena>, InstrSeq<'arena>)> {
2076     let ast::Expr(_, pos, expr_) = expr;
2077     use ast::{Expr as E, Expr_ as E_};
2078     let alloc = env.arena;
2079     let emit_generics =
2080         |e: &mut Emitter<'arena, 'decl, D>, env, fcall_args: &mut FcallArgs<'arena>| {
2081             let does_not_have_non_tparam_generics = !has_non_tparam_generics_targs(env, targs);
2082             if does_not_have_non_tparam_generics {
2083                 Ok(instr::empty(alloc))
2084             } else {
2085                 fcall_args.0 |= FcallFlags::HAS_GENERICS;
2086                 emit_reified_targs(
2087                     e,
2088                     env,
2089                     pos,
2090                     targs
2091                         .iter()
2092                         .map(|targ| &targ.1)
2093                         .collect::<Vec<_>>()
2094                         .as_slice(),
2095                 )
2096             }
2097         };
2099     let emit_fcall_func = |
2100         e: &mut Emitter<'arena, 'decl, D>,
2101         env,
2102         expr: &ast::Expr,
2103         fcall_args: FcallArgs<'arena>,
2104         caller_readonly_opt: Option<&Pos>,
2105     | -> Result<(InstrSeq<'arena>, InstrSeq<'arena>)> {
2106         let tmp = e.local_gen_mut().get_unnamed();
2107         // if the original expression was wrapped in readonly, emit a readonly expression here
2108         let res = if let Some(p) = caller_readonly_opt {
2109             emit_readonly_expr(e, env, p, expr)?
2110         } else {
2111             emit_expr(e, env, expr)?
2112         };
2113         Ok((
2114             InstrSeq::gather(
2115                 alloc,
2116                 vec![
2117                     instr::nulluninit(alloc),
2118                     instr::nulluninit(alloc),
2119                     res,
2120                     instr::popl(alloc, tmp),
2121                 ],
2122             ),
2123             InstrSeq::gather(
2124                 alloc,
2125                 vec![
2126                     instr::pushl(alloc, tmp),
2127                     instr::fcallfunc(alloc, fcall_args),
2128                 ],
2129             ),
2130         ))
2131     };
2133     match expr_ {
2134         E_::ReadonlyExpr(r) => {
2135             // If calling a Readonly expression, first recurse inside to
2136             // handle ObjGet and ClassGet prop call cases. Keep track of the position of the
2137             // outer readonly expression for use later.
2138             // TODO: use the fact that this is a readonly call in HHVM enforcement
2139             emit_call_lhs_and_fcall(e, env, r, fcall_args, targs, Some(pos))
2140         }
2141         E_::ObjGet(o) => {
2142             if o.as_ref().3 {
2143                 // Case ($x->foo)(...).
2144                 let expr = E(
2145                     (),
2146                     pos.clone(),
2147                     E_::ObjGet(Box::new((o.0.clone(), o.1.clone(), o.2.clone(), false))),
2148                 );
2149                 emit_fcall_func(e, env, &expr, fcall_args, caller_readonly_opt)
2150             } else {
2151                 // Case $x->foo(...).
2152                 // TODO: utilze caller_readonly_opt here for method calls
2153                 let emit_id = |
2154                     e: &mut Emitter<'arena, 'decl, D>,
2155                     obj,
2156                     id,
2157                     null_flavor: &ast::OgNullFlavor,
2158                     mut fcall_args,
2159                 | {
2160                     let name: method::MethodType<'arena> =
2161                         (alloc, string_utils::strip_global_ns(id)).into();
2162                     let obj = emit_object_expr(e, env, obj)?;
2163                     let generics = emit_generics(e, env, &mut fcall_args)?;
2164                     let null_flavor = from_ast_null_flavor(*null_flavor);
2165                     Ok((
2166                         InstrSeq::gather(alloc, vec![obj, instr::nulluninit(alloc)]),
2167                         InstrSeq::gather(
2168                             alloc,
2169                             vec![
2170                                 generics,
2171                                 instr::fcallobjmethodd(alloc, fcall_args, name, null_flavor),
2172                             ],
2173                         ),
2174                     ))
2175                 };
2176                 match o.as_ref() {
2177                     (obj, E(_, _, E_::String(id)), null_flavor, _) => {
2178                         emit_id(
2179                             e,
2180                             obj,
2181                             // FIXME: This is not safe--string literals are binary strings.
2182                             // There's no guarantee that they're valid UTF-8.
2183                             unsafe { std::str::from_utf8_unchecked(id.as_slice()) },
2184                             null_flavor,
2185                             fcall_args,
2186                         )
2187                     }
2188                     (E(_, pos, E_::New(new_exp)), E(_, _, E_::Id(id)), null_flavor, _)
2189                         if fcall_args.1 == 0 =>
2190                     {
2191                         let cexpr = ClassExpr::class_id_to_class_expr(
2192                             e, false, false, &env.scope, &new_exp.0,
2193                         );
2194                         match &cexpr {
2195                             ClassExpr::Id(ast_defs::Id(_, name))
2196                                 if string_utils::strip_global_ns(name) == "ReflectionClass" =>
2197                             {
2198                                 let fid = match string_utils::strip_global_ns(&id.1) {
2199                                     "isAbstract" => {
2200                                         Some("__SystemLib\\reflection_class_is_abstract")
2201                                     }
2202                                     "isInterface" => {
2203                                         Some("__SystemLib\\reflection_class_is_interface")
2204                                     }
2205                                     "isFinal" => Some("__SystemLib\\reflection_class_is_final"),
2206                                     "getName" => Some("__SystemLib\\reflection_class_get_name"),
2207                                     _ => None,
2208                                 };
2209                                 match fid {
2210                                     None => {
2211                                         emit_id(e, &o.as_ref().0, &id.1, null_flavor, fcall_args)
2212                                     }
2213                                     Some(fid) => {
2214                                         let fcall_args = FcallArgs::new(
2215                                             FcallFlags::default(),
2216                                             1,
2217                                             Slice::empty(),
2218                                             Slice::empty(),
2219                                             None,
2220                                             1,
2221                                             None,
2222                                         );
2223                                         let newobj_instrs = emit_new(e, env, pos, &new_exp, true);
2224                                         Ok((
2225                                             InstrSeq::gather(
2226                                                 alloc,
2227                                                 vec![
2228                                                     instr::nulluninit(alloc),
2229                                                     instr::nulluninit(alloc),
2230                                                     newobj_instrs?,
2231                                                 ],
2232                                             ),
2233                                             InstrSeq::gather(
2234                                                 alloc,
2235                                                 vec![instr::fcallfuncd(
2236                                                     alloc,
2237                                                     fcall_args,
2238                                                     function::FunctionType::<'arena>::from_ast_name(
2239                                                         alloc, fid,
2240                                                     ),
2241                                                 )],
2242                                             ),
2243                                         ))
2244                                     }
2245                                 }
2246                             }
2247                             _ => emit_id(e, &o.as_ref().0, &id.1, null_flavor, fcall_args),
2248                         }
2249                     }
2250                     (obj, E(_, _, E_::Id(id)), null_flavor, _) => {
2251                         emit_id(e, obj, &id.1, null_flavor, fcall_args)
2252                     }
2253                     (obj, method_expr, null_flavor, _) => {
2254                         let obj = emit_object_expr(e, env, obj)?;
2255                         let tmp = e.local_gen_mut().get_unnamed();
2256                         let null_flavor = from_ast_null_flavor(*null_flavor);
2257                         Ok((
2258                             InstrSeq::gather(
2259                                 alloc,
2260                                 vec![
2261                                     obj,
2262                                     instr::nulluninit(alloc),
2263                                     emit_expr(e, env, method_expr)?,
2264                                     instr::popl(alloc, tmp),
2265                                 ],
2266                             ),
2267                             InstrSeq::gather(
2268                                 alloc,
2269                                 vec![
2270                                     instr::pushl(alloc, tmp),
2271                                     instr::fcallobjmethod(alloc, fcall_args, null_flavor),
2272                                 ],
2273                             ),
2274                         ))
2275                     }
2276                 }
2277             }
2278         }
2279         E_::ClassConst(cls_const) => {
2280             let (cid, (_, id)) = &**cls_const;
2281             let mut cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
2282             if let ClassExpr::Id(ast_defs::Id(_, name)) = &cexpr {
2283                 if let Some(reified_var_cexpr) = get_reified_var_cexpr::<D>(env, pos, &name)? {
2284                     cexpr = reified_var_cexpr;
2285                 }
2286             }
2287             let method_id: method::MethodType = (alloc, string_utils::strip_global_ns(&id)).into();
2288             Ok(match cexpr {
2289                 // Statically known
2290                 ClassExpr::Id(ast_defs::Id(_, cname)) => {
2291                     let cid = class::ClassType::<'arena>::from_ast_name_and_mangle(alloc, &cname);
2292                     emit_symbol_refs::add_class(alloc, e, cid.clone());
2293                     let generics = emit_generics(e, env, &mut fcall_args)?;
2294                     (
2295                         InstrSeq::gather(
2296                             alloc,
2297                             vec![instr::nulluninit(alloc), instr::nulluninit(alloc)],
2298                         ),
2299                         InstrSeq::gather(
2300                             alloc,
2301                             vec![
2302                                 generics,
2303                                 instr::fcallclsmethodd(alloc, fcall_args, method_id, cid),
2304                             ],
2305                         ),
2306                     )
2307                 }
2308                 ClassExpr::Special(clsref) => {
2309                     let generics = emit_generics(e, env, &mut fcall_args)?;
2310                     (
2311                         InstrSeq::gather(
2312                             alloc,
2313                             vec![instr::nulluninit(alloc), instr::nulluninit(alloc)],
2314                         ),
2315                         InstrSeq::gather(
2316                             alloc,
2317                             vec![
2318                                 generics,
2319                                 instr::fcallclsmethodsd(alloc, fcall_args, clsref, method_id),
2320                             ],
2321                         ),
2322                     )
2323                 }
2324                 ClassExpr::Expr(expr) => {
2325                     let generics = emit_generics(e, env, &mut fcall_args)?;
2326                     (
2327                         InstrSeq::gather(
2328                             alloc,
2329                             vec![instr::nulluninit(alloc), instr::nulluninit(alloc)],
2330                         ),
2331                         InstrSeq::gather(
2332                             alloc,
2333                             vec![
2334                                 generics,
2335                                 instr::string(alloc, method_id.to_raw_string()),
2336                                 emit_expr(e, env, &expr)?,
2337                                 instr::classgetc(alloc),
2338                                 instr::fcallclsmethod(
2339                                     alloc,
2340                                     IsLogAsDynamicCallOp::DontLogAsDynamicCall,
2341                                     fcall_args,
2342                                 ),
2343                             ],
2344                         ),
2345                     )
2346                 }
2347                 ClassExpr::Reified(instrs) => {
2348                     let tmp = e.local_gen_mut().get_unnamed();
2349                     (
2350                         InstrSeq::gather(
2351                             alloc,
2352                             vec![
2353                                 instr::nulluninit(alloc),
2354                                 instr::nulluninit(alloc),
2355                                 instrs,
2356                                 instr::popl(alloc, tmp),
2357                             ],
2358                         ),
2359                         InstrSeq::gather(
2360                             alloc,
2361                             vec![
2362                                 instr::string(alloc, method_id.to_raw_string()),
2363                                 instr::pushl(alloc, tmp),
2364                                 instr::classgetc(alloc),
2365                                 instr::fcallclsmethod(
2366                                     alloc,
2367                                     IsLogAsDynamicCallOp::LogAsDynamicCall,
2368                                     fcall_args,
2369                                 ),
2370                             ],
2371                         ),
2372                     )
2373                 }
2374             })
2375         }
2376         E_::ClassGet(c) => {
2377             if c.as_ref().2 {
2378                 // Case (Foo::$bar)(...).
2379                 let expr = E(
2380                     (),
2381                     pos.clone(),
2382                     E_::ClassGet(Box::new((c.0.clone(), c.1.clone(), false))),
2383                 );
2384                 emit_fcall_func(e, env, &expr, fcall_args, caller_readonly_opt)
2385             } else {
2386                 // Case Foo::bar(...).
2387                 let (cid, cls_get_expr, _) = &**c;
2388                 let mut cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
2389                 if let ClassExpr::Id(ast_defs::Id(_, name)) = &cexpr {
2390                     if let Some(reified_var_cexpr) = get_reified_var_cexpr::<D>(env, pos, &name)? {
2391                         cexpr = reified_var_cexpr;
2392                     }
2393                 }
2394                 let emit_meth_name = |e: &mut Emitter<'arena, 'decl, D>| match &cls_get_expr {
2395                     ast::ClassGetExpr::CGstring((pos, id)) => Ok(emit_pos_then(
2396                         alloc,
2397                         pos,
2398                         instr::cgetl(alloc, Local::Named(Str::new_str(alloc, id.as_str()))),
2399                     )),
2400                     ast::ClassGetExpr::CGexpr(expr) => emit_expr(e, env, expr),
2401                 };
2402                 Ok(match cexpr {
2403                     ClassExpr::Id(cid) => {
2404                         let tmp = e.local_gen_mut().get_unnamed();
2405                         (
2406                             InstrSeq::gather(
2407                                 alloc,
2408                                 vec![
2409                                     instr::nulluninit(alloc),
2410                                     instr::nulluninit(alloc),
2411                                     emit_meth_name(e)?,
2412                                     instr::popl(alloc, tmp),
2413                                 ],
2414                             ),
2415                             InstrSeq::gather(
2416                                 alloc,
2417                                 vec![
2418                                     instr::pushl(alloc, tmp),
2419                                     emit_known_class_id(alloc, e, &cid),
2420                                     instr::fcallclsmethod(
2421                                         alloc,
2422                                         IsLogAsDynamicCallOp::LogAsDynamicCall,
2423                                         fcall_args,
2424                                     ),
2425                                 ],
2426                             ),
2427                         )
2428                     }
2429                     ClassExpr::Special(clsref) => {
2430                         let tmp = e.local_gen_mut().get_unnamed();
2431                         (
2432                             InstrSeq::gather(
2433                                 alloc,
2434                                 vec![
2435                                     instr::nulluninit(alloc),
2436                                     instr::nulluninit(alloc),
2437                                     emit_meth_name(e)?,
2438                                     instr::popl(alloc, tmp),
2439                                 ],
2440                             ),
2441                             InstrSeq::gather(
2442                                 alloc,
2443                                 vec![
2444                                     instr::pushl(alloc, tmp),
2445                                     instr::fcallclsmethods(alloc, fcall_args, clsref),
2446                                 ],
2447                             ),
2448                         )
2449                     }
2450                     ClassExpr::Expr(expr) => {
2451                         let cls = e.local_gen_mut().get_unnamed();
2452                         let meth = e.local_gen_mut().get_unnamed();
2453                         (
2454                             InstrSeq::gather(
2455                                 alloc,
2456                                 vec![
2457                                     instr::nulluninit(alloc),
2458                                     instr::nulluninit(alloc),
2459                                     emit_expr(e, env, &expr)?,
2460                                     instr::popl(alloc, cls),
2461                                     emit_meth_name(e)?,
2462                                     instr::popl(alloc, meth),
2463                                 ],
2464                             ),
2465                             InstrSeq::gather(
2466                                 alloc,
2467                                 vec![
2468                                     instr::pushl(alloc, meth),
2469                                     instr::pushl(alloc, cls),
2470                                     instr::classgetc(alloc),
2471                                     instr::fcallclsmethod(
2472                                         alloc,
2473                                         IsLogAsDynamicCallOp::LogAsDynamicCall,
2474                                         fcall_args,
2475                                     ),
2476                                 ],
2477                             ),
2478                         )
2479                     }
2480                     ClassExpr::Reified(instrs) => {
2481                         let cls = e.local_gen_mut().get_unnamed();
2482                         let meth = e.local_gen_mut().get_unnamed();
2483                         (
2484                             InstrSeq::gather(
2485                                 alloc,
2486                                 vec![
2487                                     instr::nulluninit(alloc),
2488                                     instr::nulluninit(alloc),
2489                                     instrs,
2490                                     instr::popl(alloc, cls),
2491                                     emit_meth_name(e)?,
2492                                     instr::popl(alloc, meth),
2493                                 ],
2494                             ),
2495                             InstrSeq::gather(
2496                                 alloc,
2497                                 vec![
2498                                     instr::pushl(alloc, meth),
2499                                     instr::pushl(alloc, cls),
2500                                     instr::classgetc(alloc),
2501                                     instr::fcallclsmethod(
2502                                         alloc,
2503                                         IsLogAsDynamicCallOp::LogAsDynamicCall,
2504                                         fcall_args,
2505                                     ),
2506                                 ],
2507                             ),
2508                         )
2509                     }
2510                 })
2511             }
2512         }
2513         E_::Id(id) => {
2514             let FcallArgs(flags, num_args, _, _, _, _, _) = fcall_args;
2515             let fq_id = match string_utils::strip_global_ns(&id.1) {
2516                 "min" if num_args == 2 && !flags.contains(FcallFlags::HAS_UNPACK) => {
2517                     function::FunctionType::<'arena>::from_ast_name(alloc, "__SystemLib\\min2")
2518                 }
2519                 "max" if num_args == 2 && !flags.contains(FcallFlags::HAS_UNPACK) => {
2520                     function::FunctionType::<'arena>::from_ast_name(alloc, "__SystemLib\\max2")
2521                 }
2522                 _ => (alloc, string_utils::strip_global_ns(&id.1)).into(),
2523             };
2524             let generics = emit_generics(e, env, &mut fcall_args)?;
2525             Ok((
2526                 InstrSeq::gather(
2527                     alloc,
2528                     vec![instr::nulluninit(alloc), instr::nulluninit(alloc)],
2529                 ),
2530                 InstrSeq::gather(
2531                     alloc,
2532                     vec![generics, instr::fcallfuncd(alloc, fcall_args, fq_id)],
2533                 ),
2534             ))
2535         }
2536         E_::String(s) => {
2537             // TODO(hrust) should be able to accept `let fq_id = function::from_raw_string(s);`
2538             let fq_id = (alloc, s.to_string().as_str()).into();
2539             let generics = emit_generics(e, env, &mut fcall_args)?;
2540             Ok((
2541                 InstrSeq::gather(
2542                     alloc,
2543                     vec![instr::nulluninit(alloc), instr::nulluninit(alloc)],
2544                 ),
2545                 InstrSeq::gather(
2546                     alloc,
2547                     vec![generics, instr::fcallfuncd(alloc, fcall_args, fq_id)],
2548                 ),
2549             ))
2550         }
2551         _ => emit_fcall_func(e, env, expr, fcall_args, caller_readonly_opt),
2552     }
2555 fn get_reified_var_cexpr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
2556     env: &Env<'a, 'arena>,
2557     pos: &Pos,
2558     name: &str,
2559 ) -> Result<Option<ClassExpr<'arena>>> {
2560     let alloc = env.arena;
2561     Ok(emit_reified_type_opt::<D>(env, pos, name)?.map(|instrs| {
2562         ClassExpr::Reified(InstrSeq::gather(
2563             alloc,
2564             vec![
2565                 instrs,
2566                 instr::basec(alloc, 0, MemberOpMode::Warn),
2567                 instr::querym(
2568                     alloc,
2569                     1,
2570                     QueryOp::CGet,
2571                     MemberKey::ET(Str::from("classname"), ReadonlyOp::Any),
2572                 ),
2573             ],
2574         ))
2575     }))
2578 fn emit_args_inout_setters<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
2579     e: &mut Emitter<'arena, 'decl, D>,
2580     env: &Env<'a, 'arena>,
2581     args: &[ast::Expr],
2582 ) -> Result<(InstrSeq<'arena>, InstrSeq<'arena>)> {
2583     let alloc = env.arena;
2584     let aliases = if has_inout_arg(args) {
2585         inout_locals::collect_written_variables(env, args)
2586     } else {
2587         inout_locals::new_alias_info_map()
2588     };
2589     fn emit_arg_and_inout_setter<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
2590         e: &mut Emitter<'arena, 'decl, D>,
2591         env: &Env<'a, 'arena>,
2592         i: usize,
2593         arg: &ast::Expr,
2594         aliases: &inout_locals::AliasInfoMap,
2595     ) -> Result<(InstrSeq<'arena>, InstrSeq<'arena>)> {
2596         use ast::Expr_ as E_;
2597         let alloc = env.arena;
2598         match &arg.2 {
2599             E_::Callconv(cc) if (cc.0).is_pinout() => {
2600                 match &(cc.1).2 {
2601                     // inout $var
2602                     E_::Lvar(l) => {
2603                         let local = get_local(e, env, &l.0, local_id::get_name(&l.1))?;
2604                         let move_instrs = if !env.flags.contains(hhbc_by_ref_env::Flags::IN_TRY)
2605                             && inout_locals::should_move_local_value(&local, aliases)
2606                         {
2607                             InstrSeq::gather(
2608                                 alloc,
2609                                 vec![instr::null(alloc), instr::popl(alloc, local)],
2610                             )
2611                         } else {
2612                             instr::empty(alloc)
2613                         };
2614                         Ok((
2615                             InstrSeq::gather(alloc, vec![instr::cgetl(alloc, local), move_instrs]),
2616                             instr::popl(alloc, local),
2617                         ))
2618                     }
2619                     // inout $arr[...][...]
2620                     E_::ArrayGet(ag) => {
2621                         let array_get_result = emit_array_get_(
2622                             e,
2623                             env,
2624                             &(cc.1).1,
2625                             None,
2626                             QueryOp::InOut,
2627                             &ag.0,
2628                             ag.1.as_ref(),
2629                             false,
2630                             false,
2631                             Some((i, aliases)),
2632                         )?
2633                         .0;
2634                         Ok(match array_get_result {
2635                             ArrayGetInstr::Regular(instrs) => {
2636                                 let setter_base = emit_array_get(
2637                                     e,
2638                                     env,
2639                                     &(cc.1).1,
2640                                     Some(MemberOpMode::Define),
2641                                     QueryOp::InOut,
2642                                     &ag.0,
2643                                     ag.1.as_ref(),
2644                                     true,
2645                                     false,
2646                                 )?
2647                                 .0;
2648                                 let (mk, warninstr) =
2649                                     get_elem_member_key(e, env, 0, ag.1.as_ref(), false)?;
2650                                 let setter = InstrSeq::gather(
2651                                     alloc,
2652                                     vec![
2653                                         warninstr,
2654                                         setter_base,
2655                                         instr::setm(alloc, 0, mk),
2656                                         instr::popc(alloc),
2657                                     ],
2658                                 );
2659                                 (instrs, setter)
2660                             }
2661                             ArrayGetInstr::Inout { load, store } => {
2662                                 let (mut ld, mut st) = (vec![], vec![store]);
2663                                 for (instr, local_kind_opt) in load.into_iter() {
2664                                     match local_kind_opt {
2665                                         None => ld.push(instr),
2666                                         Some((l, kind)) => {
2667                                             let unset = instr::unsetl(alloc, l);
2668                                             let set = match kind {
2669                                                 StoredValueKind::Expr => instr::setl(alloc, l),
2670                                                 _ => instr::popl(alloc, l),
2671                                             };
2672                                             ld.push(instr);
2673                                             ld.push(set);
2674                                             st.push(unset);
2675                                         }
2676                                     }
2677                                 }
2678                                 (InstrSeq::gather(alloc, ld), InstrSeq::gather(alloc, st))
2679                             }
2680                         })
2681                     }
2682                     _ => Err(unrecoverable(
2683                         "emit_arg_and_inout_setter: Unexpected inout expression type",
2684                     )),
2685                 }
2686             }
2687             _ => Ok((emit_expr(e, env, arg)?, instr::empty(alloc))),
2688         }
2689     }
2690     let (instr_args, instr_setters): (Vec<InstrSeq>, Vec<InstrSeq>) = args
2691         .iter()
2692         .enumerate()
2693         .map(|(i, arg)| emit_arg_and_inout_setter(e, env, i, arg, &aliases))
2694         .collect::<Result<Vec<_>>>()?
2695         .into_iter()
2696         .unzip();
2697     let instr_args = InstrSeq::gather(alloc, instr_args);
2698     let instr_setters = InstrSeq::gather(alloc, instr_setters);
2699     if has_inout_arg(args) {
2700         let retval = e.local_gen_mut().get_unnamed();
2701         Ok((
2702             instr_args,
2703             InstrSeq::gather(
2704                 alloc,
2705                 vec![
2706                     instr::popl(alloc, retval),
2707                     instr_setters,
2708                     instr::pushl(alloc, retval),
2709                 ],
2710             ),
2711         ))
2712     } else {
2713         Ok((instr_args, instr::empty(alloc)))
2714     }
2717 fn get_fcall_args<'arena, 'decl, D: DeclProvider<'decl>>(
2718     e: &mut Emitter<'arena, 'decl, D>,
2719     alloc: &'arena bumpalo::Bump,
2720     args: &[ast::Expr],
2721     uarg: Option<&ast::Expr>,
2722     async_eager_label: Option<Label>,
2723     context: Option<String>,
2724     lock_while_unwinding: bool,
2725     readonly_return: bool,
2726 ) -> FcallArgs<'arena> {
2727     let num_args = args.len();
2728     let num_rets = 1 + args.iter().filter(|x| is_inout_arg(*x)).count();
2729     let mut flags = FcallFlags::default();
2730     flags.set(FcallFlags::HAS_UNPACK, uarg.is_some());
2731     flags.set(FcallFlags::LOCK_WHILE_UNWINDING, lock_while_unwinding);
2732     flags.set(FcallFlags::ENFORCE_MUTABLE_RETURN, !readonly_return);
2733     let is_readonly_arg = if e
2734         .options()
2735         .hhvm
2736         .flags
2737         .contains(HhvmFlags::ENABLE_READONLY_IN_EMITTER)
2738     {
2739         |expr| is_readonly_expr(expr)
2740     } else {
2741         |_expr| false
2742     };
2743     let readonly_args = if args.iter().any(is_readonly_arg) {
2744         Slice::fill_iter(alloc, args.iter().map(is_readonly_arg))
2745     } else {
2746         Slice::empty()
2747     };
2748     FcallArgs::new(
2749         flags,
2750         num_rets,
2751         Slice::fill_iter(alloc, args.iter().map(is_inout_arg)),
2752         readonly_args,
2753         async_eager_label,
2754         num_args,
2755         context
2756             .map(|s| bumpalo::collections::String::from_str_in(s.as_str(), alloc).into_bump_str()),
2757     )
2760 fn is_inout_arg(e: &ast::Expr) -> bool {
2761     e.2.as_callconv().map_or(false, |cc| cc.0.is_pinout())
2764 fn is_readonly_expr(e: &ast::Expr) -> bool {
2765     match &e.2 {
2766         ast::Expr_::ReadonlyExpr(_) => true,
2767         _ => false,
2768     }
2770 fn has_inout_arg(es: &[ast::Expr]) -> bool {
2771     es.iter().any(is_inout_arg)
2774 fn emit_special_function<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
2775     e: &mut Emitter<'arena, 'decl, D>,
2776     env: &Env<'a, 'arena>,
2777     pos: &Pos,
2778     args: &[ast::Expr],
2779     uarg: Option<&ast::Expr>,
2780     lower_fq_name: &str,
2781 ) -> Result<Option<InstrSeq<'arena>>> {
2782     use ast::{Expr as E, Expr_ as E_};
2783     let alloc = env.arena;
2784     let nargs = args.len() + uarg.map_or(0, |_| 1);
2785     let fun_and_clsmeth_disabled = e
2786         .options()
2787         .hhvm
2788         .hack_lang
2789         .flags
2790         .contains(LangFlags::DISALLOW_FUN_AND_CLS_METH_PSEUDO_FUNCS);
2791     match (lower_fq_name, args) {
2792         (id, _) if id == special_functions::ECHO => Ok(Some(InstrSeq::gather(
2793             alloc,
2794             args.iter()
2795                 .enumerate()
2796                 .map(|(i, arg)| {
2797                     Ok(InstrSeq::gather(
2798                         alloc,
2799                         vec![
2800                             emit_expr(e, env, arg)?,
2801                             emit_pos(alloc, pos),
2802                             instr::print(alloc),
2803                             if i == nargs - 1 {
2804                                 instr::empty(alloc)
2805                             } else {
2806                                 instr::popc(alloc)
2807                             },
2808                         ],
2809                     ))
2810                 })
2811                 .collect::<Result<_>>()?,
2812         ))),
2813         ("unsafe_cast", &[]) => Ok(Some(instr::null(alloc))),
2814         ("unsafe_cast", args) => Ok(Some(emit_expr(e, env, &args[0])?)),
2815         ("HH\\Readonly\\as_mut", args) if args.len() == 1 => Ok(Some(emit_expr(e, env, &args[0])?)),
2816         ("HH\\Readonly\\as_mut", _) => Err(emit_fatal::raise_fatal_runtime(
2817             pos,
2818             format!(
2819                 "HH\\Readonly\\as_mut() expects exactly 1 parameter, {} given",
2820                 nargs.to_string()
2821             ),
2822         )),
2823         ("HH\\invariant", args) if args.len() >= 2 => {
2824             let l = e.label_gen_mut().next_regular();
2825             let expr_id = ast::Expr(
2826                 (),
2827                 pos.clone(),
2828                 ast::Expr_::mk_id(ast_defs::Id(
2829                     pos.clone(),
2830                     "\\hh\\invariant_violation".into(),
2831                 )),
2832             );
2833             let call = ast::Expr(
2834                 (),
2835                 pos.clone(),
2836                 ast::Expr_::mk_call(expr_id, vec![], args[1..].to_owned(), uarg.cloned()),
2837             );
2838             let ignored_expr = emit_ignored_expr(e, env, &Pos::make_none(), &call)?;
2839             Ok(Some(InstrSeq::gather(
2840                 alloc,
2841                 vec![
2842                     emit_expr(e, env, &args[0])?,
2843                     instr::jmpnz(alloc, l),
2844                     ignored_expr,
2845                     emit_fatal::emit_fatal_runtime(alloc, pos, "invariant_violation"),
2846                     instr::label(alloc, l),
2847                     instr::null(alloc),
2848                 ],
2849             )))
2850         }
2851         ("HH\\sequence", &[]) => Ok(Some(instr::null(alloc))),
2852         ("HH\\sequence", args) => {
2853             let es = args
2854                 .iter()
2855                 .map(|arg| emit_expr(e, env, arg))
2856                 .collect::<Result<Vec<_>>>()?;
2857             // This is horrible but we can't use `intersperse` because
2858             // `InstrSeq` doesn't implement `Clone`.
2859             let mut iss = Vec::new();
2860             let len = es.len();
2861             for (count, e) in es.into_iter().enumerate() {
2862                 iss.push(e);
2863                 if count != len - 1 {
2864                     iss.push(instr::popc(alloc));
2865                 }
2866             }
2867             Ok(Some(InstrSeq::gather(alloc, iss)))
2868         }
2869         ("class_exists", &[ref arg1, ..])
2870         | ("trait_exists", &[ref arg1, ..])
2871         | ("interface_exists", &[ref arg1, ..])
2872             if nargs == 1 || nargs == 2 =>
2873         {
2874             let class_kind = match lower_fq_name {
2875                 "class_exists" => ClassishKind::Class,
2876                 "interface_exists" => ClassishKind::Interface,
2877                 "trait_exists" => ClassishKind::Trait,
2878                 _ => return Err(unrecoverable("emit_special_function: class_kind")),
2879             };
2880             Ok(Some(InstrSeq::gather(
2881                 alloc,
2882                 vec![
2883                     emit_expr(e, env, arg1)?,
2884                     instr::cast_string(alloc),
2885                     if nargs == 1 {
2886                         instr::true_(alloc)
2887                     } else {
2888                         InstrSeq::gather(
2889                             alloc,
2890                             vec![emit_expr(e, env, &args[1])?, instr::cast_bool(alloc)],
2891                         )
2892                     },
2893                     instr::oodeclexists(alloc, class_kind),
2894                 ],
2895             )))
2896         }
2897         ("exit", _) | ("die", _) if nargs == 0 || nargs == 1 => {
2898             Ok(Some(emit_exit(e, env, args.first())?))
2899         }
2900         ("HH\\fun", _) => {
2901             if fun_and_clsmeth_disabled {
2902                 match args {
2903                     [ast::Expr(_, _, ast::Expr_::String(func_name))] => {
2904                         Err(emit_fatal::raise_fatal_parse(
2905                             pos,
2906                             format!(
2907                                 "`fun()` is disabled; switch to first-class references like `{}<>`",
2908                                 func_name
2909                             ),
2910                         ))
2911                     }
2912                     _ => Err(emit_fatal::raise_fatal_runtime(
2913                         pos,
2914                         "Constant string expected in fun()",
2915                     )),
2916                 }
2917             } else if nargs != 1 {
2918                 Err(emit_fatal::raise_fatal_runtime(
2919                     pos,
2920                     format!(
2921                         "fun() expects exactly 1 parameter, {} given",
2922                         nargs.to_string()
2923                     ),
2924                 ))
2925             } else {
2926                 match args {
2927                     [ast::Expr(_, _, ast::Expr_::String(func_name))] => {
2928                         Ok(Some(emit_hh_fun(
2929                             e,
2930                             env,
2931                             pos,
2932                             &[],
2933                             // FIXME: This is not safe--string literals are binary strings.
2934                             // There's no guarantee that they're valid UTF-8.
2935                             unsafe { std::str::from_utf8_unchecked(func_name.as_slice()) },
2936                         )?))
2937                     }
2938                     _ => Err(emit_fatal::raise_fatal_runtime(
2939                         pos,
2940                         "Constant string expected in fun()",
2941                     )),
2942                 }
2943             }
2944         }
2945         ("__systemlib\\meth_caller", _) => {
2946             // used by meth_caller() to directly emit func ptr
2947             if nargs != 1 {
2948                 return Err(emit_fatal::raise_fatal_runtime(
2949                     pos,
2950                     format!(
2951                         "fun() expects exactly 1 parameter, {} given",
2952                         nargs.to_string()
2953                     ),
2954                 ));
2955             }
2956             match args {
2957                 [E(_, _, E_::String(ref func_name))] => Ok(Some(instr::resolve_meth_caller(
2958                     alloc,
2959                     (
2960                         alloc,
2961                         string_utils::strip_global_ns(
2962                             // FIXME: This is not safe--string literals are binary strings.
2963                             // There's no guarantee that they're valid UTF-8.
2964                             unsafe { std::str::from_utf8_unchecked(func_name.as_slice()) },
2965                         ),
2966                     )
2967                         .into(),
2968                 ))),
2969                 _ => Err(emit_fatal::raise_fatal_runtime(
2970                     pos,
2971                     "Constant string expected in fun()",
2972                 )),
2973             }
2974         }
2975         ("__systemlib\\__debugger_is_uninit", _) => {
2976             if nargs != 1 {
2977                 Err(emit_fatal::raise_fatal_runtime(
2978                     pos,
2979                     format!(
2980                         "__debugger_is_uninit() expects exactly 1 parameter {} given",
2981                         nargs
2982                     ),
2983                 ))
2984             } else {
2985                 match args {
2986                     [E(_, _, E_::Lvar(id))] => Ok(Some(instr::isunsetl(
2987                         alloc,
2988                         get_local(e, env, pos, id.name())?,
2989                     ))),
2990                     _ => Err(emit_fatal::raise_fatal_runtime(
2991                         pos,
2992                         "Local variable expected in __debugger_is_uninit()",
2993                     )),
2994                 }
2995             }
2996         }
2997         ("__SystemLib\\get_enum_member_by_label", _) if e.systemlib() => {
2998             let local = match args {
2999                 [E(_, _, E_::Lvar(id))] => get_local(e, env, pos, id.name()),
3000                 _ => Err(emit_fatal::raise_fatal_runtime(
3001                     pos,
3002                     "Argument must be the label argument",
3003                 )),
3004             }?;
3005             Ok(Some(InstrSeq::gather(
3006                 alloc,
3007                 vec![instr::lateboundcls(alloc), instr::clscnsl(alloc, local)],
3008             )))
3009         }
3010         ("HH\\inst_meth", _) => match args {
3011             [obj_expr, method_name] => Ok(Some(emit_inst_meth(e, env, obj_expr, method_name)?)),
3012             _ => Err(emit_fatal::raise_fatal_runtime(
3013                 pos,
3014                 format!(
3015                     "inst_meth() expects exactly 2 parameters, {} given",
3016                     nargs.to_string()
3017                 ),
3018             )),
3019         },
3020         ("HH\\class_meth", _) if fun_and_clsmeth_disabled => Err(emit_fatal::raise_fatal_parse(
3021             pos,
3022             "`class_meth()` is disabled; switch to first-class references like `C::bar<>`",
3023         )),
3024         ("HH\\class_meth", &[ref cls, ref meth, ..]) if nargs == 2 => {
3025             if meth.2.is_string() {
3026                 if cls.2.is_string()
3027                     || cls
3028                         .2
3029                         .as_class_const()
3030                         .map_or(false, |(_, (_, id))| string_utils::is_class(id))
3031                     || cls
3032                         .2
3033                         .as_id()
3034                         .map_or(false, |ast_defs::Id(_, id)| id == pseudo_consts::G__CLASS__)
3035                 {
3036                     return Ok(Some(emit_class_meth(e, env, cls, meth)?));
3037                 }
3038             }
3039             Err(emit_fatal::raise_fatal_runtime(
3040                 pos,
3041                 concat!(
3042                     "class_meth() expects a literal class name or ::class constant, ",
3043                     "followed by a constant string that refers to a static method ",
3044                     "on that class"
3045                 ),
3046             ))
3047         }
3048         ("HH\\class_meth", _) => Err(emit_fatal::raise_fatal_runtime(
3049             pos,
3050             format!(
3051                 "class_meth() expects exactly 2 parameters, {} given",
3052                 nargs.to_string()
3053             ),
3054         )),
3055         ("HH\\global_set", _) => match *args {
3056             [ref gkey, ref gvalue] => Ok(Some(InstrSeq::gather(
3057                 alloc,
3058                 vec![
3059                     emit_expr(e, env, gkey)?,
3060                     emit_expr(e, env, gvalue)?,
3061                     emit_pos(alloc, pos),
3062                     instr::setg(alloc),
3063                     instr::popc(alloc),
3064                     instr::null(alloc),
3065                 ],
3066             ))),
3067             _ => Err(emit_fatal::raise_fatal_runtime(
3068                 pos,
3069                 format!(
3070                     "global_set() expects exactly 2 parameters, {} given",
3071                     nargs.to_string()
3072                 ),
3073             )),
3074         },
3075         ("HH\\global_unset", _) => match *args {
3076             [ref gkey] => Ok(Some(InstrSeq::gather(
3077                 alloc,
3078                 vec![
3079                     emit_expr(e, env, gkey)?,
3080                     emit_pos(alloc, pos),
3081                     instr::unsetg(alloc),
3082                     instr::null(alloc),
3083                 ],
3084             ))),
3085             _ => Err(emit_fatal::raise_fatal_runtime(
3086                 pos,
3087                 format!(
3088                     "global_unset() expects exactly 1 parameter, {} given",
3089                     nargs.to_string()
3090                 ),
3091             )),
3092         },
3093         ("__hhvm_internal_whresult", &[E(_, _, E_::Lvar(ref param))]) if e.systemlib() => {
3094             Ok(Some(InstrSeq::gather(
3095                 alloc,
3096                 vec![
3097                     instr::cgetl(
3098                         alloc,
3099                         Local::Named(Str::new_str(alloc, local_id::get_name(&param.1))),
3100                     ),
3101                     instr::whresult(alloc),
3102                 ],
3103             )))
3104         }
3105         ("HH\\array_mark_legacy", _) if args.len() == 1 || args.len() == 2 => {
3106             Ok(Some(emit_array_mark_legacy(e, env, pos, args, true)?))
3107         }
3108         ("HH\\array_unmark_legacy", _) if args.len() == 1 || args.len() == 2 => {
3109             Ok(Some(emit_array_mark_legacy(e, env, pos, args, false)?))
3110         }
3111         ("HH\\tag_provenance_here", _) if args.len() == 1 || args.len() == 2 => {
3112             Ok(Some(emit_tag_provenance_here(e, env, pos, args)?))
3113         }
3114         _ => Ok(
3115             match (args, istype_op(lower_fq_name), is_isexp_op(lower_fq_name)) {
3116                 (&[ref arg_expr], _, Some(ref h)) => {
3117                     let is_expr = emit_is(e, env, pos, &h)?;
3118                     Some(InstrSeq::gather(
3119                         alloc,
3120                         vec![emit_expr(e, env, &arg_expr)?, is_expr],
3121                     ))
3122                 }
3123                 (&[E(_, _, E_::Lvar(ref arg_id))], Some(i), _)
3124                     if superglobals::is_any_global(arg_id.name()) =>
3125                 {
3126                     Some(InstrSeq::gather(
3127                         alloc,
3128                         vec![
3129                             emit_local(e, env, BareThisOp::NoNotice, &arg_id)?,
3130                             emit_pos(alloc, pos),
3131                             instr::istypec(alloc, i),
3132                         ],
3133                     ))
3134                 }
3135                 (&[E(_, _, E_::Lvar(ref arg_id))], Some(i), _)
3136                     if !is_local_this(env, &arg_id.1) =>
3137                 {
3138                     Some(instr::istypel(
3139                         alloc,
3140                         get_local(e, env, &arg_id.0, &(arg_id.1).1)?,
3141                         i,
3142                     ))
3143                 }
3144                 (&[ref arg_expr], Some(i), _) => Some(InstrSeq::gather(
3145                     alloc,
3146                     vec![
3147                         emit_expr(e, env, &arg_expr)?,
3148                         emit_pos(alloc, pos),
3149                         instr::istypec(alloc, i),
3150                     ],
3151                 )),
3152                 _ => match get_call_builtin_func_info(lower_fq_name) {
3153                     Some((nargs, i)) if nargs == args.len() => Some(InstrSeq::gather(
3154                         alloc,
3155                         vec![
3156                             emit_exprs(e, env, args)?,
3157                             emit_pos(alloc, pos),
3158                             instr::instr(alloc, i),
3159                         ],
3160                     )),
3161                     _ => None,
3162                 },
3163             },
3164         ),
3165     }
3168 fn emit_inst_meth<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3169     e: &mut Emitter<'arena, 'decl, D>,
3170     env: &Env<'a, 'arena>,
3171     obj_expr: &ast::Expr,
3172     method_name: &ast::Expr,
3173 ) -> Result<InstrSeq<'arena>> {
3174     let alloc = env.arena;
3175     let instrs = InstrSeq::gather(
3176         alloc,
3177         vec![
3178             emit_expr(e, env, obj_expr)?,
3179             emit_expr(e, env, method_name)?,
3180             instr::new_vec_array(alloc, 2),
3181         ],
3182     );
3183     Ok(instrs)
3186 fn emit_class_meth<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3187     e: &mut Emitter<'arena, 'decl, D>,
3188     env: &Env<'a, 'arena>,
3189     cls: &ast::Expr,
3190     meth: &ast::Expr,
3191 ) -> Result<InstrSeq<'arena>> {
3192     use ast::Expr_ as E_;
3193     let alloc = env.arena;
3194     if e.options()
3195         .hhvm
3196         .flags
3197         .contains(HhvmFlags::EMIT_CLS_METH_POINTERS)
3198     {
3199         let method_id = match &meth.2 {
3200             E_::String(method_name) => (alloc, method_name.to_string().as_str()).into(),
3201             _ => return Err(unrecoverable("emit_class_meth: unhandled method")),
3202         };
3203         if let Some((cid, (_, id))) = cls.2.as_class_const() {
3204             if string_utils::is_class(id) {
3205                 return emit_class_meth_native(e, env, &cls.1, cid, method_id, &[]);
3206             }
3207         }
3208         if let Some(ast_defs::Id(_, s)) = cls.2.as_id() {
3209             if s == pseudo_consts::G__CLASS__ {
3210                 return Ok(instr::resolveclsmethods(
3211                     alloc,
3212                     SpecialClsRef::Self_,
3213                     method_id,
3214                 ));
3215             }
3216         }
3217         if let Some(class_name) = cls.2.as_string() {
3218             return Ok(instr::resolveclsmethodd(
3219                 alloc,
3220                 (
3221                     alloc,
3222                     string_utils::strip_global_ns(
3223                         // FIXME: This is not safe--string literals are binary strings.
3224                         // There's no guarantee that they're valid UTF-8.
3225                         unsafe { std::str::from_utf8_unchecked(class_name.as_slice()) },
3226                     ),
3227                 )
3228                     .into(),
3229                 method_id,
3230             ));
3231         }
3232         Err(unrecoverable("emit_class_meth: unhandled method"))
3233     } else {
3234         let instrs = InstrSeq::gather(
3235             alloc,
3236             vec![
3237                 emit_expr(e, env, cls)?,
3238                 emit_expr(e, env, meth)?,
3239                 instr::new_vec_array(alloc, 2),
3240             ],
3241         );
3242         Ok(instrs)
3243     }
3246 fn emit_class_meth_native<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3247     e: &mut Emitter<'arena, 'decl, D>,
3248     env: &Env<'a, 'arena>,
3249     pos: &Pos,
3250     cid: &ast::ClassId,
3251     method_id: MethodId<'arena>,
3252     targs: &[ast::Targ],
3253 ) -> Result<InstrSeq<'arena>> {
3254     let alloc = env.arena;
3255     let mut cexpr = ClassExpr::class_id_to_class_expr(e, false, true, &env.scope, cid);
3256     if let ClassExpr::Id(ast_defs::Id(_, name)) = &cexpr {
3257         if let Some(reified_var_cexpr) = get_reified_var_cexpr::<D>(env, pos, &name)? {
3258             cexpr = reified_var_cexpr;
3259         }
3260     }
3261     let has_generics = has_non_tparam_generics_targs(env, targs);
3262     let mut emit_generics = || -> Result<InstrSeq<'arena>> {
3263         emit_reified_targs(
3264             e,
3265             env,
3266             pos,
3267             &targs.iter().map(|targ| &targ.1).collect::<Vec<_>>(),
3268         )
3269     };
3270     Ok(match cexpr {
3271         ClassExpr::Id(ast_defs::Id(_, name)) if !has_generics => instr::resolveclsmethodd(
3272             alloc,
3273             class::ClassType::<'arena>::from_ast_name_and_mangle(alloc, &name),
3274             method_id,
3275         ),
3276         ClassExpr::Id(ast_defs::Id(_, name)) => InstrSeq::gather(
3277             alloc,
3278             vec![
3279                 emit_generics()?,
3280                 instr::resolverclsmethodd(
3281                     alloc,
3282                     class::ClassType::<'arena>::from_ast_name_and_mangle(alloc, &name),
3283                     method_id,
3284                 ),
3285             ],
3286         ),
3287         ClassExpr::Special(clsref) if !has_generics => {
3288             instr::resolveclsmethods(alloc, clsref, method_id)
3289         }
3290         ClassExpr::Special(clsref) => InstrSeq::gather(
3291             alloc,
3292             vec![
3293                 emit_generics()?,
3294                 instr::resolverclsmethods(alloc, clsref, method_id),
3295             ],
3296         ),
3297         ClassExpr::Reified(instrs) if !has_generics => InstrSeq::gather(
3298             alloc,
3299             vec![
3300                 instrs,
3301                 instr::classgetc(alloc),
3302                 instr::resolveclsmethod(alloc, method_id),
3303             ],
3304         ),
3305         ClassExpr::Reified(instrs) => InstrSeq::gather(
3306             alloc,
3307             vec![
3308                 instrs,
3309                 instr::classgetc(alloc),
3310                 emit_generics()?,
3311                 instr::resolverclsmethod(alloc, method_id),
3312             ],
3313         ),
3314         ClassExpr::Expr(_) => {
3315             return Err(unrecoverable(
3316                 "emit_class_meth_native: ClassExpr::Expr should be impossible",
3317             ));
3318         }
3319     })
3322 fn get_call_builtin_func_info<'arena>(id: impl AsRef<str>) -> Option<(usize, Instruct<'arena>)> {
3323     use {Instruct::*, InstructGet::*, InstructIsset::*, InstructMisc::*, InstructOperator::*};
3324     match id.as_ref() {
3325         "array_key_exists" => Some((2, IMisc(AKExists))),
3326         "hphp_array_idx" => Some((3, IMisc(ArrayIdx))),
3327         "intval" => Some((1, IOp(CastInt))),
3328         "boolval" => Some((1, IOp(CastBool))),
3329         "strval" => Some((1, IOp(CastString))),
3330         "floatval" | "doubleval" => Some((1, IOp(CastDouble))),
3331         "HH\\vec" => Some((1, IOp(CastVec))),
3332         "HH\\keyset" => Some((1, IOp(CastKeyset))),
3333         "HH\\dict" => Some((1, IOp(CastDict))),
3334         "HH\\varray" => Some((1, IOp(CastVec))),
3335         "HH\\darray" => Some((1, IOp(CastDict))),
3336         // TODO: enforce that this returns readonly
3337         "HH\\global_readonly_get" => Some((1, IGet(CGetG))),
3338         "HH\\global_get" => Some((1, IGet(CGetG))),
3339         "HH\\global_isset" => Some((1, IIsset(IssetG))),
3340         _ => None,
3341     }
3344 fn emit_function_pointer<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3345     e: &mut Emitter<'arena, 'decl, D>,
3346     env: &Env<'a, 'arena>,
3347     pos: &Pos,
3348     fpid: &ast::FunctionPtrId,
3349     targs: &[ast::Targ],
3350 ) -> Result<InstrSeq<'arena>> {
3351     let alloc = env.arena;
3352     let instrs = match fpid {
3353         // This is a function name. Equivalent to HH\fun('str')
3354         ast::FunctionPtrId::FPId(id) => emit_hh_fun(e, env, pos, targs, id.name())?,
3355         // class_meth
3356         ast::FunctionPtrId::FPClassConst(cid, method_id) => {
3357             // TODO(hrust) should accept `let method_id = method::MethodType::from_ast_name(&(cc.1).1);`
3358             let method_id: method::MethodType<'arena> =
3359                 (alloc, string_utils::strip_global_ns(&method_id.1)).into();
3360             emit_class_meth_native(e, env, pos, cid, method_id, targs)?
3361         }
3362     };
3363     Ok(emit_pos_then(alloc, pos, instrs))
3366 fn emit_hh_fun<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3367     e: &mut Emitter<'arena, 'decl, D>,
3368     env: &Env<'a, 'arena>,
3369     pos: &Pos,
3370     targs: &[ast::Targ],
3371     fname: &str,
3372 ) -> Result<InstrSeq<'arena>> {
3373     let alloc = env.arena;
3374     let fname = string_utils::strip_global_ns(fname);
3375     if has_non_tparam_generics_targs(env, targs) {
3376         let generics = emit_reified_targs(
3377             e,
3378             env,
3379             pos,
3380             targs
3381                 .iter()
3382                 .map(|targ| &targ.1)
3383                 .collect::<Vec<_>>()
3384                 .as_slice(),
3385         )?;
3386         Ok(InstrSeq::gather(
3387             alloc,
3388             vec![generics, instr::resolve_rfunc(alloc, (alloc, fname).into())],
3389         ))
3390     } else {
3391         Ok(instr::resolve_func(alloc, (alloc, fname).into()))
3392     }
3395 fn emit_is<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3396     e: &mut Emitter<'arena, 'decl, D>,
3397     env: &Env<'a, 'arena>,
3398     pos: &Pos,
3399     h: &ast::Hint,
3400 ) -> Result<InstrSeq<'arena>> {
3401     let alloc = env.arena;
3402     let (ts_instrs, is_static) = emit_reified_arg(e, env, pos, true, h)?;
3403     Ok(if is_static {
3404         match &*h.1 {
3405             aast_defs::Hint_::Happly(ast_defs::Id(_, id), hs)
3406                 if hs.is_empty() && string_utils::strip_hh_ns(&id) == typehints::THIS =>
3407             {
3408                 instr::islateboundcls(alloc)
3409             }
3410             _ => InstrSeq::gather(
3411                 alloc,
3412                 vec![
3413                     get_type_structure_for_hint(alloc, e, &[], &IndexSet::new(), h)?,
3414                     instr::is_type_structc_resolve(alloc),
3415                 ],
3416             ),
3417         }
3418     } else {
3419         InstrSeq::gather(
3420             alloc,
3421             vec![ts_instrs, instr::is_type_structc_dontresolve(alloc)],
3422         )
3423     })
3426 fn istype_op(id: impl AsRef<str>) -> Option<IstypeOp> {
3427     use IstypeOp::*;
3428     match id.as_ref() {
3429         "is_int" | "is_integer" | "is_long" => Some(OpInt),
3430         "is_bool" => Some(OpBool),
3431         "is_float" | "is_real" | "is_double" => Some(OpDbl),
3432         "is_string" => Some(OpStr),
3433         "is_object" => Some(OpObj),
3434         "is_null" => Some(OpNull),
3435         "is_scalar" => Some(OpScalar),
3436         "HH\\is_keyset" => Some(OpKeyset),
3437         "HH\\is_dict" => Some(OpDict),
3438         "HH\\is_vec" => Some(OpVec),
3439         "HH\\is_varray" => Some(OpVec),
3440         "HH\\is_darray" => Some(OpDict),
3441         "HH\\is_any_array" => Some(OpArrLike),
3442         "HH\\is_class_meth" => Some(OpClsMeth),
3443         "HH\\is_fun" => Some(OpFunc),
3444         "HH\\is_php_array" => Some(OpLegacyArrLike),
3445         "HH\\is_array_marked_legacy" => Some(OpLegacyArrLike),
3446         "HH\\is_class" => Some(OpClass),
3447         _ => None,
3448     }
3451 fn is_isexp_op(lower_fq_id: impl AsRef<str>) -> Option<ast::Hint> {
3452     let h = |s: &str| {
3453         Some(ast::Hint::new(
3454             Pos::make_none(),
3455             ast::Hint_::mk_happly(ast::Id(Pos::make_none(), s.into()), vec![]),
3456         ))
3457     };
3458     match lower_fq_id.as_ref() {
3459         "is_int" | "is_integer" | "is_long" => h("\\HH\\int"),
3460         "is_bool" => h("\\HH\\bool"),
3461         "is_float" | "is_real" | "is_double" => h("\\HH\\float"),
3462         "is_string" => h("\\HH\\string"),
3463         "is_null" => h("\\HH\\void"),
3464         "HH\\is_keyset" => h("\\HH\\keyset"),
3465         "HH\\is_dict" => h("\\HH\\dict"),
3466         "HH\\is_vec" => h("\\HH\\vec"),
3467         _ => None,
3468     }
3471 fn emit_eval<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3472     e: &mut Emitter<'arena, 'decl, D>,
3473     env: &Env<'a, 'arena>,
3474     pos: &Pos,
3475     expr: &ast::Expr,
3476 ) -> Result<InstrSeq<'arena>> {
3477     let alloc = env.arena;
3478     Ok(InstrSeq::gather(
3479         alloc,
3480         vec![
3481             emit_expr(e, env, expr)?,
3482             emit_pos(alloc, pos),
3483             instr::eval(alloc),
3484         ],
3485     ))
3488 #[allow(clippy::needless_lifetimes)]
3489 fn has_reified_types<'a, 'arena>(env: &Env<'a, 'arena>) -> bool {
3490     for param in env.scope.get_tparams() {
3491         match param.reified {
3492             oxidized::ast::ReifyKind::Reified => {
3493                 return true;
3494             }
3495             _ => {}
3496         }
3497     }
3498     false
3501 fn emit_call_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3502     e: &mut Emitter<'arena, 'decl, D>,
3503     env: &Env<'a, 'arena>,
3504     pos: &Pos,
3505     async_eager_label: Option<Label>,
3506     readonly_return: bool,
3507     (expr, targs, args, uarg): &(ast::Expr, Vec<ast::Targ>, Vec<ast::Expr>, Option<ast::Expr>),
3508 ) -> Result<InstrSeq<'arena>> {
3509     let alloc = env.arena;
3510     let jit_enable_rename_function = e
3511         .options()
3512         .hhvm
3513         .flags
3514         .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION);
3515     use {ast::Expr as E, ast::Expr_ as E_};
3516     match (&expr.2, &args[..], uarg) {
3517         (E_::Id(id), [E(_, _, E_::String(data))], None)
3518             if id.1 == special_functions::HHAS_ADATA =>
3519         {
3520             // FIXME: This is not safe--string literals are binary strings.
3521             // There's no guarantee that they're valid UTF-8.
3522             let v = TypedValue::mk_hhas_adata(
3523                 alloc.alloc_str(unsafe { std::str::from_utf8_unchecked(data.as_ref()) }),
3524             );
3525             Ok(emit_pos_then(alloc, pos, instr::typedvalue(alloc, v)))
3526         }
3527         (E_::Id(id), _, None) if id.1 == pseudo_functions::ISSET => {
3528             emit_call_isset_exprs(e, env, pos, args)
3529         }
3530         (E_::Id(id), args, None)
3531             if id.1 == fb::IDX
3532                 && !jit_enable_rename_function
3533                 && (args.len() == 2 || args.len() == 3) =>
3534         {
3535             emit_idx(e, env, pos, args)
3536         }
3537         (E_::Id(id), [arg1], None) if id.1 == emitter_special_functions::EVAL => {
3538             emit_eval(e, env, pos, arg1)
3539         }
3540         (E_::Id(id), [arg1], None) if id.1 == emitter_special_functions::SET_FRAME_METADATA => {
3541             Ok(InstrSeq::gather(
3542                 alloc,
3543                 vec![
3544                     emit_expr(e, env, arg1)?,
3545                     emit_pos(alloc, pos),
3546                     instr::popl(alloc, Local::Named(Slice::new("$86metadata".as_bytes()))),
3547                     instr::null(alloc),
3548                 ],
3549             ))
3550         }
3551         (E_::Id(id), [], None)
3552             if id.1 == pseudo_functions::EXIT || id.1 == pseudo_functions::DIE =>
3553         {
3554             let exit = emit_exit(e, env, None)?;
3555             Ok(emit_pos_then(alloc, pos, exit))
3556         }
3557         (E_::Id(id), [arg1], None)
3558             if id.1 == pseudo_functions::EXIT || id.1 == pseudo_functions::DIE =>
3559         {
3560             let exit = emit_exit(e, env, Some(arg1))?;
3561             Ok(emit_pos_then(alloc, pos, exit))
3562         }
3563         (E_::Id(id), [], _)
3564             if id.1 == emitter_special_functions::SYSTEMLIB_REIFIED_GENERICS
3565                 && e.systemlib()
3566                 && has_reified_types(env) =>
3567         {
3568             // Rewrite __systemlib_reified_generics() to $0ReifiedGenerics,
3569             // but only in systemlib functions that take a reified generic.
3570             let lvar = E::new(
3571                 (),
3572                 pos.clone(),
3573                 E_::Lvar(Box::new(ast::Lid(
3574                     pos.clone(),
3575                     local_id::make_unscoped(string_utils::reified::GENERICS_LOCAL_NAME),
3576                 ))),
3577             );
3578             emit_expr(e, env, &lvar)
3579         }
3580         (_, _, _) => {
3581             let instrs = emit_call(
3582                 e,
3583                 env,
3584                 pos,
3585                 expr,
3586                 targs,
3587                 args,
3588                 uarg.as_ref(),
3589                 async_eager_label,
3590                 readonly_return,
3591             )?;
3592             Ok(emit_pos_then(alloc, pos, instrs))
3593         }
3594     }
3597 pub fn emit_reified_generic_instrs<'arena>(
3598     alloc: &'arena bumpalo::Bump,
3599     pos: &Pos,
3600     is_fun: bool,
3601     index: usize,
3602 ) -> Result<InstrSeq<'arena>> {
3603     let base = if is_fun {
3604         instr::basel(
3605             alloc,
3606             Local::Named(Str::new_str(
3607                 alloc,
3608                 string_utils::reified::GENERICS_LOCAL_NAME,
3609             )),
3610             MemberOpMode::Warn,
3611         )
3612     } else {
3613         InstrSeq::gather(
3614             alloc,
3615             vec![
3616                 instr::checkthis(alloc),
3617                 instr::baseh(alloc),
3618                 instr::dim_warn_pt(
3619                     alloc,
3620                     prop::from_raw_string(alloc, string_utils::reified::PROP_NAME),
3621                     ReadonlyOp::Any,
3622                 ),
3623             ],
3624         )
3625     };
3626     Ok(emit_pos_then(
3627         alloc,
3628         pos,
3629         InstrSeq::gather(
3630             alloc,
3631             vec![
3632                 base,
3633                 instr::querym(
3634                     alloc,
3635                     0,
3636                     QueryOp::CGet,
3637                     MemberKey::EI(index.try_into().unwrap(), ReadonlyOp::Any),
3638                 ),
3639             ],
3640         ),
3641     ))
3644 fn emit_reified_type<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3645     env: &Env<'a, 'arena>,
3646     pos: &Pos,
3647     name: &str,
3648 ) -> Result<InstrSeq<'arena>> {
3649     emit_reified_type_opt::<D>(env, pos, name)?
3650         .ok_or_else(|| emit_fatal::raise_fatal_runtime(&Pos::make_none(), "Invalid reified param"))
3653 fn emit_reified_type_opt<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3654     env: &Env<'a, 'arena>,
3655     pos: &Pos,
3656     name: &str,
3657 ) -> Result<Option<InstrSeq<'arena>>> {
3658     let alloc = env.arena;
3659     let is_in_lambda = env.scope.is_in_lambda();
3660     let cget_instr = |is_fun, i| {
3661         instr::cgetl(
3662             env.arena,
3663             Local::Named(Str::new_str(
3664                 alloc,
3665                 string_utils::reified::reified_generic_captured_name(is_fun, i).as_str(),
3666             )),
3667         )
3668     };
3669     let check = |is_soft| -> Result<()> {
3670         if is_soft {
3671             Err(emit_fatal::raise_fatal_parse(
3672                 pos,
3673                 format!(
3674                     "{} is annotated to be a soft reified generic, it cannot be used until the __Soft annotation is removed",
3675                     name
3676                 ),
3677             ))
3678         } else {
3679             Ok(())
3680         }
3681     };
3682     let emit = |(i, is_soft), is_fun| {
3683         check(is_soft)?;
3684         Ok(Some(if is_in_lambda {
3685             cget_instr(is_fun, i)
3686         } else {
3687             emit_reified_generic_instrs(alloc, pos, is_fun, i)?
3688         }))
3689     };
3690     match is_reified_tparam(env, true, name) {
3691         Some((i, is_soft)) => emit((i, is_soft), true),
3692         None => match is_reified_tparam(env, false, name) {
3693             Some((i, is_soft)) => emit((i, is_soft), false),
3694             None => Ok(None),
3695         },
3696     }
3699 fn emit_known_class_id<'arena, 'decl, D: DeclProvider<'decl>>(
3700     alloc: &'arena bumpalo::Bump,
3701     e: &mut Emitter<'arena, 'decl, D>,
3702     id: &ast_defs::Id,
3703 ) -> InstrSeq<'arena> {
3704     let cid = class::ClassType::from_ast_name(alloc, &id.1);
3705     let cid_string = instr::string(alloc, cid.to_raw_string());
3706     emit_symbol_refs::add_class(alloc, e, cid);
3707     InstrSeq::gather(alloc, vec![cid_string, instr::classgetc(alloc)])
3710 fn emit_load_class_ref<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3711     e: &mut Emitter<'arena, 'decl, D>,
3712     env: &Env<'a, 'arena>,
3713     pos: &Pos,
3714     cexpr: ClassExpr<'arena>,
3715 ) -> Result<InstrSeq<'arena>> {
3716     let alloc = env.arena;
3717     let instrs = match cexpr {
3718         ClassExpr::Special(SpecialClsRef::Self_) => instr::self_(alloc),
3719         ClassExpr::Special(SpecialClsRef::Static) => instr::lateboundcls(alloc),
3720         ClassExpr::Special(SpecialClsRef::Parent) => instr::parent(alloc),
3721         ClassExpr::Id(id) => emit_known_class_id(alloc, e, &id),
3722         ClassExpr::Expr(expr) => InstrSeq::gather(
3723             alloc,
3724             vec![
3725                 emit_pos(alloc, pos),
3726                 emit_expr(e, env, &expr)?,
3727                 instr::classgetc(alloc),
3728             ],
3729         ),
3730         ClassExpr::Reified(instrs) => InstrSeq::gather(
3731             alloc,
3732             vec![emit_pos(alloc, pos), instrs, instr::classgetc(alloc)],
3733         ),
3734     };
3735     Ok(emit_pos_then(alloc, pos, instrs))
3738 fn emit_new<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3739     e: &mut Emitter<'arena, 'decl, D>,
3740     env: &Env<'a, 'arena>,
3741     pos: &Pos,
3742     (cid, targs, args, uarg, _): &(
3743         ast::ClassId,
3744         Vec<ast::Targ>,
3745         Vec<ast::Expr>,
3746         Option<ast::Expr>,
3747         (),
3748     ),
3749     is_reflection_class_builtin: bool,
3750 ) -> Result<InstrSeq<'arena>> {
3751     let alloc = env.arena;
3752     if has_inout_arg(args) {
3753         return Err(unrecoverable("Unexpected inout arg in new expr"));
3754     }
3755     let resolve_self = match &cid.2.as_ciexpr() {
3756         Some(ci_expr) => match ci_expr.as_id() {
3757             Some(ast_defs::Id(_, n)) if string_utils::is_self(n) => env
3758                 .scope
3759                 .get_class_tparams()
3760                 .iter()
3761                 .all(|tp| tp.reified.is_erased()),
3762             Some(ast_defs::Id(_, n)) if string_utils::is_parent(n) => {
3763                 env.scope
3764                     .get_class()
3765                     .map_or(true, |cls| match cls.get_extends() {
3766                         [h, ..] => {
3767                             h.1.as_happly()
3768                                 .map_or(true, |(_, l)| !has_non_tparam_generics(env, l))
3769                         }
3770                         _ => true,
3771                     })
3772             }
3773             _ => true,
3774         },
3775         _ => true,
3776     };
3777     use HasGenericsOp as H;
3778     let cexpr = ClassExpr::class_id_to_class_expr(e, false, resolve_self, &env.scope, cid);
3779     let (cexpr, has_generics) = match &cexpr {
3780         ClassExpr::Id(ast_defs::Id(_, name)) => match emit_reified_type_opt::<D>(env, pos, name)? {
3781             Some(instrs) => {
3782                 if targs.is_empty() {
3783                     (ClassExpr::Reified(instrs), H::MaybeGenerics)
3784                 } else {
3785                     return Err(emit_fatal::raise_fatal_parse(
3786                         pos,
3787                         "Cannot have higher kinded reified generics",
3788                     ));
3789                 }
3790             }
3791             None if !has_non_tparam_generics_targs(env, targs) => (cexpr, H::NoGenerics),
3792             None => (cexpr, H::HasGenerics),
3793         },
3794         _ => (cexpr, H::NoGenerics),
3795     };
3796     if is_reflection_class_builtin {
3797         scope::with_unnamed_locals(alloc, e, |alloc, e| {
3798             let (instr_args, _) = emit_args_inout_setters(e, env, args)?;
3799             let instr_uargs = match uarg {
3800                 None => instr::empty(alloc),
3801                 Some(uarg) => emit_expr(e, env, uarg)?,
3802             };
3803             Ok((
3804                 instr::empty(alloc),
3805                 InstrSeq::gather(alloc, vec![instr_args, instr_uargs]),
3806                 instr::empty(alloc),
3807             ))
3808         })
3809     } else {
3810         let newobj_instrs = match cexpr {
3811             ClassExpr::Id(ast_defs::Id(_, cname)) => {
3812                 let id = class::ClassType::<'arena>::from_ast_name_and_mangle(alloc, &cname);
3813                 emit_symbol_refs::add_class(alloc, e, id);
3814                 match has_generics {
3815                     H::NoGenerics => InstrSeq::gather(
3816                         alloc,
3817                         vec![emit_pos(alloc, pos), instr::newobjd(alloc, id)],
3818                     ),
3819                     H::HasGenerics => InstrSeq::gather(
3820                         alloc,
3821                         vec![
3822                             emit_pos(alloc, pos),
3823                             emit_reified_targs(
3824                                 e,
3825                                 env,
3826                                 pos,
3827                                 &targs.iter().map(|t| &t.1).collect::<Vec<_>>(),
3828                             )?,
3829                             instr::newobjrd(alloc, id),
3830                         ],
3831                     ),
3832                     H::MaybeGenerics => {
3833                         return Err(unrecoverable(
3834                             "Internal error: This case should have been transformed",
3835                         ));
3836                     }
3837                 }
3838             }
3839             ClassExpr::Special(cls_ref) => InstrSeq::gather(
3840                 alloc,
3841                 vec![emit_pos(alloc, pos), instr::newobjs(alloc, cls_ref)],
3842             ),
3843             ClassExpr::Reified(instrs) if has_generics == H::MaybeGenerics => InstrSeq::gather(
3844                 alloc,
3845                 vec![instrs, instr::classgetts(alloc), instr::newobjr(alloc)],
3846             ),
3847             _ => InstrSeq::gather(
3848                 alloc,
3849                 vec![
3850                     emit_load_class_ref(e, env, pos, cexpr)?,
3851                     instr::newobj(alloc),
3852                 ],
3853             ),
3854         };
3855         scope::with_unnamed_locals(alloc, e, |alloc, e| {
3856             let (instr_args, _) = emit_args_inout_setters(e, env, args)?;
3857             let instr_uargs = match uarg {
3858                 None => instr::empty(alloc),
3859                 Some(uarg) => emit_expr(e, env, uarg)?,
3860             };
3861             Ok((
3862                 instr::empty(alloc),
3863                 InstrSeq::gather(
3864                     alloc,
3865                     vec![
3866                         newobj_instrs,
3867                         instr::dup(alloc),
3868                         instr::nulluninit(alloc),
3869                         instr_args,
3870                         instr_uargs,
3871                         emit_pos(alloc, pos),
3872                         instr::fcallctor(
3873                             alloc,
3874                             get_fcall_args(
3875                                 e,
3876                                 alloc,
3877                                 args,
3878                                 uarg.as_ref(),
3879                                 None,
3880                                 env.call_context.clone(),
3881                                 true,
3882                                 true, // we do not need to enforce readonly return for constructors
3883                             ),
3884                         ),
3885                         instr::popc(alloc),
3886                         instr::lockobj(alloc),
3887                     ],
3888                 ),
3889                 instr::empty(alloc),
3890             ))
3891         })
3892     }
3895 fn emit_obj_get<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3896     e: &mut Emitter<'arena, 'decl, D>,
3897     env: &Env<'a, 'arena>,
3898     pos: &Pos,
3899     query_op: QueryOp,
3900     expr: &ast::Expr,
3901     prop: &ast::Expr,
3902     nullflavor: &ast_defs::OgNullFlavor,
3903     null_coalesce_assignment: bool,
3904     readonly_get: bool, // obj_get enclosed in readonly expression
3905 ) -> Result<(InstrSeq<'arena>, Option<NumParams>)> {
3906     let readonly_op = if readonly_get {
3907         ReadonlyOp::Any
3908     } else {
3909         ReadonlyOp::Mutable
3910     };
3911     let alloc = env.arena;
3912     if let Some(ast::Lid(pos, id)) = expr.2.as_lvar() {
3913         if local_id::get_name(&id) == special_idents::THIS
3914             && nullflavor.eq(&ast_defs::OgNullFlavor::OGNullsafe)
3915         {
3916             return Err(emit_fatal::raise_fatal_parse(
3917                 pos,
3918                 "?-> is not allowed with $this",
3919             ));
3920         }
3921     }
3922     if let Some(ast_defs::Id(_, s)) = prop.2.as_id() {
3923         if string_utils::is_xhp(s) {
3924             return Ok((emit_xhp_obj_get(e, env, pos, &expr, s, nullflavor)?, None));
3925         }
3926     }
3927     let mode = if null_coalesce_assignment {
3928         MemberOpMode::Warn
3929     } else {
3930         get_querym_op_mode(&query_op)
3931     };
3932     let prop_stack_size = emit_prop_expr(
3933         e,
3934         env,
3935         nullflavor,
3936         0,
3937         prop,
3938         null_coalesce_assignment,
3939         ReadonlyOp::Any,
3940     )?
3941     .2;
3942     let (
3943         base_expr_instrs_begin,
3944         base_expr_instrs_end,
3945         base_setup_instrs,
3946         base_stack_size,
3947         cls_stack_size,
3948     ) = emit_base(
3949         e,
3950         env,
3951         expr,
3952         mode,
3953         true,
3954         BareThisOp::Notice,
3955         null_coalesce_assignment,
3956         prop_stack_size,
3957         0,
3958         readonly_op,
3959     )?;
3960     let (mk, prop_instrs, _) = emit_prop_expr(
3961         e,
3962         env,
3963         nullflavor,
3964         cls_stack_size,
3965         prop,
3966         null_coalesce_assignment,
3967         readonly_op,
3968     )?;
3969     let total_stack_size = (prop_stack_size + base_stack_size + cls_stack_size) as usize;
3970     let num_params = if null_coalesce_assignment {
3971         0
3972     } else {
3973         total_stack_size
3974     };
3975     let final_instr = instr::querym(alloc, num_params, query_op, mk);
3976     // Don't pop elems/props from the stack during the lookup for null
3977     // coalesce assignment in case we do a write later.
3978     let querym_n_unpopped = if null_coalesce_assignment {
3979         Some(total_stack_size)
3980     } else {
3981         None
3982     };
3983     let instr = InstrSeq::gather(
3984         alloc,
3985         vec![
3986             base_expr_instrs_begin,
3987             prop_instrs,
3988             base_expr_instrs_end,
3989             emit_pos(alloc, pos),
3990             base_setup_instrs,
3991             final_instr,
3992         ],
3993     );
3994     Ok((instr, querym_n_unpopped))
3997 // Get the member key for a property, and return any instructions and
3998 // the size of the stack in the case that the property cannot be
3999 // placed inline in the instruction.
4000 fn emit_prop_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4001     e: &mut Emitter<'arena, 'decl, D>,
4002     env: &Env<'a, 'arena>,
4003     nullflavor: &ast_defs::OgNullFlavor,
4004     stack_index: StackIndex,
4005     prop: &ast::Expr,
4006     null_coalesce_assignment: bool,
4007     readonly_op: ReadonlyOp,
4008 ) -> Result<(MemberKey<'arena>, InstrSeq<'arena>, StackIndex)> {
4009     let alloc = env.arena;
4011     let mk = match &prop.2 {
4012         ast::Expr_::Id(id) => {
4013             let ast_defs::Id(pos, name) = &**id;
4014             if name.starts_with('$') {
4015                 MemberKey::PL(get_local(e, env, pos, name)?, readonly_op)
4016             } else {
4017                 // Special case for known property name
4018                 let pid: prop::PropType<'arena> = prop::PropType::<'arena>::from_ast_name(
4019                     alloc,
4020                     string_utils::strip_global_ns(&name),
4021                 );
4022                 match nullflavor {
4023                     ast_defs::OgNullFlavor::OGNullthrows => MemberKey::PT(pid, readonly_op),
4024                     ast_defs::OgNullFlavor::OGNullsafe => MemberKey::QT(pid, readonly_op),
4025                 }
4026             }
4027         }
4028         // Special case for known property name
4029         ast::Expr_::String(name) => {
4030             let pid: prop::PropType<'arena> = prop::PropType::<'arena>::from_ast_name(
4031                 alloc,
4032                 string_utils::strip_global_ns(
4033                     // FIXME: This is not safe--string literals are binary strings.
4034                     // There's no guarantee that they're valid UTF-8.
4035                     unsafe { std::str::from_utf8_unchecked(name.as_slice()) },
4036                 ),
4037             );
4038             match nullflavor {
4039                 ast_defs::OgNullFlavor::OGNullthrows => MemberKey::PT(pid, readonly_op),
4040                 ast_defs::OgNullFlavor::OGNullsafe => MemberKey::QT(pid, readonly_op),
4041             }
4042         }
4043         ast::Expr_::Lvar(lid) if !(is_local_this(env, &lid.1)) => MemberKey::PL(
4044             get_local(e, env, &lid.0, local_id::get_name(&lid.1))?,
4045             readonly_op,
4046         ),
4047         _ => {
4048             // General case
4049             MemberKey::PC(stack_index, readonly_op)
4050         }
4051     };
4052     // For nullsafe access, insist that property is known
4053     Ok(match mk {
4054         MemberKey::PL(_, _) | MemberKey::PC(_, _)
4055             if nullflavor.eq(&ast_defs::OgNullFlavor::OGNullsafe) =>
4056         {
4057             return Err(emit_fatal::raise_fatal_parse(
4058                 &prop.1,
4059                 "?-> can only be used with scalar property names",
4060             ));
4061         }
4062         MemberKey::PC(_, _) => (mk, emit_expr(e, env, prop)?, 1),
4063         MemberKey::PL(local, readonly_op) if null_coalesce_assignment => (
4064             MemberKey::PC(stack_index, readonly_op),
4065             instr::cgetl(alloc, local),
4066             1,
4067         ),
4068         _ => (mk, instr::empty(alloc), 0),
4069     })
4072 fn emit_xhp_obj_get<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4073     e: &mut Emitter<'arena, 'decl, D>,
4074     env: &Env<'a, 'arena>,
4075     pos: &Pos,
4076     expr: &ast::Expr,
4077     s: &str,
4078     nullflavor: &ast_defs::OgNullFlavor,
4079 ) -> Result<InstrSeq<'arena>> {
4080     use ast::Expr as E;
4081     use ast::Expr_ as E_;
4082     let f = E(
4083         (),
4084         pos.clone(),
4085         E_::mk_obj_get(
4086             expr.clone(),
4087             E(
4088                 (),
4089                 pos.clone(),
4090                 E_::mk_id(ast_defs::Id(pos.clone(), "getAttribute".into())),
4091             ),
4092             nullflavor.clone(),
4093             false,
4094         ),
4095     );
4096     let args = vec![E(
4097         (),
4098         pos.clone(),
4099         E_::mk_string(string_utils::clean(s).into()),
4100     )];
4101     emit_call(e, env, pos, &f, &[], &args[..], None, None, false)
4104 fn emit_array_get<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4105     e: &mut Emitter<'arena, 'decl, D>,
4106     env: &Env<'a, 'arena>,
4107     outer_pos: &Pos,
4108     mode: Option<MemberOpMode>,
4109     query_op: QueryOp,
4110     base: &ast::Expr,
4111     elem: Option<&ast::Expr>,
4112     no_final: bool,
4113     null_coalesce_assignment: bool,
4114 ) -> Result<(InstrSeq<'arena>, Option<usize>)> {
4115     let result = emit_array_get_(
4116         e,
4117         env,
4118         outer_pos,
4119         mode,
4120         query_op,
4121         base,
4122         elem,
4123         no_final,
4124         null_coalesce_assignment,
4125         None,
4126     )?;
4127     match result {
4128         (ArrayGetInstr::Regular(i), querym_n_unpopped) => Ok((i, querym_n_unpopped)),
4129         (ArrayGetInstr::Inout { .. }, _) => Err(unrecoverable("unexpected inout")),
4130     }
4133 fn emit_array_get_<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4134     e: &mut Emitter<'arena, 'decl, D>,
4135     env: &Env<'a, 'arena>,
4136     outer_pos: &Pos,
4137     mode: Option<MemberOpMode>,
4138     query_op: QueryOp,
4139     base_expr: &ast::Expr,
4140     elem: Option<&ast::Expr>,
4141     no_final: bool,
4142     null_coalesce_assignment: bool,
4143     inout_param_info: Option<(usize, &inout_locals::AliasInfoMap)>,
4144 ) -> Result<(ArrayGetInstr<'arena>, Option<usize>)> {
4145     use ast::Expr as E;
4146     let alloc = env.arena;
4147     match (base_expr, elem) {
4148         (E(_, pos, _), None)
4149             if !env
4150                 .flags
4151                 .contains(hhbc_by_ref_env::Flags::ALLOWS_ARRAY_APPEND) =>
4152         {
4153             Err(emit_fatal::raise_fatal_runtime(
4154                 pos,
4155                 "Can't use [] for reading",
4156             ))
4157         }
4158         _ => {
4159             let local_temp_kind = get_local_temp_kind(env, false, inout_param_info, elem);
4160             let mode = if null_coalesce_assignment {
4161                 MemberOpMode::Warn
4162             } else {
4163                 mode.unwrap_or_else(|| get_querym_op_mode(&query_op))
4164             };
4165             let (elem_instrs, elem_stack_size) =
4166                 emit_elem(e, env, elem, local_temp_kind, null_coalesce_assignment)?;
4167             let base_result = emit_base_(
4168                 e,
4169                 env,
4170                 base_expr,
4171                 mode,
4172                 false,
4173                 match query_op {
4174                     QueryOp::Isset => BareThisOp::NoNotice,
4175                     _ => BareThisOp::Notice,
4176                 },
4177                 null_coalesce_assignment,
4178                 elem_stack_size,
4179                 0,
4180                 inout_param_info,
4181                 ReadonlyOp::Any, // array get on reading has no restrictions
4182             )?;
4183             let cls_stack_size = match &base_result {
4184                 ArrayGetBase::Regular(base) => base.cls_stack_size,
4185                 ArrayGetBase::Inout { load, .. } => load.cls_stack_size,
4186             };
4187             let (memberkey, warninstr) =
4188                 get_elem_member_key(e, env, cls_stack_size, elem, null_coalesce_assignment)?;
4189             let mut querym_n_unpopped = None;
4190             let mut make_final =
4191                 |total_stack_size: StackIndex, memberkey: MemberKey<'arena>| -> InstrSeq {
4192                     if no_final {
4193                         instr::empty(alloc)
4194                     } else if null_coalesce_assignment {
4195                         querym_n_unpopped = Some(total_stack_size as usize);
4196                         instr::querym(alloc, 0, query_op, memberkey)
4197                     } else {
4198                         instr::querym(alloc, total_stack_size as usize, query_op, memberkey)
4199                     }
4200                 };
4201             let instr = match (base_result, local_temp_kind) {
4202                 (ArrayGetBase::Regular(base), None) =>
4203                 // neither base nor expression needs to store anything
4204                 {
4205                     ArrayGetInstr::Regular(InstrSeq::gather(
4206                         alloc,
4207                         vec![
4208                             warninstr,
4209                             base.base_instrs,
4210                             elem_instrs,
4211                             base.cls_instrs,
4212                             emit_pos(alloc, outer_pos),
4213                             base.setup_instrs,
4214                             make_final(
4215                                 base.base_stack_size + base.cls_stack_size + elem_stack_size,
4216                                 memberkey,
4217                             ),
4218                         ],
4219                     ))
4220                 }
4221                 (ArrayGetBase::Regular(base), Some(local_kind)) => {
4222                     // base does not need temp locals but index expression does
4223                     let local = e.local_gen_mut().get_unnamed();
4224                     let load = vec![
4225                         // load base and indexer, value of indexer will be saved in local
4226                         (
4227                             InstrSeq::gather(
4228                                 alloc,
4229                                 vec![InstrSeq::clone(alloc, &base.base_instrs), elem_instrs],
4230                             ),
4231                             Some((local, local_kind)),
4232                         ),
4233                         // finish loading the value
4234                         (
4235                             InstrSeq::gather(
4236                                 alloc,
4237                                 vec![
4238                                     warninstr,
4239                                     base.base_instrs,
4240                                     emit_pos(alloc, outer_pos),
4241                                     base.setup_instrs,
4242                                     make_final(
4243                                         base.base_stack_size
4244                                             + base.cls_stack_size
4245                                             + elem_stack_size,
4246                                         memberkey,
4247                                     ),
4248                                 ],
4249                             ),
4250                             None,
4251                         ),
4252                     ];
4253                     let store = InstrSeq::gather(
4254                         alloc,
4255                         vec![
4256                             emit_store_for_simple_base(
4257                                 e,
4258                                 env,
4259                                 outer_pos,
4260                                 elem_stack_size,
4261                                 base_expr,
4262                                 local,
4263                                 false,
4264                                 ReadonlyOp::Any,
4265                             )?,
4266                             instr::popc(alloc),
4267                         ],
4268                     );
4269                     ArrayGetInstr::Inout { load, store }
4270                 }
4271                 (
4272                     ArrayGetBase::Inout {
4273                         load:
4274                             ArrayGetBaseData {
4275                                 mut base_instrs,
4276                                 cls_instrs,
4277                                 setup_instrs,
4278                                 base_stack_size,
4279                                 cls_stack_size,
4280                             },
4281                         store,
4282                     },
4283                     None,
4284                 ) => {
4285                     // base needs temp locals, indexer - does not,
4286                     // simply concat two instruction sequences
4287                     base_instrs.push((
4288                         InstrSeq::gather(
4289                             alloc,
4290                             vec![
4291                                 warninstr,
4292                                 elem_instrs,
4293                                 cls_instrs,
4294                                 emit_pos(alloc, outer_pos),
4295                                 setup_instrs,
4296                                 make_final(
4297                                     base_stack_size + cls_stack_size + elem_stack_size,
4298                                     memberkey.clone(),
4299                                 ),
4300                             ],
4301                         ),
4302                         None,
4303                     ));
4304                     let store = InstrSeq::gather(
4305                         alloc,
4306                         vec![store, instr::setm(alloc, 0, memberkey), instr::popc(alloc)],
4307                     );
4308                     ArrayGetInstr::Inout {
4309                         load: base_instrs,
4310                         store,
4311                     }
4312                 }
4313                 (
4314                     ArrayGetBase::Inout {
4315                         load:
4316                             ArrayGetBaseData {
4317                                 mut base_instrs,
4318                                 cls_instrs,
4319                                 setup_instrs,
4320                                 base_stack_size,
4321                                 cls_stack_size,
4322                             },
4323                         store,
4324                     },
4325                     Some(local_kind),
4326                 ) => {
4327                     // both base and index need temp locals,
4328                     // create local for index value
4329                     let local = e.local_gen_mut().get_unnamed();
4330                     base_instrs.push((elem_instrs, Some((local, local_kind))));
4331                     base_instrs.push((
4332                         InstrSeq::gather(
4333                             alloc,
4334                             vec![
4335                                 warninstr,
4336                                 cls_instrs,
4337                                 emit_pos(alloc, outer_pos),
4338                                 setup_instrs,
4339                                 make_final(
4340                                     base_stack_size + cls_stack_size + elem_stack_size,
4341                                     memberkey,
4342                                 ),
4343                             ],
4344                         ),
4345                         None,
4346                     ));
4347                     let store = InstrSeq::gather(
4348                         alloc,
4349                         vec![
4350                             store,
4351                             instr::setm(alloc, 0, MemberKey::EL(local, ReadonlyOp::Any)),
4352                             instr::popc(alloc),
4353                         ],
4354                     );
4355                     ArrayGetInstr::Inout {
4356                         load: base_instrs,
4357                         store,
4358                     }
4359                 }
4360             };
4361             Ok((instr, querym_n_unpopped))
4362         }
4363     }
4366 fn is_special_class_constant_accessed_with_class_id(cname: &ast::ClassId_, id: &str) -> bool {
4367     let is_self_parent_or_static = match cname {
4368         ast::ClassId_::CIexpr(ast::Expr(_, _, ast::Expr_::Id(id))) => {
4369             string_utils::is_self(&id.1)
4370                 || string_utils::is_parent(&id.1)
4371                 || string_utils::is_static(&id.1)
4372         }
4373         _ => false,
4374     };
4375     string_utils::is_class(id) && !is_self_parent_or_static
4378 fn emit_elem<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4379     e: &mut Emitter<'arena, 'decl, D>,
4380     env: &Env<'a, 'arena>,
4381     elem: Option<&ast::Expr>,
4382     local_temp_kind: Option<StoredValueKind>,
4383     null_coalesce_assignment: bool,
4384 ) -> Result<(InstrSeq<'arena>, StackIndex)> {
4385     let alloc = env.arena;
4386     Ok(match elem {
4387         None => (instr::empty(alloc), 0),
4388         Some(expr) if expr.2.is_int() || expr.2.is_string() => (instr::empty(alloc), 0),
4389         Some(expr) => match &expr.2 {
4390             ast::Expr_::Lvar(x) if !is_local_this(env, &x.1) => {
4391                 if local_temp_kind.is_some() {
4392                     (
4393                         instr::cgetquietl(
4394                             alloc,
4395                             get_local(e, env, &x.0, local_id::get_name(&x.1))?,
4396                         ),
4397                         0,
4398                     )
4399                 } else if null_coalesce_assignment {
4400                     (
4401                         instr::cgetl(alloc, get_local(e, env, &x.0, local_id::get_name(&x.1))?),
4402                         1,
4403                     )
4404                 } else {
4405                     (instr::empty(alloc), 0)
4406                 }
4407             }
4408             ast::Expr_::ClassConst(x)
4409                 if is_special_class_constant_accessed_with_class_id(&(x.0).2, &(x.1).1) =>
4410             {
4411                 (instr::empty(alloc), 0)
4412             }
4413             _ => (emit_expr(e, env, expr)?, 1),
4414         },
4415     })
4418 fn get_elem_member_key<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4419     e: &mut Emitter<'arena, 'decl, D>,
4420     env: &Env<'a, 'arena>,
4421     stack_index: StackIndex,
4422     elem: Option<&ast::Expr>,
4423     null_coalesce_assignment: bool,
4424 ) -> Result<(MemberKey<'arena>, InstrSeq<'arena>)> {
4425     use ast::ClassId_ as CI_;
4426     use ast::Expr as E;
4427     use ast::Expr_ as E_;
4428     let alloc = env.arena;
4429     match elem {
4430         // ELement missing (so it's array append)
4431         None => Ok((MemberKey::W, instr::empty(alloc))),
4432         Some(elem_expr) => match &elem_expr.2 {
4433             // Special case for local
4434             E_::Lvar(x) if !is_local_this(env, &x.1) => Ok((
4435                 {
4436                     if null_coalesce_assignment {
4437                         MemberKey::EC(stack_index, ReadonlyOp::Any)
4438                     } else {
4439                         MemberKey::EL(
4440                             get_local(e, env, &x.0, local_id::get_name(&x.1))?,
4441                             ReadonlyOp::Any,
4442                         )
4443                     }
4444                 },
4445                 instr::empty(alloc),
4446             )),
4447             // Special case for literal integer
4448             E_::Int(s) => match ast_constant_folder::expr_to_typed_value(alloc, e, elem_expr) {
4449                 Ok(TypedValue::Int(i)) => {
4450                     Ok((MemberKey::EI(i, ReadonlyOp::Any), instr::empty(alloc)))
4451                 }
4452                 _ => Err(Unrecoverable(format!("{} is not a valid integer index", s))),
4453             },
4454             // Special case for literal string
4455             E_::String(s) => {
4456                 // FIXME: This is not safe--string literals are binary strings.
4457                 // There's no guarantee that they're valid UTF-8.
4458                 let s = unsafe { std::str::from_utf8_unchecked(s.as_slice()) };
4459                 let s = bumpalo::collections::String::from_str_in(s, alloc).into_bump_str();
4460                 Ok((
4461                     MemberKey::ET(Str::from(s), ReadonlyOp::Any),
4462                     instr::empty(alloc),
4463                 ))
4464             }
4465             // Special case for class name
4466             E_::ClassConst(x)
4467                 if is_special_class_constant_accessed_with_class_id(&(x.0).2, &(x.1).1) =>
4468             {
4469                 let cname = match (&(x.0).2, env.scope.get_class()) {
4470                     (CI_::CIself, Some(cd)) => string_utils::strip_global_ns(cd.get_name_str()),
4471                     (CI_::CIexpr(E(_, _, E_::Id(id))), _) => string_utils::strip_global_ns(&id.1),
4472                     (CI_::CI(id), _) => string_utils::strip_global_ns(&id.1),
4473                     _ => {
4474                         return Err(Unrecoverable(
4475                             "Unreachable due to is_special_class_constant_accessed_with_class_id"
4476                                 .into(),
4477                         ));
4478                     }
4479                 };
4480                 let fq_id =
4481                     class::ClassType::<'arena>::from_ast_name(alloc, &cname).to_raw_string();
4482                 if e.options().emit_class_pointers() > 0 {
4483                     Ok((
4484                         MemberKey::ET(Str::from(fq_id), ReadonlyOp::Any),
4485                         instr::raise_class_string_conversion_warning(alloc),
4486                     ))
4487                 } else {
4488                     Ok((
4489                         MemberKey::ET(Str::from(fq_id), ReadonlyOp::Any),
4490                         instr::empty(alloc),
4491                     ))
4492                 }
4493             }
4494             _ => {
4495                 // General case
4496                 Ok((
4497                     MemberKey::EC(stack_index, ReadonlyOp::Any),
4498                     instr::empty(alloc),
4499                 ))
4500             }
4501         },
4502     }
4505 fn emit_store_for_simple_base<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4506     e: &mut Emitter<'arena, 'decl, D>,
4507     env: &Env<'a, 'arena>,
4508     pos: &Pos,
4509     elem_stack_size: isize,
4510     base: &ast::Expr,
4511     local: Local<'arena>,
4512     is_base: bool,
4513     readonly_op: ReadonlyOp,
4514 ) -> Result<InstrSeq<'arena>> {
4515     let alloc = env.arena;
4516     let (base_expr_instrs_begin, base_expr_instrs_end, base_setup_instrs, _, _) = emit_base(
4517         e,
4518         env,
4519         base,
4520         MemberOpMode::Define,
4521         false,
4522         BareThisOp::Notice,
4523         false,
4524         elem_stack_size,
4525         0,
4526         readonly_op,
4527     )?;
4528     let memberkey = MemberKey::EL(local, ReadonlyOp::Any);
4529     Ok(InstrSeq::gather(
4530         alloc,
4531         vec![
4532             base_expr_instrs_begin,
4533             base_expr_instrs_end,
4534             emit_pos(alloc, pos),
4535             base_setup_instrs,
4536             if is_base {
4537                 instr::dim(alloc, MemberOpMode::Define, memberkey)
4538             } else {
4539                 instr::setm(alloc, 0, memberkey)
4540             },
4541         ],
4542     ))
4545 fn get_querym_op_mode(query_op: &QueryOp) -> MemberOpMode {
4546     match query_op {
4547         QueryOp::InOut => MemberOpMode::InOut,
4548         QueryOp::CGet => MemberOpMode::Warn,
4549         _ => MemberOpMode::ModeNone,
4550     }
4553 fn emit_class_get<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4554     e: &mut Emitter<'arena, 'decl, D>,
4555     env: &Env<'a, 'arena>,
4556     query_op: QueryOp,
4557     cid: &ast::ClassId,
4558     prop: &ast::ClassGetExpr,
4559     readonly_op: ReadonlyOp,
4560 ) -> Result<InstrSeq<'arena>> {
4561     let alloc = env.arena;
4562     let cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
4563     Ok(InstrSeq::gather(
4564         alloc,
4565         vec![
4566             InstrSeq::from((alloc, emit_class_expr(e, env, cexpr, prop)?)),
4567             match query_op {
4568                 QueryOp::CGet => instr::cgets(alloc, readonly_op),
4569                 QueryOp::Isset => instr::issets(alloc),
4570                 QueryOp::CGetQuiet => {
4571                     return Err(Unrecoverable("emit_class_get: CGetQuiet".into()));
4572                 }
4573                 QueryOp::InOut => return Err(Unrecoverable("emit_class_get: InOut".into())),
4574             },
4575         ],
4576     ))
4579 fn emit_conditional_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4580     e: &mut Emitter<'arena, 'decl, D>,
4581     env: &Env<'a, 'arena>,
4582     pos: &Pos,
4583     etest: &ast::Expr,
4584     etrue: &Option<ast::Expr>,
4585     efalse: &ast::Expr,
4586 ) -> Result<InstrSeq<'arena>> {
4587     let alloc = env.arena;
4588     Ok(match etrue.as_ref() {
4589         Some(etrue) => {
4590             let false_label = e.label_gen_mut().next_regular();
4591             let end_label = e.label_gen_mut().next_regular();
4592             let r = emit_jmpz(e, env, etest, false_label)?;
4593             // only emit false branch if false_label is used
4594             let false_branch = if r.is_label_used {
4595                 InstrSeq::gather(
4596                     alloc,
4597                     vec![instr::label(alloc, false_label), emit_expr(e, env, efalse)?],
4598                 )
4599             } else {
4600                 instr::empty(alloc)
4601             };
4602             // only emit true branch if there is fallthrough from condition
4603             let true_branch = if r.is_fallthrough {
4604                 InstrSeq::gather(
4605                     alloc,
4606                     vec![
4607                         emit_expr(e, env, etrue)?,
4608                         emit_pos(alloc, pos),
4609                         instr::jmp(alloc, end_label),
4610                     ],
4611                 )
4612             } else {
4613                 instr::empty(alloc)
4614             };
4615             InstrSeq::gather(
4616                 alloc,
4617                 vec![
4618                     r.instrs,
4619                     true_branch,
4620                     false_branch,
4621                     // end_label is used to jump out of true branch so they should be emitted together
4622                     if r.is_fallthrough {
4623                         instr::label(alloc, end_label)
4624                     } else {
4625                         instr::empty(alloc)
4626                     },
4627                 ],
4628             )
4629         }
4630         None => {
4631             let end_label = e.label_gen_mut().next_regular();
4632             let efalse_instr = emit_expr(e, env, efalse)?;
4633             let etest_instr = emit_expr(e, env, etest)?;
4634             InstrSeq::gather(
4635                 alloc,
4636                 vec![
4637                     etest_instr,
4638                     instr::dup(alloc),
4639                     instr::jmpnz(alloc, end_label),
4640                     instr::popc(alloc),
4641                     efalse_instr,
4642                     instr::label(alloc, end_label),
4643                 ],
4644             )
4645         }
4646     })
4649 fn emit_local<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4650     e: &mut Emitter<'arena, 'decl, D>,
4651     env: &Env<'a, 'arena>,
4652     notice: BareThisOp,
4653     lid: &aast_defs::Lid,
4654 ) -> Result<InstrSeq<'arena>> {
4655     let alloc = env.arena;
4656     let ast::Lid(pos, id) = lid;
4657     let id_name = local_id::get_name(id);
4658     if superglobals::is_superglobal(id_name) {
4659         Ok(InstrSeq::gather(
4660             alloc,
4661             vec![
4662                 instr::string(alloc, string_utils::locals::strip_dollar(id_name)),
4663                 emit_pos(alloc, pos),
4664                 instr::cgetg(alloc),
4665             ],
4666         ))
4667     } else {
4668         let local = get_local(e, env, pos, id_name)?;
4669         Ok(
4670             if is_local_this(env, id) && !env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS) {
4671                 emit_pos_then(alloc, pos, instr::barethis(alloc, notice))
4672             } else {
4673                 instr::cgetl(alloc, local)
4674             },
4675         )
4676     }
4679 fn emit_class_const<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4680     e: &mut Emitter<'arena, 'decl, D>,
4681     env: &Env<'a, 'arena>,
4682     pos: &Pos,
4683     cid: &ast::ClassId,
4684     id: &ast_defs::Pstring,
4685 ) -> Result<InstrSeq<'arena>> {
4686     let alloc = env.arena;
4687     let mut cexpr = ClassExpr::class_id_to_class_expr(e, false, true, &env.scope, cid);
4688     if let ClassExpr::Id(ast_defs::Id(_, name)) = &cexpr {
4689         if let Some(reified_var_cexpr) = get_reified_var_cexpr::<D>(env, pos, &name)? {
4690             cexpr = reified_var_cexpr;
4691         }
4692     }
4693     match cexpr {
4694         ClassExpr::Id(ast_defs::Id(pos, name)) => {
4695             let cid = class::ClassType::from_ast_name_and_mangle(alloc, &name);
4696             let cname = cid.to_raw_string();
4697             Ok(if string_utils::is_class(&id.1) {
4698                 if e.options().emit_class_pointers() == 1 {
4699                     emit_pos_then(alloc, &pos, instr::resolveclass(alloc, cid))
4700                 } else if e.options().emit_class_pointers() == 2 {
4701                     emit_pos_then(alloc, &pos, instr::lazyclass(alloc, cid))
4702                 } else {
4703                     emit_pos_then(alloc, &pos, instr::string(alloc, cname))
4704                 }
4705             } else {
4706                 emit_symbol_refs::add_class(alloc, e, cid.clone());
4707                 // TODO(hrust) enabel `let const_id = r#const::ConstType::from_ast_name(&id.1);`,
4708                 // `from_ast_name` should be able to accpet Cow<str>
4709                 let const_id: r#const::ConstType =
4710                     (alloc, string_utils::strip_global_ns(&id.1)).into();
4711                 emit_pos_then(alloc, &pos, instr::clscnsd(alloc, const_id, cid))
4712             })
4713         }
4714         _ => {
4715             let load_const = if string_utils::is_class(&id.1) {
4716                 if e.options().emit_class_pointers() == 2 {
4717                     instr::lazyclassfromclass(alloc)
4718                 } else {
4719                     instr::classname(alloc)
4720                 }
4721             } else {
4722                 // TODO(hrust) enabel `let const_id = r#const::ConstType::from_ast_name(&id.1);`,
4723                 // `from_ast_name` should be able to accpet Cow<str>
4724                 let const_id: r#const::ConstType =
4725                     (alloc, string_utils::strip_global_ns(&id.1)).into();
4726                 instr::clscns(alloc, const_id)
4727             };
4728             if string_utils::is_class(&id.1) && e.options().emit_class_pointers() == 1 {
4729                 emit_load_class_ref(e, env, pos, cexpr)
4730             } else {
4731                 Ok(InstrSeq::gather(
4732                     alloc,
4733                     vec![emit_load_class_ref(e, env, pos, cexpr)?, load_const],
4734                 ))
4735             }
4736         }
4737     }
4740 fn emit_unop<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4741     e: &mut Emitter<'arena, 'decl, D>,
4742     env: &Env<'a, 'arena>,
4743     pos: &Pos,
4744     (uop, expr): &(ast_defs::Uop, ast::Expr),
4745 ) -> Result<InstrSeq<'arena>> {
4746     use ast_defs::Uop as U;
4747     let alloc = env.arena;
4748     match uop {
4749         U::Utild | U::Unot => Ok(InstrSeq::gather(
4750             alloc,
4751             vec![
4752                 emit_expr(e, env, expr)?,
4753                 emit_pos_then(alloc, pos, from_unop(alloc, e.options(), uop)?),
4754             ],
4755         )),
4756         U::Uplus | U::Uminus => Ok(InstrSeq::gather(
4757             alloc,
4758             vec![
4759                 emit_pos(alloc, pos),
4760                 instr::int(alloc, 0),
4761                 emit_expr(e, env, expr)?,
4762                 emit_pos_then(alloc, pos, from_unop(alloc, e.options(), uop)?),
4763             ],
4764         )),
4765         U::Uincr | U::Udecr | U::Upincr | U::Updecr => emit_lval_op(
4766             e,
4767             env,
4768             pos,
4769             LValOp::IncDec(unop_to_incdec_op(e.options(), uop)?),
4770             expr,
4771             None,
4772             false,
4773         ),
4774         U::Usilence => e.local_scope(|e| {
4775             let temp_local = e.local_gen_mut().get_unnamed();
4776             Ok(InstrSeq::gather(
4777                 alloc,
4778                 vec![
4779                     emit_pos(alloc, pos),
4780                     instr::silence_start(alloc, temp_local),
4781                     {
4782                         let try_instrs = emit_expr(e, env, expr)?;
4783                         let catch_instrs = InstrSeq::gather(
4784                             alloc,
4785                             vec![emit_pos(alloc, pos), instr::silence_end(alloc, temp_local)],
4786                         );
4787                         InstrSeq::create_try_catch(
4788                             alloc,
4789                             e.label_gen_mut(),
4790                             None,
4791                             false, /* skip_throw */
4792                             try_instrs,
4793                             catch_instrs,
4794                         )
4795                     },
4796                     emit_pos(alloc, pos),
4797                     instr::silence_end(alloc, temp_local),
4798                 ],
4799             ))
4800         }),
4801     }
4804 fn unop_to_incdec_op(opts: &Options, op: &ast_defs::Uop) -> Result<IncdecOp> {
4805     let if_check_or = |op1, op2| Ok(if opts.check_int_overflow() { op1 } else { op2 });
4806     use {ast_defs::Uop as U, IncdecOp as I};
4807     match op {
4808         U::Uincr => if_check_or(I::PreIncO, I::PreInc),
4809         U::Udecr => if_check_or(I::PreDecO, I::PreDec),
4810         U::Upincr => if_check_or(I::PostIncO, I::PostInc),
4811         U::Updecr => if_check_or(I::PostDecO, I::PostDec),
4812         _ => Err(Unrecoverable("invalid incdec op".into())),
4813     }
4816 fn from_unop<'arena>(
4817     alloc: &'arena bumpalo::Bump,
4818     opts: &Options,
4819     op: &ast_defs::Uop,
4820 ) -> Result<InstrSeq<'arena>> {
4821     use ast_defs::Uop as U;
4822     Ok(match op {
4823         U::Utild => instr::bitnot(alloc),
4824         U::Unot => instr::not(alloc),
4825         U::Uplus => {
4826             if opts.check_int_overflow() {
4827                 instr::addo(alloc)
4828             } else {
4829                 instr::add(alloc)
4830             }
4831         }
4832         U::Uminus => {
4833             if opts.check_int_overflow() {
4834                 instr::subo(alloc)
4835             } else {
4836                 instr::sub(alloc)
4837             }
4838         }
4839         _ => {
4840             return Err(Unrecoverable(
4841                 "this unary operation cannot be translated".into(),
4842             ));
4843         }
4844     })
4847 fn binop_to_eqop(opts: &Options, op: &ast_defs::Bop) -> Option<EqOp> {
4848     use {ast_defs::Bop as B, EqOp::*};
4849     match op {
4850         B::Plus => Some(if opts.check_int_overflow() {
4851             PlusEqualO
4852         } else {
4853             PlusEqual
4854         }),
4855         B::Minus => Some(if opts.check_int_overflow() {
4856             MinusEqualO
4857         } else {
4858             MinusEqual
4859         }),
4860         B::Star => Some(if opts.check_int_overflow() {
4861             MulEqualO
4862         } else {
4863             MulEqual
4864         }),
4865         B::Slash => Some(DivEqual),
4866         B::Starstar => Some(PowEqual),
4867         B::Amp => Some(AndEqual),
4868         B::Bar => Some(OrEqual),
4869         B::Xor => Some(XorEqual),
4870         B::Ltlt => Some(SlEqual),
4871         B::Gtgt => Some(SrEqual),
4872         B::Percent => Some(ModEqual),
4873         B::Dot => Some(ConcatEqual),
4874         _ => None,
4875     }
4878 #[allow(clippy::needless_lifetimes)]
4879 fn optimize_null_checks<'arena, 'decl, D: DeclProvider<'decl>>(
4880     e: &Emitter<'arena, 'decl, D>,
4881 ) -> bool {
4882     e.options()
4883         .hack_compiler_flags
4884         .contains(CompilerFlags::OPTIMIZE_NULL_CHECKS)
4887 fn from_binop<'arena>(
4888     alloc: &'arena bumpalo::Bump,
4889     opts: &Options,
4890     op: &ast_defs::Bop,
4891 ) -> Result<InstrSeq<'arena>> {
4892     use ast_defs::Bop as B;
4893     Ok(match op {
4894         B::Plus => {
4895             if opts.check_int_overflow() {
4896                 instr::addo(alloc)
4897             } else {
4898                 instr::add(alloc)
4899             }
4900         }
4901         B::Minus => {
4902             if opts.check_int_overflow() {
4903                 instr::subo(alloc)
4904             } else {
4905                 instr::sub(alloc)
4906             }
4907         }
4908         B::Star => {
4909             if opts.check_int_overflow() {
4910                 instr::mulo(alloc)
4911             } else {
4912                 instr::mul(alloc)
4913             }
4914         }
4915         B::Slash => instr::div(alloc),
4916         B::Eqeq => instr::eq(alloc),
4917         B::Eqeqeq => instr::same(alloc),
4918         B::Starstar => instr::pow(alloc),
4919         B::Diff => instr::neq(alloc),
4920         B::Diff2 => instr::nsame(alloc),
4921         B::Lt => instr::lt(alloc),
4922         B::Lte => instr::lte(alloc),
4923         B::Gt => instr::gt(alloc),
4924         B::Gte => instr::gte(alloc),
4925         B::Dot => instr::concat(alloc),
4926         B::Amp => instr::bitand(alloc),
4927         B::Bar => instr::bitor(alloc),
4928         B::Ltlt => instr::shl(alloc),
4929         B::Gtgt => instr::shr(alloc),
4930         B::Cmp => instr::cmp(alloc),
4931         B::Percent => instr::mod_(alloc),
4932         B::Xor => instr::bitxor(alloc),
4933         B::Eq(_) => return Err(Unrecoverable("assignment is emitted differently".into())),
4934         B::QuestionQuestion => {
4935             return Err(Unrecoverable(
4936                 "null coalescence is emitted differently".into(),
4937             ));
4938         }
4939         B::Barbar | B::Ampamp => {
4940             return Err(Unrecoverable(
4941                 "short-circuiting operator cannot be generated as a simple binop".into(),
4942             ));
4943         }
4944     })
4947 fn emit_first_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4948     e: &mut Emitter<'arena, 'decl, D>,
4949     env: &Env<'a, 'arena>,
4950     expr: &ast::Expr,
4951 ) -> Result<(InstrSeq<'arena>, bool)> {
4952     let alloc = env.arena;
4953     Ok(match &expr.2 {
4954         ast::Expr_::Lvar(l)
4955             if !((is_local_this(env, &l.1) && !env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS))
4956                 || superglobals::is_any_global(local_id::get_name(&l.1))) =>
4957         {
4958             (
4959                 instr::cgetl2(alloc, get_local(e, env, &l.0, local_id::get_name(&l.1))?),
4960                 true,
4961             )
4962         }
4963         _ => (emit_expr(e, env, expr)?, false),
4964     })
4967 pub fn emit_two_exprs<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4968     e: &mut Emitter<'arena, 'decl, D>,
4969     env: &Env<'a, 'arena>,
4970     outer_pos: &Pos,
4971     e1: &ast::Expr,
4972     e2: &ast::Expr,
4973 ) -> Result<InstrSeq<'arena>> {
4974     let alloc = env.arena;
4975     let (instrs1, is_under_top) = emit_first_expr(e, env, e1)?;
4976     let instrs2 = emit_expr(e, env, e2)?;
4977     let instrs2_is_var = e2.2.is_lvar();
4978     Ok(InstrSeq::gather(
4979         alloc,
4980         if is_under_top {
4981             if instrs2_is_var {
4982                 vec![emit_pos(alloc, outer_pos), instrs2, instrs1]
4983             } else {
4984                 vec![instrs2, emit_pos(alloc, outer_pos), instrs1]
4985             }
4986         } else if instrs2_is_var {
4987             vec![instrs1, emit_pos(alloc, outer_pos), instrs2]
4988         } else {
4989             vec![instrs1, instrs2, emit_pos(alloc, outer_pos)]
4990         },
4991     ))
4994 fn emit_readonly_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4995     e: &mut Emitter<'arena, 'decl, D>,
4996     env: &Env<'a, 'arena>,
4997     pos: &Pos,
4998     expr: &aast::Expr<(), ()>,
4999 ) -> Result<InstrSeq<'arena>> {
5000     match &expr.2 {
5001         aast::Expr_::ObjGet(x) => {
5002             Ok(emit_obj_get(e, env, pos, QueryOp::CGet, &x.0, &x.1, &x.2, false, true)?.0)
5003         }
5004         aast::Expr_::Call(c) => emit_call_expr(e, env, pos, None, true, c),
5005         aast::Expr_::ClassGet(x) => {
5006             emit_class_get(e, env, QueryOp::CGet, &x.0, &x.1, ReadonlyOp::Any)
5007         }
5008         _ => emit_expr(e, env, expr),
5009     }
5012 fn emit_quiet_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5013     e: &mut Emitter<'arena, 'decl, D>,
5014     env: &Env<'a, 'arena>,
5015     pos: &Pos,
5016     expr: &ast::Expr,
5017     null_coalesce_assignment: bool,
5018 ) -> Result<(InstrSeq<'arena>, Option<NumParams>)> {
5019     let alloc = env.arena;
5020     match &expr.2 {
5021         ast::Expr_::Lvar(lid) if !is_local_this(env, &lid.1) => Ok((
5022             instr::cgetquietl(alloc, get_local(e, env, pos, local_id::get_name(&lid.1))?),
5023             None,
5024         )),
5025         ast::Expr_::ArrayGet(x) => emit_array_get(
5026             e,
5027             env,
5028             pos,
5029             None,
5030             QueryOp::CGetQuiet,
5031             &x.0,
5032             x.1.as_ref(),
5033             false,
5034             null_coalesce_assignment,
5035         ),
5036         ast::Expr_::ObjGet(x) => {
5037             if x.as_ref().3 {
5038                 Ok((emit_expr(e, env, expr)?, None))
5039             } else {
5040                 emit_obj_get(
5041                     e,
5042                     env,
5043                     pos,
5044                     QueryOp::CGetQuiet,
5045                     &x.0,
5046                     &x.1,
5047                     &x.2,
5048                     null_coalesce_assignment,
5049                     false,
5050                 )
5051             }
5052         }
5053         _ => Ok((emit_expr(e, env, expr)?, None)),
5054     }
5057 fn emit_null_coalesce_assignment<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5058     e: &mut Emitter<'arena, 'decl, D>,
5059     env: &Env<'a, 'arena>,
5060     pos: &Pos,
5061     e1: &ast::Expr,
5062     e2: &ast::Expr,
5063 ) -> Result<InstrSeq<'arena>> {
5064     let alloc = env.arena;
5065     let end_label = e.label_gen_mut().next_regular();
5066     let do_set_label = e.label_gen_mut().next_regular();
5067     let l_nonnull = e.local_gen_mut().get_unnamed();
5068     let (quiet_instr, querym_n_unpopped) = emit_quiet_expr(e, env, pos, e1, true)?;
5069     let emit_popc_n = |n_unpopped| match n_unpopped {
5070         Some(n) => InstrSeq::gather(
5071             alloc,
5072             iter::repeat_with(|| instr::popc(alloc))
5073                 .take(n)
5074                 .collect::<Vec<_>>(),
5075         ),
5076         None => instr::empty(alloc),
5077     };
5078     Ok(InstrSeq::gather(
5079         alloc,
5080         vec![
5081             quiet_instr,
5082             instr::dup(alloc),
5083             instr::istypec(alloc, IstypeOp::OpNull),
5084             instr::jmpnz(alloc, do_set_label),
5085             instr::popl(alloc, l_nonnull),
5086             emit_popc_n(querym_n_unpopped),
5087             instr::pushl(alloc, l_nonnull),
5088             instr::jmp(alloc, end_label),
5089             instr::label(alloc, do_set_label),
5090             instr::popc(alloc),
5091             emit_lval_op(e, env, pos, LValOp::Set, e1, Some(e2), true)?,
5092             instr::label(alloc, end_label),
5093         ],
5094     ))
5097 fn emit_short_circuit_op<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5098     e: &mut Emitter<'arena, 'decl, D>,
5099     env: &Env<'a, 'arena>,
5100     pos: &Pos,
5101     expr: &ast::Expr,
5102 ) -> Result<InstrSeq<'arena>> {
5103     let alloc = env.arena;
5104     let its_true = e.label_gen_mut().next_regular();
5105     let its_done = e.label_gen_mut().next_regular();
5106     let jmp_instrs = emit_jmpnz(e, env, expr, its_true)?;
5107     Ok(if jmp_instrs.is_fallthrough {
5108         InstrSeq::gather(
5109             alloc,
5110             vec![
5111                 jmp_instrs.instrs,
5112                 emit_pos(alloc, pos),
5113                 instr::false_(alloc),
5114                 instr::jmp(alloc, its_done),
5115                 if jmp_instrs.is_label_used {
5116                     InstrSeq::gather(
5117                         alloc,
5118                         vec![
5119                             instr::label(alloc, its_true),
5120                             emit_pos(alloc, pos),
5121                             instr::true_(alloc),
5122                         ],
5123                     )
5124                 } else {
5125                     instr::empty(alloc)
5126                 },
5127                 instr::label(alloc, its_done),
5128             ],
5129         )
5130     } else {
5131         InstrSeq::gather(
5132             alloc,
5133             vec![
5134                 jmp_instrs.instrs,
5135                 if jmp_instrs.is_label_used {
5136                     InstrSeq::gather(
5137                         alloc,
5138                         vec![
5139                             instr::label(alloc, its_true),
5140                             emit_pos(alloc, pos),
5141                             instr::true_(alloc),
5142                         ],
5143                     )
5144                 } else {
5145                     instr::empty(alloc)
5146                 },
5147             ],
5148         )
5149     })
5152 fn emit_binop<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5153     e: &mut Emitter<'arena, 'decl, D>,
5154     env: &Env<'a, 'arena>,
5155     pos: &Pos,
5156     expr: &ast::Expr,
5157 ) -> Result<InstrSeq<'arena>> {
5158     let alloc = env.arena;
5159     let (op, e1, e2) = expr.2.as_binop().unwrap();
5160     use ast_defs::Bop as B;
5161     match op {
5162         B::Ampamp | B::Barbar => emit_short_circuit_op(e, env, pos, &expr),
5163         B::Eq(None) => emit_lval_op(e, env, pos, LValOp::Set, e1, Some(e2), false),
5164         B::Eq(Some(eop)) if eop.is_question_question() => {
5165             emit_null_coalesce_assignment(e, env, pos, e1, e2)
5166         }
5167         B::Eq(Some(eop)) => match binop_to_eqop(e.options(), eop) {
5168             None => Err(Unrecoverable("illegal eq op".into())),
5169             Some(op) => emit_lval_op(e, env, pos, LValOp::SetOp(op), e1, Some(e2), false),
5170         },
5171         B::QuestionQuestion => {
5172             let end_label = e.label_gen_mut().next_regular();
5173             let rhs = emit_expr(e, env, e2)?;
5174             Ok(InstrSeq::gather(
5175                 alloc,
5176                 vec![
5177                     emit_quiet_expr(e, env, pos, e1, false)?.0,
5178                     instr::dup(alloc),
5179                     instr::istypec(alloc, IstypeOp::OpNull),
5180                     instr::not(alloc),
5181                     instr::jmpnz(alloc, end_label),
5182                     instr::popc(alloc),
5183                     rhs,
5184                     instr::label(alloc, end_label),
5185                 ],
5186             ))
5187         }
5188         _ => {
5189             let default = |e: &mut Emitter<'arena, 'decl, D>| {
5190                 Ok(InstrSeq::gather(
5191                     alloc,
5192                     vec![
5193                         emit_two_exprs(e, env, pos, e1, e2)?,
5194                         from_binop(alloc, e.options(), op)?,
5195                     ],
5196                 ))
5197             };
5198             if optimize_null_checks(e) {
5199                 match op {
5200                     B::Eqeqeq if e2.2.is_null() => emit_is_null(e, env, e1),
5201                     B::Eqeqeq if e1.2.is_null() => emit_is_null(e, env, e2),
5202                     B::Diff2 if e2.2.is_null() => Ok(InstrSeq::gather(
5203                         alloc,
5204                         vec![emit_is_null(e, env, e1)?, instr::not(alloc)],
5205                     )),
5206                     B::Diff2 if e1.2.is_null() => Ok(InstrSeq::gather(
5207                         alloc,
5208                         vec![emit_is_null(e, env, e2)?, instr::not(alloc)],
5209                     )),
5210                     _ => default(e),
5211                 }
5212             } else {
5213                 default(e)
5214             }
5215         }
5216     }
5219 fn emit_pipe<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5220     e: &mut Emitter<'arena, 'decl, D>,
5221     env: &Env<'a, 'arena>,
5222     (_, e1, e2): &(aast_defs::Lid, ast::Expr, ast::Expr),
5223 ) -> Result<InstrSeq<'arena>> {
5224     let alloc = env.arena;
5225     let lhs_instrs = emit_expr(e, env, e1)?;
5226     scope::with_unnamed_local(alloc, e, |alloc, e, local| {
5227         // TODO(hrust) avoid cloning env
5228         let mut pipe_env = env.clone();
5229         pipe_env.with_pipe_var(local);
5230         let rhs_instrs = emit_expr(e, &pipe_env, e2)?;
5231         Ok((
5232             InstrSeq::gather(alloc, vec![lhs_instrs, instr::popl(alloc, local)]),
5233             rhs_instrs,
5234             instr::unsetl(alloc, local),
5235         ))
5236     })
5239 fn emit_as<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5240     e: &mut Emitter<'arena, 'decl, D>,
5241     env: &Env<'a, 'arena>,
5242     pos: &Pos,
5243     (expr, h, is_nullable): &(ast::Expr, aast_defs::Hint, bool),
5244 ) -> Result<InstrSeq<'arena>> {
5245     let alloc = env.arena;
5246     e.local_scope(|e| {
5247         let arg_local = e.local_gen_mut().get_unnamed();
5248         let type_struct_local = e.local_gen_mut().get_unnamed();
5249         let (ts_instrs, is_static) = emit_reified_arg(e, env, pos, true, h)?;
5250         let then_label = e.label_gen_mut().next_regular();
5251         let done_label = e.label_gen_mut().next_regular();
5252         let main_block = |ts_instrs, resolve| {
5253             InstrSeq::gather(
5254                 alloc,
5255                 vec![
5256                     ts_instrs,
5257                     instr::setl(alloc, type_struct_local),
5258                     match resolve {
5259                         TypestructResolveOp::Resolve => instr::is_type_structc_resolve(alloc),
5260                         TypestructResolveOp::DontResolve => {
5261                             instr::is_type_structc_dontresolve(alloc)
5262                         }
5263                     },
5264                     instr::jmpnz(alloc, then_label),
5265                     if *is_nullable {
5266                         InstrSeq::gather(
5267                             alloc,
5268                             vec![instr::null(alloc), instr::jmp(alloc, done_label)],
5269                         )
5270                     } else {
5271                         InstrSeq::gather(
5272                             alloc,
5273                             vec![
5274                                 instr::pushl(alloc, arg_local),
5275                                 instr::pushl(alloc, type_struct_local),
5276                                 instr::throwastypestructexception(alloc),
5277                             ],
5278                         )
5279                     },
5280                 ],
5281             )
5282         };
5283         let i2 = if is_static {
5284             main_block(
5285                 get_type_structure_for_hint(alloc, e, &[], &IndexSet::new(), h)?,
5286                 TypestructResolveOp::Resolve,
5287             )
5288         } else {
5289             main_block(ts_instrs, TypestructResolveOp::DontResolve)
5290         };
5291         let i1 = emit_expr(e, env, expr)?;
5292         Ok(InstrSeq::gather(
5293             alloc,
5294             vec![
5295                 i1,
5296                 instr::setl(alloc, arg_local),
5297                 i2,
5298                 instr::label(alloc, then_label),
5299                 instr::pushl(alloc, arg_local),
5300                 instr::unsetl(alloc, type_struct_local),
5301                 instr::label(alloc, done_label),
5302             ],
5303         ))
5304     })
5307 fn emit_cast<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5308     e: &mut Emitter<'arena, 'decl, D>,
5309     env: &Env<'a, 'arena>,
5310     pos: &Pos,
5311     hint: &aast_defs::Hint_,
5312     expr: &ast::Expr,
5313 ) -> Result<InstrSeq<'arena>> {
5314     let alloc = env.arena;
5315     use aast_defs::Hint_ as H_;
5316     let op = match hint {
5317         H_::Happly(ast_defs::Id(_, id), hints) if hints.is_empty() => {
5318             let id = string_utils::strip_ns(id);
5319             match string_utils::strip_hh_ns(&id).as_ref() {
5320                 typehints::INT => instr::cast_int(alloc),
5321                 typehints::BOOL => instr::cast_bool(alloc),
5322                 typehints::STRING => instr::cast_string(alloc),
5323                 typehints::FLOAT => instr::cast_double(alloc),
5324                 _ => {
5325                     return Err(emit_fatal::raise_fatal_parse(
5326                         pos,
5327                         format!("Invalid cast type: {}", id),
5328                     ));
5329                 }
5330             }
5331         }
5332         _ => return Err(emit_fatal::raise_fatal_parse(pos, "Invalid cast type")),
5333     };
5334     Ok(InstrSeq::gather(
5335         alloc,
5336         vec![emit_expr(e, env, expr)?, emit_pos(alloc, pos), op],
5337     ))
5340 pub fn emit_unset_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5341     e: &mut Emitter<'arena, 'decl, D>,
5342     env: &Env<'a, 'arena>,
5343     expr: &ast::Expr,
5344 ) -> Result<InstrSeq<'arena>> {
5345     let alloc = env.arena;
5346     emit_lval_op_nonlist(
5347         e,
5348         env,
5349         &expr.1,
5350         LValOp::Unset,
5351         expr,
5352         instr::empty(alloc),
5353         0,
5354         false,
5355         false,
5356     )
5359 pub fn emit_set_range_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5360     e: &mut Emitter<'arena, 'decl, D>,
5361     env: &mut Env<'a, 'arena>,
5362     pos: &Pos,
5363     name: &str,
5364     kind: Setrange,
5365     args: &[&ast::Expr],
5366 ) -> Result<InstrSeq<'arena>> {
5367     let alloc = env.arena;
5368     let raise_fatal = |msg: &str| {
5369         Err(emit_fatal::raise_fatal_parse(
5370             pos,
5371             format!("{} {}", name, msg),
5372         ))
5373     };
5375     let (base, offset, src, args) = if args.len() >= 3 {
5376         (&args[0], &args[1], &args[2], &args[3..])
5377     } else {
5378         return raise_fatal("expects at least 3 arguments");
5379     };
5381     let count_instrs = match (args, kind.vec) {
5382         ([c], true) => emit_expr(e, env, c)?,
5383         ([], _) => instr::int(alloc, -1),
5384         (_, false) => return raise_fatal("expects no more than 3 arguments"),
5385         (_, true) => return raise_fatal("expects no more than 4 arguments"),
5386     };
5388     let (base_expr, cls_expr, base_setup, base_stack, cls_stack) = emit_base(
5389         e,
5390         env,
5391         base,
5392         MemberOpMode::Define,
5393         false, /* is_object */
5394         BareThisOp::Notice,
5395         false,           /*null_coalesce_assignment*/
5396         3,               /* base_offset */
5397         3,               /* rhs_stack_size */
5398         ReadonlyOp::Any, /* readonly_op */
5399     )?;
5400     Ok(InstrSeq::gather(
5401         alloc,
5402         vec![
5403             base_expr,
5404             cls_expr,
5405             emit_expr(e, env, offset)?,
5406             emit_expr(e, env, src)?,
5407             count_instrs,
5408             base_setup,
5409             instr::instr(
5410                 alloc,
5411                 Instruct::IFinal(InstructFinal::SetRangeM(
5412                     (base_stack + cls_stack)
5413                         .try_into()
5414                         .expect("StackIndex overflow"),
5415                     kind.size.try_into().expect("Setrange size overflow"),
5416                     kind.op,
5417                 )),
5418             ),
5419         ],
5420     ))
5423 pub fn is_reified_tparam<'a, 'arena>(
5424     env: &Env<'a, 'arena>,
5425     is_fun: bool,
5426     name: &str,
5427 ) -> Option<(usize, bool)> {
5428     let is = |tparams: &[ast::Tparam]| {
5429         let is_soft = |ual: &Vec<ast::UserAttribute>| {
5430             ual.iter().any(|ua| user_attributes::is_soft(&ua.name.1))
5431         };
5432         use ast::ReifyKind::*;
5433         tparams.iter().enumerate().find_map(|(i, tp)| {
5434             if (tp.reified == Reified || tp.reified == SoftReified) && tp.name.1 == name {
5435                 Some((i, is_soft(&tp.user_attributes)))
5436             } else {
5437                 None
5438             }
5439         })
5440     };
5441     if is_fun {
5442         is(env.scope.get_fun_tparams())
5443     } else {
5444         is(&env.scope.get_class_tparams()[..])
5445     }
5448 /// Emit code for a base expression `expr` that forms part of
5449 /// an element access `expr[elem]` or field access `expr->fld`.
5450 /// The instructions are divided into three sections:
5451 ///   1. base and element/property expression instructions:
5452 ///      push non-trivial base and key values on the stack
5453 ///   2. class instructions: emitted when the base is a static property access.
5454 ///      A sequence of instructions that pushes the property and the class on the
5455 ///      stack to be consumed by a BaseSC. (Foo::$bar)
5456 ///   3. base selector instructions: a sequence of Base/Dim instructions that
5457 ///      actually constructs the base address from "member keys" that are inlined
5458 ///      in the instructions, or pulled from the key values that
5459 ///      were pushed on the stack in section 1.
5460 ///   4. (constructed by the caller) a final accessor e.g. QueryM or setter
5461 ///      e.g. SetOpM instruction that has the final key inlined in the
5462 ///      instruction, or pulled from the key values that were pushed on the
5463 ///      stack in section 1.
5465 /// The function returns a 5-tuple:
5466 /// (base_instrs, cls_instrs, base_setup_instrs, base_stack_size, cls_stack_size)
5467 /// where base_instrs is section 1 above, cls_instrs is section 2, base_setup_instrs
5468 /// is section 3, stack_size is the number of values pushed on the stack by
5469 /// section 1, and cls_stack_size is the number of values pushed on the stack by
5470 /// section 2.
5472 /// For example, the r-value expression $arr[3][$ix+2]
5473 /// will compile to
5474 ///   # Section 1, pushing the value of $ix+2 on the stack
5475 ///   Int 2
5476 ///   CGetL2 $ix
5477 ///   AddO
5478 ///   # Section 2, constructing the base address of $arr[3]
5479 ///   BaseL $arr Warn
5480 ///   Dim Warn EI:3
5481 ///   # Section 3, indexing the array using the value at stack position 0 (EC:0)
5482 ///   QueryM 1 CGet EC:0
5484 fn emit_base<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5485     e: &mut Emitter<'arena, 'decl, D>,
5486     env: &Env<'a, 'arena>,
5487     expr: &ast::Expr,
5488     mode: MemberOpMode,
5489     is_object: bool,
5490     notice: BareThisOp,
5491     null_coalesce_assignment: bool,
5492     base_offset: StackIndex,
5493     rhs_stack_size: StackIndex,
5494     readonly_enforcement: ReadonlyOp, // this value depends on where we are emitting the base
5495 ) -> Result<(
5496     InstrSeq<'arena>,
5497     InstrSeq<'arena>,
5498     InstrSeq<'arena>,
5499     StackIndex,
5500     StackIndex,
5501 )> {
5502     let result = emit_base_(
5503         e,
5504         env,
5505         expr,
5506         mode,
5507         is_object,
5508         notice,
5509         null_coalesce_assignment,
5510         base_offset,
5511         rhs_stack_size,
5512         None,
5513         readonly_enforcement,
5514     )?;
5515     match result {
5516         ArrayGetBase::Regular(i) => Ok((
5517             i.base_instrs,
5518             i.cls_instrs,
5519             i.setup_instrs,
5520             i.base_stack_size as isize,
5521             i.cls_stack_size as isize,
5522         )),
5523         ArrayGetBase::Inout { .. } => Err(unrecoverable("unexpected input")),
5524     }
5527 fn is_trivial(env: &Env, is_base: bool, expr: &ast::Expr) -> bool {
5528     use ast::Expr_ as E_;
5529     match &expr.2 {
5530         E_::Int(_) | E_::String(_) => true,
5531         E_::Lvar(x) => !is_local_this(env, &x.1) || env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS),
5532         E_::ArrayGet(_) if !is_base => false,
5533         E_::ArrayGet(x) => {
5534             is_trivial(env, is_base, &x.0)
5535                 && (x.1)
5536                     .as_ref()
5537                     .map_or(true, |e| is_trivial(env, is_base, &e))
5538         }
5539         _ => false,
5540     }
5543 fn get_local_temp_kind<'a, 'arena>(
5544     env: &Env<'a, 'arena>,
5545     is_base: bool,
5546     inout_param_info: Option<(usize, &inout_locals::AliasInfoMap)>,
5547     expr: Option<&ast::Expr>,
5548 ) -> Option<StoredValueKind> {
5549     match (expr, inout_param_info) {
5550         (_, None) => None,
5551         (Some(ast::Expr(_, _, ast::Expr_::Lvar(id))), Some((i, aliases)))
5552             if inout_locals::should_save_local_value(id.name(), i, aliases) =>
5553         {
5554             Some(StoredValueKind::Local)
5555         }
5556         (Some(e), _) => {
5557             if is_trivial(env, is_base, e) {
5558                 None
5559             } else {
5560                 Some(StoredValueKind::Expr)
5561             }
5562         }
5563         (None, _) => None,
5564     }
5567 fn emit_base_<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5568     e: &mut Emitter<'arena, 'decl, D>,
5569     env: &Env<'a, 'arena>,
5570     expr: &ast::Expr,
5571     mode: MemberOpMode,
5572     is_object: bool,
5573     notice: BareThisOp,
5574     null_coalesce_assignment: bool,
5575     base_offset: StackIndex,
5576     rhs_stack_size: StackIndex,
5577     inout_param_info: Option<(usize, &inout_locals::AliasInfoMap)>,
5578     readonly_op: ReadonlyOp,
5579 ) -> Result<ArrayGetBase<'arena>> {
5580     let alloc = env.arena;
5581     let pos = &expr.1;
5582     let expr_ = &expr.2;
5583     let base_mode = if mode == MemberOpMode::InOut {
5584         MemberOpMode::Warn
5585     } else {
5586         mode
5587     };
5588     let local_temp_kind = get_local_temp_kind(env, true, inout_param_info, Some(expr));
5589     let emit_default = |
5590         e: &mut Emitter<'arena, 'decl, D>,
5591         base_instrs,
5592         cls_instrs,
5593         setup_instrs,
5594         base_stack_size,
5595         cls_stack_size,
5596     | {
5597         match local_temp_kind {
5598             Some(local_temp) => {
5599                 let local = e.local_gen_mut().get_unnamed();
5600                 ArrayGetBase::Inout {
5601                     load: ArrayGetBaseData {
5602                         base_instrs: vec![(base_instrs, Some((local, local_temp)))],
5603                         cls_instrs,
5604                         setup_instrs,
5605                         base_stack_size,
5606                         cls_stack_size,
5607                     },
5608                     store: instr::basel(alloc, local, MemberOpMode::Define),
5609                 }
5610             }
5611             _ => ArrayGetBase::Regular(ArrayGetBaseData {
5612                 base_instrs,
5613                 cls_instrs,
5614                 setup_instrs,
5615                 base_stack_size,
5616                 cls_stack_size,
5617             }),
5618         }
5619     };
5621     let emit_expr_default =
5622         |e: &mut Emitter<'arena, 'decl, D>, env, expr: &ast::Expr| -> Result<ArrayGetBase> {
5623             let base_expr_instrs = emit_expr(e, env, expr)?;
5624             Ok(emit_default(
5625                 e,
5626                 base_expr_instrs,
5627                 instr::empty(alloc),
5628                 emit_pos_then(alloc, pos, instr::basec(alloc, base_offset, base_mode)),
5629                 1,
5630                 0,
5631             ))
5632         };
5634     use ast::Expr_ as E_;
5635     match expr_ {
5636         E_::Lvar(x) if superglobals::is_superglobal(&(x.1).1) => {
5637             let base_instrs = emit_pos_then(
5638                 alloc,
5639                 &x.0,
5640                 instr::string(alloc, string_utils::locals::strip_dollar(&(x.1).1)),
5641             );
5643             Ok(emit_default(
5644                 e,
5645                 base_instrs,
5646                 instr::empty(alloc),
5647                 instr::basegc(alloc, base_offset, base_mode),
5648                 1,
5649                 0,
5650             ))
5651         }
5652         E_::Lvar(x) if is_object && (x.1).1 == special_idents::THIS => {
5653             let base_instrs = emit_pos_then(alloc, &x.0, instr::checkthis(alloc));
5654             Ok(emit_default(
5655                 e,
5656                 base_instrs,
5657                 instr::empty(alloc),
5658                 instr::baseh(alloc),
5659                 0,
5660                 0,
5661             ))
5662         }
5663         E_::Lvar(x)
5664             if !is_local_this(env, &x.1) || env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS) =>
5665         {
5666             let v = get_local(e, env, &x.0, &(x.1).1)?;
5667             let base_instr = if local_temp_kind.is_some() {
5668                 instr::cgetquietl(alloc, v)
5669             } else {
5670                 instr::empty(alloc)
5671             };
5672             Ok(emit_default(
5673                 e,
5674                 base_instr,
5675                 instr::empty(alloc),
5676                 instr::basel(alloc, v, base_mode),
5677                 0,
5678                 0,
5679             ))
5680         }
5681         E_::Lvar(lid) => {
5682             let local = emit_local(e, env, notice, lid)?;
5683             Ok(emit_default(
5684                 e,
5685                 local,
5686                 instr::empty(alloc),
5687                 instr::basec(alloc, base_offset, base_mode),
5688                 1,
5689                 0,
5690             ))
5691         }
5692         E_::ArrayGet(x) => match (&(x.0).1, x.1.as_ref()) {
5693             // $a[] can not be used as the base of an array get unless as an lval
5694             (_, None)
5695                 if !env
5696                     .flags
5697                     .contains(hhbc_by_ref_env::Flags::ALLOWS_ARRAY_APPEND) =>
5698             {
5699                 Err(emit_fatal::raise_fatal_runtime(
5700                     pos,
5701                     "Can't use [] for reading",
5702                 ))
5703             }
5704             // base is in turn array_get - do a specific handling for inout params
5705             // if necessary
5706             (_, opt_elem_expr) => {
5707                 let base_expr = &x.0;
5708                 let local_temp_kind =
5709                     get_local_temp_kind(env, false, inout_param_info, opt_elem_expr);
5710                 let (elem_instrs, elem_stack_size) = emit_elem(
5711                     e,
5712                     env,
5713                     opt_elem_expr,
5714                     local_temp_kind,
5715                     null_coalesce_assignment,
5716                 )?;
5717                 let base_result = emit_base_(
5718                     e,
5719                     env,
5720                     base_expr,
5721                     mode,
5722                     false,
5723                     notice,
5724                     null_coalesce_assignment,
5725                     base_offset + elem_stack_size,
5726                     rhs_stack_size,
5727                     inout_param_info,
5728                     readonly_op, // continue passing readonly enforcement up
5729                 )?;
5730                 let cls_stack_size = match &base_result {
5731                     ArrayGetBase::Regular(base) => base.cls_stack_size,
5732                     ArrayGetBase::Inout { load, .. } => load.cls_stack_size,
5733                 };
5734                 let (mk, warninstr) = get_elem_member_key(
5735                     e,
5736                     env,
5737                     base_offset + cls_stack_size,
5738                     opt_elem_expr,
5739                     null_coalesce_assignment,
5740                 )?;
5741                 let make_setup_instrs = |base_setup_instrs: InstrSeq<'arena>| {
5742                     InstrSeq::gather(
5743                         alloc,
5744                         vec![warninstr, base_setup_instrs, instr::dim(alloc, mode, mk)],
5745                     )
5746                 };
5747                 Ok(match (base_result, local_temp_kind) {
5748                     // both base and index don't use temps - fallback to default handler
5749                     (ArrayGetBase::Regular(base), None) => emit_default(
5750                         e,
5751                         InstrSeq::gather(alloc, vec![base.base_instrs, elem_instrs]),
5752                         base.cls_instrs,
5753                         make_setup_instrs(base.setup_instrs),
5754                         base.base_stack_size + elem_stack_size,
5755                         base.cls_stack_size,
5756                     ),
5757                     // base does not need temps but index does
5758                     (ArrayGetBase::Regular(base), Some(local_temp)) => {
5759                         let local = e.local_gen_mut().get_unnamed();
5760                         let base_instrs =
5761                             InstrSeq::gather(alloc, vec![base.base_instrs, elem_instrs]);
5762                         ArrayGetBase::Inout {
5763                             load: ArrayGetBaseData {
5764                                 // store result of instr_begin to temp
5765                                 base_instrs: vec![(base_instrs, Some((local, local_temp)))],
5766                                 cls_instrs: base.cls_instrs,
5767                                 setup_instrs: make_setup_instrs(base.setup_instrs),
5768                                 base_stack_size: base.base_stack_size + elem_stack_size,
5769                                 cls_stack_size: base.cls_stack_size,
5770                             },
5771                             store: emit_store_for_simple_base(
5772                                 e,
5773                                 env,
5774                                 pos,
5775                                 elem_stack_size,
5776                                 base_expr,
5777                                 local,
5778                                 true,
5779                                 readonly_op,
5780                             )?,
5781                         }
5782                     }
5783                     // base needs temps, index - does not
5784                     (
5785                         ArrayGetBase::Inout {
5786                             load:
5787                                 ArrayGetBaseData {
5788                                     mut base_instrs,
5789                                     cls_instrs,
5790                                     setup_instrs,
5791                                     base_stack_size,
5792                                     cls_stack_size,
5793                                 },
5794                             store,
5795                         },
5796                         None,
5797                     ) => {
5798                         base_instrs.push((elem_instrs, None));
5799                         ArrayGetBase::Inout {
5800                             load: ArrayGetBaseData {
5801                                 base_instrs,
5802                                 cls_instrs,
5803                                 setup_instrs: make_setup_instrs(setup_instrs),
5804                                 base_stack_size: base_stack_size + elem_stack_size,
5805                                 cls_stack_size,
5806                             },
5807                             store: InstrSeq::gather(
5808                                 alloc,
5809                                 vec![store, instr::dim(alloc, MemberOpMode::Define, mk)],
5810                             ),
5811                         }
5812                     }
5813                     // both base and index needs locals
5814                     (
5815                         ArrayGetBase::Inout {
5816                             load:
5817                                 ArrayGetBaseData {
5818                                     mut base_instrs,
5819                                     cls_instrs,
5820                                     setup_instrs,
5821                                     base_stack_size,
5822                                     cls_stack_size,
5823                                 },
5824                             store,
5825                         },
5826                         Some(local_kind),
5827                     ) => {
5828                         let local = e.local_gen_mut().get_unnamed();
5829                         base_instrs.push((elem_instrs, Some((local, local_kind))));
5830                         ArrayGetBase::Inout {
5831                             load: ArrayGetBaseData {
5832                                 base_instrs,
5833                                 cls_instrs,
5834                                 setup_instrs: make_setup_instrs(setup_instrs),
5835                                 base_stack_size: base_stack_size + elem_stack_size,
5836                                 cls_stack_size,
5837                             },
5838                             store: InstrSeq::gather(
5839                                 alloc,
5840                                 vec![
5841                                     store,
5842                                     instr::dim(
5843                                         alloc,
5844                                         MemberOpMode::Define,
5845                                         MemberKey::EL(local, ReadonlyOp::Any),
5846                                     ),
5847                                 ],
5848                             ),
5849                         }
5850                     }
5851                 })
5852             }
5853         },
5854         E_::ObjGet(x) => {
5855             if x.as_ref().3 {
5856                 emit_expr_default(e, env, expr)
5857             } else {
5858                 let (base_expr, prop_expr, null_flavor, _) = &**x;
5859                 Ok(match prop_expr.2.as_id() {
5860                     Some(ast_defs::Id(_, s)) if string_utils::is_xhp(&s) => {
5861                         let base_instrs =
5862                             emit_xhp_obj_get(e, env, pos, base_expr, &s, null_flavor)?;
5863                         emit_default(
5864                             e,
5865                             base_instrs,
5866                             instr::empty(alloc),
5867                             instr::basec(alloc, base_offset, base_mode),
5868                             1,
5869                             0,
5870                         )
5871                     }
5872                     _ => {
5873                         let prop_stack_size = emit_prop_expr(
5874                             e,
5875                             env,
5876                             null_flavor,
5877                             0,
5878                             prop_expr,
5879                             null_coalesce_assignment,
5880                             ReadonlyOp::Any, // just getting stack size here
5881                         )?
5882                         .2;
5883                         let (
5884                             base_expr_instrs_begin,
5885                             base_expr_instrs_end,
5886                             base_setup_instrs,
5887                             base_stack_size,
5888                             cls_stack_size,
5889                         ) = emit_base(
5890                             e,
5891                             env,
5892                             base_expr,
5893                             mode,
5894                             true,
5895                             BareThisOp::Notice,
5896                             null_coalesce_assignment,
5897                             base_offset + prop_stack_size,
5898                             rhs_stack_size,
5899                             ReadonlyOp::Mutable, // the rest of the base must be completely mutable
5900                         )?;
5901                         let (mk, prop_instrs, _) = emit_prop_expr(
5902                             e,
5903                             env,
5904                             null_flavor,
5905                             base_offset + cls_stack_size,
5906                             prop_expr,
5907                             null_coalesce_assignment,
5908                             readonly_op, // use the current enforcement
5909                         )?;
5910                         let total_stack_size = prop_stack_size + base_stack_size;
5911                         let final_instr = instr::dim(alloc, mode, mk);
5912                         emit_default(
5913                             e,
5914                             InstrSeq::gather(alloc, vec![base_expr_instrs_begin, prop_instrs]),
5915                             base_expr_instrs_end,
5916                             InstrSeq::gather(alloc, vec![base_setup_instrs, final_instr]),
5917                             total_stack_size,
5918                             cls_stack_size,
5919                         )
5920                     }
5921                 })
5922             }
5923         }
5924         E_::ClassGet(x) => {
5925             if x.2 {
5926                 emit_expr_default(e, env, expr)
5927             } else {
5928                 let (cid, prop, _) = &**x;
5929                 let cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
5930                 let (cexpr_begin, cexpr_end) = emit_class_expr(e, env, cexpr, prop)?;
5931                 Ok(emit_default(
5932                     e,
5933                     cexpr_begin,
5934                     cexpr_end,
5935                     instr::basesc(
5936                         alloc,
5937                         base_offset + 1,
5938                         rhs_stack_size,
5939                         base_mode,
5940                         readonly_op,
5941                     ),
5942                     1,
5943                     1,
5944                 ))
5945             }
5946         }
5947         _ => emit_expr_default(e, env, expr),
5948     }
5951 pub fn emit_ignored_exprs<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5952     emitter: &mut Emitter<'arena, 'decl, D>,
5953     env: &Env<'a, 'arena>,
5954     pos: &Pos,
5955     exprs: &[ast::Expr],
5956 ) -> Result<InstrSeq<'arena>> {
5957     let alloc = env.arena;
5958     exprs
5959         .iter()
5960         .map(|e| emit_ignored_expr(emitter, env, pos, e))
5961         .collect::<Result<Vec<_>>>()
5962         .map(|x| InstrSeq::gather(alloc, x))
5965 // TODO(hrust): change pos from &Pos to Option<&Pos>, since Pos::make_none() still allocate mem.
5966 pub fn emit_ignored_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5967     emitter: &mut Emitter<'arena, 'decl, D>,
5968     env: &Env<'a, 'arena>,
5969     pos: &Pos,
5970     expr: &ast::Expr,
5971 ) -> Result<InstrSeq<'arena>> {
5972     let alloc = env.arena;
5973     Ok(InstrSeq::gather(
5974         alloc,
5975         vec![
5976             emit_expr(emitter, env, expr)?,
5977             emit_pos_then(alloc, pos, instr::popc(alloc)),
5978         ],
5979     ))
5982 pub fn emit_lval_op<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5983     e: &mut Emitter<'arena, 'decl, D>,
5984     env: &Env<'a, 'arena>,
5985     pos: &Pos,
5986     op: LValOp,
5987     expr1: &ast::Expr,
5988     expr2: Option<&ast::Expr>,
5989     null_coalesce_assignment: bool,
5990 ) -> Result<InstrSeq<'arena>> {
5991     let alloc = env.arena;
5992     match (op, &expr1.2, expr2) {
5993         (LValOp::Set, ast::Expr_::List(l), Some(expr2)) => {
5994             let instr_rhs = emit_expr(e, env, expr2)?;
5995             let has_elements = l.iter().any(|e| !e.2.is_omitted());
5996             if !has_elements {
5997                 Ok(instr_rhs)
5998             } else {
5999                 scope::with_unnamed_local(alloc, e, |alloc, e, local| {
6000                     let loc = if can_use_as_rhs_in_list_assignment(&expr2.2)? {
6001                         Some(&local)
6002                     } else {
6003                         None
6004                     };
6005                     let (instr_lhs, instr_assign) =
6006                         emit_lval_op_list(e, env, pos, loc, &[], expr1, false)?;
6007                     Ok((
6008                         InstrSeq::gather(
6009                             alloc,
6010                             vec![instr_lhs, instr_rhs, instr::popl(alloc, local)],
6011                         ),
6012                         instr_assign,
6013                         instr::pushl(alloc, local),
6014                     ))
6015                 })
6016             }
6017         }
6018         _ => e.local_scope(|e| {
6019             let (rhs_instrs, rhs_stack_size, rhs_readonly) = match expr2 {
6020                 None => (instr::empty(alloc), 0, false),
6021                 Some(aast::Expr(_, _, aast::Expr_::Yield(af))) => {
6022                     let temp = e.local_gen_mut().get_unnamed();
6023                     (
6024                         InstrSeq::gather(
6025                             alloc,
6026                             vec![
6027                                 emit_yield(e, env, pos, af)?,
6028                                 instr::setl(alloc, temp),
6029                                 instr::popc(alloc),
6030                                 instr::pushl(alloc, temp),
6031                             ],
6032                         ),
6033                         1,
6034                         false,
6035                     )
6036                 }
6037                 Some(expr) => (emit_expr(e, env, expr)?, 1, is_readonly_expr(expr)),
6038             };
6039             emit_lval_op_nonlist(
6040                 e,
6041                 env,
6042                 pos,
6043                 op,
6044                 expr1,
6045                 rhs_instrs,
6046                 rhs_stack_size,
6047                 rhs_readonly,
6048                 null_coalesce_assignment,
6049             )
6050         }),
6051     }
6054 fn can_use_as_rhs_in_list_assignment(expr: &ast::Expr_) -> Result<bool> {
6055     use aast::Expr_ as E_;
6056     Ok(match expr {
6057         E_::Call(c)
6058             if ((c.0).2)
6059                 .as_id()
6060                 .map_or(false, |id| id.1 == special_functions::ECHO) =>
6061         {
6062             false
6063         }
6064         E_::ObjGet(o) if !o.as_ref().3 => true,
6065         E_::ClassGet(c) if !c.as_ref().2 => true,
6066         E_::Lvar(_)
6067         | E_::ArrayGet(_)
6068         | E_::Call(_)
6069         | E_::FunctionPointer(_)
6070         | E_::New(_)
6071         | E_::Record(_)
6072         | E_::Yield(_)
6073         | E_::Cast(_)
6074         | E_::Eif(_)
6075         | E_::Tuple(_)
6076         | E_::Varray(_)
6077         | E_::Darray(_)
6078         | E_::Collection(_)
6079         | E_::Clone(_)
6080         | E_::Unop(_)
6081         | E_::As(_)
6082         | E_::Await(_)
6083         | E_::ReadonlyExpr(_)
6084         | E_::ClassConst(_) => true,
6085         E_::Pipe(p) => can_use_as_rhs_in_list_assignment(&(p.2).2)?,
6086         E_::Binop(b) => {
6087             if let ast_defs::Bop::Eq(None) = &b.0 {
6088                 if (b.1).2.is_list() {
6089                     return can_use_as_rhs_in_list_assignment(&(b.2).2);
6090                 }
6091             }
6092             b.0.is_plus() || b.0.is_question_question() || b.0.is_any_eq()
6093         }
6094         _ => false,
6095     })
6098 // Given a local $local and a list of integer array indices i_1, ..., i_n,
6099 // generate code to extract the value of $local[i_n]...[i_1]:
6100 //   BaseL $local Warn
6101 //   Dim Warn EI:i_n ...
6102 //   Dim Warn EI:i_2
6103 //   QueryM 0 CGet EI:i_1
6104 fn emit_array_get_fixed<'arena, 'decl, D: DeclProvider<'decl>>(
6105     alloc: &'arena bumpalo::Bump,
6106     last_usage: bool,
6107     local: Local<'arena>,
6108     indices: &[isize],
6109 ) -> InstrSeq<'arena> {
6110     let (base, stack_count) = if last_usage {
6111         (
6112             InstrSeq::gather(
6113                 alloc,
6114                 vec![
6115                     instr::pushl(alloc, local),
6116                     instr::basec(alloc, 0, MemberOpMode::Warn),
6117                 ],
6118             ),
6119             1,
6120         )
6121     } else {
6122         (instr::basel(alloc, local, MemberOpMode::Warn), 0)
6123     };
6124     let indices = InstrSeq::gather(
6125         alloc,
6126         indices
6127             .iter()
6128             .enumerate()
6129             .rev()
6130             .map(|(i, ix)| {
6131                 let mk = MemberKey::EI(*ix as i64, ReadonlyOp::Any);
6132                 if i == 0 {
6133                     instr::querym(alloc, stack_count, QueryOp::CGet, mk)
6134                 } else {
6135                     instr::dim(alloc, MemberOpMode::Warn, mk)
6136                 }
6137             })
6138             .collect(),
6139     );
6140     InstrSeq::gather(alloc, vec![base, indices])
6143 // Generate code for each lvalue assignment in a list destructuring expression.
6144 // Lvalues are assigned right-to-left, regardless of the nesting structure. So
6145 //      list($a, list($b, $c)) = $d
6146 //  and list(list($a, $b), $c) = $d
6147 //  will both assign to $c, $b and $a in that order.
6148 //  Returns a pair of instructions:
6149 //  1. initialization part of the left hand side
6150 //  2. assignment
6151 //  this is necessary to handle cases like:
6152 //  list($a[$f()]) = b();
6153 //  here f() should be invoked before b()
6154 pub fn emit_lval_op_list<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
6155     e: &mut Emitter<'arena, 'decl, D>,
6156     env: &Env<'a, 'arena>,
6157     outer_pos: &Pos,
6158     local: Option<&Local<'arena>>,
6159     indices: &[isize],
6160     expr: &ast::Expr,
6161     last_usage: bool,
6162 ) -> Result<(InstrSeq<'arena>, InstrSeq<'arena>)> {
6163     use ast::Expr_ as E_;
6164     use hhbc_by_ref_options::Php7Flags;
6165     let alloc = env.arena;
6166     let is_ltr = e.options().php7_flags.contains(Php7Flags::LTR_ASSIGN);
6167     match &expr.2 {
6168         E_::List(exprs) => {
6169             let last_non_omitted = if last_usage {
6170                 // last usage of the local will happen when processing last non-omitted
6171                 // element in the list - find it
6172                 if is_ltr {
6173                     exprs.iter().rposition(|v| !v.2.is_omitted())
6174                 } else {
6175                     // in right-to-left case result list will be reversed
6176                     // so we need to find first non-omitted expression
6177                     exprs.iter().rev().rposition(|v| !v.2.is_omitted())
6178                 }
6179             } else {
6180                 None
6181             };
6182             let (lhs_instrs, set_instrs): (Vec<InstrSeq<'arena>>, Vec<InstrSeq<'arena>>) = exprs
6183                 .iter()
6184                 .enumerate()
6185                 .map(|(i, expr)| {
6186                     let mut new_indices = vec![i as isize];
6187                     new_indices.extend_from_slice(indices);
6188                     emit_lval_op_list(
6189                         e,
6190                         env,
6191                         outer_pos,
6192                         local,
6193                         &new_indices[..],
6194                         expr,
6195                         last_non_omitted.map_or(false, |j| j == i),
6196                     )
6197                 })
6198                 .collect::<Result<Vec<_>>>()?
6199                 .into_iter()
6200                 .unzip();
6201             Ok((
6202                 InstrSeq::gather(alloc, lhs_instrs),
6203                 InstrSeq::gather(
6204                     alloc,
6205                     if !is_ltr {
6206                         set_instrs.into_iter().rev().collect()
6207                     } else {
6208                         set_instrs
6209                     },
6210                 ),
6211             ))
6212         }
6213         E_::Omitted => Ok((instr::empty(alloc), instr::empty(alloc))),
6214         _ => {
6215             // Generate code to access the element from the array
6216             let access_instrs = match (local, indices) {
6217                 (Some(loc), [_, ..]) => {
6218                     emit_array_get_fixed::<D>(alloc, last_usage, loc.to_owned(), indices)
6219                 }
6220                 (Some(loc), []) => {
6221                     if last_usage {
6222                         instr::pushl(alloc, loc.to_owned())
6223                     } else {
6224                         instr::cgetl(alloc, loc.to_owned())
6225                     }
6226                 }
6227                 (None, _) => instr::null(alloc),
6228             };
6229             // Generate code to assign to the lvalue *)
6230             // Return pair: side effects to initialize lhs + assignment
6231             let (lhs_instrs, rhs_instrs, set_op) = emit_lval_op_nonlist_steps(
6232                 e,
6233                 env,
6234                 outer_pos,
6235                 LValOp::Set,
6236                 expr,
6237                 access_instrs,
6238                 1,
6239                 false,
6240                 false, // TODO: readonly assignment (list expressions)
6241             )?;
6242             Ok(if is_ltr {
6243                 (
6244                     instr::empty(alloc),
6245                     InstrSeq::gather(
6246                         alloc,
6247                         vec![lhs_instrs, rhs_instrs, set_op, instr::popc(alloc)],
6248                     ),
6249                 )
6250             } else {
6251                 (
6252                     lhs_instrs,
6253                     InstrSeq::gather(
6254                         alloc,
6255                         vec![instr::empty(alloc), rhs_instrs, set_op, instr::popc(alloc)],
6256                     ),
6257                 )
6258             })
6259         }
6260     }
6263 pub fn emit_lval_op_nonlist<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
6264     e: &mut Emitter<'arena, 'decl, D>,
6265     env: &Env<'a, 'arena>,
6266     outer_pos: &Pos,
6267     op: LValOp,
6268     expr: &ast::Expr,
6269     rhs_instrs: InstrSeq<'arena>,
6270     rhs_stack_size: isize,
6271     rhs_readonly: bool,
6272     null_coalesce_assignment: bool,
6273 ) -> Result<InstrSeq<'arena>> {
6274     let alloc = env.arena;
6275     emit_lval_op_nonlist_steps(
6276         e,
6277         env,
6278         outer_pos,
6279         op,
6280         expr,
6281         rhs_instrs,
6282         rhs_stack_size,
6283         rhs_readonly,
6284         null_coalesce_assignment,
6285     )
6286     .map(|(lhs, rhs, setop)| InstrSeq::gather(alloc, vec![lhs, rhs, setop]))
6289 pub fn emit_final_global_op<'arena, 'decl, D: DeclProvider<'decl>>(
6290     alloc: &'arena bumpalo::Bump,
6291     pos: &Pos,
6292     op: LValOp,
6293 ) -> InstrSeq<'arena> {
6294     use LValOp as L;
6295     match op {
6296         L::Set => emit_pos_then(alloc, pos, instr::setg(alloc)),
6297         L::SetOp(op) => instr::setopg(alloc, op),
6298         L::IncDec(op) => instr::incdecg(alloc, op),
6299         L::Unset => emit_pos_then(alloc, pos, instr::unsetg(alloc)),
6300     }
6303 pub fn emit_final_local_op<'arena, 'decl, D: DeclProvider<'decl>>(
6304     alloc: &'arena bumpalo::Bump,
6305     pos: &Pos,
6306     op: LValOp,
6307     lid: Local<'arena>,
6308 ) -> InstrSeq<'arena> {
6309     use LValOp as L;
6310     emit_pos_then(
6311         alloc,
6312         pos,
6313         match op {
6314             L::Set => instr::setl(alloc, lid),
6315             L::SetOp(op) => instr::setopl(alloc, lid, op),
6316             L::IncDec(op) => instr::incdecl(alloc, lid, op),
6317             L::Unset => instr::unsetl(alloc, lid),
6318         },
6319     )
6322 fn emit_final_member_op<'arena, 'decl, D: DeclProvider<'decl>>(
6323     alloc: &'arena bumpalo::Bump,
6324     stack_size: usize,
6325     op: LValOp,
6326     mk: MemberKey<'arena>,
6327 ) -> InstrSeq<'arena> {
6328     use LValOp as L;
6329     match op {
6330         L::Set => instr::setm(alloc, stack_size, mk),
6331         L::SetOp(op) => instr::setopm(alloc, stack_size, op, mk),
6332         L::IncDec(op) => instr::incdecm(alloc, stack_size, op, mk),
6333         L::Unset => instr::unsetm(alloc, stack_size, mk),
6334     }
6337 fn emit_final_static_op<'arena, 'decl, D: DeclProvider<'decl>>(
6338     alloc: &'arena bumpalo::Bump,
6339     cid: &ast::ClassId,
6340     prop: &ast::ClassGetExpr,
6341     op: LValOp,
6342 ) -> Result<InstrSeq<'arena>> {
6343     use LValOp as L;
6344     Ok(match op {
6345         L::Set => instr::sets(alloc, ReadonlyOp::Any),
6346         L::SetOp(op) => instr::setops(alloc, op),
6347         L::IncDec(op) => instr::incdecs(alloc, op),
6348         L::Unset => {
6349             let pos = match prop {
6350                 ast::ClassGetExpr::CGstring((pos, _))
6351                 | ast::ClassGetExpr::CGexpr(ast::Expr(_, pos, _)) => pos,
6352             };
6353             let cid = text_of_class_id(cid);
6354             let id = text_of_prop(prop);
6355             emit_fatal::emit_fatal_runtime(
6356                 alloc,
6357                 pos,
6358                 format!(
6359                     "Attempt to unset static property {}::{}",
6360                     string_utils::strip_ns(&cid),
6361                     id,
6362                 ),
6363             )
6364         }
6365     })
6368 pub fn emit_lval_op_nonlist_steps<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
6369     e: &mut Emitter<'arena, 'decl, D>,
6370     env: &Env<'a, 'arena>,
6371     outer_pos: &Pos,
6372     op: LValOp,
6373     expr: &ast::Expr,
6374     rhs_instrs: InstrSeq<'arena>,
6375     rhs_stack_size: isize,
6376     rhs_readonly: bool,
6377     null_coalesce_assignment: bool,
6378 ) -> Result<(InstrSeq<'arena>, InstrSeq<'arena>, InstrSeq<'arena>)> {
6379     let f = |alloc: &'arena bumpalo::Bump, env: &mut Env<'a, 'arena>| {
6380         use ast::Expr_ as E_;
6381         let pos = &expr.1;
6382         Ok(match &expr.2 {
6383             E_::Lvar(v) if superglobals::is_any_global(local_id::get_name(&v.1)) => (
6384                 emit_pos_then(
6385                     alloc,
6386                     &v.0,
6387                     instr::string(alloc, string_utils::lstrip(local_id::get_name(&v.1), "$")),
6388                 ),
6389                 rhs_instrs,
6390                 emit_final_global_op::<D>(alloc, outer_pos, op),
6391             ),
6392             E_::Lvar(v) if is_local_this(env, &v.1) && op.is_incdec() => (
6393                 emit_local(e, env, BareThisOp::Notice, v)?,
6394                 rhs_instrs,
6395                 instr::empty(alloc),
6396             ),
6397             E_::Lvar(v) if !is_local_this(env, &v.1) || op == LValOp::Unset => {
6398                 (instr::empty(alloc), rhs_instrs, {
6399                     let lid = get_local(e, env, &v.0, &(v.1).1)?;
6400                     emit_final_local_op::<D>(alloc, outer_pos, op, lid)
6401                 })
6402             }
6403             E_::ArrayGet(x) => match (&(x.0).1, x.1.as_ref()) {
6404                 (_, None)
6405                     if !env
6406                         .flags
6407                         .contains(hhbc_by_ref_env::Flags::ALLOWS_ARRAY_APPEND) =>
6408                 {
6409                     return Err(emit_fatal::raise_fatal_runtime(
6410                         pos,
6411                         "Can't use [] for reading",
6412                     ));
6413                 }
6414                 (_, opt_elem_expr) => {
6415                     let mode = match op {
6416                         LValOp::Unset => MemberOpMode::Unset,
6417                         _ => MemberOpMode::Define,
6418                     };
6419                     let (elem_instrs, elem_stack_size) =
6420                         emit_elem(e, env, opt_elem_expr, None, null_coalesce_assignment)?;
6421                     let base_offset = elem_stack_size + rhs_stack_size;
6422                     let readonly_op = if rhs_readonly {
6423                         ReadonlyOp::CheckROCOW // writing a readonly value requires a readony copy on write array
6424                     } else {
6425                         ReadonlyOp::CheckMutROCOW // writing a mut value requires left side to be mutable or a ROCOW
6426                     };
6427                     let (
6428                         base_expr_instrs_begin,
6429                         base_expr_instrs_end,
6430                         base_setup_instrs,
6431                         base_stack_size,
6432                         cls_stack_size,
6433                     ) = emit_base(
6434                         e,
6435                         env,
6436                         &x.0,
6437                         mode,
6438                         false,
6439                         BareThisOp::Notice,
6440                         null_coalesce_assignment,
6441                         base_offset,
6442                         rhs_stack_size,
6443                         readonly_op,
6444                     )?;
6445                     let (mk, warninstr) = get_elem_member_key(
6446                         e,
6447                         env,
6448                         rhs_stack_size + cls_stack_size,
6449                         opt_elem_expr,
6450                         null_coalesce_assignment,
6451                     )?;
6452                     let total_stack_size = elem_stack_size + base_stack_size + cls_stack_size;
6453                     let final_instr = emit_pos_then(
6454                         alloc,
6455                         pos,
6456                         emit_final_member_op::<D>(alloc, total_stack_size as usize, op, mk),
6457                     );
6458                     (
6459                         // Don't emit instructions for elems as these were not popped from
6460                         // the stack by the final member op during the lookup of a null
6461                         // coalesce assignment.
6462                         if null_coalesce_assignment {
6463                             instr::empty(alloc)
6464                         } else {
6465                             InstrSeq::gather(
6466                                 alloc,
6467                                 vec![base_expr_instrs_begin, elem_instrs, base_expr_instrs_end],
6468                             )
6469                         },
6470                         rhs_instrs,
6471                         InstrSeq::gather(
6472                             alloc,
6473                             vec![
6474                                 emit_pos(alloc, pos),
6475                                 warninstr,
6476                                 base_setup_instrs,
6477                                 final_instr,
6478                             ],
6479                         ),
6480                     )
6481                 }
6482             },
6483             E_::ObjGet(x) if !x.as_ref().3 => {
6484                 let (e1, e2, nullflavor, _) = &**x;
6485                 if nullflavor.eq(&ast_defs::OgNullFlavor::OGNullsafe) {
6486                     return Err(emit_fatal::raise_fatal_parse(
6487                         pos,
6488                         "?-> is not allowed in write context",
6489                     ));
6490                 }
6491                 let mode = match op {
6492                     LValOp::Unset => MemberOpMode::Unset,
6493                     _ => MemberOpMode::Define,
6494                 };
6495                 let readonly_op = if rhs_readonly {
6496                     ReadonlyOp::Readonly
6497                 } else {
6498                     ReadonlyOp::Any
6499                 };
6500                 let prop_stack_size = emit_prop_expr(
6501                     e,
6502                     env,
6503                     nullflavor,
6504                     0,
6505                     e2,
6506                     null_coalesce_assignment,
6507                     readonly_op,
6508                 )?
6509                 .2;
6510                 let base_offset = prop_stack_size + rhs_stack_size;
6511                 let (
6512                     base_expr_instrs_begin,
6513                     base_expr_instrs_end,
6514                     base_setup_instrs,
6515                     base_stack_size,
6516                     cls_stack_size,
6517                 ) = emit_base(
6518                     e,
6519                     env,
6520                     e1,
6521                     mode,
6522                     true,
6523                     BareThisOp::Notice,
6524                     null_coalesce_assignment,
6525                     base_offset,
6526                     rhs_stack_size,
6527                     ReadonlyOp::Mutable, // writing to a property requires everything in the base to be mutable
6528                 )?;
6529                 let (mk, prop_instrs, _) = emit_prop_expr(
6530                     e,
6531                     env,
6532                     nullflavor,
6533                     rhs_stack_size + cls_stack_size,
6534                     e2,
6535                     null_coalesce_assignment,
6536                     readonly_op,
6537                 )?;
6538                 let total_stack_size = prop_stack_size + base_stack_size + cls_stack_size;
6539                 let final_instr = emit_pos_then(
6540                     alloc,
6541                     pos,
6542                     emit_final_member_op::<D>(alloc, total_stack_size as usize, op, mk),
6543                 );
6544                 (
6545                     // Don't emit instructions for props as these were not popped from
6546                     // the stack by the final member op during the lookup of a null
6547                     // coalesce assignment.
6548                     if null_coalesce_assignment {
6549                         instr::empty(alloc)
6550                     } else {
6551                         InstrSeq::gather(
6552                             alloc,
6553                             vec![base_expr_instrs_begin, prop_instrs, base_expr_instrs_end],
6554                         )
6555                     },
6556                     rhs_instrs,
6557                     InstrSeq::gather(alloc, vec![base_setup_instrs, final_instr]),
6558                 )
6559             }
6560             E_::ClassGet(x) if !x.as_ref().2 => {
6561                 let (cid, prop, _) = &**x;
6562                 let cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
6563                 let final_instr_ = emit_final_static_op::<D>(alloc, cid, prop, op)?;
6564                 let final_instr = emit_pos_then(alloc, pos, final_instr_);
6565                 (
6566                     InstrSeq::from((alloc, emit_class_expr(e, env, cexpr, prop)?)),
6567                     rhs_instrs,
6568                     final_instr,
6569                 )
6570             }
6571             E_::Unop(uop) => (
6572                 instr::empty(alloc),
6573                 rhs_instrs,
6574                 InstrSeq::gather(
6575                     alloc,
6576                     vec![
6577                         emit_lval_op_nonlist(
6578                             e,
6579                             env,
6580                             pos,
6581                             op,
6582                             &uop.1,
6583                             instr::empty(alloc),
6584                             rhs_stack_size,
6585                             false,
6586                             false, // all unary operations (++, --, etc) are on primitives, so no HHVM readonly checks
6587                         )?,
6588                         from_unop(alloc, e.options(), &uop.0)?,
6589                     ],
6590                 ),
6591             ),
6592             _ => {
6593                 return Err(emit_fatal::raise_fatal_parse(
6594                     pos,
6595                     "Can't use return value in write context",
6596                 ));
6597             }
6598         })
6599     };
6600     // TODO(shiqicao): remove clone!
6601     let alloc = env.arena;
6602     let mut env = env.clone();
6603     match op {
6604         LValOp::Set | LValOp::SetOp(_) | LValOp::IncDec(_) => {
6605             env.with_allows_array_append(alloc, f)
6606         }
6607         _ => f(alloc, &mut env),
6608     }
6611 fn emit_class_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
6612     e: &mut Emitter<'arena, 'decl, D>,
6613     env: &Env<'a, 'arena>,
6614     cexpr: ClassExpr<'arena>,
6615     prop: &ast::ClassGetExpr,
6616 ) -> Result<(InstrSeq<'arena>, InstrSeq<'arena>)> {
6617     let load_prop = |alloc: &'arena bumpalo::Bump, e: &mut Emitter<'arena, 'decl, D>| match prop {
6618         ast::ClassGetExpr::CGstring((pos, id)) => Ok(emit_pos_then(
6619             alloc,
6620             pos,
6621             instr::string(alloc, string_utils::locals::strip_dollar(id)),
6622         )),
6623         ast::ClassGetExpr::CGexpr(expr) => emit_expr(e, env, expr),
6624     };
6625     let alloc = env.arena;
6626     Ok(match &cexpr {
6627         ClassExpr::Expr(expr)
6628             if expr.2.is_call()
6629                 || expr.2.is_binop()
6630                 || expr.2.is_class_get()
6631                 || expr
6632                     .2
6633                     .as_lvar()
6634                     .map_or(false, |ast::Lid(_, id)| local_id::get_name(id) == "$this") =>
6635         {
6636             let cexpr_local = emit_expr(e, env, expr)?;
6637             (
6638                 instr::empty(alloc),
6639                 InstrSeq::gather(
6640                     alloc,
6641                     vec![
6642                         cexpr_local,
6643                         scope::stash_top_in_unnamed_local(alloc, e, load_prop)?,
6644                         instr::classgetc(alloc),
6645                     ],
6646                 ),
6647             )
6648         }
6649         _ => {
6650             let pos = match prop {
6651                 ast::ClassGetExpr::CGstring((pos, _))
6652                 | ast::ClassGetExpr::CGexpr(ast::Expr(_, pos, _)) => pos,
6653             };
6654             (
6655                 load_prop(alloc, e)?,
6656                 emit_load_class_ref(e, env, pos, cexpr)?,
6657             )
6658         }
6659     })
6662 pub fn fixup_type_arg<'a, 'b, 'arena>(
6663     env: &Env<'b, 'arena>,
6664     isas: bool,
6665     hint: &'a ast::Hint,
6666 ) -> Result<impl AsRef<ast::Hint> + 'a> {
6667     struct Checker<'s> {
6668         erased_tparams: &'s [&'s str],
6669         isas: bool,
6670     }
6671     impl<'ast, 's> Visitor<'ast> for Checker<'s> {
6672         type P = AstParams<(), Option<Error>>;
6674         fn object(&mut self) -> &mut dyn Visitor<'ast, P = Self::P> {
6675             self
6676         }
6678         fn visit_hint_fun(
6679             &mut self,
6680             c: &mut (),
6681             hf: &ast::HintFun,
6682         ) -> StdResult<(), Option<Error>> {
6683             hf.param_tys.accept(c, self.object())?;
6684             hf.return_ty.accept(c, self.object())
6685         }
6687         fn visit_hint(&mut self, c: &mut (), h: &ast::Hint) -> StdResult<(), Option<Error>> {
6688             use ast::{Hint_ as H_, Id};
6689             match h.1.as_ref() {
6690                 H_::Happly(Id(_, id), _)
6691                     if self.erased_tparams.contains(&id.as_str()) && self.isas =>
6692                 {
6693                     return Err(Some(emit_fatal::raise_fatal_parse(
6694                         &h.0,
6695                         "Erased generics are not allowed in is/as expressions",
6696                     )));
6697                 }
6698                 H_::Haccess(_, _) => return Ok(()),
6699                 _ => {}
6700             }
6701             h.recurse(c, self.object())
6702         }
6704         fn visit_hint_(&mut self, c: &mut (), h: &ast::Hint_) -> StdResult<(), Option<Error>> {
6705             use ast::{Hint_ as H_, Id};
6706             match h {
6707                 H_::Happly(Id(_, id), _) if self.erased_tparams.contains(&id.as_str()) => Err(None),
6708                 _ => h.recurse(c, self.object()),
6709             }
6710         }
6711     }
6713     struct Updater<'s> {
6714         erased_tparams: &'s [&'s str],
6715     }
6716     impl<'ast, 's> VisitorMut<'ast> for Updater<'s> {
6717         type P = AstParams<(), ()>;
6719         fn object(&mut self) -> &mut dyn VisitorMut<'ast, P = Self::P> {
6720             self
6721         }
6723         fn visit_hint_fun(&mut self, c: &mut (), hf: &mut ast::HintFun) -> StdResult<(), ()> {
6724             <Vec<ast::Hint> as NodeMut<Self::P>>::accept(&mut hf.param_tys, c, self.object())?;
6725             <ast::Hint as NodeMut<Self::P>>::accept(&mut hf.return_ty, c, self.object())
6726         }
6728         fn visit_hint_(&mut self, c: &mut (), h: &mut ast::Hint_) -> StdResult<(), ()> {
6729             use ast::{Hint_ as H_, Id};
6730             match h {
6731                 H_::Happly(Id(_, id), _) if self.erased_tparams.contains(&id.as_str()) => {
6732                     Ok(*id = "_".into())
6733                 }
6734                 _ => h.recurse(c, self.object()),
6735             }
6736         }
6737     }
6738     let erased_tparams = get_erased_tparams(env);
6739     let erased_tparams = erased_tparams.as_slice();
6740     let mut checker = Checker {
6741         erased_tparams,
6742         isas,
6743     };
6744     match visit(&mut checker, &mut (), hint) {
6745         Ok(()) => Ok(Either::Left(hint)),
6746         Err(Some(error)) => Err(error),
6747         Err(None) => {
6748             let mut updater = Updater { erased_tparams };
6749             let mut hint = hint.clone();
6750             visit_mut(&mut updater, &mut (), &mut hint).unwrap();
6751             Ok(Either::Right(hint))
6752         }
6753     }
6756 pub fn emit_reified_arg<'b, 'arena, 'decl, D: DeclProvider<'decl>>(
6757     e: &mut Emitter<'arena, 'decl, D>,
6758     env: &Env<'b, 'arena>,
6759     pos: &Pos,
6760     isas: bool,
6761     hint: &ast::Hint,
6762 ) -> Result<(InstrSeq<'arena>, bool)> {
6763     struct Collector<'ast, 'a> {
6764         current_tags: &'a HashSet<&'a str>,
6765         acc: IndexSet<&'ast str>,
6766     }
6768     impl<'ast, 'a> Collector<'ast, 'a> {
6769         fn add_name(&mut self, name: &'ast str) {
6770             if self.current_tags.contains(name) && !self.acc.contains(name) {
6771                 self.acc.insert(name);
6772             }
6773         }
6774     }
6776     impl<'ast, 'a> Visitor<'ast> for Collector<'ast, 'a> {
6777         type P = AstParams<(), ()>;
6779         fn object(&mut self) -> &mut dyn Visitor<'ast, P = Self::P> {
6780             self
6781         }
6783         fn visit_hint_(&mut self, c: &mut (), h_: &'ast ast::Hint_) -> StdResult<(), ()> {
6784             use ast::{Hint_ as H_, Id};
6785             match h_ {
6786                 H_::Haccess(_, sids) => Ok(sids.iter().for_each(|Id(_, name)| self.add_name(name))),
6787                 H_::Habstr(name, h) | H_::Happly(Id(_, name), h) => {
6788                     self.add_name(name);
6789                     h.accept(c, self.object())
6790                 }
6791                 _ => h_.recurse(c, self.object()),
6792             }
6793         }
6794     }
6795     let hint = fixup_type_arg(env, isas, hint)?;
6796     let hint = hint.as_ref();
6797     fn f<'a>(mut acc: HashSet<&'a str>, tparam: &'a ast::Tparam) -> HashSet<&'a str> {
6798         if tparam.reified != ast::ReifyKind::Erased {
6799             acc.insert(&tparam.name.1);
6800         }
6801         acc
6802     }
6803     let current_tags = env
6804         .scope
6805         .get_fun_tparams()
6806         .iter()
6807         .fold(HashSet::<&str>::default(), |acc, t| f(acc, &*t));
6808     let class_tparams = env.scope.get_class_tparams();
6809     let current_tags = class_tparams
6810         .iter()
6811         .fold(current_tags, |acc, t| f(acc, &*t));
6812     let mut collector = Collector {
6813         current_tags: &current_tags,
6814         acc: IndexSet::new(),
6815     };
6816     visit(&mut collector, &mut (), hint).unwrap();
6817     match hint.1.as_ref() {
6818         ast::Hint_::Happly(ast::Id(_, name), hs)
6819             if hs.is_empty() && current_tags.contains(name.as_str()) =>
6820         {
6821             Ok((emit_reified_type::<D>(env, pos, name)?, false))
6822         }
6823         _ => {
6824             let alloc = env.arena;
6825             let ts = get_type_structure_for_hint(alloc, e, &[], &collector.acc, hint)?;
6826             let ts_list = if collector.acc.is_empty() {
6827                 ts
6828             } else {
6829                 let values = collector
6830                     .acc
6831                     .iter()
6832                     .map(|v| emit_reified_type::<D>(env, pos, v))
6833                     .collect::<Result<Vec<_>>>()?;
6834                 InstrSeq::gather(alloc, vec![InstrSeq::gather(alloc, values), ts])
6835             };
6836             Ok((
6837                 InstrSeq::gather(
6838                     alloc,
6839                     vec![
6840                         ts_list,
6841                         instr::combine_and_resolve_type_struct(
6842                             alloc,
6843                             (collector.acc.len() + 1) as isize,
6844                         ),
6845                     ],
6846                 ),
6847                 collector.acc.is_empty(),
6848             ))
6849         }
6850     }
6853 pub fn get_local<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
6854     e: &mut Emitter<'arena, 'decl, D>,
6855     env: &Env<'a, 'arena>,
6856     pos: &Pos,
6857     s: &str,
6858 ) -> std::result::Result<Local<'arena>, hhbc_by_ref_instruction_sequence::Error> {
6859     let alloc: &'arena bumpalo::Bump = env.arena;
6860     if s == special_idents::DOLLAR_DOLLAR {
6861         match &env.pipe_var {
6862             None => Err(emit_fatal::raise_fatal_runtime(
6863                 pos,
6864                 "Pipe variables must occur only in the RHS of pipe expressions",
6865             )),
6866             Some(var) => Ok(*var),
6867         }
6868     } else if special_idents::is_tmp_var(s) {
6869         Ok(*e.local_gen().get_unnamed_for_tempname(s))
6870     } else {
6871         Ok(Local::Named(Str::new_str(alloc, s)))
6872     }
6875 pub fn emit_is_null<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
6876     e: &mut Emitter<'arena, 'decl, D>,
6877     env: &Env<'a, 'arena>,
6878     expr: &ast::Expr,
6879 ) -> Result<InstrSeq<'arena>> {
6880     let alloc = env.arena;
6881     if let Some(ast::Lid(pos, id)) = expr.2.as_lvar() {
6882         if !is_local_this(env, id) {
6883             return Ok(instr::istypel(
6884                 alloc,
6885                 get_local(e, env, pos, local_id::get_name(id))?,
6886                 IstypeOp::OpNull,
6887             ));
6888         }
6889     }
6891     Ok(InstrSeq::gather(
6892         alloc,
6893         vec![
6894             emit_expr(e, env, expr)?,
6895             instr::istypec(alloc, IstypeOp::OpNull),
6896         ],
6897     ))
6900 pub fn emit_jmpnz<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
6901     e: &mut Emitter<'arena, 'decl, D>,
6902     env: &Env<'a, 'arena>,
6903     expr: &ast::Expr,
6904     label: Label,
6905 ) -> Result<EmitJmpResult<'arena>> {
6906     let alloc: &'arena bumpalo::Bump = env.arena;
6907     let ast::Expr(_, pos, expr_) = expr;
6908     let opt = optimize_null_checks(e);
6909     Ok(
6910         match ast_constant_folder::expr_to_typed_value(alloc, e, expr) {
6911             Ok(tv) => {
6912                 if Into::<bool>::into(tv) {
6913                     EmitJmpResult {
6914                         instrs: emit_pos_then(alloc, pos, instr::jmp(alloc, label)),
6915                         is_fallthrough: false,
6916                         is_label_used: true,
6917                     }
6918                 } else {
6919                     EmitJmpResult {
6920                         instrs: emit_pos_then(alloc, pos, instr::empty(alloc)),
6921                         is_fallthrough: true,
6922                         is_label_used: false,
6923                     }
6924                 }
6925             }
6926             Err(_) => {
6927                 use {ast::Expr_ as E, ast_defs::Uop as U};
6928                 match expr_ {
6929                     E::Unop(uo) if uo.0 == U::Unot => emit_jmpz(e, env, &uo.1, label)?,
6930                     E::Binop(bo) if bo.0.is_barbar() => {
6931                         let r1 = emit_jmpnz(e, env, &bo.1, label)?;
6932                         if r1.is_fallthrough {
6933                             let r2 = emit_jmpnz(e, env, &bo.2, label)?;
6934                             EmitJmpResult {
6935                                 instrs: emit_pos_then(
6936                                     alloc,
6937                                     pos,
6938                                     InstrSeq::gather(alloc, vec![r1.instrs, r2.instrs]),
6939                                 ),
6940                                 is_fallthrough: r2.is_fallthrough,
6941                                 is_label_used: r1.is_label_used || r2.is_label_used,
6942                             }
6943                         } else {
6944                             r1
6945                         }
6946                     }
6947                     E::Binop(bo) if bo.0.is_ampamp() => {
6948                         let skip_label = e.label_gen_mut().next_regular();
6949                         let r1 = emit_jmpz(e, env, &bo.1, skip_label)?;
6950                         if !r1.is_fallthrough {
6951                             EmitJmpResult {
6952                                 instrs: emit_pos_then(
6953                                     alloc,
6954                                     pos,
6955                                     InstrSeq::gather(
6956                                         alloc,
6957                                         if r1.is_label_used {
6958                                             vec![r1.instrs, instr::label(alloc, skip_label)]
6959                                         } else {
6960                                             vec![r1.instrs]
6961                                         },
6962                                     ),
6963                                 ),
6964                                 is_fallthrough: r1.is_label_used,
6965                                 is_label_used: false,
6966                             }
6967                         } else {
6968                             let r2 = emit_jmpnz(e, env, &bo.2, label)?;
6969                             EmitJmpResult {
6970                                 instrs: emit_pos_then(
6971                                     alloc,
6972                                     pos,
6973                                     InstrSeq::gather(
6974                                         alloc,
6975                                         if r1.is_label_used {
6976                                             vec![
6977                                                 r1.instrs,
6978                                                 r2.instrs,
6979                                                 instr::label(alloc, skip_label),
6980                                             ]
6981                                         } else {
6982                                             vec![r1.instrs, r2.instrs]
6983                                         },
6984                                     ),
6985                                 ),
6986                                 is_fallthrough: r2.is_fallthrough || r1.is_label_used,
6987                                 is_label_used: r2.is_label_used,
6988                             }
6989                         }
6990                     }
6991                     E::Binop(bo)
6992                         if bo.0.is_eqeqeq()
6993                             && ((bo.1).2.is_null() || (bo.2).2.is_null())
6994                             && opt =>
6995                     {
6996                         let is_null =
6997                             emit_is_null(e, env, if (bo.1).2.is_null() { &bo.2 } else { &bo.1 })?;
6998                         EmitJmpResult {
6999                             instrs: emit_pos_then(
7000                                 alloc,
7001                                 pos,
7002                                 InstrSeq::gather(alloc, vec![is_null, instr::jmpnz(alloc, label)]),
7003                             ),
7004                             is_fallthrough: true,
7005                             is_label_used: true,
7006                         }
7007                     }
7008                     E::Binop(bo)
7009                         if bo.0.is_diff2() && ((bo.1).2.is_null() || (bo.2).2.is_null()) && opt =>
7010                     {
7011                         let is_null =
7012                             emit_is_null(e, env, if (bo.1).2.is_null() { &bo.2 } else { &bo.1 })?;
7013                         EmitJmpResult {
7014                             instrs: emit_pos_then(
7015                                 alloc,
7016                                 pos,
7017                                 InstrSeq::gather(alloc, vec![is_null, instr::jmpz(alloc, label)]),
7018                             ),
7019                             is_fallthrough: true,
7020                             is_label_used: true,
7021                         }
7022                     }
7023                     _ => {
7024                         let instr = emit_expr(e, env, expr)?;
7025                         EmitJmpResult {
7026                             instrs: emit_pos_then(
7027                                 alloc,
7028                                 pos,
7029                                 InstrSeq::gather(alloc, vec![instr, instr::jmpnz(alloc, label)]),
7030                             ),
7031                             is_fallthrough: true,
7032                             is_label_used: true,
7033                         }
7034                     }
7035                 }
7036             }
7037         },
7038     )
7041 pub fn emit_jmpz<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
7042     e: &mut Emitter<'arena, 'decl, D>,
7043     env: &Env<'a, 'arena>,
7044     expr: &ast::Expr,
7045     label: Label,
7046 ) -> std::result::Result<EmitJmpResult<'arena>, hhbc_by_ref_instruction_sequence::Error> {
7047     let alloc: &'arena bumpalo::Bump = env.arena;
7048     let ast::Expr(_, pos, expr_) = expr;
7049     let opt = optimize_null_checks(e);
7050     Ok(
7051         match ast_constant_folder::expr_to_typed_value(alloc, e, expr) {
7052             Ok(v) => {
7053                 let b: bool = v.into();
7054                 if b {
7055                     EmitJmpResult {
7056                         instrs: emit_pos_then(alloc, pos, instr::empty(alloc)),
7057                         is_fallthrough: true,
7058                         is_label_used: false,
7059                     }
7060                 } else {
7061                     EmitJmpResult {
7062                         instrs: emit_pos_then(alloc, pos, instr::jmp(alloc, label)),
7063                         is_fallthrough: false,
7064                         is_label_used: true,
7065                     }
7066                 }
7067             }
7068             Err(_) => {
7069                 use {ast::Expr_ as E, ast_defs::Uop as U};
7070                 match expr_ {
7071                     E::Unop(uo) if uo.0 == U::Unot => emit_jmpnz(e, env, &uo.1, label)?,
7072                     E::Binop(bo) if bo.0.is_barbar() => {
7073                         let skip_label = e.label_gen_mut().next_regular();
7074                         let r1 = emit_jmpnz(e, env, &bo.1, skip_label)?;
7075                         if !r1.is_fallthrough {
7076                             EmitJmpResult {
7077                                 instrs: emit_pos_then(
7078                                     alloc,
7079                                     pos,
7080                                     InstrSeq::gather(
7081                                         alloc,
7082                                         if r1.is_label_used {
7083                                             vec![r1.instrs, instr::label(alloc, skip_label)]
7084                                         } else {
7085                                             vec![r1.instrs]
7086                                         },
7087                                     ),
7088                                 ),
7089                                 is_fallthrough: r1.is_label_used,
7090                                 is_label_used: false,
7091                             }
7092                         } else {
7093                             let r2 = emit_jmpz(e, env, &bo.2, label)?;
7094                             EmitJmpResult {
7095                                 instrs: emit_pos_then(
7096                                     alloc,
7097                                     pos,
7098                                     InstrSeq::gather(
7099                                         alloc,
7100                                         if r1.is_label_used {
7101                                             vec![
7102                                                 r1.instrs,
7103                                                 r2.instrs,
7104                                                 instr::label(alloc, skip_label),
7105                                             ]
7106                                         } else {
7107                                             vec![r1.instrs, r2.instrs]
7108                                         },
7109                                     ),
7110                                 ),
7111                                 is_fallthrough: r2.is_fallthrough || r1.is_label_used,
7112                                 is_label_used: r2.is_label_used,
7113                             }
7114                         }
7115                     }
7116                     E::Binop(bo) if bo.0.is_ampamp() => {
7117                         let r1 = emit_jmpz(e, env, &bo.1, label)?;
7118                         if r1.is_fallthrough {
7119                             let r2 = emit_jmpz(e, env, &bo.2, label)?;
7120                             EmitJmpResult {
7121                                 instrs: emit_pos_then(
7122                                     alloc,
7123                                     pos,
7124                                     InstrSeq::gather(alloc, vec![r1.instrs, r2.instrs]),
7125                                 ),
7126                                 is_fallthrough: r2.is_fallthrough,
7127                                 is_label_used: r1.is_label_used || r2.is_label_used,
7128                             }
7129                         } else {
7130                             EmitJmpResult {
7131                                 instrs: emit_pos_then(alloc, pos, r1.instrs),
7132                                 is_fallthrough: false,
7133                                 is_label_used: r1.is_label_used,
7134                             }
7135                         }
7136                     }
7137                     E::Binop(bo)
7138                         if bo.0.is_eqeqeq()
7139                             && ((bo.1).2.is_null() || (bo.2).2.is_null())
7140                             && opt =>
7141                     {
7142                         let is_null =
7143                             emit_is_null(e, env, if (bo.1).2.is_null() { &bo.2 } else { &bo.1 })?;
7144                         EmitJmpResult {
7145                             instrs: emit_pos_then(
7146                                 alloc,
7147                                 pos,
7148                                 InstrSeq::gather(alloc, vec![is_null, instr::jmpz(alloc, label)]),
7149                             ),
7150                             is_fallthrough: true,
7151                             is_label_used: true,
7152                         }
7153                     }
7154                     E::Binop(bo)
7155                         if bo.0.is_diff2() && ((bo.1).2.is_null() || (bo.2).2.is_null()) && opt =>
7156                     {
7157                         let is_null =
7158                             emit_is_null(e, env, if (bo.1).2.is_null() { &bo.2 } else { &bo.1 })?;
7159                         EmitJmpResult {
7160                             instrs: emit_pos_then(
7161                                 alloc,
7162                                 pos,
7163                                 InstrSeq::gather(alloc, vec![is_null, instr::jmpnz(alloc, label)]),
7164                             ),
7165                             is_fallthrough: true,
7166                             is_label_used: true,
7167                         }
7168                     }
7169                     _ => {
7170                         let instr = emit_expr(e, env, expr)?;
7171                         EmitJmpResult {
7172                             instrs: emit_pos_then(
7173                                 alloc,
7174                                 pos,
7175                                 InstrSeq::gather(alloc, vec![instr, instr::jmpz(alloc, label)]),
7176                             ),
7177                             is_fallthrough: true,
7178                             is_label_used: true,
7179                         }
7180                     }
7181                 }
7182             }
7183         },
7184     )