1 // Copyright (c) Facebook, Inc. and its affiliates.
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::{
21 Error::{self, Unrecoverable},
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,
38 aast_visitor::{visit, visit_mut, AstParams, Node, NodeMut, Visitor, VisitorMut},
39 ast, ast_defs, local_id,
45 use indexmap::IndexSet;
47 collections::BTreeMap, convert::TryInto, iter, result::Result as StdResult, str::FromStr,
51 pub struct EmitJmpResult<'arena> {
52 // generated instruction sequence
53 pub instrs: InstrSeq<'arena>,
54 // does instruction sequence fall through
56 // was label associated with emit operation used
60 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
69 fn is_incdec(&self) -> bool {
70 if let Self::IncDec(_) = self {
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()
86 use oxidized::{aast_defs::Lid, aast_visitor, aast_visitor::Node, ast, ast_defs};
87 use std::marker::PhantomData;
89 pub(super) struct AliasInfo {
95 impl Default for AliasInfo {
96 fn default() -> Self {
98 first_inout: std::isize::MAX,
99 last_write: std::isize::MIN,
106 pub(super) fn add_inout(&mut self, i: isize) {
107 if i < self.first_inout {
108 self.first_inout = i;
112 pub(super) fn add_write(&mut self, i: isize) {
113 if i > self.last_write {
118 pub(super) fn add_use(&mut self) {
122 pub(super) fn in_range(&self, i: isize) -> bool {
123 i > self.first_inout || i <= self.last_write
126 pub(super) fn has_single_ref(&self) -> bool {
131 pub(super) type AliasInfoMap<'ast> = HashMap<&'ast str, AliasInfo>;
133 pub(super) fn new_alias_info_map<'ast>() -> AliasInfoMap<'ast> {
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);
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);
145 fn add_use<'ast>(name: &'ast str, map: &mut AliasInfoMap<'ast>) {
146 map.entry(name.as_ref()).or_default().add_use();
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 {
154 .map_or(false, |alias| alias.in_range(i as isize))
157 pub(super) fn should_move_local_value<'arena>(
158 local: &Local<'arena>,
159 aliases: &AliasInfoMap,
162 Local::Named(name) => aliases
164 .map_or(true, |alias| alias.has_single_ref()),
165 Local::Unnamed(_) => false,
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();
176 .for_each(|(i, arg)| handle_arg(env, true, i, arg, &mut acc));
180 fn handle_arg<'ast, 'arena>(
181 env: &Env<'ast, 'arena>,
184 arg: &'ast ast::Expr,
185 acc: &mut AliasInfoMap<'ast>,
187 use ast::{Expr, Expr_};
188 let Expr(_, _, e) = arg;
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);
195 add_inout(lid.1.as_str(), i, acc);
197 add_write(lid.1.as_str(), i, acc);
202 if let Some(Lid(_, (_, id))) = e.as_lvar() {
203 return add_use(id.as_str(), acc);
205 // dive into argument value
207 &mut Visitor(PhantomData),
208 &mut Ctx { state: acc, env, i },
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>,
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> {
231 c: &mut Ctx<'r, 'ast, 'arena>,
233 ) -> std::result::Result<(), ()> {
234 // f(inout $v) or f(&$v)
235 if let ast::Expr_::Call(expr) = p {
236 let (_, _, args, uarg) = &**expr;
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)
244 p.recurse(c, self.object())?;
247 ast::Expr_::Binop(expr) => {
248 let (bop, left, _) = &**expr;
249 if let ast_defs::Bop::Eq(_) = bop {
250 collect_lvars_hs(c, left)
254 ast::Expr_::Unop(expr) => {
255 let (uop, e) = &**expr;
257 ast_defs::Uop::Uincr | ast_defs::Uop::Udecr => collect_lvars_hs(c, e),
262 ast::Expr_::Lvar(expr) => {
263 let Lid(_, (_, id)) = &**expr;
264 add_use(id, &mut c.state);
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;
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);
283 ast::Expr_::List(exprs) => exprs.iter().for_each(|expr| collect_lvars_hs(ctx, expr)),
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>,
293 targ_map: &IndexSet<&str>,
295 ) -> std::result::Result<InstrSeq<'arena>, hhbc_by_ref_instruction_sequence::Error> {
296 let targ_map: BTreeMap<&str, i64> = targ_map
299 .map(|(i, n)| (*n, i as i64))
301 let tv = emit_type_constant::hint_to_type_constant(
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 {
320 /// kind of value stored in local
321 #[derive(Debug, Clone, Copy)]
322 pub enum StoredValueKind {
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
333 /// setl/popl l; depending on local_kind
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:
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>,
355 struct ArrayGetBaseData<'arena, 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
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>,
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>> {
384 let alloc = env.arena;
385 let ast::Expr(_, pos, expr) = expression;
388 | Expr_::EnumClassLabel(_)
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)))
398 Expr_::PrefixedString(e) => emit_expr(emitter, env, &e.1),
400 let Lid(pos, _) = &**e;
404 emit_pos(alloc, pos),
405 emit_local(emitter, env, BareThisOp::Notice, e)?,
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)?;
418 vec![emit_expr(emitter, env, e)?, is],
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;
433 opt_elem_expr.as_ref(),
439 Expr_::ObjGet(e) => Ok(emit_obj_get(
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(
458 emit_collection(emitter, env, expression, &mk_afkvalues(&e.1), None)?,
460 Expr_::Varray(e) => Ok(emit_pos_then(
463 emit_collection(emitter, env, expression, &mk_afvalues(&e.1), None)?,
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),
476 emit_named_collection(emitter, env, pos, expression, &fields, collection_typ)
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)
483 Expr_::KeyValCollection(e) => {
484 let (kind, _, fields) = &**e;
485 let fields = mk_afkvalues(
489 .map(|ast::Field(e1, e2)| (e1, e2))
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),
498 emit_named_collection(emitter, env, pos, expression, &fields, collection_typ)
500 Expr_::Clone(e) => Ok(emit_pos_then(alloc, pos, emit_clone(emitter, env, e)?)),
501 Expr_::Shape(e) => Ok(emit_pos_then(
504 emit_shape(emitter, env, expression, e)?,
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(
512 emit_lambda(emitter, env, &e.0, &e.1)?,
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)
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",
524 Expr_::Callconv(_) => Err(unrecoverable(
525 "emit_callconv: This should have been caught at emit_arg",
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",
532 Expr_::List(_) => Err(emit_fatal::raise_fatal_parse(
534 "list() can only be used as an lvar. Did you mean to use tuple()?",
536 Expr_::Tuple(e) => Ok(emit_pos_then(
539 emit_collection(emitter, env, expression, &mk_afvalues(&e), None)?,
542 Expr_::This | Expr_::Lplaceholder(_) | Expr_::Dollardollar(_) => {
543 unimplemented!("TODO(hrust) Codegen after naming pass on AAST")
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",
551 | Expr_::MethodCaller(_)
552 | Expr_::SmethodId(_)
553 | Expr_::Hole(_) => {
554 unimplemented!("TODO(hrust)")
559 fn emit_exprs<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
560 e: &mut Emitter<'arena, 'decl, D>,
561 env: &Env<'a, 'arena>,
563 ) -> Result<InstrSeq<'arena>> {
564 let alloc = env.arena;
565 if exprs.is_empty() {
566 Ok(instr::empty(alloc))
572 .map(|expr| emit_expr(e, env, expr))
573 .collect::<Result<Vec<_>>>()?,
578 fn emit_id<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
579 emitter: &mut Emitter<'arena, 'decl, D>,
580 env: &Env<'a, 'arena>,
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(
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")
601 G__NAMESPACE__ => instr::string(alloc, env.namespace.name.as_ref().map_or("", |s| &s[..])),
602 EXIT | DIE => return emit_exit(emitter, env, None),
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))));
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;
623 expr_opt.map_or_else(|| Ok(instr::int(alloc, 0)), |e| emit_expr(emitter, env, e))?,
629 fn emit_yield<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
630 e: &mut Emitter<'arena, 'decl, D>,
631 env: &Env<'a, 'arena>,
634 ) -> Result<InstrSeq<'arena>> {
635 let alloc = env.arena;
637 ast::Afield::AFvalue(v) => InstrSeq::gather(
640 emit_expr(e, env, v)?,
641 emit_pos(alloc, pos),
642 instr::yield_(alloc),
645 ast::Afield::AFkvalue(k, v) => InstrSeq::gather(
648 emit_expr(e, env, k)?,
649 emit_expr(e, env, v)?,
650 emit_pos(alloc, pos),
651 instr::yieldk(alloc),
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('/') {
663 fn split_var_lit(e: &ast::Expr) -> (String, String) {
665 ast::Expr_::Binop(x) if x.0.is_dot() => {
666 let (v, l) = split_var_lit(&x.2);
668 let (var, lit) = split_var_lit(&x.1);
669 (var, format!("{}{}", lit, l))
674 ast::Expr_::String(lit) => (String::new(), lit.to_string()),
675 _ => (text_of_expr(e), String::new()),
678 let (mut var, mut lit) = split_var_lit(e);
679 if var == pseudo_consts::G__DIR__ {
681 strip_backslash(&mut lit);
684 if std::path::Path::new(lit.as_str()).is_relative() {
685 IncludePath::SearchPathRelative(Str::new_str(alloc, lit))
687 IncludePath::Absolute(Str::new_str(alloc, lit))
690 strip_backslash(&mut lit);
691 IncludePath::IncludeRootRelative(Str::new_str(alloc, var), Str::new_str(alloc, lit))
695 fn text_of_expr(e: &ast::Expr) -> String {
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_))
704 _ => "unknown".into(),
706 _ => "unknown".into(),
710 fn text_of_class_id(cid: &ast::ClassId) -> String {
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(),
720 fn text_of_prop(prop: &ast::ClassGetExpr) -> String {
722 ast::ClassGetExpr::CGstring((_, s)) => s.into(),
723 ast::ClassGetExpr::CGexpr(e) => text_of_expr(e),
727 fn emit_import<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
728 e: &mut Emitter<'arena, 'decl, D>,
729 env: &Env<'a, 'arena>,
731 flavor: &ast::ImportFlavor,
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))
748 _ => (emit_expr(e, env, expr)?, instr::reqonce(alloc)),
754 vec![expr_instrs, emit_pos(alloc, pos), import_op_instr],
758 fn emit_string2<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
759 e: &mut Emitter<'arena, 'decl, D>,
760 env: &Env<'a, 'arena>,
763 ) -> Result<InstrSeq<'arena>> {
764 let alloc = env.arena;
766 Err(unrecoverable("String2 with zero araguments is impossible"))
767 } else if es.len() == 1 {
771 emit_expr(e, env, &es[0])?,
772 emit_pos(alloc, pos),
773 instr::cast_string(alloc),
780 emit_two_exprs(e, env, &es[0].1, &es[0], &es[1])?,
781 emit_pos(alloc, pos),
782 instr::concat(alloc),
791 emit_expr(e, env, expr)?,
792 emit_pos(alloc, pos),
793 instr::concat(alloc),
797 .collect::<Result<_>>()?,
804 fn emit_clone<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
805 e: &mut Emitter<'arena, 'decl, D>,
806 env: &Env<'a, 'arena>,
808 ) -> Result<InstrSeq<'arena>> {
809 let alloc = env.arena;
812 vec![emit_expr(e, env, expr)?, instr::clone(alloc)],
816 fn emit_lambda<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
817 e: &mut Emitter<'arena, 'decl, D>,
818 env: &Env<'a, 'arena>,
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
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();
837 .map(|ast::Lid(pos, id)| {
838 match string_utils::reified::is_captured_generic(local_id::get_name(id)) {
839 Some((is_fun, i)) => {
843 Local::Named(Str::new_str(
845 string_utils::reified::reified_generic_captured_name(
852 emit_reified_generic_instrs(
861 let lid = get_local(e, env, pos, local_id::get_name(id))?;
863 instr::cgetl(alloc, lid)
865 instr::cugetl(alloc, lid)
870 .collect::<Result<Vec<_>>>()?,
872 instr::createcl(alloc, ids.len(), cls_num),
877 pub fn emit_await<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
878 emitter: &mut Emitter<'arena, 'decl, D>,
879 env: &Env<'a, 'arena>,
882 ) -> Result<InstrSeq<'arena>> {
883 let ast::Expr(_, _, e) = expr;
884 let alloc = env.arena;
885 let cant_inline_gen_functions = !emitter
889 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION);
891 Some((ast::Expr(_, _, ast::Expr_::Id(id)), _, args, None))
892 if (cant_inline_gen_functions
894 && string_utils::strip_global_ns(&(*id.1)) == "gena") =>
896 inline_gena_call(emitter, env, &args[0])
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)?
904 _ => emit_expr(emitter, env, expr)?,
910 emit_pos(alloc, pos),
912 instr::istypec(alloc, IstypeOp::OpNull),
913 instr::jmpnz(alloc, after_await.clone()),
914 instr::await_(alloc),
915 instr::label(alloc, after_await),
922 fn inline_gena_call<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
923 emitter: &mut Emitter<'arena, 'decl, D>,
924 env: &Env<'a, 'arena>,
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(
936 instr::cast_dict(alloc),
937 instr::popl(alloc, arr_local),
941 let inner = InstrSeq::gather(
944 instr::nulluninit(alloc),
945 instr::nulluninit(alloc),
946 instr::cgetl(alloc, arr_local),
947 instr::fcallclsmethodd(
950 FcallFlags::default(),
954 Some(async_eager_label),
958 method::from_raw_string(alloc, "fromDict"),
959 class::from_raw_string(alloc, "HH\\AwaitAllWaitHandle"),
961 instr::await_(alloc),
962 instr::label(alloc, async_eager_label),
967 instr::cgetl(alloc, arr_local),
968 |alloc, val_local, key_local| {
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)),
984 let after = instr::pushl(alloc, arr_local);
986 Ok((before, inner, after))
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>,
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 {
1009 key_id: Just(key_id),
1012 let iter_init = InstrSeq::gather(
1016 instr::iterinit(alloc, iter_args.clone(), loop_end),
1019 let iterate = InstrSeq::gather(
1022 instr::label(alloc, loop_next),
1023 f(alloc, val_id, key_id),
1024 instr::iternext(alloc, iter_args, loop_next),
1027 let iter_done = InstrSeq::gather(
1030 instr::unsetl(alloc, val_id),
1031 instr::unsetl(alloc, key_id),
1032 instr::label(alloc, loop_end),
1035 Ok((iter_init, iterate, iter_done))
1039 fn emit_shape<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1040 emitter: &mut Emitter<'arena, 'decl, D>,
1041 env: &Env<'a, 'arena>,
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>,
1048 field: &ast_defs::ShapeFieldName,
1049 ) -> Result<ast::Expr_> {
1050 use ast_defs::ShapeFieldName as SF;
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()
1058 return Err(emit_fatal::raise_fatal_parse(
1060 "Reified generics cannot be used in shape keys",
1063 ast::Expr_::mk_class_const(
1064 ast::ClassId((), pos.clone(), ast::ClassId_::CI(id.clone())),
1072 // TODO(hrust): avoid clone
1080 extract_shape_field_name_pstring(env, pos, f)?,
1085 .collect::<Result<Vec<_>>>()?;
1089 &ast::Expr((), pos.clone(), ast::Expr_::mk_darray(None, fl)),
1093 fn emit_vec_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1094 e: &mut Emitter<'arena, 'decl, D>,
1095 env: &Env<'a, 'arena>,
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),
1106 fn emit_named_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1107 e: &mut Emitter<'arena, 'decl, D>,
1108 env: &Env<'a, 'arena>,
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))
1122 emit_vec_collection(e, env, pos, fields)?,
1123 instr::colfromarray(alloc, collection_type),
1128 let emit_map_or_set = |e: &mut Emitter<'arena, 'decl, D>, collection_type| {
1129 if fields.is_empty() {
1133 instr::newcol(alloc, collection_type),
1136 emit_collection(e, env, expr, fields, Some(collection_type))
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))
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(
1155 ast::Afield::AFvalue(v) => emit_expr(e, env, v),
1156 _ => Err(unrecoverable("impossible Pair argument")),
1158 .collect::<Result<_>>()?,
1160 instr::new_pair(alloc),
1163 _ => Err(unrecoverable("Unexpected named collection type")),
1167 fn emit_named_collection_str<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1168 e: &mut Emitter<'arena, 'decl, D>,
1169 env: &Env<'a, 'arena>,
1171 (ast_defs::Id(pos, name), _, fields): &(
1173 Option<ast::CollectionTarg>,
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 {
1185 "ImmVector" => ImmVector,
1192 return Err(unrecoverable(format!(
1193 "collection: {} does not exist",
1198 emit_named_collection(e, env, pos, expr, fields, ctype)
1201 fn mk_afkvalues(es: &[(ast::Expr, ast::Expr)]) -> Vec<ast::Afield> {
1204 .map(|(e1, e2)| ast::Afield::mk_afkvalue(e1, e2))
1208 fn mk_afvalues(es: &[ast::Expr]) -> Vec<ast::Afield> {
1211 .map(ast::Afield::mk_afvalue)
1215 fn emit_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1216 e: &mut Emitter<'arena, 'decl, D>,
1217 env: &Env<'a, 'arena>,
1219 fields: &[ast::Afield],
1220 transform_to_collection: Option<CollectionType>,
1221 ) -> Result<InstrSeq<'arena>> {
1222 let alloc = env.arena;
1224 match ast_constant_folder::expr_to_typed_value_(
1225 alloc, e, expr, true, /*allow_map*/
1226 false, /*force_class_const*/
1228 Ok(tv) => emit_static_collection::<D>(env, transform_to_collection, pos, tv),
1229 Err(_) => emit_dynamic_collection(e, env, expr, fields),
1233 fn emit_static_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1234 env: &Env<'a, 'arena>,
1235 transform_to_collection: Option<CollectionType>,
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),
1244 Ok(InstrSeq::gather(
1247 emit_pos(alloc, pos),
1248 instr::typedvalue(alloc, tv),
1254 fn expr_and_new<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1255 e: &mut Emitter<'arena, 'decl, D>,
1256 env: &Env<'a, 'arena>,
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;
1264 ast::Afield::AFvalue(v) => Ok(InstrSeq::gather(
1267 emit_expr(e, env, v)?,
1268 emit_pos(alloc, pos),
1272 ast::Afield::AFkvalue(k, v) => Ok(InstrSeq::gather(
1274 vec![emit_two_exprs(e, env, &k.1, k, v)?, instr_to_add],
1279 fn emit_keyvalue_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1280 e: &mut Emitter<'arena, 'decl, D>,
1281 env: &Env<'a, 'arena>,
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))
1293 instr::colfromarray(alloc, ctype),
1294 InstrSeq::gather(alloc, vec![instr::dup(alloc), instr::add_elemc(alloc)]),
1297 let emitted_pos = emit_pos(alloc, pos);
1298 Ok(InstrSeq::gather(
1301 InstrSeq::<'arena>::clone(alloc, &emitted_pos),
1302 instr::lit_const(alloc, constructor),
1310 InstrSeq::<'arena>::clone(alloc, &add_elem_instr),
1311 instr::add_elemc(alloc),
1315 .collect::<Result<_>>()
1316 .map(|s| InstrSeq::gather(alloc, s))?,
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("_", "");
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;
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)
1345 } else if BINARY.is_match(s) {
1346 u64::from_str_radix(&BINARY.replace(s, "${sign}${digits}"), *BINARY_RADIX)
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() {
1358 let pow = s.split('.').last().unwrap().len();
1359 (i as f64) / f64::from(radix).powi(pow as i32)
1361 _ => return Err(()),
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()
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)
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() {
1390 f64::from_str(s).map_err(|_| ()).and_then(validate_float)
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,
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
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()) },
1418 uniq_keys.insert(s);
1420 are_all_keys_non_numeric_strings = false;
1424 are_all_keys_non_numeric_strings = false;
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
1434 fn emit_struct_array<
1438 D: DeclProvider<'decl>,
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>,
1448 fields: &[ast::Afield],
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
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)?,
1464 let mut k = k.clone();
1465 ast_constant_folder::fold_expr(&mut k, alloc, e)
1466 .map_err(|e| unrecoverable(format!("{}", e)))?;
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)?,
1474 _ => Err(unrecoverable("Key must be a string")),
1478 _ => Err(unrecoverable("impossible")),
1480 .collect::<Result<Vec<(String, InstrSeq<'arena>)>>>()?
1483 let keys_ = bumpalo::collections::Vec::from_iter_in(
1485 .map(|x| bumpalo::collections::String::from_str_in(x.as_str(), alloc).into_bump_str()),
1489 Ok(InstrSeq::gather(
1492 InstrSeq::gather(alloc, value_instrs),
1493 emit_pos(alloc, pos),
1494 ctor(alloc, e, keys_)?,
1499 fn emit_dynamic_collection<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1500 e: &mut Emitter<'arena, 'decl, D>,
1501 env: &Env<'a, 'arena>,
1503 fields: &[ast::Afield],
1504 ) -> Result<InstrSeq<'arena>> {
1505 let alloc = env.arena;
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))
1514 let ctor = InstructLitConst::NewDictArray(count as isize);
1515 emit_keyvalue_collection(e, env, pos, fields, CollectionType::Dict, ctor)
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(
1523 emit_struct_array(e, env, pos, fields, |alloc, _, x| {
1524 Ok(instr::newstructdict(alloc, x))
1526 emit_pos(alloc, pos),
1527 instr::colfromarray(alloc, ctype),
1531 let ctor = InstructLitConst::NewDictArray(count as isize);
1532 emit_keyvalue_collection(e, env, pos, fields, ctype, ctor)
1535 use ast::Expr_ as E_;
1537 E_::ValCollection(v) if v.0 == ast::VcKind::Vec => {
1538 emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewVec)
1540 E_::Collection(v) if (v.0).1 == "vec" => {
1541 emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewVec)
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)
1547 E_::Collection(v) if (v.0).1 == "keyset" => {
1548 emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewKeysetArray)
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)
1555 E_::ValCollection(v) if v.0 == ast::VcKind::Set => {
1556 emit_collection_helper(e, CollectionType::Set)
1558 E_::Collection(v) if string_utils::strip_ns(&(v.0).1) == "ImmSet" => {
1559 emit_collection_helper(e, CollectionType::ImmSet)
1561 E_::ValCollection(v) if v.0 == ast::VcKind::ImmSet => {
1562 emit_collection_helper(e, CollectionType::ImmSet)
1564 E_::Collection(v) if string_utils::strip_ns(&(v.0).1) == "Map" => {
1565 emit_collection_helper(e, CollectionType::Map)
1567 E_::KeyValCollection(v) if v.0 == ast::KvcKind::Map => {
1568 emit_collection_helper(e, CollectionType::Map)
1570 E_::Collection(v) if string_utils::strip_ns(&(v.0).1) == "ImmMap" => {
1571 emit_collection_helper(e, CollectionType::ImmMap)
1573 E_::KeyValCollection(v) if v.0 == ast::KvcKind::ImmMap => {
1574 emit_collection_helper(e, CollectionType::ImmMap)
1577 let instrs = emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewVec);
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))
1588 let constr = InstructLitConst::NewDictArray(count as isize);
1590 emit_keyvalue_collection(e, env, pos, fields, CollectionType::Array, constr);
1594 _ => Err(unrecoverable("plain PHP arrays cannot be constructed")),
1598 fn emit_value_only_collection<
1602 D: DeclProvider<'decl>,
1603 F: FnOnce(isize) -> InstructLitConst<'arena>,
1605 e: &mut Emitter<'arena, 'decl, D>,
1606 env: &Env<'a, 'arena>,
1608 fields: &[ast::Afield],
1610 ) -> Result<InstrSeq<'arena>> {
1611 let alloc = env.arena;
1612 let limit = *(e.options().max_array_elem_size_on_the_stack.get()) as usize;
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())?)
1623 Ok(InstrSeq::gather(
1626 InstrSeq::gather(alloc, instrs),
1627 emit_pos(alloc, pos),
1628 instr::lit_const(alloc, constructor(exprs.len() as isize)),
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));
1642 Ok(InstrSeq::gather(alloc, instrs))
1644 let (x1, x2) = fields.split_at(std::cmp::min(fields.len(), limit));
1646 ([], []) => instr::empty(alloc),
1647 (_, []) => inline(alloc, e, x1)?,
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])
1656 fn emit_record<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1657 e: &mut Emitter<'arena, 'decl, D>,
1658 env: &Env<'a, 'arena>,
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))
1671 fn emit_call_isset_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1672 e: &mut Emitter<'arena, 'decl, D>,
1673 env: &Env<'a, 'arena>,
1676 ) -> Result<InstrSeq<'arena>> {
1677 let alloc = env.arena;
1679 if let Some((base_expr, opt_elem_expr)) = expr.2.as_array_get() {
1680 return Ok(emit_array_get(
1687 opt_elem_expr.as_ref(),
1693 if let Some((cid, id, _)) = expr.2.as_class_get() {
1694 return emit_class_get(e, env, QueryOp::Isset, cid, id, ReadonlyOp::Any);
1696 if let Some((expr_, prop, nullflavor, _)) = expr.2.as_obj_get() {
1697 return Ok(emit_obj_get(
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) {
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),
1722 } else if is_local_this(env, &lid.1)
1723 && !env.flags.contains(hhbc_by_ref_env::Flags::NEEDS_LOCAL_THIS)
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),
1739 instr::issetl(alloc, get_local(e, env, &lid.0, name)?),
1743 Ok(InstrSeq::gather(
1746 emit_expr(e, env, expr)?,
1747 instr::istypec(alloc, IstypeOp::OpNull),
1753 fn emit_call_isset_exprs<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1754 e: &mut Emitter<'arena, 'decl, D>,
1755 env: &Env<'a, 'arena>,
1757 exprs: &[ast::Expr],
1758 ) -> Result<InstrSeq<'arena>> {
1759 let alloc = env.arena;
1761 [] => Err(emit_fatal::raise_fatal_parse(
1763 "Cannot use isset() without any arguments",
1765 [expr] => emit_call_isset_expr(e, env, pos, expr),
1767 let its_done = e.label_gen_mut().next_regular();
1768 Ok(InstrSeq::gather(
1777 Ok(InstrSeq::gather(
1780 emit_call_isset_expr(e, env, pos, expr)?,
1781 if i < exprs.len() - 1 {
1786 instr::jmpz(alloc, its_done),
1796 .collect::<Result<Vec<_>>>()?,
1798 instr::label(alloc, its_done),
1805 fn emit_tag_provenance_here<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1806 e: &mut Emitter<'arena, 'decl, D>,
1807 env: &Env<'a, 'arena>,
1810 ) -> Result<InstrSeq<'arena>> {
1811 let alloc = env.arena;
1812 let pop = if es.len() == 1 {
1817 Ok(InstrSeq::gather(
1819 vec![emit_exprs(e, env, es)?, emit_pos(alloc, pos), pop],
1823 fn emit_array_mark_legacy<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1824 e: &mut Emitter<'arena, 'decl, D>,
1825 env: &Env<'a, 'arena>,
1829 ) -> Result<InstrSeq<'arena>> {
1830 let alloc = env.arena;
1831 let default = if es.len() == 1 {
1832 instr::false_(alloc)
1836 let mark = if legacy {
1837 instr::instr(alloc, Instruct::IMisc(InstructMisc::ArrayMarkLegacy))
1839 instr::instr(alloc, Instruct::IMisc(InstructMisc::ArrayUnmarkLegacy))
1841 Ok(InstrSeq::gather(
1843 vec![emit_exprs(e, env, es)?, emit_pos(alloc, pos), default, mark],
1847 fn emit_idx<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1848 e: &mut Emitter<'arena, 'decl, D>,
1849 env: &Env<'a, 'arena>,
1852 ) -> Result<InstrSeq<'arena>> {
1853 let alloc = env.arena;
1854 let default = if es.len() == 2 {
1859 Ok(InstrSeq::gather(
1862 emit_exprs(e, env, es)?,
1863 emit_pos(alloc, pos),
1870 fn emit_call<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1871 e: &mut Emitter<'arena, 'decl, D>,
1872 env: &Env<'a, 'arena>,
1875 targs: &[ast::Targ],
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);
1886 let fcall_args = get_fcall_args(
1892 env.call_context.clone(),
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)
1903 .unwrap_or_else(|| {
1904 emit_call_default(e, env, pos, expr, targs, args, uarg, fcall_args)
1910 fn emit_call_default<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1911 e: &mut Emitter<'arena, 'decl, D>,
1912 env: &Env<'a, 'arena>,
1915 targs: &[ast::Targ],
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),
1931 instr::empty(alloc),
1937 iter::repeat_with(|| instr::nulluninit(alloc))
1939 .collect::<Vec<_>>(),
1944 emit_pos(alloc, pos),
1949 instr::empty(alloc),
1954 pub fn emit_reified_targs<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
1955 e: &mut Emitter<'arena, 'decl, D>,
1956 env: &Env<'a, 'arena>,
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();
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)| {
1972 && !is_soft(&tp.user_attributes)
1973 && tp.reified.is_reified()
1977 Ok(if !is_in_lambda && same_as_targs(¤t_fun_tparams) {
1980 Local::Named(Str::new_str(
1982 string_utils::reified::GENERICS_LOCAL_NAME,
1985 } else if !is_in_lambda && same_as_targs(¤t_cls_tparams[..]) {
1989 instr::checkthis(alloc),
1990 instr::baseh(alloc),
1996 prop::from_raw_string(alloc, string_utils::reified::PROP_NAME),
2010 .map(|h| Ok(emit_reified_arg(e, env, pos, false, h)?.0))
2011 .collect::<Result<Vec<_>>>()?,
2013 instr::new_vec_array(alloc, targs.len() as isize),
2019 fn get_erased_tparams<'a, 'arena>(env: &'a Env<'a, 'arena>) -> Vec<&'a str> {
2023 .filter_map(|tparam| match tparam.reified {
2024 ast::ReifyKind::Erased => Some(tparam.name.1.as_str()),
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| {
2035 .map_or(true, |(id, _)| !erased_tparams.contains(&id.1.as_str()))
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| {
2045 .map_or(true, |(id, _)| !erased_tparams.contains(&id.1.as_str()))
2049 fn from_ast_null_flavor(nullflavor: ast::OgNullFlavor) -> ObjNullFlavor {
2051 ast::OgNullFlavor::OGNullsafe => ObjNullFlavor::NullSafe,
2052 ast::OgNullFlavor::OGNullthrows => ObjNullFlavor::NullThrows,
2056 fn emit_object_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
2057 e: &mut Emitter<'arena, 'decl, D>,
2058 env: &Env<'a, 'arena>,
2060 ) -> Result<InstrSeq<'arena>> {
2061 let alloc = env.arena;
2063 ast::Expr_::Lvar(x) if is_local_this(env, &x.1) => Ok(instr::this(alloc)),
2064 _ => emit_expr(e, env, expr),
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>,
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;
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))
2085 fcall_args.0 |= FcallFlags::HAS_GENERICS;
2092 .map(|targ| &targ.1)
2093 .collect::<Vec<_>>()
2099 let emit_fcall_func = |
2100 e: &mut Emitter<'arena, 'decl, D>,
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)?
2111 emit_expr(e, env, expr)?
2117 instr::nulluninit(alloc),
2118 instr::nulluninit(alloc),
2120 instr::popl(alloc, tmp),
2126 instr::pushl(alloc, tmp),
2127 instr::fcallfunc(alloc, fcall_args),
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))
2143 // Case ($x->foo)(...).
2147 E_::ObjGet(Box::new((o.0.clone(), o.1.clone(), o.2.clone(), false))),
2149 emit_fcall_func(e, env, &expr, fcall_args, caller_readonly_opt)
2151 // Case $x->foo(...).
2152 // TODO: utilze caller_readonly_opt here for method calls
2154 e: &mut Emitter<'arena, 'decl, D>,
2157 null_flavor: &ast::OgNullFlavor,
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);
2166 InstrSeq::gather(alloc, vec![obj, instr::nulluninit(alloc)]),
2171 instr::fcallobjmethodd(alloc, fcall_args, name, null_flavor),
2177 (obj, E(_, _, E_::String(id)), null_flavor, _) => {
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()) },
2188 (E(_, pos, E_::New(new_exp)), E(_, _, E_::Id(id)), null_flavor, _)
2189 if fcall_args.1 == 0 =>
2191 let cexpr = ClassExpr::class_id_to_class_expr(
2192 e, false, false, &env.scope, &new_exp.0,
2195 ClassExpr::Id(ast_defs::Id(_, name))
2196 if string_utils::strip_global_ns(name) == "ReflectionClass" =>
2198 let fid = match string_utils::strip_global_ns(&id.1) {
2200 Some("__SystemLib\\reflection_class_is_abstract")
2203 Some("__SystemLib\\reflection_class_is_interface")
2205 "isFinal" => Some("__SystemLib\\reflection_class_is_final"),
2206 "getName" => Some("__SystemLib\\reflection_class_get_name"),
2211 emit_id(e, &o.as_ref().0, &id.1, null_flavor, fcall_args)
2214 let fcall_args = FcallArgs::new(
2215 FcallFlags::default(),
2223 let newobj_instrs = emit_new(e, env, pos, &new_exp, true);
2228 instr::nulluninit(alloc),
2229 instr::nulluninit(alloc),
2235 vec![instr::fcallfuncd(
2238 function::FunctionType::<'arena>::from_ast_name(
2247 _ => emit_id(e, &o.as_ref().0, &id.1, null_flavor, fcall_args),
2250 (obj, E(_, _, E_::Id(id)), null_flavor, _) => {
2251 emit_id(e, obj, &id.1, null_flavor, fcall_args)
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);
2262 instr::nulluninit(alloc),
2263 emit_expr(e, env, method_expr)?,
2264 instr::popl(alloc, tmp),
2270 instr::pushl(alloc, tmp),
2271 instr::fcallobjmethod(alloc, fcall_args, null_flavor),
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;
2287 let method_id: method::MethodType = (alloc, string_utils::strip_global_ns(&id)).into();
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)?;
2297 vec![instr::nulluninit(alloc), instr::nulluninit(alloc)],
2303 instr::fcallclsmethodd(alloc, fcall_args, method_id, cid),
2308 ClassExpr::Special(clsref) => {
2309 let generics = emit_generics(e, env, &mut fcall_args)?;
2313 vec![instr::nulluninit(alloc), instr::nulluninit(alloc)],
2319 instr::fcallclsmethodsd(alloc, fcall_args, clsref, method_id),
2324 ClassExpr::Expr(expr) => {
2325 let generics = emit_generics(e, env, &mut fcall_args)?;
2329 vec![instr::nulluninit(alloc), instr::nulluninit(alloc)],
2335 instr::string(alloc, method_id.to_raw_string()),
2336 emit_expr(e, env, &expr)?,
2337 instr::classgetc(alloc),
2338 instr::fcallclsmethod(
2340 IsLogAsDynamicCallOp::DontLogAsDynamicCall,
2347 ClassExpr::Reified(instrs) => {
2348 let tmp = e.local_gen_mut().get_unnamed();
2353 instr::nulluninit(alloc),
2354 instr::nulluninit(alloc),
2356 instr::popl(alloc, tmp),
2362 instr::string(alloc, method_id.to_raw_string()),
2363 instr::pushl(alloc, tmp),
2364 instr::classgetc(alloc),
2365 instr::fcallclsmethod(
2367 IsLogAsDynamicCallOp::LogAsDynamicCall,
2376 E_::ClassGet(c) => {
2378 // Case (Foo::$bar)(...).
2382 E_::ClassGet(Box::new((c.0.clone(), c.1.clone(), false))),
2384 emit_fcall_func(e, env, &expr, fcall_args, caller_readonly_opt)
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;
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(
2398 instr::cgetl(alloc, Local::Named(Str::new_str(alloc, id.as_str()))),
2400 ast::ClassGetExpr::CGexpr(expr) => emit_expr(e, env, expr),
2403 ClassExpr::Id(cid) => {
2404 let tmp = e.local_gen_mut().get_unnamed();
2409 instr::nulluninit(alloc),
2410 instr::nulluninit(alloc),
2412 instr::popl(alloc, tmp),
2418 instr::pushl(alloc, tmp),
2419 emit_known_class_id(alloc, e, &cid),
2420 instr::fcallclsmethod(
2422 IsLogAsDynamicCallOp::LogAsDynamicCall,
2429 ClassExpr::Special(clsref) => {
2430 let tmp = e.local_gen_mut().get_unnamed();
2435 instr::nulluninit(alloc),
2436 instr::nulluninit(alloc),
2438 instr::popl(alloc, tmp),
2444 instr::pushl(alloc, tmp),
2445 instr::fcallclsmethods(alloc, fcall_args, clsref),
2450 ClassExpr::Expr(expr) => {
2451 let cls = e.local_gen_mut().get_unnamed();
2452 let meth = e.local_gen_mut().get_unnamed();
2457 instr::nulluninit(alloc),
2458 instr::nulluninit(alloc),
2459 emit_expr(e, env, &expr)?,
2460 instr::popl(alloc, cls),
2462 instr::popl(alloc, meth),
2468 instr::pushl(alloc, meth),
2469 instr::pushl(alloc, cls),
2470 instr::classgetc(alloc),
2471 instr::fcallclsmethod(
2473 IsLogAsDynamicCallOp::LogAsDynamicCall,
2480 ClassExpr::Reified(instrs) => {
2481 let cls = e.local_gen_mut().get_unnamed();
2482 let meth = e.local_gen_mut().get_unnamed();
2487 instr::nulluninit(alloc),
2488 instr::nulluninit(alloc),
2490 instr::popl(alloc, cls),
2492 instr::popl(alloc, meth),
2498 instr::pushl(alloc, meth),
2499 instr::pushl(alloc, cls),
2500 instr::classgetc(alloc),
2501 instr::fcallclsmethod(
2503 IsLogAsDynamicCallOp::LogAsDynamicCall,
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")
2519 "max" if num_args == 2 && !flags.contains(FcallFlags::HAS_UNPACK) => {
2520 function::FunctionType::<'arena>::from_ast_name(alloc, "__SystemLib\\max2")
2522 _ => (alloc, string_utils::strip_global_ns(&id.1)).into(),
2524 let generics = emit_generics(e, env, &mut fcall_args)?;
2528 vec![instr::nulluninit(alloc), instr::nulluninit(alloc)],
2532 vec![generics, instr::fcallfuncd(alloc, fcall_args, fq_id)],
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)?;
2543 vec![instr::nulluninit(alloc), instr::nulluninit(alloc)],
2547 vec![generics, instr::fcallfuncd(alloc, fcall_args, fq_id)],
2551 _ => emit_fcall_func(e, env, expr, fcall_args, caller_readonly_opt),
2555 fn get_reified_var_cexpr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
2556 env: &Env<'a, 'arena>,
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(
2566 instr::basec(alloc, 0, MemberOpMode::Warn),
2571 MemberKey::ET(Str::from("classname"), ReadonlyOp::Any),
2578 fn emit_args_inout_setters<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
2579 e: &mut Emitter<'arena, 'decl, D>,
2580 env: &Env<'a, 'arena>,
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)
2587 inout_locals::new_alias_info_map()
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>,
2594 aliases: &inout_locals::AliasInfoMap,
2595 ) -> Result<(InstrSeq<'arena>, InstrSeq<'arena>)> {
2596 use ast::Expr_ as E_;
2597 let alloc = env.arena;
2599 E_::Callconv(cc) if (cc.0).is_pinout() => {
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)
2609 vec![instr::null(alloc), instr::popl(alloc, local)],
2615 InstrSeq::gather(alloc, vec![instr::cgetl(alloc, local), move_instrs]),
2616 instr::popl(alloc, local),
2619 // inout $arr[...][...]
2620 E_::ArrayGet(ag) => {
2621 let array_get_result = emit_array_get_(
2634 Ok(match array_get_result {
2635 ArrayGetInstr::Regular(instrs) => {
2636 let setter_base = emit_array_get(
2640 Some(MemberOpMode::Define),
2648 let (mk, warninstr) =
2649 get_elem_member_key(e, env, 0, ag.1.as_ref(), false)?;
2650 let setter = InstrSeq::gather(
2655 instr::setm(alloc, 0, mk),
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),
2678 (InstrSeq::gather(alloc, ld), InstrSeq::gather(alloc, st))
2682 _ => Err(unrecoverable(
2683 "emit_arg_and_inout_setter: Unexpected inout expression type",
2687 _ => Ok((emit_expr(e, env, arg)?, instr::empty(alloc))),
2690 let (instr_args, instr_setters): (Vec<InstrSeq>, Vec<InstrSeq>) = args
2693 .map(|(i, arg)| emit_arg_and_inout_setter(e, env, i, arg, &aliases))
2694 .collect::<Result<Vec<_>>>()?
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();
2706 instr::popl(alloc, retval),
2708 instr::pushl(alloc, retval),
2713 Ok((instr_args, instr::empty(alloc)))
2717 fn get_fcall_args<'arena, 'decl, D: DeclProvider<'decl>>(
2718 e: &mut Emitter<'arena, 'decl, D>,
2719 alloc: &'arena bumpalo::Bump,
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
2737 .contains(HhvmFlags::ENABLE_READONLY_IN_EMITTER)
2739 |expr| is_readonly_expr(expr)
2743 let readonly_args = if args.iter().any(is_readonly_arg) {
2744 Slice::fill_iter(alloc, args.iter().map(is_readonly_arg))
2751 Slice::fill_iter(alloc, args.iter().map(is_inout_arg)),
2756 .map(|s| bumpalo::collections::String::from_str_in(s.as_str(), alloc).into_bump_str()),
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 {
2766 ast::Expr_::ReadonlyExpr(_) => true,
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>,
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
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(
2797 Ok(InstrSeq::gather(
2800 emit_expr(e, env, arg)?,
2801 emit_pos(alloc, pos),
2802 instr::print(alloc),
2811 .collect::<Result<_>>()?,
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(
2819 "HH\\Readonly\\as_mut() expects exactly 1 parameter, {} given",
2823 ("HH\\invariant", args) if args.len() >= 2 => {
2824 let l = e.label_gen_mut().next_regular();
2825 let expr_id = ast::Expr(
2828 ast::Expr_::mk_id(ast_defs::Id(
2830 "\\hh\\invariant_violation".into(),
2833 let call = ast::Expr(
2836 ast::Expr_::mk_call(expr_id, vec![], args[1..].to_owned(), uarg.cloned()),
2838 let ignored_expr = emit_ignored_expr(e, env, &Pos::make_none(), &call)?;
2839 Ok(Some(InstrSeq::gather(
2842 emit_expr(e, env, &args[0])?,
2843 instr::jmpnz(alloc, l),
2845 emit_fatal::emit_fatal_runtime(alloc, pos, "invariant_violation"),
2846 instr::label(alloc, l),
2851 ("HH\\sequence", &[]) => Ok(Some(instr::null(alloc))),
2852 ("HH\\sequence", args) => {
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();
2861 for (count, e) in es.into_iter().enumerate() {
2863 if count != len - 1 {
2864 iss.push(instr::popc(alloc));
2867 Ok(Some(InstrSeq::gather(alloc, iss)))
2869 ("class_exists", &[ref arg1, ..])
2870 | ("trait_exists", &[ref arg1, ..])
2871 | ("interface_exists", &[ref arg1, ..])
2872 if nargs == 1 || nargs == 2 =>
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")),
2880 Ok(Some(InstrSeq::gather(
2883 emit_expr(e, env, arg1)?,
2884 instr::cast_string(alloc),
2890 vec![emit_expr(e, env, &args[1])?, instr::cast_bool(alloc)],
2893 instr::oodeclexists(alloc, class_kind),
2897 ("exit", _) | ("die", _) if nargs == 0 || nargs == 1 => {
2898 Ok(Some(emit_exit(e, env, args.first())?))
2901 if fun_and_clsmeth_disabled {
2903 [ast::Expr(_, _, ast::Expr_::String(func_name))] => {
2904 Err(emit_fatal::raise_fatal_parse(
2907 "`fun()` is disabled; switch to first-class references like `{}<>`",
2912 _ => Err(emit_fatal::raise_fatal_runtime(
2914 "Constant string expected in fun()",
2917 } else if nargs != 1 {
2918 Err(emit_fatal::raise_fatal_runtime(
2921 "fun() expects exactly 1 parameter, {} given",
2927 [ast::Expr(_, _, ast::Expr_::String(func_name))] => {
2928 Ok(Some(emit_hh_fun(
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()) },
2938 _ => Err(emit_fatal::raise_fatal_runtime(
2940 "Constant string expected in fun()",
2945 ("__systemlib\\meth_caller", _) => {
2946 // used by meth_caller() to directly emit func ptr
2948 return Err(emit_fatal::raise_fatal_runtime(
2951 "fun() expects exactly 1 parameter, {} given",
2957 [E(_, _, E_::String(ref func_name))] => Ok(Some(instr::resolve_meth_caller(
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()) },
2969 _ => Err(emit_fatal::raise_fatal_runtime(
2971 "Constant string expected in fun()",
2975 ("__systemlib\\__debugger_is_uninit", _) => {
2977 Err(emit_fatal::raise_fatal_runtime(
2980 "__debugger_is_uninit() expects exactly 1 parameter {} given",
2986 [E(_, _, E_::Lvar(id))] => Ok(Some(instr::isunsetl(
2988 get_local(e, env, pos, id.name())?,
2990 _ => Err(emit_fatal::raise_fatal_runtime(
2992 "Local variable expected in __debugger_is_uninit()",
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(
3002 "Argument must be the label argument",
3005 Ok(Some(InstrSeq::gather(
3007 vec![instr::lateboundcls(alloc), instr::clscnsl(alloc, local)],
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(
3015 "inst_meth() expects exactly 2 parameters, {} given",
3020 ("HH\\class_meth", _) if fun_and_clsmeth_disabled => Err(emit_fatal::raise_fatal_parse(
3022 "`class_meth()` is disabled; switch to first-class references like `C::bar<>`",
3024 ("HH\\class_meth", &[ref cls, ref meth, ..]) if nargs == 2 => {
3025 if meth.2.is_string() {
3026 if cls.2.is_string()
3030 .map_or(false, |(_, (_, id))| string_utils::is_class(id))
3034 .map_or(false, |ast_defs::Id(_, id)| id == pseudo_consts::G__CLASS__)
3036 return Ok(Some(emit_class_meth(e, env, cls, meth)?));
3039 Err(emit_fatal::raise_fatal_runtime(
3042 "class_meth() expects a literal class name or ::class constant, ",
3043 "followed by a constant string that refers to a static method ",
3048 ("HH\\class_meth", _) => Err(emit_fatal::raise_fatal_runtime(
3051 "class_meth() expects exactly 2 parameters, {} given",
3055 ("HH\\global_set", _) => match *args {
3056 [ref gkey, ref gvalue] => Ok(Some(InstrSeq::gather(
3059 emit_expr(e, env, gkey)?,
3060 emit_expr(e, env, gvalue)?,
3061 emit_pos(alloc, pos),
3067 _ => Err(emit_fatal::raise_fatal_runtime(
3070 "global_set() expects exactly 2 parameters, {} given",
3075 ("HH\\global_unset", _) => match *args {
3076 [ref gkey] => Ok(Some(InstrSeq::gather(
3079 emit_expr(e, env, gkey)?,
3080 emit_pos(alloc, pos),
3081 instr::unsetg(alloc),
3085 _ => Err(emit_fatal::raise_fatal_runtime(
3088 "global_unset() expects exactly 1 parameter, {} given",
3093 ("__hhvm_internal_whresult", &[E(_, _, E_::Lvar(ref param))]) if e.systemlib() => {
3094 Ok(Some(InstrSeq::gather(
3099 Local::Named(Str::new_str(alloc, local_id::get_name(¶m.1))),
3101 instr::whresult(alloc),
3105 ("HH\\array_mark_legacy", _) if args.len() == 1 || args.len() == 2 => {
3106 Ok(Some(emit_array_mark_legacy(e, env, pos, args, true)?))
3108 ("HH\\array_unmark_legacy", _) if args.len() == 1 || args.len() == 2 => {
3109 Ok(Some(emit_array_mark_legacy(e, env, pos, args, false)?))
3111 ("HH\\tag_provenance_here", _) if args.len() == 1 || args.len() == 2 => {
3112 Ok(Some(emit_tag_provenance_here(e, env, pos, args)?))
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(
3120 vec![emit_expr(e, env, &arg_expr)?, is_expr],
3123 (&[E(_, _, E_::Lvar(ref arg_id))], Some(i), _)
3124 if superglobals::is_any_global(arg_id.name()) =>
3126 Some(InstrSeq::gather(
3129 emit_local(e, env, BareThisOp::NoNotice, &arg_id)?,
3130 emit_pos(alloc, pos),
3131 instr::istypec(alloc, i),
3135 (&[E(_, _, E_::Lvar(ref arg_id))], Some(i), _)
3136 if !is_local_this(env, &arg_id.1) =>
3138 Some(instr::istypel(
3140 get_local(e, env, &arg_id.0, &(arg_id.1).1)?,
3144 (&[ref arg_expr], Some(i), _) => Some(InstrSeq::gather(
3147 emit_expr(e, env, &arg_expr)?,
3148 emit_pos(alloc, pos),
3149 instr::istypec(alloc, i),
3152 _ => match get_call_builtin_func_info(lower_fq_name) {
3153 Some((nargs, i)) if nargs == args.len() => Some(InstrSeq::gather(
3156 emit_exprs(e, env, args)?,
3157 emit_pos(alloc, pos),
3158 instr::instr(alloc, i),
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(
3178 emit_expr(e, env, obj_expr)?,
3179 emit_expr(e, env, method_name)?,
3180 instr::new_vec_array(alloc, 2),
3186 fn emit_class_meth<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3187 e: &mut Emitter<'arena, 'decl, D>,
3188 env: &Env<'a, 'arena>,
3191 ) -> Result<InstrSeq<'arena>> {
3192 use ast::Expr_ as E_;
3193 let alloc = env.arena;
3197 .contains(HhvmFlags::EMIT_CLS_METH_POINTERS)
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")),
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, &[]);
3208 if let Some(ast_defs::Id(_, s)) = cls.2.as_id() {
3209 if s == pseudo_consts::G__CLASS__ {
3210 return Ok(instr::resolveclsmethods(
3212 SpecialClsRef::Self_,
3217 if let Some(class_name) = cls.2.as_string() {
3218 return Ok(instr::resolveclsmethodd(
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()) },
3232 Err(unrecoverable("emit_class_meth: unhandled method"))
3234 let instrs = InstrSeq::gather(
3237 emit_expr(e, env, cls)?,
3238 emit_expr(e, env, meth)?,
3239 instr::new_vec_array(alloc, 2),
3246 fn emit_class_meth_native<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3247 e: &mut Emitter<'arena, 'decl, D>,
3248 env: &Env<'a, 'arena>,
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;
3261 let has_generics = has_non_tparam_generics_targs(env, targs);
3262 let mut emit_generics = || -> Result<InstrSeq<'arena>> {
3267 &targs.iter().map(|targ| &targ.1).collect::<Vec<_>>(),
3271 ClassExpr::Id(ast_defs::Id(_, name)) if !has_generics => instr::resolveclsmethodd(
3273 class::ClassType::<'arena>::from_ast_name_and_mangle(alloc, &name),
3276 ClassExpr::Id(ast_defs::Id(_, name)) => InstrSeq::gather(
3280 instr::resolverclsmethodd(
3282 class::ClassType::<'arena>::from_ast_name_and_mangle(alloc, &name),
3287 ClassExpr::Special(clsref) if !has_generics => {
3288 instr::resolveclsmethods(alloc, clsref, method_id)
3290 ClassExpr::Special(clsref) => InstrSeq::gather(
3294 instr::resolverclsmethods(alloc, clsref, method_id),
3297 ClassExpr::Reified(instrs) if !has_generics => InstrSeq::gather(
3301 instr::classgetc(alloc),
3302 instr::resolveclsmethod(alloc, method_id),
3305 ClassExpr::Reified(instrs) => InstrSeq::gather(
3309 instr::classgetc(alloc),
3311 instr::resolverclsmethod(alloc, method_id),
3314 ClassExpr::Expr(_) => {
3315 return Err(unrecoverable(
3316 "emit_class_meth_native: ClassExpr::Expr should be impossible",
3322 fn get_call_builtin_func_info<'arena>(id: impl AsRef<str>) -> Option<(usize, Instruct<'arena>)> {
3323 use {Instruct::*, InstructGet::*, InstructIsset::*, InstructMisc::*, InstructOperator::*};
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))),
3344 fn emit_function_pointer<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3345 e: &mut Emitter<'arena, 'decl, D>,
3346 env: &Env<'a, 'arena>,
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())?,
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)?
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>,
3370 targs: &[ast::Targ],
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(
3382 .map(|targ| &targ.1)
3383 .collect::<Vec<_>>()
3386 Ok(InstrSeq::gather(
3388 vec![generics, instr::resolve_rfunc(alloc, (alloc, fname).into())],
3391 Ok(instr::resolve_func(alloc, (alloc, fname).into()))
3395 fn emit_is<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3396 e: &mut Emitter<'arena, 'decl, D>,
3397 env: &Env<'a, 'arena>,
3400 ) -> Result<InstrSeq<'arena>> {
3401 let alloc = env.arena;
3402 let (ts_instrs, is_static) = emit_reified_arg(e, env, pos, true, h)?;
3405 aast_defs::Hint_::Happly(ast_defs::Id(_, id), hs)
3406 if hs.is_empty() && string_utils::strip_hh_ns(&id) == typehints::THIS =>
3408 instr::islateboundcls(alloc)
3410 _ => InstrSeq::gather(
3413 get_type_structure_for_hint(alloc, e, &[], &IndexSet::new(), h)?,
3414 instr::is_type_structc_resolve(alloc),
3421 vec![ts_instrs, instr::is_type_structc_dontresolve(alloc)],
3426 fn istype_op(id: impl AsRef<str>) -> Option<IstypeOp> {
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),
3451 fn is_isexp_op(lower_fq_id: impl AsRef<str>) -> Option<ast::Hint> {
3453 Some(ast::Hint::new(
3455 ast::Hint_::mk_happly(ast::Id(Pos::make_none(), s.into()), vec![]),
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"),
3471 fn emit_eval<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3472 e: &mut Emitter<'arena, 'decl, D>,
3473 env: &Env<'a, 'arena>,
3476 ) -> Result<InstrSeq<'arena>> {
3477 let alloc = env.arena;
3478 Ok(InstrSeq::gather(
3481 emit_expr(e, env, expr)?,
3482 emit_pos(alloc, pos),
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 => {
3501 fn emit_call_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3502 e: &mut Emitter<'arena, 'decl, D>,
3503 env: &Env<'a, 'arena>,
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
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 =>
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()) }),
3525 Ok(emit_pos_then(alloc, pos, instr::typedvalue(alloc, v)))
3527 (E_::Id(id), _, None) if id.1 == pseudo_functions::ISSET => {
3528 emit_call_isset_exprs(e, env, pos, args)
3530 (E_::Id(id), args, None)
3532 && !jit_enable_rename_function
3533 && (args.len() == 2 || args.len() == 3) =>
3535 emit_idx(e, env, pos, args)
3537 (E_::Id(id), [arg1], None) if id.1 == emitter_special_functions::EVAL => {
3538 emit_eval(e, env, pos, arg1)
3540 (E_::Id(id), [arg1], None) if id.1 == emitter_special_functions::SET_FRAME_METADATA => {
3541 Ok(InstrSeq::gather(
3544 emit_expr(e, env, arg1)?,
3545 emit_pos(alloc, pos),
3546 instr::popl(alloc, Local::Named(Slice::new("$86metadata".as_bytes()))),
3551 (E_::Id(id), [], None)
3552 if id.1 == pseudo_functions::EXIT || id.1 == pseudo_functions::DIE =>
3554 let exit = emit_exit(e, env, None)?;
3555 Ok(emit_pos_then(alloc, pos, exit))
3557 (E_::Id(id), [arg1], None)
3558 if id.1 == pseudo_functions::EXIT || id.1 == pseudo_functions::DIE =>
3560 let exit = emit_exit(e, env, Some(arg1))?;
3561 Ok(emit_pos_then(alloc, pos, exit))
3564 if id.1 == emitter_special_functions::SYSTEMLIB_REIFIED_GENERICS
3566 && has_reified_types(env) =>
3568 // Rewrite __systemlib_reified_generics() to $0ReifiedGenerics,
3569 // but only in systemlib functions that take a reified generic.
3573 E_::Lvar(Box::new(ast::Lid(
3575 local_id::make_unscoped(string_utils::reified::GENERICS_LOCAL_NAME),
3578 emit_expr(e, env, &lvar)
3581 let instrs = emit_call(
3592 Ok(emit_pos_then(alloc, pos, instrs))
3597 pub fn emit_reified_generic_instrs<'arena>(
3598 alloc: &'arena bumpalo::Bump,
3602 ) -> Result<InstrSeq<'arena>> {
3603 let base = if is_fun {
3606 Local::Named(Str::new_str(
3608 string_utils::reified::GENERICS_LOCAL_NAME,
3616 instr::checkthis(alloc),
3617 instr::baseh(alloc),
3620 prop::from_raw_string(alloc, string_utils::reified::PROP_NAME),
3637 MemberKey::EI(index.try_into().unwrap(), ReadonlyOp::Any),
3644 fn emit_reified_type<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3645 env: &Env<'a, 'arena>,
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>,
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| {
3663 Local::Named(Str::new_str(
3665 string_utils::reified::reified_generic_captured_name(is_fun, i).as_str(),
3669 let check = |is_soft| -> Result<()> {
3671 Err(emit_fatal::raise_fatal_parse(
3674 "{} is annotated to be a soft reified generic, it cannot be used until the __Soft annotation is removed",
3682 let emit = |(i, is_soft), is_fun| {
3684 Ok(Some(if is_in_lambda {
3685 cget_instr(is_fun, i)
3687 emit_reified_generic_instrs(alloc, pos, is_fun, i)?
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),
3699 fn emit_known_class_id<'arena, 'decl, D: DeclProvider<'decl>>(
3700 alloc: &'arena bumpalo::Bump,
3701 e: &mut Emitter<'arena, 'decl, D>,
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>,
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(
3725 emit_pos(alloc, pos),
3726 emit_expr(e, env, &expr)?,
3727 instr::classgetc(alloc),
3730 ClassExpr::Reified(instrs) => InstrSeq::gather(
3732 vec![emit_pos(alloc, pos), instrs, instr::classgetc(alloc)],
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>,
3742 (cid, targs, args, uarg, _): &(
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"));
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
3759 .get_class_tparams()
3761 .all(|tp| tp.reified.is_erased()),
3762 Some(ast_defs::Id(_, n)) if string_utils::is_parent(n) => {
3765 .map_or(true, |cls| match cls.get_extends() {
3768 .map_or(true, |(_, l)| !has_non_tparam_generics(env, l))
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)? {
3782 if targs.is_empty() {
3783 (ClassExpr::Reified(instrs), H::MaybeGenerics)
3785 return Err(emit_fatal::raise_fatal_parse(
3787 "Cannot have higher kinded reified generics",
3791 None if !has_non_tparam_generics_targs(env, targs) => (cexpr, H::NoGenerics),
3792 None => (cexpr, H::HasGenerics),
3794 _ => (cexpr, H::NoGenerics),
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)?,
3804 instr::empty(alloc),
3805 InstrSeq::gather(alloc, vec![instr_args, instr_uargs]),
3806 instr::empty(alloc),
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(
3817 vec![emit_pos(alloc, pos), instr::newobjd(alloc, id)],
3819 H::HasGenerics => InstrSeq::gather(
3822 emit_pos(alloc, pos),
3827 &targs.iter().map(|t| &t.1).collect::<Vec<_>>(),
3829 instr::newobjrd(alloc, id),
3832 H::MaybeGenerics => {
3833 return Err(unrecoverable(
3834 "Internal error: This case should have been transformed",
3839 ClassExpr::Special(cls_ref) => InstrSeq::gather(
3841 vec![emit_pos(alloc, pos), instr::newobjs(alloc, cls_ref)],
3843 ClassExpr::Reified(instrs) if has_generics == H::MaybeGenerics => InstrSeq::gather(
3845 vec![instrs, instr::classgetts(alloc), instr::newobjr(alloc)],
3847 _ => InstrSeq::gather(
3850 emit_load_class_ref(e, env, pos, cexpr)?,
3851 instr::newobj(alloc),
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)?,
3862 instr::empty(alloc),
3868 instr::nulluninit(alloc),
3871 emit_pos(alloc, pos),
3880 env.call_context.clone(),
3882 true, // we do not need to enforce readonly return for constructors
3886 instr::lockobj(alloc),
3889 instr::empty(alloc),
3895 fn emit_obj_get<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
3896 e: &mut Emitter<'arena, 'decl, D>,
3897 env: &Env<'a, 'arena>,
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 {
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)
3916 return Err(emit_fatal::raise_fatal_parse(
3918 "?-> is not allowed with $this",
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));
3927 let mode = if null_coalesce_assignment {
3930 get_querym_op_mode(&query_op)
3932 let prop_stack_size = emit_prop_expr(
3938 null_coalesce_assignment,
3943 base_expr_instrs_begin,
3944 base_expr_instrs_end,
3955 null_coalesce_assignment,
3960 let (mk, prop_instrs, _) = emit_prop_expr(
3966 null_coalesce_assignment,
3969 let total_stack_size = (prop_stack_size + base_stack_size + cls_stack_size) as usize;
3970 let num_params = if null_coalesce_assignment {
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)
3983 let instr = InstrSeq::gather(
3986 base_expr_instrs_begin,
3988 base_expr_instrs_end,
3989 emit_pos(alloc, pos),
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,
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)
4017 // Special case for known property name
4018 let pid: prop::PropType<'arena> = prop::PropType::<'arena>::from_ast_name(
4020 string_utils::strip_global_ns(&name),
4023 ast_defs::OgNullFlavor::OGNullthrows => MemberKey::PT(pid, readonly_op),
4024 ast_defs::OgNullFlavor::OGNullsafe => MemberKey::QT(pid, readonly_op),
4028 // Special case for known property name
4029 ast::Expr_::String(name) => {
4030 let pid: prop::PropType<'arena> = prop::PropType::<'arena>::from_ast_name(
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()) },
4039 ast_defs::OgNullFlavor::OGNullthrows => MemberKey::PT(pid, readonly_op),
4040 ast_defs::OgNullFlavor::OGNullsafe => MemberKey::QT(pid, readonly_op),
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))?,
4049 MemberKey::PC(stack_index, readonly_op)
4052 // For nullsafe access, insist that property is known
4054 MemberKey::PL(_, _) | MemberKey::PC(_, _)
4055 if nullflavor.eq(&ast_defs::OgNullFlavor::OGNullsafe) =>
4057 return Err(emit_fatal::raise_fatal_parse(
4059 "?-> can only be used with scalar property names",
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),
4068 _ => (mk, instr::empty(alloc), 0),
4072 fn emit_xhp_obj_get<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4073 e: &mut Emitter<'arena, 'decl, D>,
4074 env: &Env<'a, 'arena>,
4078 nullflavor: &ast_defs::OgNullFlavor,
4079 ) -> Result<InstrSeq<'arena>> {
4081 use ast::Expr_ as E_;
4090 E_::mk_id(ast_defs::Id(pos.clone(), "getAttribute".into())),
4099 E_::mk_string(string_utils::clean(s).into()),
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>,
4108 mode: Option<MemberOpMode>,
4111 elem: Option<&ast::Expr>,
4113 null_coalesce_assignment: bool,
4114 ) -> Result<(InstrSeq<'arena>, Option<usize>)> {
4115 let result = emit_array_get_(
4124 null_coalesce_assignment,
4128 (ArrayGetInstr::Regular(i), querym_n_unpopped) => Ok((i, querym_n_unpopped)),
4129 (ArrayGetInstr::Inout { .. }, _) => Err(unrecoverable("unexpected inout")),
4133 fn emit_array_get_<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4134 e: &mut Emitter<'arena, 'decl, D>,
4135 env: &Env<'a, 'arena>,
4137 mode: Option<MemberOpMode>,
4139 base_expr: &ast::Expr,
4140 elem: Option<&ast::Expr>,
4142 null_coalesce_assignment: bool,
4143 inout_param_info: Option<(usize, &inout_locals::AliasInfoMap)>,
4144 ) -> Result<(ArrayGetInstr<'arena>, Option<usize>)> {
4146 let alloc = env.arena;
4147 match (base_expr, elem) {
4148 (E(_, pos, _), None)
4151 .contains(hhbc_by_ref_env::Flags::ALLOWS_ARRAY_APPEND) =>
4153 Err(emit_fatal::raise_fatal_runtime(
4155 "Can't use [] for reading",
4159 let local_temp_kind = get_local_temp_kind(env, false, inout_param_info, elem);
4160 let mode = if null_coalesce_assignment {
4163 mode.unwrap_or_else(|| get_querym_op_mode(&query_op))
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_(
4174 QueryOp::Isset => BareThisOp::NoNotice,
4175 _ => BareThisOp::Notice,
4177 null_coalesce_assignment,
4181 ReadonlyOp::Any, // array get on reading has no restrictions
4183 let cls_stack_size = match &base_result {
4184 ArrayGetBase::Regular(base) => base.cls_stack_size,
4185 ArrayGetBase::Inout { load, .. } => load.cls_stack_size,
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 {
4194 } else if null_coalesce_assignment {
4195 querym_n_unpopped = Some(total_stack_size as usize);
4196 instr::querym(alloc, 0, query_op, memberkey)
4198 instr::querym(alloc, total_stack_size as usize, query_op, memberkey)
4201 let instr = match (base_result, local_temp_kind) {
4202 (ArrayGetBase::Regular(base), None) =>
4203 // neither base nor expression needs to store anything
4205 ArrayGetInstr::Regular(InstrSeq::gather(
4212 emit_pos(alloc, outer_pos),
4215 base.base_stack_size + base.cls_stack_size + elem_stack_size,
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();
4225 // load base and indexer, value of indexer will be saved in local
4229 vec![InstrSeq::clone(alloc, &base.base_instrs), elem_instrs],
4231 Some((local, local_kind)),
4233 // finish loading the value
4240 emit_pos(alloc, outer_pos),
4243 base.base_stack_size
4244 + base.cls_stack_size
4253 let store = InstrSeq::gather(
4256 emit_store_for_simple_base(
4269 ArrayGetInstr::Inout { load, store }
4272 ArrayGetBase::Inout {
4285 // base needs temp locals, indexer - does not,
4286 // simply concat two instruction sequences
4294 emit_pos(alloc, outer_pos),
4297 base_stack_size + cls_stack_size + elem_stack_size,
4304 let store = InstrSeq::gather(
4306 vec![store, instr::setm(alloc, 0, memberkey), instr::popc(alloc)],
4308 ArrayGetInstr::Inout {
4314 ArrayGetBase::Inout {
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))));
4337 emit_pos(alloc, outer_pos),
4340 base_stack_size + cls_stack_size + elem_stack_size,
4347 let store = InstrSeq::gather(
4351 instr::setm(alloc, 0, MemberKey::EL(local, ReadonlyOp::Any)),
4355 ArrayGetInstr::Inout {
4361 Ok((instr, querym_n_unpopped))
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)
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;
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() {
4395 get_local(e, env, &x.0, local_id::get_name(&x.1))?,
4399 } else if null_coalesce_assignment {
4401 instr::cgetl(alloc, get_local(e, env, &x.0, local_id::get_name(&x.1))?),
4405 (instr::empty(alloc), 0)
4408 ast::Expr_::ClassConst(x)
4409 if is_special_class_constant_accessed_with_class_id(&(x.0).2, &(x.1).1) =>
4411 (instr::empty(alloc), 0)
4413 _ => (emit_expr(e, env, expr)?, 1),
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_;
4427 use ast::Expr_ as E_;
4428 let alloc = env.arena;
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((
4436 if null_coalesce_assignment {
4437 MemberKey::EC(stack_index, ReadonlyOp::Any)
4440 get_local(e, env, &x.0, local_id::get_name(&x.1))?,
4445 instr::empty(alloc),
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)))
4452 _ => Err(Unrecoverable(format!("{} is not a valid integer index", s))),
4454 // Special case for literal string
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();
4461 MemberKey::ET(Str::from(s), ReadonlyOp::Any),
4462 instr::empty(alloc),
4465 // Special case for class name
4467 if is_special_class_constant_accessed_with_class_id(&(x.0).2, &(x.1).1) =>
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),
4474 return Err(Unrecoverable(
4475 "Unreachable due to is_special_class_constant_accessed_with_class_id"
4481 class::ClassType::<'arena>::from_ast_name(alloc, &cname).to_raw_string();
4482 if e.options().emit_class_pointers() > 0 {
4484 MemberKey::ET(Str::from(fq_id), ReadonlyOp::Any),
4485 instr::raise_class_string_conversion_warning(alloc),
4489 MemberKey::ET(Str::from(fq_id), ReadonlyOp::Any),
4490 instr::empty(alloc),
4497 MemberKey::EC(stack_index, ReadonlyOp::Any),
4498 instr::empty(alloc),
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>,
4509 elem_stack_size: isize,
4511 local: Local<'arena>,
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(
4520 MemberOpMode::Define,
4528 let memberkey = MemberKey::EL(local, ReadonlyOp::Any);
4529 Ok(InstrSeq::gather(
4532 base_expr_instrs_begin,
4533 base_expr_instrs_end,
4534 emit_pos(alloc, pos),
4537 instr::dim(alloc, MemberOpMode::Define, memberkey)
4539 instr::setm(alloc, 0, memberkey)
4545 fn get_querym_op_mode(query_op: &QueryOp) -> MemberOpMode {
4547 QueryOp::InOut => MemberOpMode::InOut,
4548 QueryOp::CGet => MemberOpMode::Warn,
4549 _ => MemberOpMode::ModeNone,
4553 fn emit_class_get<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4554 e: &mut Emitter<'arena, 'decl, D>,
4555 env: &Env<'a, 'arena>,
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(
4566 InstrSeq::from((alloc, emit_class_expr(e, env, cexpr, prop)?)),
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()));
4573 QueryOp::InOut => return Err(Unrecoverable("emit_class_get: InOut".into())),
4579 fn emit_conditional_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4580 e: &mut Emitter<'arena, 'decl, D>,
4581 env: &Env<'a, 'arena>,
4584 etrue: &Option<ast::Expr>,
4586 ) -> Result<InstrSeq<'arena>> {
4587 let alloc = env.arena;
4588 Ok(match etrue.as_ref() {
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 {
4597 vec![instr::label(alloc, false_label), emit_expr(e, env, efalse)?],
4602 // only emit true branch if there is fallthrough from condition
4603 let true_branch = if r.is_fallthrough {
4607 emit_expr(e, env, etrue)?,
4608 emit_pos(alloc, pos),
4609 instr::jmp(alloc, end_label),
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)
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)?;
4639 instr::jmpnz(alloc, end_label),
4642 instr::label(alloc, end_label),
4649 fn emit_local<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4650 e: &mut Emitter<'arena, 'decl, D>,
4651 env: &Env<'a, 'arena>,
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(
4662 instr::string(alloc, string_utils::locals::strip_dollar(id_name)),
4663 emit_pos(alloc, pos),
4664 instr::cgetg(alloc),
4668 let local = get_local(e, env, pos, id_name)?;
4670 if is_local_this(env, id) && !env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS) {
4671 emit_pos_then(alloc, pos, instr::barethis(alloc, notice))
4673 instr::cgetl(alloc, local)
4679 fn emit_class_const<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4680 e: &mut Emitter<'arena, 'decl, D>,
4681 env: &Env<'a, 'arena>,
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;
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))
4703 emit_pos_then(alloc, &pos, instr::string(alloc, cname))
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))
4715 let load_const = if string_utils::is_class(&id.1) {
4716 if e.options().emit_class_pointers() == 2 {
4717 instr::lazyclassfromclass(alloc)
4719 instr::classname(alloc)
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)
4728 if string_utils::is_class(&id.1) && e.options().emit_class_pointers() == 1 {
4729 emit_load_class_ref(e, env, pos, cexpr)
4731 Ok(InstrSeq::gather(
4733 vec![emit_load_class_ref(e, env, pos, cexpr)?, load_const],
4740 fn emit_unop<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4741 e: &mut Emitter<'arena, 'decl, D>,
4742 env: &Env<'a, 'arena>,
4744 (uop, expr): &(ast_defs::Uop, ast::Expr),
4745 ) -> Result<InstrSeq<'arena>> {
4746 use ast_defs::Uop as U;
4747 let alloc = env.arena;
4749 U::Utild | U::Unot => Ok(InstrSeq::gather(
4752 emit_expr(e, env, expr)?,
4753 emit_pos_then(alloc, pos, from_unop(alloc, e.options(), uop)?),
4756 U::Uplus | U::Uminus => Ok(InstrSeq::gather(
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)?),
4765 U::Uincr | U::Udecr | U::Upincr | U::Updecr => emit_lval_op(
4769 LValOp::IncDec(unop_to_incdec_op(e.options(), uop)?),
4774 U::Usilence => e.local_scope(|e| {
4775 let temp_local = e.local_gen_mut().get_unnamed();
4776 Ok(InstrSeq::gather(
4779 emit_pos(alloc, pos),
4780 instr::silence_start(alloc, temp_local),
4782 let try_instrs = emit_expr(e, env, expr)?;
4783 let catch_instrs = InstrSeq::gather(
4785 vec![emit_pos(alloc, pos), instr::silence_end(alloc, temp_local)],
4787 InstrSeq::create_try_catch(
4791 false, /* skip_throw */
4796 emit_pos(alloc, pos),
4797 instr::silence_end(alloc, temp_local),
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};
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())),
4816 fn from_unop<'arena>(
4817 alloc: &'arena bumpalo::Bump,
4820 ) -> Result<InstrSeq<'arena>> {
4821 use ast_defs::Uop as U;
4823 U::Utild => instr::bitnot(alloc),
4824 U::Unot => instr::not(alloc),
4826 if opts.check_int_overflow() {
4833 if opts.check_int_overflow() {
4840 return Err(Unrecoverable(
4841 "this unary operation cannot be translated".into(),
4847 fn binop_to_eqop(opts: &Options, op: &ast_defs::Bop) -> Option<EqOp> {
4848 use {ast_defs::Bop as B, EqOp::*};
4850 B::Plus => Some(if opts.check_int_overflow() {
4855 B::Minus => Some(if opts.check_int_overflow() {
4860 B::Star => Some(if opts.check_int_overflow() {
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),
4878 #[allow(clippy::needless_lifetimes)]
4879 fn optimize_null_checks<'arena, 'decl, D: DeclProvider<'decl>>(
4880 e: &Emitter<'arena, 'decl, D>,
4883 .hack_compiler_flags
4884 .contains(CompilerFlags::OPTIMIZE_NULL_CHECKS)
4887 fn from_binop<'arena>(
4888 alloc: &'arena bumpalo::Bump,
4891 ) -> Result<InstrSeq<'arena>> {
4892 use ast_defs::Bop as B;
4895 if opts.check_int_overflow() {
4902 if opts.check_int_overflow() {
4909 if opts.check_int_overflow() {
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(),
4939 B::Barbar | B::Ampamp => {
4940 return Err(Unrecoverable(
4941 "short-circuiting operator cannot be generated as a simple binop".into(),
4947 fn emit_first_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4948 e: &mut Emitter<'arena, 'decl, D>,
4949 env: &Env<'a, 'arena>,
4951 ) -> Result<(InstrSeq<'arena>, bool)> {
4952 let alloc = env.arena;
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))) =>
4959 instr::cgetl2(alloc, get_local(e, env, &l.0, local_id::get_name(&l.1))?),
4963 _ => (emit_expr(e, env, expr)?, false),
4967 pub fn emit_two_exprs<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4968 e: &mut Emitter<'arena, 'decl, D>,
4969 env: &Env<'a, 'arena>,
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(
4982 vec![emit_pos(alloc, outer_pos), instrs2, instrs1]
4984 vec![instrs2, emit_pos(alloc, outer_pos), instrs1]
4986 } else if instrs2_is_var {
4987 vec![instrs1, emit_pos(alloc, outer_pos), instrs2]
4989 vec![instrs1, instrs2, emit_pos(alloc, outer_pos)]
4994 fn emit_readonly_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
4995 e: &mut Emitter<'arena, 'decl, D>,
4996 env: &Env<'a, 'arena>,
4998 expr: &aast::Expr<(), ()>,
4999 ) -> Result<InstrSeq<'arena>> {
5001 aast::Expr_::ObjGet(x) => {
5002 Ok(emit_obj_get(e, env, pos, QueryOp::CGet, &x.0, &x.1, &x.2, false, true)?.0)
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)
5008 _ => emit_expr(e, env, expr),
5012 fn emit_quiet_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5013 e: &mut Emitter<'arena, 'decl, D>,
5014 env: &Env<'a, 'arena>,
5017 null_coalesce_assignment: bool,
5018 ) -> Result<(InstrSeq<'arena>, Option<NumParams>)> {
5019 let alloc = env.arena;
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))?),
5025 ast::Expr_::ArrayGet(x) => emit_array_get(
5034 null_coalesce_assignment,
5036 ast::Expr_::ObjGet(x) => {
5038 Ok((emit_expr(e, env, expr)?, None))
5048 null_coalesce_assignment,
5053 _ => Ok((emit_expr(e, env, expr)?, None)),
5057 fn emit_null_coalesce_assignment<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5058 e: &mut Emitter<'arena, 'decl, D>,
5059 env: &Env<'a, 'arena>,
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(
5072 iter::repeat_with(|| instr::popc(alloc))
5074 .collect::<Vec<_>>(),
5076 None => instr::empty(alloc),
5078 Ok(InstrSeq::gather(
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),
5091 emit_lval_op(e, env, pos, LValOp::Set, e1, Some(e2), true)?,
5092 instr::label(alloc, end_label),
5097 fn emit_short_circuit_op<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5098 e: &mut Emitter<'arena, 'decl, D>,
5099 env: &Env<'a, 'arena>,
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 {
5112 emit_pos(alloc, pos),
5113 instr::false_(alloc),
5114 instr::jmp(alloc, its_done),
5115 if jmp_instrs.is_label_used {
5119 instr::label(alloc, its_true),
5120 emit_pos(alloc, pos),
5121 instr::true_(alloc),
5127 instr::label(alloc, its_done),
5135 if jmp_instrs.is_label_used {
5139 instr::label(alloc, its_true),
5140 emit_pos(alloc, pos),
5141 instr::true_(alloc),
5152 fn emit_binop<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5153 e: &mut Emitter<'arena, 'decl, D>,
5154 env: &Env<'a, 'arena>,
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;
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)
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),
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(
5177 emit_quiet_expr(e, env, pos, e1, false)?.0,
5179 instr::istypec(alloc, IstypeOp::OpNull),
5181 instr::jmpnz(alloc, end_label),
5184 instr::label(alloc, end_label),
5189 let default = |e: &mut Emitter<'arena, 'decl, D>| {
5190 Ok(InstrSeq::gather(
5193 emit_two_exprs(e, env, pos, e1, e2)?,
5194 from_binop(alloc, e.options(), op)?,
5198 if optimize_null_checks(e) {
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(
5204 vec![emit_is_null(e, env, e1)?, instr::not(alloc)],
5206 B::Diff2 if e1.2.is_null() => Ok(InstrSeq::gather(
5208 vec![emit_is_null(e, env, e2)?, instr::not(alloc)],
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)?;
5232 InstrSeq::gather(alloc, vec![lhs_instrs, instr::popl(alloc, local)]),
5234 instr::unsetl(alloc, local),
5239 fn emit_as<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5240 e: &mut Emitter<'arena, 'decl, D>,
5241 env: &Env<'a, 'arena>,
5243 (expr, h, is_nullable): &(ast::Expr, aast_defs::Hint, bool),
5244 ) -> Result<InstrSeq<'arena>> {
5245 let alloc = env.arena;
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| {
5257 instr::setl(alloc, type_struct_local),
5259 TypestructResolveOp::Resolve => instr::is_type_structc_resolve(alloc),
5260 TypestructResolveOp::DontResolve => {
5261 instr::is_type_structc_dontresolve(alloc)
5264 instr::jmpnz(alloc, then_label),
5268 vec![instr::null(alloc), instr::jmp(alloc, done_label)],
5274 instr::pushl(alloc, arg_local),
5275 instr::pushl(alloc, type_struct_local),
5276 instr::throwastypestructexception(alloc),
5283 let i2 = if is_static {
5285 get_type_structure_for_hint(alloc, e, &[], &IndexSet::new(), h)?,
5286 TypestructResolveOp::Resolve,
5289 main_block(ts_instrs, TypestructResolveOp::DontResolve)
5291 let i1 = emit_expr(e, env, expr)?;
5292 Ok(InstrSeq::gather(
5296 instr::setl(alloc, arg_local),
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),
5307 fn emit_cast<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5308 e: &mut Emitter<'arena, 'decl, D>,
5309 env: &Env<'a, 'arena>,
5311 hint: &aast_defs::Hint_,
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),
5325 return Err(emit_fatal::raise_fatal_parse(
5327 format!("Invalid cast type: {}", id),
5332 _ => return Err(emit_fatal::raise_fatal_parse(pos, "Invalid cast type")),
5334 Ok(InstrSeq::gather(
5336 vec![emit_expr(e, env, expr)?, emit_pos(alloc, pos), op],
5340 pub fn emit_unset_expr<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5341 e: &mut Emitter<'arena, 'decl, D>,
5342 env: &Env<'a, 'arena>,
5344 ) -> Result<InstrSeq<'arena>> {
5345 let alloc = env.arena;
5346 emit_lval_op_nonlist(
5352 instr::empty(alloc),
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>,
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(
5371 format!("{} {}", name, msg),
5375 let (base, offset, src, args) = if args.len() >= 3 {
5376 (&args[0], &args[1], &args[2], &args[3..])
5378 return raise_fatal("expects at least 3 arguments");
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"),
5388 let (base_expr, cls_expr, base_setup, base_stack, cls_stack) = emit_base(
5392 MemberOpMode::Define,
5393 false, /* is_object */
5395 false, /*null_coalesce_assignment*/
5396 3, /* base_offset */
5397 3, /* rhs_stack_size */
5398 ReadonlyOp::Any, /* readonly_op */
5400 Ok(InstrSeq::gather(
5405 emit_expr(e, env, offset)?,
5406 emit_expr(e, env, src)?,
5411 Instruct::IFinal(InstructFinal::SetRangeM(
5412 (base_stack + cls_stack)
5414 .expect("StackIndex overflow"),
5415 kind.size.try_into().expect("Setrange size overflow"),
5423 pub fn is_reified_tparam<'a, 'arena>(
5424 env: &Env<'a, 'arena>,
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))
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)))
5442 is(env.scope.get_fun_tparams())
5444 is(&env.scope.get_class_tparams()[..])
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
5472 /// For example, the r-value expression $arr[3][$ix+2]
5474 /// # Section 1, pushing the value of $ix+2 on the stack
5478 /// # Section 2, constructing the base address of $arr[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>,
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
5502 let result = emit_base_(
5509 null_coalesce_assignment,
5513 readonly_enforcement,
5516 ArrayGetBase::Regular(i) => Ok((
5520 i.base_stack_size as isize,
5521 i.cls_stack_size as isize,
5523 ArrayGetBase::Inout { .. } => Err(unrecoverable("unexpected input")),
5527 fn is_trivial(env: &Env, is_base: bool, expr: &ast::Expr) -> bool {
5528 use ast::Expr_ as E_;
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)
5537 .map_or(true, |e| is_trivial(env, is_base, &e))
5543 fn get_local_temp_kind<'a, 'arena>(
5544 env: &Env<'a, 'arena>,
5546 inout_param_info: Option<(usize, &inout_locals::AliasInfoMap)>,
5547 expr: Option<&ast::Expr>,
5548 ) -> Option<StoredValueKind> {
5549 match (expr, inout_param_info) {
5551 (Some(ast::Expr(_, _, ast::Expr_::Lvar(id))), Some((i, aliases)))
5552 if inout_locals::should_save_local_value(id.name(), i, aliases) =>
5554 Some(StoredValueKind::Local)
5557 if is_trivial(env, is_base, e) {
5560 Some(StoredValueKind::Expr)
5567 fn emit_base_<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5568 e: &mut Emitter<'arena, 'decl, D>,
5569 env: &Env<'a, 'arena>,
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;
5582 let expr_ = &expr.2;
5583 let base_mode = if mode == MemberOpMode::InOut {
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>,
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)))],
5608 store: instr::basel(alloc, local, MemberOpMode::Define),
5611 _ => ArrayGetBase::Regular(ArrayGetBaseData {
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)?;
5627 instr::empty(alloc),
5628 emit_pos_then(alloc, pos, instr::basec(alloc, base_offset, base_mode)),
5634 use ast::Expr_ as E_;
5636 E_::Lvar(x) if superglobals::is_superglobal(&(x.1).1) => {
5637 let base_instrs = emit_pos_then(
5640 instr::string(alloc, string_utils::locals::strip_dollar(&(x.1).1)),
5646 instr::empty(alloc),
5647 instr::basegc(alloc, base_offset, base_mode),
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));
5657 instr::empty(alloc),
5658 instr::baseh(alloc),
5664 if !is_local_this(env, &x.1) || env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS) =>
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)
5675 instr::empty(alloc),
5676 instr::basel(alloc, v, base_mode),
5682 let local = emit_local(e, env, notice, lid)?;
5686 instr::empty(alloc),
5687 instr::basec(alloc, base_offset, base_mode),
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
5697 .contains(hhbc_by_ref_env::Flags::ALLOWS_ARRAY_APPEND) =>
5699 Err(emit_fatal::raise_fatal_runtime(
5701 "Can't use [] for reading",
5704 // base is in turn array_get - do a specific handling for inout params
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(
5715 null_coalesce_assignment,
5717 let base_result = emit_base_(
5724 null_coalesce_assignment,
5725 base_offset + elem_stack_size,
5728 readonly_op, // continue passing readonly enforcement up
5730 let cls_stack_size = match &base_result {
5731 ArrayGetBase::Regular(base) => base.cls_stack_size,
5732 ArrayGetBase::Inout { load, .. } => load.cls_stack_size,
5734 let (mk, warninstr) = get_elem_member_key(
5737 base_offset + cls_stack_size,
5739 null_coalesce_assignment,
5741 let make_setup_instrs = |base_setup_instrs: InstrSeq<'arena>| {
5744 vec![warninstr, base_setup_instrs, instr::dim(alloc, mode, mk)],
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(
5751 InstrSeq::gather(alloc, vec![base.base_instrs, elem_instrs]),
5753 make_setup_instrs(base.setup_instrs),
5754 base.base_stack_size + elem_stack_size,
5755 base.cls_stack_size,
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();
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,
5771 store: emit_store_for_simple_base(
5783 // base needs temps, index - does not
5785 ArrayGetBase::Inout {
5798 base_instrs.push((elem_instrs, None));
5799 ArrayGetBase::Inout {
5800 load: ArrayGetBaseData {
5803 setup_instrs: make_setup_instrs(setup_instrs),
5804 base_stack_size: base_stack_size + elem_stack_size,
5807 store: InstrSeq::gather(
5809 vec![store, instr::dim(alloc, MemberOpMode::Define, mk)],
5813 // both base and index needs locals
5815 ArrayGetBase::Inout {
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 {
5834 setup_instrs: make_setup_instrs(setup_instrs),
5835 base_stack_size: base_stack_size + elem_stack_size,
5838 store: InstrSeq::gather(
5844 MemberOpMode::Define,
5845 MemberKey::EL(local, ReadonlyOp::Any),
5856 emit_expr_default(e, env, expr)
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) => {
5862 emit_xhp_obj_get(e, env, pos, base_expr, &s, null_flavor)?;
5866 instr::empty(alloc),
5867 instr::basec(alloc, base_offset, base_mode),
5873 let prop_stack_size = emit_prop_expr(
5879 null_coalesce_assignment,
5880 ReadonlyOp::Any, // just getting stack size here
5884 base_expr_instrs_begin,
5885 base_expr_instrs_end,
5896 null_coalesce_assignment,
5897 base_offset + prop_stack_size,
5899 ReadonlyOp::Mutable, // the rest of the base must be completely mutable
5901 let (mk, prop_instrs, _) = emit_prop_expr(
5905 base_offset + cls_stack_size,
5907 null_coalesce_assignment,
5908 readonly_op, // use the current enforcement
5910 let total_stack_size = prop_stack_size + base_stack_size;
5911 let final_instr = instr::dim(alloc, mode, mk);
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]),
5924 E_::ClassGet(x) => {
5926 emit_expr_default(e, env, expr)
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)?;
5947 _ => emit_expr_default(e, env, expr),
5951 pub fn emit_ignored_exprs<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5952 emitter: &mut Emitter<'arena, 'decl, D>,
5953 env: &Env<'a, 'arena>,
5955 exprs: &[ast::Expr],
5956 ) -> Result<InstrSeq<'arena>> {
5957 let alloc = env.arena;
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>,
5971 ) -> Result<InstrSeq<'arena>> {
5972 let alloc = env.arena;
5973 Ok(InstrSeq::gather(
5976 emit_expr(emitter, env, expr)?,
5977 emit_pos_then(alloc, pos, instr::popc(alloc)),
5982 pub fn emit_lval_op<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
5983 e: &mut Emitter<'arena, 'decl, D>,
5984 env: &Env<'a, 'arena>,
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());
5999 scope::with_unnamed_local(alloc, e, |alloc, e, local| {
6000 let loc = if can_use_as_rhs_in_list_assignment(&expr2.2)? {
6005 let (instr_lhs, instr_assign) =
6006 emit_lval_op_list(e, env, pos, loc, &[], expr1, false)?;
6010 vec![instr_lhs, instr_rhs, instr::popl(alloc, local)],
6013 instr::pushl(alloc, local),
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();
6027 emit_yield(e, env, pos, af)?,
6028 instr::setl(alloc, temp),
6030 instr::pushl(alloc, temp),
6037 Some(expr) => (emit_expr(e, env, expr)?, 1, is_readonly_expr(expr)),
6039 emit_lval_op_nonlist(
6048 null_coalesce_assignment,
6054 fn can_use_as_rhs_in_list_assignment(expr: &ast::Expr_) -> Result<bool> {
6055 use aast::Expr_ as E_;
6060 .map_or(false, |id| id.1 == special_functions::ECHO) =>
6064 E_::ObjGet(o) if !o.as_ref().3 => true,
6065 E_::ClassGet(c) if !c.as_ref().2 => true,
6069 | E_::FunctionPointer(_)
6083 | E_::ReadonlyExpr(_)
6084 | E_::ClassConst(_) => true,
6085 E_::Pipe(p) => can_use_as_rhs_in_list_assignment(&(p.2).2)?,
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);
6092 b.0.is_plus() || b.0.is_question_question() || b.0.is_any_eq()
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 ...
6103 // QueryM 0 CGet EI:i_1
6104 fn emit_array_get_fixed<'arena, 'decl, D: DeclProvider<'decl>>(
6105 alloc: &'arena bumpalo::Bump,
6107 local: Local<'arena>,
6109 ) -> InstrSeq<'arena> {
6110 let (base, stack_count) = if last_usage {
6115 instr::pushl(alloc, local),
6116 instr::basec(alloc, 0, MemberOpMode::Warn),
6122 (instr::basel(alloc, local, MemberOpMode::Warn), 0)
6124 let indices = InstrSeq::gather(
6131 let mk = MemberKey::EI(*ix as i64, ReadonlyOp::Any);
6133 instr::querym(alloc, stack_count, QueryOp::CGet, mk)
6135 instr::dim(alloc, MemberOpMode::Warn, mk)
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
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>,
6158 local: Option<&Local<'arena>>,
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);
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
6173 exprs.iter().rposition(|v| !v.2.is_omitted())
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())
6182 let (lhs_instrs, set_instrs): (Vec<InstrSeq<'arena>>, Vec<InstrSeq<'arena>>) = exprs
6186 let mut new_indices = vec![i as isize];
6187 new_indices.extend_from_slice(indices);
6195 last_non_omitted.map_or(false, |j| j == i),
6198 .collect::<Result<Vec<_>>>()?
6202 InstrSeq::gather(alloc, lhs_instrs),
6206 set_instrs.into_iter().rev().collect()
6213 E_::Omitted => Ok((instr::empty(alloc), instr::empty(alloc))),
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)
6220 (Some(loc), []) => {
6222 instr::pushl(alloc, loc.to_owned())
6224 instr::cgetl(alloc, loc.to_owned())
6227 (None, _) => instr::null(alloc),
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(
6240 false, // TODO: readonly assignment (list expressions)
6244 instr::empty(alloc),
6247 vec![lhs_instrs, rhs_instrs, set_op, instr::popc(alloc)],
6255 vec![instr::empty(alloc), rhs_instrs, set_op, instr::popc(alloc)],
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>,
6269 rhs_instrs: InstrSeq<'arena>,
6270 rhs_stack_size: isize,
6272 null_coalesce_assignment: bool,
6273 ) -> Result<InstrSeq<'arena>> {
6274 let alloc = env.arena;
6275 emit_lval_op_nonlist_steps(
6284 null_coalesce_assignment,
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,
6293 ) -> InstrSeq<'arena> {
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)),
6303 pub fn emit_final_local_op<'arena, 'decl, D: DeclProvider<'decl>>(
6304 alloc: &'arena bumpalo::Bump,
6308 ) -> InstrSeq<'arena> {
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),
6322 fn emit_final_member_op<'arena, 'decl, D: DeclProvider<'decl>>(
6323 alloc: &'arena bumpalo::Bump,
6326 mk: MemberKey<'arena>,
6327 ) -> InstrSeq<'arena> {
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),
6337 fn emit_final_static_op<'arena, 'decl, D: DeclProvider<'decl>>(
6338 alloc: &'arena bumpalo::Bump,
6340 prop: &ast::ClassGetExpr,
6342 ) -> Result<InstrSeq<'arena>> {
6345 L::Set => instr::sets(alloc, ReadonlyOp::Any),
6346 L::SetOp(op) => instr::setops(alloc, op),
6347 L::IncDec(op) => instr::incdecs(alloc, op),
6349 let pos = match prop {
6350 ast::ClassGetExpr::CGstring((pos, _))
6351 | ast::ClassGetExpr::CGexpr(ast::Expr(_, pos, _)) => pos,
6353 let cid = text_of_class_id(cid);
6354 let id = text_of_prop(prop);
6355 emit_fatal::emit_fatal_runtime(
6359 "Attempt to unset static property {}::{}",
6360 string_utils::strip_ns(&cid),
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>,
6374 rhs_instrs: InstrSeq<'arena>,
6375 rhs_stack_size: isize,
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_;
6383 E_::Lvar(v) if superglobals::is_any_global(local_id::get_name(&v.1)) => (
6387 instr::string(alloc, string_utils::lstrip(local_id::get_name(&v.1), "$")),
6390 emit_final_global_op::<D>(alloc, outer_pos, op),
6392 E_::Lvar(v) if is_local_this(env, &v.1) && op.is_incdec() => (
6393 emit_local(e, env, BareThisOp::Notice, v)?,
6395 instr::empty(alloc),
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)
6403 E_::ArrayGet(x) => match (&(x.0).1, x.1.as_ref()) {
6407 .contains(hhbc_by_ref_env::Flags::ALLOWS_ARRAY_APPEND) =>
6409 return Err(emit_fatal::raise_fatal_runtime(
6411 "Can't use [] for reading",
6414 (_, opt_elem_expr) => {
6415 let mode = match op {
6416 LValOp::Unset => MemberOpMode::Unset,
6417 _ => MemberOpMode::Define,
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
6425 ReadonlyOp::CheckMutROCOW // writing a mut value requires left side to be mutable or a ROCOW
6428 base_expr_instrs_begin,
6429 base_expr_instrs_end,
6440 null_coalesce_assignment,
6445 let (mk, warninstr) = get_elem_member_key(
6448 rhs_stack_size + cls_stack_size,
6450 null_coalesce_assignment,
6452 let total_stack_size = elem_stack_size + base_stack_size + cls_stack_size;
6453 let final_instr = emit_pos_then(
6456 emit_final_member_op::<D>(alloc, total_stack_size as usize, op, mk),
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 {
6467 vec![base_expr_instrs_begin, elem_instrs, base_expr_instrs_end],
6474 emit_pos(alloc, pos),
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(
6488 "?-> is not allowed in write context",
6491 let mode = match op {
6492 LValOp::Unset => MemberOpMode::Unset,
6493 _ => MemberOpMode::Define,
6495 let readonly_op = if rhs_readonly {
6496 ReadonlyOp::Readonly
6500 let prop_stack_size = emit_prop_expr(
6506 null_coalesce_assignment,
6510 let base_offset = prop_stack_size + rhs_stack_size;
6512 base_expr_instrs_begin,
6513 base_expr_instrs_end,
6524 null_coalesce_assignment,
6527 ReadonlyOp::Mutable, // writing to a property requires everything in the base to be mutable
6529 let (mk, prop_instrs, _) = emit_prop_expr(
6533 rhs_stack_size + cls_stack_size,
6535 null_coalesce_assignment,
6538 let total_stack_size = prop_stack_size + base_stack_size + cls_stack_size;
6539 let final_instr = emit_pos_then(
6542 emit_final_member_op::<D>(alloc, total_stack_size as usize, op, mk),
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 {
6553 vec![base_expr_instrs_begin, prop_instrs, base_expr_instrs_end],
6557 InstrSeq::gather(alloc, vec![base_setup_instrs, final_instr]),
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_);
6566 InstrSeq::from((alloc, emit_class_expr(e, env, cexpr, prop)?)),
6572 instr::empty(alloc),
6577 emit_lval_op_nonlist(
6583 instr::empty(alloc),
6586 false, // all unary operations (++, --, etc) are on primitives, so no HHVM readonly checks
6588 from_unop(alloc, e.options(), &uop.0)?,
6593 return Err(emit_fatal::raise_fatal_parse(
6595 "Can't use return value in write context",
6600 // TODO(shiqicao): remove clone!
6601 let alloc = env.arena;
6602 let mut env = env.clone();
6604 LValOp::Set | LValOp::SetOp(_) | LValOp::IncDec(_) => {
6605 env.with_allows_array_append(alloc, f)
6607 _ => f(alloc, &mut env),
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(
6621 instr::string(alloc, string_utils::locals::strip_dollar(id)),
6623 ast::ClassGetExpr::CGexpr(expr) => emit_expr(e, env, expr),
6625 let alloc = env.arena;
6627 ClassExpr::Expr(expr)
6629 || expr.2.is_binop()
6630 || expr.2.is_class_get()
6634 .map_or(false, |ast::Lid(_, id)| local_id::get_name(id) == "$this") =>
6636 let cexpr_local = emit_expr(e, env, expr)?;
6638 instr::empty(alloc),
6643 scope::stash_top_in_unnamed_local(alloc, e, load_prop)?,
6644 instr::classgetc(alloc),
6650 let pos = match prop {
6651 ast::ClassGetExpr::CGstring((pos, _))
6652 | ast::ClassGetExpr::CGexpr(ast::Expr(_, pos, _)) => pos,
6655 load_prop(alloc, e)?,
6656 emit_load_class_ref(e, env, pos, cexpr)?,
6662 pub fn fixup_type_arg<'a, 'b, 'arena>(
6663 env: &Env<'b, 'arena>,
6665 hint: &'a ast::Hint,
6666 ) -> Result<impl AsRef<ast::Hint> + 'a> {
6667 struct Checker<'s> {
6668 erased_tparams: &'s [&'s str],
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> {
6682 ) -> StdResult<(), Option<Error>> {
6683 hf.param_tys.accept(c, self.object())?;
6684 hf.return_ty.accept(c, self.object())
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 =>
6693 return Err(Some(emit_fatal::raise_fatal_parse(
6695 "Erased generics are not allowed in is/as expressions",
6698 H_::Haccess(_, _) => return Ok(()),
6701 h.recurse(c, self.object())
6704 fn visit_hint_(&mut self, c: &mut (), h: &ast::Hint_) -> StdResult<(), Option<Error>> {
6705 use ast::{Hint_ as H_, Id};
6707 H_::Happly(Id(_, id), _) if self.erased_tparams.contains(&id.as_str()) => Err(None),
6708 _ => h.recurse(c, self.object()),
6713 struct Updater<'s> {
6714 erased_tparams: &'s [&'s str],
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> {
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())
6728 fn visit_hint_(&mut self, c: &mut (), h: &mut ast::Hint_) -> StdResult<(), ()> {
6729 use ast::{Hint_ as H_, Id};
6731 H_::Happly(Id(_, id), _) if self.erased_tparams.contains(&id.as_str()) => {
6732 Ok(*id = "_".into())
6734 _ => h.recurse(c, self.object()),
6738 let erased_tparams = get_erased_tparams(env);
6739 let erased_tparams = erased_tparams.as_slice();
6740 let mut checker = Checker {
6744 match visit(&mut checker, &mut (), hint) {
6745 Ok(()) => Ok(Either::Left(hint)),
6746 Err(Some(error)) => Err(error),
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))
6756 pub fn emit_reified_arg<'b, 'arena, 'decl, D: DeclProvider<'decl>>(
6757 e: &mut Emitter<'arena, 'decl, D>,
6758 env: &Env<'b, 'arena>,
6762 ) -> Result<(InstrSeq<'arena>, bool)> {
6763 struct Collector<'ast, 'a> {
6764 current_tags: &'a HashSet<&'a str>,
6765 acc: IndexSet<&'ast str>,
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);
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> {
6783 fn visit_hint_(&mut self, c: &mut (), h_: &'ast ast::Hint_) -> StdResult<(), ()> {
6784 use ast::{Hint_ as H_, Id};
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())
6791 _ => h_.recurse(c, self.object()),
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);
6803 let current_tags = env
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
6811 .fold(current_tags, |acc, t| f(acc, &*t));
6812 let mut collector = Collector {
6813 current_tags: ¤t_tags,
6814 acc: IndexSet::new(),
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()) =>
6821 Ok((emit_reified_type::<D>(env, pos, name)?, false))
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() {
6829 let values = collector
6832 .map(|v| emit_reified_type::<D>(env, pos, v))
6833 .collect::<Result<Vec<_>>>()?;
6834 InstrSeq::gather(alloc, vec![InstrSeq::gather(alloc, values), ts])
6841 instr::combine_and_resolve_type_struct(
6843 (collector.acc.len() + 1) as isize,
6847 collector.acc.is_empty(),
6853 pub fn get_local<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
6854 e: &mut Emitter<'arena, 'decl, D>,
6855 env: &Env<'a, 'arena>,
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(
6864 "Pipe variables must occur only in the RHS of pipe expressions",
6866 Some(var) => Ok(*var),
6868 } else if special_idents::is_tmp_var(s) {
6869 Ok(*e.local_gen().get_unnamed_for_tempname(s))
6871 Ok(Local::Named(Str::new_str(alloc, s)))
6875 pub fn emit_is_null<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
6876 e: &mut Emitter<'arena, 'decl, D>,
6877 env: &Env<'a, 'arena>,
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(
6885 get_local(e, env, pos, local_id::get_name(id))?,
6891 Ok(InstrSeq::gather(
6894 emit_expr(e, env, expr)?,
6895 instr::istypec(alloc, IstypeOp::OpNull),
6900 pub fn emit_jmpnz<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
6901 e: &mut Emitter<'arena, 'decl, D>,
6902 env: &Env<'a, 'arena>,
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);
6910 match ast_constant_folder::expr_to_typed_value(alloc, e, expr) {
6912 if Into::<bool>::into(tv) {
6914 instrs: emit_pos_then(alloc, pos, instr::jmp(alloc, label)),
6915 is_fallthrough: false,
6916 is_label_used: true,
6920 instrs: emit_pos_then(alloc, pos, instr::empty(alloc)),
6921 is_fallthrough: true,
6922 is_label_used: false,
6927 use {ast::Expr_ as E, ast_defs::Uop as U};
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)?;
6935 instrs: emit_pos_then(
6938 InstrSeq::gather(alloc, vec![r1.instrs, r2.instrs]),
6940 is_fallthrough: r2.is_fallthrough,
6941 is_label_used: r1.is_label_used || r2.is_label_used,
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 {
6952 instrs: emit_pos_then(
6957 if r1.is_label_used {
6958 vec![r1.instrs, instr::label(alloc, skip_label)]
6964 is_fallthrough: r1.is_label_used,
6965 is_label_used: false,
6968 let r2 = emit_jmpnz(e, env, &bo.2, label)?;
6970 instrs: emit_pos_then(
6975 if r1.is_label_used {
6979 instr::label(alloc, skip_label),
6982 vec![r1.instrs, r2.instrs]
6986 is_fallthrough: r2.is_fallthrough || r1.is_label_used,
6987 is_label_used: r2.is_label_used,
6993 && ((bo.1).2.is_null() || (bo.2).2.is_null())
6997 emit_is_null(e, env, if (bo.1).2.is_null() { &bo.2 } else { &bo.1 })?;
6999 instrs: emit_pos_then(
7002 InstrSeq::gather(alloc, vec![is_null, instr::jmpnz(alloc, label)]),
7004 is_fallthrough: true,
7005 is_label_used: true,
7009 if bo.0.is_diff2() && ((bo.1).2.is_null() || (bo.2).2.is_null()) && opt =>
7012 emit_is_null(e, env, if (bo.1).2.is_null() { &bo.2 } else { &bo.1 })?;
7014 instrs: emit_pos_then(
7017 InstrSeq::gather(alloc, vec![is_null, instr::jmpz(alloc, label)]),
7019 is_fallthrough: true,
7020 is_label_used: true,
7024 let instr = emit_expr(e, env, expr)?;
7026 instrs: emit_pos_then(
7029 InstrSeq::gather(alloc, vec![instr, instr::jmpnz(alloc, label)]),
7031 is_fallthrough: true,
7032 is_label_used: true,
7041 pub fn emit_jmpz<'a, 'arena, 'decl, D: DeclProvider<'decl>>(
7042 e: &mut Emitter<'arena, 'decl, D>,
7043 env: &Env<'a, 'arena>,
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);
7051 match ast_constant_folder::expr_to_typed_value(alloc, e, expr) {
7053 let b: bool = v.into();
7056 instrs: emit_pos_then(alloc, pos, instr::empty(alloc)),
7057 is_fallthrough: true,
7058 is_label_used: false,
7062 instrs: emit_pos_then(alloc, pos, instr::jmp(alloc, label)),
7063 is_fallthrough: false,
7064 is_label_used: true,
7069 use {ast::Expr_ as E, ast_defs::Uop as U};
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 {
7077 instrs: emit_pos_then(
7082 if r1.is_label_used {
7083 vec![r1.instrs, instr::label(alloc, skip_label)]
7089 is_fallthrough: r1.is_label_used,
7090 is_label_used: false,
7093 let r2 = emit_jmpz(e, env, &bo.2, label)?;
7095 instrs: emit_pos_then(
7100 if r1.is_label_used {
7104 instr::label(alloc, skip_label),
7107 vec![r1.instrs, r2.instrs]
7111 is_fallthrough: r2.is_fallthrough || r1.is_label_used,
7112 is_label_used: r2.is_label_used,
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)?;
7121 instrs: emit_pos_then(
7124 InstrSeq::gather(alloc, vec![r1.instrs, r2.instrs]),
7126 is_fallthrough: r2.is_fallthrough,
7127 is_label_used: r1.is_label_used || r2.is_label_used,
7131 instrs: emit_pos_then(alloc, pos, r1.instrs),
7132 is_fallthrough: false,
7133 is_label_used: r1.is_label_used,
7139 && ((bo.1).2.is_null() || (bo.2).2.is_null())
7143 emit_is_null(e, env, if (bo.1).2.is_null() { &bo.2 } else { &bo.1 })?;
7145 instrs: emit_pos_then(
7148 InstrSeq::gather(alloc, vec![is_null, instr::jmpz(alloc, label)]),
7150 is_fallthrough: true,
7151 is_label_used: true,
7155 if bo.0.is_diff2() && ((bo.1).2.is_null() || (bo.2).2.is_null()) && opt =>
7158 emit_is_null(e, env, if (bo.1).2.is_null() { &bo.2 } else { &bo.1 })?;
7160 instrs: emit_pos_then(
7163 InstrSeq::gather(alloc, vec![is_null, instr::jmpnz(alloc, label)]),
7165 is_fallthrough: true,
7166 is_label_used: true,
7170 let instr = emit_expr(e, env, expr)?;
7172 instrs: emit_pos_then(
7175 InstrSeq::gather(alloc, vec![instr, instr::jmpz(alloc, label)]),
7177 is_fallthrough: true,
7178 is_label_used: true,