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 ast_class_expr_rust::ClassExpr;
7 use ast_constant_folder_rust as ast_constant_folder;
8 use emit_adata_rust as emit_adata;
9 use emit_fatal_rust as emit_fatal;
10 use emit_pos_rust::{emit_pos, emit_pos_then};
11 use emit_symbol_refs_rust as emit_symbol_refs;
12 use emit_type_constant_rust as emit_type_constant;
13 use env::{emitter::Emitter, local, Env, Flags as EnvFlags};
14 use hhas_symbol_refs_rust::IncludePath;
16 use hhbc_id_rust::{class, r#const, function, method, prop, Id};
17 use hhbc_string_utils_rust as string_utils;
18 use instruction_sequence_rust::{
20 Error::{self, Unrecoverable},
23 use itertools::{Either, Itertools};
24 use label_rust::Label;
25 use lazy_static::lazy_static;
26 use naming_special_names_rust::{
27 emitter_special_functions, fb, pseudo_consts, pseudo_functions, special_functions,
28 special_idents, superglobals, typehints, user_attributes,
30 use options::{CompilerFlags, HhvmFlags, LangFlags, Options};
33 aast_visitor::{visit, visit_mut, AstParams, Node, NodeMut, Visitor, VisitorMut},
34 ast as tast, ast_defs, local_id,
38 use runtime::TypedValue;
39 use scope_rust::scope;
41 use indexmap::IndexSet;
43 collections::{BTreeMap, HashSet},
46 result::Result as StdResult,
51 pub struct EmitJmpResult {
52 // generated instruction sequence
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(env: &Env, lid: &local_id::LocalId) -> bool {
78 local_id::get_name(lid) == special_idents::THIS
79 && env.scope.has_this()
80 && !env.scope.is_toplevel()
85 use oxidized::{aast_defs::Lid, aast_visitor, aast_visitor::Node, ast as tast, ast_defs};
86 use std::{collections::HashMap, marker::PhantomData};
88 pub(super) struct AliasInfo {
94 impl Default for AliasInfo {
95 fn default() -> Self {
97 first_inout: std::isize::MAX,
98 last_write: std::isize::MIN,
105 pub(super) fn add_inout(&mut self, i: isize) {
106 if i < self.first_inout {
107 self.first_inout = i;
111 pub(super) fn add_write(&mut self, i: isize) {
112 if i > self.last_write {
117 pub(super) fn add_use(&mut self) {
121 pub(super) fn in_range(&self, i: isize) -> bool {
122 i > self.first_inout || i <= self.last_write
125 pub(super) fn has_single_ref(&self) -> bool {
130 pub(super) type AliasInfoMap = HashMap<String, AliasInfo>;
132 fn add_write(name: String, i: usize, map: &mut AliasInfoMap) {
133 map.entry(name).or_default().add_write(i as isize);
136 fn add_inout(name: String, i: usize, map: &mut AliasInfoMap) {
137 map.entry(name).or_default().add_inout(i as isize);
140 fn add_use(name: String, map: &mut AliasInfoMap) {
141 map.entry(name).or_default().add_use();
144 // determines if value of a local 'name' that appear in parameter 'i'
145 // should be saved to local because it might be overwritten later
146 pub(super) fn should_save_local_value(name: &str, i: usize, aliases: &AliasInfoMap) -> bool {
149 .map_or(false, |alias| alias.in_range(i as isize))
152 pub(super) fn should_move_local_value(local: &local::Type, aliases: &AliasInfoMap) -> bool {
154 local::Type::Named(name) => aliases
156 .map_or(true, |alias| alias.has_single_ref()),
157 local::Type::Unnamed(_) => false,
161 pub(super) fn collect_written_variables(env: &Env, args: &[tast::Expr]) -> AliasInfoMap {
162 let mut acc = HashMap::new();
165 .for_each(|(i, arg)| handle_arg(env, true, i, arg, &mut acc));
169 fn handle_arg(env: &Env, is_top: bool, i: usize, arg: &tast::Expr, acc: &mut AliasInfoMap) {
170 use tast::{Expr, Expr_};
171 let Expr(_, e) = arg;
173 if let Some((ast_defs::ParamKind::Pinout, Expr(_, Expr_::Lvar(lid)))) = e.as_callconv() {
174 let Lid(_, lid) = &**lid;
175 if !is_local_this(env, &lid) {
176 add_use(lid.1.to_string(), acc);
178 add_inout(lid.1.to_string(), i, acc);
180 add_write(lid.1.to_string(), i, acc);
185 if let Some(Lid(_, (_, id))) = e.as_lvar() {
186 return add_use(id.to_string(), acc);
188 // dive into argument value
191 phantom: PhantomData,
193 &mut Ctx { state: acc, env, i },
200 phantom: PhantomData<&'a str>,
204 // TODO(shiqicao): Change AliasInfoMap to AliasInfoMap<'ast>
205 state: &'a mut AliasInfoMap,
210 impl<'ast, 'a> aast_visitor::Visitor<'ast> for Visitor<'a> {
211 type P = aast_visitor::AstParams<Ctx<'a>, ()>;
213 fn object(&mut self) -> &mut dyn aast_visitor::Visitor<'ast, P = Self::P> {
220 p: &'ast tast::Expr_,
221 ) -> std::result::Result<(), ()> {
222 // f(inout $v) or f(&$v)
223 if let tast::Expr_::Call(expr) = p {
224 let (_, _, args, uarg) = &**expr;
226 .for_each(|arg| handle_arg(&c.env, false, c.i, arg, &mut c.state));
228 .map(|arg| handle_arg(&c.env, false, c.i, arg, &mut c.state));
231 p.recurse(c, self.object())?;
234 tast::Expr_::Binop(expr) => {
235 let (bop, left, _) = &**expr;
236 if let ast_defs::Bop::Eq(_) = bop {
237 collect_lvars_hs(c, left)
241 tast::Expr_::Unop(expr) => {
242 let (uop, e) = &**expr;
244 ast_defs::Uop::Uincr | ast_defs::Uop::Udecr => collect_lvars_hs(c, e),
249 tast::Expr_::Lvar(expr) => {
250 let Lid(_, (_, id)) = &**expr;
251 add_use(id.to_string(), &mut c.state);
259 // collect lvars on the left hand side of '=' operator
260 fn collect_lvars_hs(ctx: &mut Ctx, expr: &tast::Expr) {
261 let tast::Expr(_, e) = expr;
263 tast::Expr_::Lvar(lid) => {
264 let Lid(_, lid) = &**lid;
265 if !is_local_this(&ctx.env, &lid) {
266 add_use(lid.1.to_string(), &mut ctx.state);
267 add_write(lid.1.to_string(), ctx.i, &mut ctx.state);
270 tast::Expr_::List(exprs) => exprs.iter().for_each(|expr| collect_lvars_hs(ctx, expr)),
276 pub fn wrap_array_mark_legacy(e: &Emitter, ins: InstrSeq) -> InstrSeq {
277 if mark_as_legacy(e.options()) {
278 InstrSeq::gather(vec![
281 instr::instr(Instruct::IMisc(InstructMisc::ArrayMarkLegacy)),
288 pub fn wrap_array_unmark_legacy(e: &Emitter, ins: InstrSeq) -> InstrSeq {
289 if mark_as_legacy(e.options()) {
290 InstrSeq::gather(vec![
293 instr::instr(Instruct::IMisc(InstructMisc::ArrayUnmarkLegacy)),
300 pub fn get_type_structure_for_hint(
303 targ_map: &IndexSet<&str>,
305 ) -> Result<InstrSeq> {
306 let targ_map: BTreeMap<&str, i64> = targ_map
309 .map(|(i, n)| (*n, i as i64))
311 let tv = emit_type_constant::hint_to_type_constant(
319 let i = emit_adata::get_array_identifier(e, &tv);
320 Ok(if hack_arr_dv_arrs(e.options()) {
321 instr::lit_const(InstructLitConst::Dict(i))
323 instr::lit_const(InstructLitConst::Array(i))
327 pub struct Setrange {
333 /// kind of value stored in local
334 #[derive(Debug, Clone, Copy)]
335 pub enum StoredValueKind {
340 /// represents sequence of instructions interleaved with temp locals.
341 /// <(i, None) :: rest> - is emitted i :: <rest> (commonly used for final instructions in sequence)
342 /// <(i, Some(l, local_kind)) :: rest> is emitted as
346 /// setl/popl l; depending on local_kind
353 type InstrSeqWithLocals = Vec<(InstrSeq, Option<(local::Type, StoredValueKind)>)>;
355 /// result of emit_array_get
357 /// regular $a[..] that does not need to spill anything
359 /// subscript expression used as inout argument that need to spill intermediate values:
361 /// instruction sequence with locals to load value
362 load: InstrSeqWithLocals,
363 /// instruction to set value back (can use locals defined in load part)
368 struct ArrayGetBaseData<T> {
370 cls_instrs: InstrSeq,
371 setup_instrs: InstrSeq,
372 base_stack_size: StackIndex,
373 cls_stack_size: StackIndex,
376 /// result of emit_base
378 /// regular <base> part in <base>[..] that does not need to spill anything
379 Regular(ArrayGetBaseData<InstrSeq>),
380 /// base of subscript expression used as inout argument that need to spill
381 /// intermediate values
383 /// instructions to load base part
384 load: ArrayGetBaseData<InstrSeqWithLocals>,
385 /// instruction to load base part for setting inout argument back
390 pub fn emit_expr(emitter: &mut Emitter, env: &Env, expression: &tast::Expr) -> Result {
393 let tast::Expr(pos, expr) = expression;
402 let v = ast_constant_folder::expr_to_typed_value(emitter, &env.namespace, expression)
403 .map_err(|_| unrecoverable("expr_to_typed_value failed"))?;
404 Ok(emit_pos_then(pos, instr::typedvalue(v)))
406 Expr_::PrefixedString(e) => emit_expr(emitter, env, &e.1),
408 let Lid(pos, _) = &**e;
409 Ok(InstrSeq::gather(vec![
411 emit_local(emitter, env, BareThisOp::Notice, e)?,
414 Expr_::ClassConst(e) => emit_class_const(emitter, env, pos, &e.0, &e.1),
415 Expr_::Unop(e) => emit_unop(emitter, env, pos, e),
416 Expr_::Binop(_) => emit_binop(emitter, env, pos, expression),
417 Expr_::Pipe(e) => emit_pipe(emitter, env, e),
418 Expr_::Is(is_expr) => {
419 let (e, h) = &**is_expr;
420 let is = emit_is(emitter, env, pos, h)?;
421 Ok(InstrSeq::gather(vec![emit_expr(emitter, env, e)?, is]))
423 Expr_::As(e) => emit_as(emitter, env, pos, e),
424 Expr_::Cast(e) => emit_cast(emitter, env, pos, &(e.0).1, &e.1),
425 Expr_::Eif(e) => emit_conditional_expr(emitter, env, pos, &e.0, &e.1, &e.2),
426 Expr_::ArrayGet(e) => {
427 let (base_expr, opt_elem_expr) = &**e;
435 opt_elem_expr.as_ref(),
441 Expr_::ObjGet(e) => {
446 Expr_::ObjGet(Box::new((e.0.clone(), e.1.clone(), e.2.clone(), false))),
448 emit_expr(emitter, env, &e)
451 Ok(emit_obj_get(emitter, env, pos, QueryOp::CGet, &e.0, &e.1, &e.2, e.3)?.0)
454 Expr_::Call(c) => emit_call_expr(emitter, env, pos, None, c),
455 Expr_::New(e) => emit_new(emitter, env, pos, e, false),
456 Expr_::FunctionPointer(fp) => emit_function_pointer(emitter, env, pos, &fp.0, &fp.1),
457 Expr_::Record(e) => emit_record(emitter, env, pos, e),
458 Expr_::Darray(e) => Ok(emit_pos_then(
460 emit_collection(emitter, env, expression, &mk_afkvalues(&e.1), None)?,
462 Expr_::Varray(e) => Ok(emit_pos_then(
464 emit_collection(emitter, env, expression, &mk_afvalues(&e.1), None)?,
466 Expr_::Collection(e) => emit_named_collection_str(emitter, env, expression, e),
467 Expr_::ValCollection(e) => {
468 let (kind, _, es) = &**e;
469 let fields = mk_afvalues(es);
470 let collection_typ = match kind {
471 aast_defs::VcKind::Vector => CollectionType::Vector,
472 aast_defs::VcKind::ImmVector => CollectionType::ImmVector,
473 aast_defs::VcKind::Set => CollectionType::Set,
474 aast_defs::VcKind::ImmSet => CollectionType::ImmSet,
475 _ => return emit_collection(emitter, env, expression, &fields, None),
477 emit_named_collection(emitter, env, pos, expression, &fields, collection_typ)
480 let (_, e1, e2) = (**e).to_owned();
481 let fields = mk_afvalues(&vec![e1, e2]);
482 emit_named_collection(emitter, env, pos, expression, &fields, CollectionType::Pair)
484 Expr_::KeyValCollection(e) => {
485 let (kind, _, fields) = &**e;
486 let fields = mk_afkvalues(
490 .map(|tast::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(pos, emit_clone(emitter, env, e)?)),
501 Expr_::Shape(e) => Ok(emit_pos_then(pos, emit_shape(emitter, env, expression, e)?)),
502 Expr_::Await(e) => emit_await(emitter, env, pos, e),
503 // TODO: emit readonly expressions
504 Expr_::ReadonlyExpr(e) => emit_expr(emitter, env, e),
505 Expr_::Yield(e) => emit_yield(emitter, env, pos, e),
506 Expr_::Efun(e) => Ok(emit_pos_then(pos, emit_lambda(emitter, env, &e.0, &e.1)?)),
508 Expr_::ClassGet(e) => {
510 emit_class_get(emitter, env, QueryOp::CGet, &e.0, &e.1)
514 Expr_::ClassGet(Box::new((e.0.clone(), e.1.clone(), false))),
516 emit_expr(emitter, env, &e)
520 Expr_::String2(es) => emit_string2(emitter, env, pos, es),
521 Expr_::Id(e) => Ok(emit_pos_then(pos, emit_id(emitter, env, e)?)),
522 Expr_::Xml(_) => Err(unrecoverable(
523 "emit_xhp: syntax should have been converted during rewriting",
525 Expr_::Callconv(_) => Err(unrecoverable(
526 "emit_callconv: This should have been caught at emit_arg",
528 Expr_::Import(e) => emit_import(emitter, env, pos, &e.0, &e.1),
529 Expr_::Omitted => Ok(instr::empty()),
530 Expr_::Lfun(_) => Err(unrecoverable(
531 "expected Lfun to be converted to Efun during closure conversion emit_expr",
533 Expr_::List(_) => Err(emit_fatal::raise_fatal_parse(
535 "list() can only be used as an lvar. Did you mean to use tuple()?",
537 Expr_::Any => Err(unrecoverable("Cannot codegen from an Any node")),
538 Expr_::This | Expr_::Lplaceholder(_) | Expr_::Dollardollar(_) => {
539 unimplemented!("TODO(hrust) Codegen after naming pass on AAST")
541 Expr_::ExpressionTree(et) => emit_expr(emitter, env, &et.desugared_expr),
542 _ => unimplemented!("TODO(hrust)"),
546 fn emit_exprs(e: &mut Emitter, env: &Env, exprs: &[tast::Expr]) -> Result {
547 if exprs.is_empty() {
553 .map(|expr| emit_expr(e, env, expr))
554 .collect::<Result<Vec<_>>>()?,
559 fn emit_id(emitter: &mut Emitter, env: &Env, id: &tast::Sid) -> Result {
560 use pseudo_consts::*;
561 use InstructLitConst::*;
563 let ast_defs::Id(p, s) = id;
564 let res = match s.as_str() {
565 G__FILE__ => instr::lit_const(File),
566 G__DIR__ => instr::lit_const(Dir),
567 G__METHOD__ => instr::lit_const(Method),
568 G__FUNCTION_CREDENTIAL__ => instr::lit_const(FuncCred),
569 G__CLASS__ => InstrSeq::gather(vec![instr::self_(), instr::classname()]),
570 G__COMPILER_FRONTEND__ => instr::string("hackc"),
571 G__LINE__ => instr::int(p.info_pos_extended().1.try_into().map_err(|_| {
572 emit_fatal::raise_fatal_parse(p, "error converting end of line from usize to isize")
574 G__NAMESPACE__ => instr::string(env.namespace.name.as_ref().map_or("", |s| &s[..])),
575 EXIT | DIE => return emit_exit(emitter, env, None),
577 // panic!("TODO: uncomment after D19350786 lands")
578 // let cid: ConstId = r#const::Type::from_ast_name(&s);
579 let cid: ConstId = string_utils::strip_global_ns(&s).to_string().into();
580 emit_symbol_refs::State::add_constant(emitter, cid.clone());
581 return Ok(emit_pos_then(p, instr::lit_const(CnsE(cid))));
587 fn emit_exit(emitter: &mut Emitter, env: &Env, expr_opt: Option<&tast::Expr>) -> Result {
588 Ok(InstrSeq::gather(vec![
589 expr_opt.map_or_else(|| Ok(instr::int(0)), |e| emit_expr(emitter, env, e))?,
594 fn emit_yield(e: &mut Emitter, env: &Env, pos: &Pos, af: &tast::Afield) -> Result {
596 tast::Afield::AFvalue(v) => {
597 InstrSeq::gather(vec![emit_expr(e, env, v)?, emit_pos(pos), instr::yield_()])
599 tast::Afield::AFkvalue(k, v) => InstrSeq::gather(vec![
600 emit_expr(e, env, k)?,
601 emit_expr(e, env, v)?,
608 fn parse_include(e: &tast::Expr) -> IncludePath {
609 fn strip_backslash(s: &mut String) {
610 if s.starts_with("/") {
614 fn split_var_lit(e: &tast::Expr) -> (String, String) {
616 tast::Expr_::Binop(x) if x.0.is_dot() => {
617 let (v, l) = split_var_lit(&x.2);
619 let (var, lit) = split_var_lit(&x.1);
620 (var, format!("{}{}", lit, l))
625 tast::Expr_::String(lit) => (String::new(), lit.to_string()),
626 _ => (text_of_expr(e), String::new()),
629 let (mut var, mut lit) = split_var_lit(e);
630 if var == pseudo_consts::G__DIR__ {
632 strip_backslash(&mut lit);
635 if std::path::Path::new(lit.as_str()).is_relative() {
636 IncludePath::SearchPathRelative(lit)
638 IncludePath::Absolute(lit)
641 strip_backslash(&mut lit);
642 IncludePath::IncludeRootRelative(var, lit)
646 fn text_of_expr(e: &tast::Expr) -> String {
648 tast::Expr_::String(s) => format!("\'{}\'", s),
649 tast::Expr_::Id(id) => id.1.to_string(),
650 tast::Expr_::Lvar(lid) => local_id::get_name(&lid.1).to_string(),
651 tast::Expr_::ArrayGet(x) => match ((x.0).1.as_lvar(), x.1.as_ref()) {
652 (Some(tast::Lid(_, id)), Some(e_)) => {
653 format!("{}[{}]", local_id::get_name(&id), text_of_expr(e_))
655 _ => "unknown".into(),
657 _ => "unknown".into(),
661 fn text_of_class_id(cid: &tast::ClassId) -> String {
663 tast::ClassId_::CIparent => "parent".into(),
664 tast::ClassId_::CIself => "self".into(),
665 tast::ClassId_::CIstatic => "static".into(),
666 tast::ClassId_::CIexpr(e) => text_of_expr(e),
667 tast::ClassId_::CI(ast_defs::Id(_, id)) => id.into(),
671 fn text_of_prop(prop: &tast::ClassGetExpr) -> String {
673 tast::ClassGetExpr::CGstring((_, s)) => s.into(),
674 tast::ClassGetExpr::CGexpr(e) => text_of_expr(e),
682 flavor: &tast::ImportFlavor,
685 use tast::ImportFlavor;
686 let inc = parse_include(expr);
687 emit_symbol_refs::State::add_include(e, inc.clone());
688 let (expr_instrs, import_op_instr) = match flavor {
689 ImportFlavor::Include => (emit_expr(e, env, expr)?, instr::incl()),
690 ImportFlavor::Require => (emit_expr(e, env, expr)?, instr::req()),
691 ImportFlavor::IncludeOnce => (emit_expr(e, env, expr)?, instr::inclonce()),
692 ImportFlavor::RequireOnce => {
693 match inc.into_doc_root_relative(e.options().hhvm.include_roots.get()) {
694 IncludePath::DocRootRelative(path) => {
695 let expr = tast::Expr(pos.clone(), tast::Expr_::String(path.into()));
696 (emit_expr(e, env, &expr)?, instr::reqdoc())
698 _ => (emit_expr(e, env, expr)?, instr::reqonce()),
702 Ok(InstrSeq::gather(vec![
709 fn emit_string2(e: &mut Emitter, env: &Env, pos: &Pos, es: &Vec<tast::Expr>) -> Result {
711 Err(unrecoverable("String2 with zero araguments is impossible"))
712 } else if es.len() == 1 {
713 Ok(InstrSeq::gather(vec![
714 emit_expr(e, env, &es[0])?,
716 instr::cast_string(),
719 Ok(InstrSeq::gather(vec![
720 emit_two_exprs(e, env, &es[0].0, &es[0], &es[1])?,
727 Ok(InstrSeq::gather(vec![
728 emit_expr(e, env, expr)?,
733 .collect::<Result<_>>()?,
739 fn emit_clone(e: &mut Emitter, env: &Env, expr: &tast::Expr) -> Result {
740 Ok(InstrSeq::gather(vec![
741 emit_expr(e, env, expr)?,
746 fn emit_lambda(e: &mut Emitter, env: &Env, fndef: &tast::Fun_, ids: &[aast_defs::Lid]) -> Result {
747 use global_state::LazyState;
748 // Closure conversion puts the class number used for CreateCl in the "name"
749 // of the function definition
750 let fndef_name = &(fndef.name).1;
751 let cls_num = fndef_name
753 .map_err(|err| Unrecoverable(err.to_string()))?;
754 let explicit_use = e.emit_state().explicit_use_set.contains(fndef_name);
755 let is_in_lambda = env.scope.is_in_lambda();
756 Ok(InstrSeq::gather(vec![
759 .map(|tast::Lid(pos, id)| {
760 match string_utils::reified::is_captured_generic(local_id::get_name(id)) {
761 Some((is_fun, i)) => {
763 Ok(instr::cgetl(local::Type::Named(
764 string_utils::reified::reified_generic_captured_name(
769 emit_reified_generic_instrs(&Pos::make_none(), is_fun, i as usize)
773 let lid = get_local(e, env, pos, local_id::get_name(id))?;
782 .collect::<Result<Vec<_>>>()?,
784 instr::createcl(ids.len(), cls_num),
788 pub fn emit_await(emitter: &mut Emitter, env: &Env, pos: &Pos, expr: &tast::Expr) -> Result {
789 let tast::Expr(_, e) = expr;
790 let cant_inline_gen_functions = !emitter
794 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION);
796 Some((tast::Expr(_, tast::Expr_::Id(id)), _, args, None))
797 if (cant_inline_gen_functions
799 && string_utils::strip_global_ns(&(*id.1)) == "gena") =>
801 return inline_gena_call(emitter, env, &args[0]);
804 let after_await = emitter.label_gen_mut().next_regular();
805 let instrs = match e {
806 tast::Expr_::Call(c) => {
807 emit_call_expr(emitter, env, pos, Some(after_await.clone()), &*c)?
809 _ => emit_expr(emitter, env, expr)?,
811 Ok(InstrSeq::gather(vec![
815 instr::istypec(IstypeOp::OpNull),
816 instr::jmpnz(after_await.clone()),
818 instr::label(after_await),
824 fn hack_arr_dv_arrs(opts: &Options) -> bool {
825 opts.hhvm.flags.contains(HhvmFlags::HACK_ARR_DV_ARRS)
828 fn mark_as_legacy(opts: &Options) -> bool {
829 opts.hhvm.flags.contains(HhvmFlags::HACK_ARR_DV_ARRS)
830 && opts.hhvm.flags.contains(HhvmFlags::HACK_ARR_DV_ARR_MARK)
833 fn inline_gena_call(emitter: &mut Emitter, env: &Env, arg: &tast::Expr) -> Result {
834 let load_arr = emit_expr(emitter, env, arg)?;
835 let async_eager_label = emitter.label_gen_mut().next_regular();
836 let hack_arr_dv_arrs = hack_arr_dv_arrs(emitter.options());
838 scope::with_unnamed_local(emitter, |e, arr_local| {
839 let before = InstrSeq::gather(vec![
841 if hack_arr_dv_arrs {
846 instr::popl(arr_local.clone()),
849 let inner = InstrSeq::gather(vec![
852 instr::cgetl(arr_local.clone()),
853 instr::fcallclsmethodd(
855 FcallFlags::default(),
858 Some(async_eager_label.clone()),
862 method::from_raw_string(if hack_arr_dv_arrs {
867 class::from_raw_string("HH\\AwaitAllWaitHandle"),
870 instr::label(async_eager_label.clone()),
874 instr::cgetl(arr_local.clone()),
875 |val_local, key_local| {
876 InstrSeq::gather(vec![
877 instr::cgetl(val_local),
879 instr::basel(arr_local.clone(), MemberOpMode::Define),
880 instr::setm(0, MemberKey::EL(key_local, ReadOnlyOp::Any)),
887 let after = instr::pushl(arr_local);
889 Ok((before, inner, after))
893 fn emit_iter<F: FnOnce(local::Type, local::Type) -> InstrSeq>(
895 collection: InstrSeq,
898 scope::with_unnamed_locals_and_iterators(e, |e| {
899 let iter_id = e.iterator_mut().get();
900 let val_id = e.local_gen_mut().get_unnamed();
901 let key_id = e.local_gen_mut().get_unnamed();
902 let loop_end = e.label_gen_mut().next_regular();
903 let loop_next = e.label_gen_mut().next_regular();
904 let iter_args = IterArgs {
906 key_id: Some(key_id.clone()),
907 val_id: val_id.clone(),
909 let iter_init = InstrSeq::gather(vec![
911 instr::iterinit(iter_args.clone(), loop_end.clone()),
913 let iterate = InstrSeq::gather(vec![
914 instr::label(loop_next.clone()),
915 f(val_id.clone(), key_id.clone()),
916 instr::iternext(iter_args, loop_next),
918 let iter_done = InstrSeq::gather(vec![
919 instr::unsetl(val_id),
920 instr::unsetl(key_id),
921 instr::label(loop_end),
923 Ok((iter_init, iterate, iter_done))
928 emitter: &mut Emitter,
931 fl: &[(ast_defs::ShapeFieldName, tast::Expr)],
933 fn extract_shape_field_name_pstring(
936 field: &ast_defs::ShapeFieldName,
937 ) -> Result<tast::Expr_> {
938 use ast_defs::ShapeFieldName as SF;
940 SF::SFlitInt(s) => tast::Expr_::mk_int(s.1.clone()),
941 SF::SFlitStr(s) => tast::Expr_::mk_string(s.1.clone()),
942 SF::SFclassConst(id, p) => {
943 if is_reified_tparam(env, true, &id.1).is_some()
944 || is_reified_tparam(env, false, &id.1).is_some()
946 return Err(emit_fatal::raise_fatal_parse(
948 "Reified generics cannot be used in shape keys",
951 tast::Expr_::mk_class_const(
952 tast::ClassId(pos.clone(), tast::ClassId_::CI(id.clone())),
960 // TODO(hrust): avoid clone
965 tast::Expr(pos.clone(), extract_shape_field_name_pstring(env, pos, f)?),
969 .collect::<Result<Vec<_>>>()?;
973 &tast::Expr(pos.clone(), tast::Expr_::mk_darray(None, fl)),
977 fn emit_vec_collection(
981 fields: &Vec<tast::Afield>,
983 match ast_constant_folder::vec_to_typed_value(e, &env.namespace, pos, fields) {
984 Ok(tv) => emit_static_collection(e, env, None, pos, tv),
985 Err(_) => emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewVec),
989 fn emit_named_collection(
994 fields: &Vec<tast::Afield>,
995 collection_type: CollectionType,
997 let emit_vector_like = |e: &mut Emitter, collection_type| {
998 Ok(if fields.is_empty() {
999 emit_pos_then(pos, instr::newcol(collection_type))
1001 InstrSeq::gather(vec![
1002 emit_vec_collection(e, env, pos, fields)?,
1003 instr::colfromarray(collection_type),
1007 let emit_map_or_set = |e: &mut Emitter, collection_type| {
1008 if fields.is_empty() {
1009 Ok(emit_pos_then(pos, instr::newcol(collection_type)))
1011 emit_collection(e, env, expr, fields, Some(collection_type))
1014 use CollectionType as C;
1015 match collection_type {
1016 C::Dict | C::Vec | C::Keyset => {
1017 let instr = emit_collection(e, env, expr, fields, None)?;
1018 Ok(emit_pos_then(pos, instr))
1020 C::Vector | C::ImmVector => emit_vector_like(e, collection_type),
1021 C::Map | C::ImmMap | C::Set | C::ImmSet => emit_map_or_set(e, collection_type),
1022 C::Pair => Ok(InstrSeq::gather(vec![
1027 tast::Afield::AFvalue(v) => emit_expr(e, env, v),
1028 _ => Err(unrecoverable("impossible Pair argument")),
1030 .collect::<Result<_>>()?,
1034 _ => Err(unrecoverable("Unexpected named collection type")),
1038 fn emit_named_collection_str(
1042 (ast_defs::Id(pos, name), _, fields): &(
1044 Option<tast::CollectionTarg>,
1048 let name = string_utils::strip_ns(name);
1049 let name = string_utils::types::fix_casing(name.as_ref());
1050 use CollectionType::*;
1051 let ctype = match name {
1056 "ImmVector" => ImmVector,
1063 return Err(unrecoverable(format!(
1064 "collection: {} does not exist",
1069 emit_named_collection(e, env, pos, expr, fields, ctype)
1072 fn mk_afkvalues(es: &Vec<(tast::Expr, tast::Expr)>) -> Vec<tast::Afield> {
1075 .map(|(e1, e2)| tast::Afield::mk_afkvalue(e1, e2))
1079 fn mk_afvalues(es: &Vec<tast::Expr>) -> Vec<tast::Afield> {
1082 .map(|e| tast::Afield::mk_afvalue(e))
1090 fields: &[tast::Afield],
1091 transform_to_collection: Option<CollectionType>,
1094 match ast_constant_folder::expr_to_typed_value_(
1099 false, /*force_class_const*/
1101 Ok(tv) => emit_static_collection(e, env, transform_to_collection, pos, tv),
1102 Err(_) => emit_dynamic_collection(e, env, expr, fields),
1106 fn emit_static_collection(
1109 transform_to_collection: Option<CollectionType>,
1113 let arrprov_enabled = e.options().hhvm.flags.contains(HhvmFlags::ARRAY_PROVENANCE);
1114 let transform_instr = match transform_to_collection {
1115 Some(collection_type) => instr::colfromarray(collection_type),
1116 _ => instr::empty(),
1119 if arrprov_enabled && env.scope.has_function_attribute("__ProvenanceSkipFrame") {
1120 InstrSeq::gather(vec![
1122 instr::typedvalue(tv),
1124 instr::instr(Instruct::IMisc(InstructMisc::TagProvenanceHere)),
1128 InstrSeq::gather(vec![emit_pos(pos), instr::typedvalue(tv), transform_instr])
1137 instr_to_add_new: InstrSeq,
1138 instr_to_add: InstrSeq,
1139 field: &tast::Afield,
1142 tast::Afield::AFvalue(v) => Ok(InstrSeq::gather(vec![
1143 emit_expr(e, env, v)?,
1147 tast::Afield::AFkvalue(k, v) => Ok(InstrSeq::gather(vec![
1148 emit_two_exprs(e, env, &k.0, k, v)?,
1154 fn emit_keyvalue_collection(
1158 fields: &[tast::Afield],
1159 ctype: CollectionType,
1160 constructor: InstructLitConst,
1162 let (transform_instr, add_elem_instr) = match ctype {
1163 CollectionType::Dict | CollectionType::Array => (instr::empty(), instr::add_new_elemc()),
1165 instr::colfromarray(ctype),
1166 InstrSeq::gather(vec![instr::dup(), instr::add_elemc()]),
1169 let emitted_pos = emit_pos(pos);
1170 Ok(InstrSeq::gather(vec![
1171 emitted_pos.clone(),
1172 instr::lit_const(constructor),
1175 .map(|f| expr_and_new(e, env, pos, add_elem_instr.clone(), instr::add_elemc(), f))
1176 .collect::<Result<_>>()
1177 .map(InstrSeq::gather)?,
1183 fn non_numeric(s: &str) -> bool {
1184 // Note(hrust): OCaml Int64.of_string and float_of_string ignore underscores
1185 let s = s.replace("_", "");
1187 static ref HEX: Regex = Regex::new(r"(?P<sign>^-?)0[xX](?P<digits>.*)").unwrap();
1188 static ref OCTAL: Regex = Regex::new(r"(?P<sign>^-?)0[oO](?P<digits>.*)").unwrap();
1189 static ref BINARY: Regex = Regex::new(r"(?P<sign>^-?)0[bB](?P<digits>.*)").unwrap();
1190 static ref FLOAT: Regex =
1191 Regex::new(r"(?P<int>\d*)\.(?P<dec>[0-9--0]*)(?P<zeros>0*)").unwrap();
1192 static ref NEG_FLOAT: Regex =
1193 Regex::new(r"(?P<int>-\d*)\.(?P<dec>[0-9--0]*)(?P<zeros>0*)").unwrap();
1194 static ref HEX_RADIX: u32 = 16;
1195 static ref OCTAL_RADIX: u32 = 8;
1196 static ref BINARY_RADIX: u32 = 2;
1198 fn int_from_str(s: &str) -> std::result::Result<i64, ()> {
1199 // Note(hrust): OCaml Int64.of_string reads decimal, hexadecimal, octal, and binary
1200 (if HEX.is_match(s) {
1201 u64::from_str_radix(&HEX.replace(s, "${sign}${digits}"), *HEX_RADIX).map(|x| x as i64)
1202 } else if OCTAL.is_match(s) {
1203 u64::from_str_radix(&OCTAL.replace(s, "${sign}${digits}"), *OCTAL_RADIX)
1205 } else if BINARY.is_match(s) {
1206 u64::from_str_radix(&BINARY.replace(s, "${sign}${digits}"), *BINARY_RADIX)
1213 fn float_from_str_radix(s: &str, radix: u32) -> std::result::Result<f64, ()> {
1214 let i = i64::from_str_radix(&s.replace(".", ""), radix).map_err(|_| ())?;
1215 Ok(match s.matches(".").count() {
1218 let pow = s.split('.').last().unwrap().len();
1219 (i as f64) / f64::from(radix).powi(pow as i32)
1221 _ => return Err(()),
1224 fn out_of_bounds(s: &str) -> bool {
1225 // compare strings instead of floats to avoid rounding imprecision
1226 if FLOAT.is_match(s) {
1227 FLOAT.replace(s, "${int}.${dec}").trim_end_matches(".") > i64::MAX.to_string().as_str()
1228 } else if NEG_FLOAT.is_match(s) {
1229 NEG_FLOAT.replace(s, "${int}.${dec}").trim_end_matches(".")
1230 > i64::MIN.to_string().as_str()
1235 fn float_from_str(s: &str) -> std::result::Result<f64, ()> {
1236 // Note(hrust): OCaml float_of_string ignores leading whitespace, reads decimal and hexadecimal
1237 let s = s.trim_start();
1238 if HEX.is_match(s) {
1239 float_from_str_radix(&HEX.replace(s, "${sign}${digits}"), *HEX_RADIX)
1242 |f: f64| out_of_bounds(s) && (f > i64::MAX as f64 || f < i64::MIN as f64);
1243 let validate_float = |f: f64| {
1244 if out_of_bounds(f) || f.is_infinite() || f.is_nan() {
1250 f64::from_str(s).map_err(|_| ()).and_then(validate_float)
1253 int_from_str(&s).is_err() && float_from_str(&s).is_err()
1259 fields: &[tast::Afield],
1260 allow_numerics: bool,
1262 let mut are_all_keys_non_numeric_strings = true;
1263 let mut uniq_keys = std::collections::HashSet::<bstr::BString>::new();
1264 for f in fields.iter() {
1265 if let tast::Afield::AFkvalue(key, _) = f {
1266 // TODO(hrust): if key is String, don't clone and call fold_expr
1267 let mut key = key.clone();
1268 ast_constant_folder::fold_expr(&mut key, e, &env.namespace)
1269 .map_err(|e| unrecoverable(format!("{}", e)))?;
1270 if let tast::Expr(_, tast::Expr_::String(s)) = key {
1271 are_all_keys_non_numeric_strings = are_all_keys_non_numeric_strings
1273 // FIXME: This is not safe--string literals are binary strings.
1274 // There's no guarantee that they're valid UTF-8.
1275 unsafe { std::str::from_utf8_unchecked(s.as_slice().into()) },
1277 uniq_keys.insert(s);
1279 are_all_keys_non_numeric_strings = false;
1283 are_all_keys_non_numeric_strings = false;
1285 let num_keys = fields.len();
1286 let limit = *(e.options().max_array_elem_size_on_the_stack.get()) as usize;
1287 Ok((allow_numerics || are_all_keys_non_numeric_strings)
1288 && uniq_keys.len() == num_keys
1289 && num_keys <= limit
1293 fn emit_struct_array<C: FnOnce(&mut Emitter, Vec<String>) -> Result<InstrSeq>>(
1297 fields: &[tast::Afield],
1300 use tast::{Expr as E, Expr_ as E_};
1301 let (keys, value_instrs) = fields
1304 tast::Afield::AFkvalue(k, v) => match k {
1305 E(_, E_::String(s)) => Ok((
1306 // FIXME: This is not safe--string literals are binary strings.
1307 // There's no guarantee that they're valid UTF-8.
1308 unsafe { String::from_utf8_unchecked(s.clone().into()) },
1309 emit_expr(e, env, v)?,
1312 let mut k = k.clone();
1313 ast_constant_folder::fold_expr(&mut k, e, &env.namespace)
1314 .map_err(|e| unrecoverable(format!("{}", e)))?;
1316 E(_, E_::String(s)) => Ok((
1317 // FIXME: This is not safe--string literals are binary strings.
1318 // There's no guarantee that they're valid UTF-8.
1319 unsafe { String::from_utf8_unchecked(s.clone().into()) },
1320 emit_expr(e, env, v)?,
1322 _ => Err(unrecoverable("Key must be a string")),
1326 _ => Err(unrecoverable("impossible")),
1328 .collect::<Result<Vec<(String, InstrSeq)>>>()?
1331 Ok(InstrSeq::gather(vec![
1332 InstrSeq::gather(value_instrs),
1338 fn emit_dynamic_collection(
1342 fields: &[tast::Afield],
1345 let count = fields.len();
1346 let emit_dict = |e: &mut Emitter| {
1347 if is_struct_init(e, env, fields, true)? {
1348 emit_struct_array(e, env, pos, fields, |_, x| Ok(instr::newstructdict(x)))
1350 let ctor = InstructLitConst::NewDictArray(count as isize);
1351 emit_keyvalue_collection(e, env, pos, fields, CollectionType::Dict, ctor)
1354 let emit_collection_helper = |e: &mut Emitter, ctype| {
1355 if is_struct_init(e, env, fields, true)? {
1356 Ok(InstrSeq::gather(vec![
1357 emit_struct_array(e, env, pos, fields, |_, x| Ok(instr::newstructdict(x)))?,
1359 instr::colfromarray(ctype),
1362 let ctor = InstructLitConst::NewDictArray(count as isize);
1363 emit_keyvalue_collection(e, env, pos, fields, ctype, ctor)
1366 use tast::Expr_ as E_;
1368 E_::ValCollection(v) if v.0 == tast::VcKind::Vec => {
1369 emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewVec)
1371 E_::Collection(v) if (v.0).1 == "vec" => {
1372 emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewVec)
1374 E_::ValCollection(v) if v.0 == tast::VcKind::Keyset => {
1375 emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewKeysetArray)
1377 E_::Collection(v) if (v.0).1 == "keyset" => {
1378 emit_value_only_collection(e, env, pos, fields, InstructLitConst::NewKeysetArray)
1380 E_::Collection(v) if (v.0).1 == "dict" => emit_dict(e),
1381 E_::KeyValCollection(v) if v.0 == tast::KvcKind::Dict => emit_dict(e),
1382 E_::Collection(v) if string_utils::strip_ns(&(v.0).1) == "Set" => {
1383 emit_collection_helper(e, CollectionType::Set)
1385 E_::ValCollection(v) if v.0 == tast::VcKind::Set => {
1386 emit_collection_helper(e, CollectionType::Set)
1388 E_::Collection(v) if string_utils::strip_ns(&(v.0).1) == "ImmSet" => {
1389 emit_collection_helper(e, CollectionType::ImmSet)
1391 E_::ValCollection(v) if v.0 == tast::VcKind::ImmSet => {
1392 emit_collection_helper(e, CollectionType::ImmSet)
1394 E_::Collection(v) if string_utils::strip_ns(&(v.0).1) == "Map" => {
1395 emit_collection_helper(e, CollectionType::Map)
1397 E_::KeyValCollection(v) if v.0 == tast::KvcKind::Map => {
1398 emit_collection_helper(e, CollectionType::Map)
1400 E_::Collection(v) if string_utils::strip_ns(&(v.0).1) == "ImmMap" => {
1401 emit_collection_helper(e, CollectionType::ImmMap)
1403 E_::KeyValCollection(v) if v.0 == tast::KvcKind::ImmMap => {
1404 emit_collection_helper(e, CollectionType::ImmMap)
1407 let hack_arr_dv_arrs = hack_arr_dv_arrs(e.options());
1408 let instrs = emit_value_only_collection(e, env, pos, fields, |n| {
1409 if hack_arr_dv_arrs {
1410 InstructLitConst::NewVec(n)
1412 InstructLitConst::NewVArray(n)
1415 Ok(wrap_array_mark_legacy(e, instrs?))
1418 if is_struct_init(e, env, fields, false /* allow_numerics */)? {
1419 let hack_arr_dv_arrs = hack_arr_dv_arrs(e.options());
1420 let instrs = emit_struct_array(e, env, pos, fields, |_, arg| {
1421 let instr = if hack_arr_dv_arrs {
1422 instr::newstructdict(arg)
1424 instr::newstructdarray(arg)
1426 Ok(emit_pos_then(pos, instr))
1428 Ok(wrap_array_mark_legacy(e, instrs?))
1430 let constr = if hack_arr_dv_arrs(e.options()) {
1431 InstructLitConst::NewDictArray(count as isize)
1433 InstructLitConst::NewDArray(count as isize)
1436 emit_keyvalue_collection(e, env, pos, fields, CollectionType::Array, constr);
1437 Ok(wrap_array_mark_legacy(e, instrs?))
1440 _ => Err(unrecoverable("plain PHP arrays cannot be constructed")),
1444 fn emit_value_only_collection<F: FnOnce(isize) -> InstructLitConst>(
1448 fields: &[tast::Afield],
1451 let limit = *(e.options().max_array_elem_size_on_the_stack.get()) as usize;
1452 let inline = |e: &mut Emitter, exprs: &[tast::Afield]| -> Result {
1453 Ok(InstrSeq::gather(vec![
1457 .map(|f| emit_expr(e, env, f.value()))
1458 .collect::<Result<_>>()?,
1461 instr::lit_const(constructor(exprs.len() as isize)),
1464 let outofline = |e: &mut Emitter, exprs: &[tast::Afield]| -> Result {
1465 Ok(InstrSeq::gather(
1469 Ok(InstrSeq::gather(vec![
1470 emit_expr(e, env, f.value())?,
1471 instr::add_new_elemc(),
1474 .collect::<Result<_>>()?,
1477 let (x1, x2) = fields.split_at(std::cmp::min(fields.len(), limit));
1479 ([], []) => instr::empty(),
1480 (_, []) => inline(e, x1)?,
1482 let outofline_instrs = outofline(e, x2)?;
1483 let inline_instrs = inline(e, x1)?;
1484 InstrSeq::gather(vec![inline_instrs, outofline_instrs])
1493 (cid, es): &(tast::Sid, Vec<(tast::Expr, tast::Expr)>),
1495 let es = mk_afkvalues(es);
1496 let id = class::Type::from_ast_name_and_mangle(&cid.1);
1497 emit_symbol_refs::State::add_class(e, id.clone());
1498 emit_struct_array(e, env, pos, &es, |_, keys| Ok(instr::new_record(id, keys)))
1501 fn emit_call_isset_expr(e: &mut Emitter, env: &Env, outer_pos: &Pos, expr: &tast::Expr) -> Result {
1503 if let Some((base_expr, opt_elem_expr)) = expr.1.as_array_get() {
1504 return Ok(emit_array_get(
1511 opt_elem_expr.as_ref(),
1517 if let Some((cid, id, _)) = expr.1.as_class_get() {
1518 return emit_class_get(e, env, QueryOp::Isset, cid, id);
1520 if let Some((expr_, prop, nullflavor, _)) = expr.1.as_obj_get() {
1521 return Ok(emit_obj_get(e, env, pos, QueryOp::Isset, expr_, prop, nullflavor, false)?.0);
1523 if let Some(lid) = expr.1.as_lvar() {
1524 let name = local_id::get_name(&lid.1);
1525 return Ok(if superglobals::is_any_global(&name) {
1526 InstrSeq::gather(vec![
1527 emit_pos(outer_pos),
1528 instr::string(string_utils::locals::strip_dollar(&name)),
1529 emit_pos(outer_pos),
1532 } else if is_local_this(env, &lid.1) && !env.flags.contains(env::Flags::NEEDS_LOCAL_THIS) {
1533 InstrSeq::gather(vec![
1534 emit_pos(outer_pos),
1535 emit_local(e, env, BareThisOp::NoNotice, lid)?,
1536 emit_pos(outer_pos),
1537 instr::istypec(IstypeOp::OpNull),
1541 emit_pos_then(outer_pos, instr::issetl(get_local(e, env, &lid.0, name)?))
1544 Ok(InstrSeq::gather(vec![
1545 emit_expr(e, env, expr)?,
1546 instr::istypec(IstypeOp::OpNull),
1551 fn emit_call_isset_exprs(e: &mut Emitter, env: &Env, pos: &Pos, exprs: &[tast::Expr]) -> Result {
1553 [] => Err(emit_fatal::raise_fatal_parse(
1555 "Cannot use isset() without any arguments",
1557 [expr] => emit_call_isset_expr(e, env, pos, expr),
1559 let its_done = e.label_gen_mut().next_regular();
1560 Ok(InstrSeq::gather(vec![
1566 Ok(InstrSeq::gather(vec![
1567 emit_call_isset_expr(e, env, pos, expr)?,
1568 if i < exprs.len() - 1 {
1569 InstrSeq::gather(vec![
1571 instr::jmpz(its_done.clone()),
1579 .collect::<Result<Vec<_>>>()?,
1581 instr::label(its_done),
1587 fn emit_tag_provenance_here(e: &mut Emitter, env: &Env, pos: &Pos, es: &[tast::Expr]) -> Result {
1588 let default = if es.len() == 1 {
1593 let tag = instr::instr(Instruct::IMisc(InstructMisc::TagProvenanceHere));
1594 Ok(InstrSeq::gather(vec![
1595 emit_exprs(e, env, es)?,
1602 fn emit_array_mark_legacy(
1609 let default = if es.len() == 1 {
1614 let mark = if legacy {
1615 instr::instr(Instruct::IMisc(InstructMisc::ArrayMarkLegacy))
1617 instr::instr(Instruct::IMisc(InstructMisc::ArrayUnmarkLegacy))
1619 Ok(InstrSeq::gather(vec![
1620 emit_exprs(e, env, es)?,
1627 fn emit_idx(e: &mut Emitter, env: &Env, pos: &Pos, es: &[tast::Expr]) -> Result {
1628 let default = if es.len() == 2 {
1633 Ok(InstrSeq::gather(vec![
1634 emit_exprs(e, env, es)?,
1646 targs: &[tast::Targ],
1647 args: &[tast::Expr],
1648 uarg: Option<&tast::Expr>,
1649 async_eager_label: Option<Label>,
1651 if let Some(ast_defs::Id(_, s)) = expr.as_id() {
1652 let fid = function::Type::from_ast_name(s);
1653 emit_symbol_refs::add_function(e, fid);
1655 let fcall_args = get_fcall_args(
1659 env.call_context.clone(),
1662 match expr.1.as_id() {
1663 None => emit_call_default(e, env, pos, expr, targs, args, uarg, fcall_args),
1664 Some(ast_defs::Id(_, id)) => {
1665 let fq = function::Type::from_ast_name(id);
1666 let lower_fq_name = fq.to_raw_string();
1667 emit_special_function(e, env, pos, args, uarg, lower_fq_name)
1669 .unwrap_or_else(|| {
1670 emit_call_default(e, env, pos, expr, targs, args, uarg, fcall_args)
1676 fn emit_call_default(
1681 targs: &[tast::Targ],
1682 args: &[tast::Expr],
1683 uarg: Option<&tast::Expr>,
1684 fcall_args: FcallArgs,
1686 scope::with_unnamed_locals(e, |e| {
1687 let FcallArgs(_, _, num_ret, _, _, _) = &fcall_args;
1688 let num_uninit = num_ret - 1;
1689 let (lhs, fcall) = emit_call_lhs_and_fcall(e, env, expr, fcall_args, targs)?;
1690 let (args, inout_setters) = emit_args_inout_setters(e, env, args)?;
1691 let uargs = uarg.map_or(Ok(instr::empty()), |uarg| emit_expr(e, env, uarg))?;
1694 InstrSeq::gather(vec![
1696 iter::repeat(instr::nulluninit())
1698 .collect::<Vec<_>>(),
1712 pub fn emit_reified_targs(e: &mut Emitter, env: &Env, pos: &Pos, targs: &[&tast::Hint]) -> Result {
1713 let current_fun_tparams = env.scope.get_fun_tparams();
1714 let current_cls_tparams = env.scope.get_class_tparams();
1715 let is_in_lambda = env.scope.is_in_lambda();
1717 |ual: &Vec<tast::UserAttribute>| ual.iter().any(|ua| user_attributes::is_soft(&ua.name.1));
1718 let same_as_targs = |tparams: &[tast::Tparam]| {
1719 tparams.len() == targs.len()
1720 && tparams.iter().zip(targs).all(|(tp, ta)| {
1721 ta.1.as_happly().map_or(false, |(id, hs)| {
1724 && !is_soft(&tp.user_attributes)
1725 && tp.reified.is_reified()
1729 Ok(if !is_in_lambda && same_as_targs(¤t_fun_tparams) {
1730 instr::cgetl(local::Type::Named(
1731 string_utils::reified::GENERICS_LOCAL_NAME.into(),
1733 } else if !is_in_lambda && same_as_targs(¤t_cls_tparams[..]) {
1734 InstrSeq::gather(vec![
1741 prop::from_raw_string(string_utils::reified::PROP_NAME),
1747 let instrs = InstrSeq::gather(vec![
1751 .map(|h| Ok(emit_reified_arg(e, env, pos, false, h)?.0))
1752 .collect::<Result<Vec<_>>>()?,
1754 if hack_arr_dv_arrs(e.options()) {
1755 instr::new_vec_array(targs.len() as isize)
1757 instr::new_varray(targs.len() as isize)
1760 wrap_array_mark_legacy(e, instrs)
1764 fn get_erased_tparams<'a>(env: &'a Env<'a>) -> Vec<&'a str> {
1768 .filter_map(|tparam| match tparam.reified {
1769 tast::ReifyKind::Erased => Some(tparam.name.1.as_str()),
1775 pub fn has_non_tparam_generics(env: &Env, hints: &[tast::Hint]) -> bool {
1776 let erased_tparams = get_erased_tparams(env);
1777 hints.iter().any(|hint| {
1780 .map_or(true, |(id, _)| !erased_tparams.contains(&id.1.as_str()))
1784 fn has_non_tparam_generics_targs(env: &Env, targs: &[tast::Targ]) -> bool {
1785 let erased_tparams = get_erased_tparams(env);
1786 targs.iter().any(|targ| {
1790 .map_or(true, |(id, _)| !erased_tparams.contains(&id.1.as_str()))
1794 fn from_ast_null_flavor(nullflavor: tast::OgNullFlavor) -> ObjNullFlavor {
1796 tast::OgNullFlavor::OGNullsafe => ObjNullFlavor::NullSafe,
1797 tast::OgNullFlavor::OGNullthrows => ObjNullFlavor::NullThrows,
1801 fn emit_object_expr(e: &mut Emitter, env: &Env, expr: &tast::Expr) -> Result {
1803 tast::Expr_::Lvar(x) if is_local_this(env, &x.1) => Ok(instr::this()),
1804 _ => emit_expr(e, env, expr),
1808 fn emit_call_lhs_and_fcall(
1812 mut fcall_args: FcallArgs,
1813 targs: &[tast::Targ],
1814 ) -> Result<(InstrSeq, InstrSeq)> {
1815 let tast::Expr(pos, expr_) = expr;
1816 use tast::{Expr as E, Expr_ as E_};
1818 let emit_generics = |e: &mut Emitter, env, fcall_args: &mut FcallArgs| {
1819 let does_not_have_non_tparam_generics = !has_non_tparam_generics_targs(env, targs);
1820 if does_not_have_non_tparam_generics {
1823 *(&mut fcall_args.0) = fcall_args.0 | FcallFlags::HAS_GENERICS;
1830 .map(|targ| &targ.1)
1831 .collect::<Vec<_>>()
1837 let emit_fcall_func = |
1841 fcall_args: FcallArgs,
1842 | -> Result<(InstrSeq, InstrSeq)> {
1843 let tmp = e.local_gen_mut().get_unnamed();
1845 InstrSeq::gather(vec![
1846 instr::nulluninit(),
1847 instr::nulluninit(),
1848 emit_expr(e, env, expr)?,
1849 instr::popl(tmp.clone()),
1851 InstrSeq::gather(vec![instr::pushl(tmp), instr::fcallfunc(fcall_args)]),
1858 // Case ($x->foo)(...).
1861 E_::ObjGet(Box::new((o.0.clone(), o.1.clone(), o.2.clone(), false))),
1863 emit_fcall_func(e, env, &expr, fcall_args)
1865 // Case $x->foo(...).
1867 |e: &mut Emitter, obj, id, null_flavor: &tast::OgNullFlavor, mut fcall_args| {
1868 // TODO(hrust): enable let name = method::Type::from_ast_name(id);
1869 let name: method::Type =
1870 string_utils::strip_global_ns(id).to_string().into();
1871 let obj = emit_object_expr(e, env, obj)?;
1872 let generics = emit_generics(e, env, &mut fcall_args)?;
1873 let null_flavor = from_ast_null_flavor(*null_flavor);
1875 InstrSeq::gather(vec![obj, instr::nulluninit()]),
1876 InstrSeq::gather(vec![
1878 instr::fcallobjmethodd(fcall_args, name, null_flavor),
1883 (obj, E(_, E_::String(id)), null_flavor, _) => {
1887 // FIXME: This is not safe--string literals are binary strings.
1888 // There's no guarantee that they're valid UTF-8.
1889 unsafe { std::str::from_utf8_unchecked(id.as_slice().into()) },
1894 (E(pos, E_::New(new_exp)), E(_, E_::Id(id)), null_flavor, _)
1895 if fcall_args.1 == 0 =>
1897 let cexpr = ClassExpr::class_id_to_class_expr(
1898 e, false, false, &env.scope, &new_exp.0,
1901 ClassExpr::Id(ast_defs::Id(_, name))
1902 if string_utils::strip_global_ns(name) == "ReflectionClass" =>
1904 let fid = match string_utils::strip_global_ns(&id.1) {
1906 Some("__SystemLib\\reflection_class_is_abstract")
1909 Some("__SystemLib\\reflection_class_is_interface")
1911 "isFinal" => Some("__SystemLib\\reflection_class_is_final"),
1912 "getName" => Some("__SystemLib\\reflection_class_get_name"),
1917 emit_id(e, &o.as_ref().0, &id.1, null_flavor, fcall_args)
1920 let fcall_args = FcallArgs::new(
1921 FcallFlags::default(),
1928 let newobj_instrs = emit_new(e, env, pos, &new_exp, true);
1930 InstrSeq::gather(vec![
1931 instr::nulluninit(),
1932 instr::nulluninit(),
1935 InstrSeq::gather(vec![instr::fcallfuncd(
1937 function::Type::from_ast_name(fid),
1943 _ => emit_id(e, &o.as_ref().0, &id.1, null_flavor, fcall_args),
1946 (obj, E(_, E_::Id(id)), null_flavor, _) => {
1947 emit_id(e, obj, &id.1, null_flavor, fcall_args)
1949 (obj, method_expr, null_flavor, _) => {
1950 let obj = emit_object_expr(e, env, obj)?;
1951 let tmp = e.local_gen_mut().get_unnamed();
1952 let null_flavor = from_ast_null_flavor(*null_flavor);
1954 InstrSeq::gather(vec![
1956 instr::nulluninit(),
1957 emit_expr(e, env, method_expr)?,
1958 instr::popl(tmp.clone()),
1960 InstrSeq::gather(vec![
1962 instr::fcallobjmethod(fcall_args, null_flavor),
1969 E_::ClassConst(cls_const) => {
1970 let (cid, (_, id)) = &**cls_const;
1971 let mut cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
1972 if let ClassExpr::Id(ast_defs::Id(_, name)) = &cexpr {
1973 if let Some(reified_var_cexpr) = get_reified_var_cexpr(env, pos, &name)? {
1974 cexpr = reified_var_cexpr;
1977 // TODO(hrust) enabel `let method_id = method::Type::from_ast_name(&id);`,
1978 // `from_ast_name` should be able to accpet Cow<str>
1979 let method_id: method::Type = string_utils::strip_global_ns(&id).to_string().into();
1982 ClassExpr::Id(ast_defs::Id(_, cname)) => {
1983 let cid = class::Type::from_ast_name_and_mangle(&cname);
1984 emit_symbol_refs::State::add_class(e, cid.clone());
1985 let generics = emit_generics(e, env, &mut fcall_args)?;
1987 InstrSeq::gather(vec![instr::nulluninit(), instr::nulluninit()]),
1988 InstrSeq::gather(vec![
1990 instr::fcallclsmethodd(fcall_args, method_id, cid),
1994 ClassExpr::Special(clsref) => {
1995 let generics = emit_generics(e, env, &mut fcall_args)?;
1997 InstrSeq::gather(vec![instr::nulluninit(), instr::nulluninit()]),
1998 InstrSeq::gather(vec![
2000 instr::fcallclsmethodsd(fcall_args, clsref, method_id),
2004 ClassExpr::Expr(expr) => {
2005 let generics = emit_generics(e, env, &mut fcall_args)?;
2007 InstrSeq::gather(vec![instr::nulluninit(), instr::nulluninit()]),
2008 InstrSeq::gather(vec![
2010 instr::string(method_id.to_raw_string()),
2011 emit_expr(e, env, &expr)?,
2013 instr::fcallclsmethod(
2014 IsLogAsDynamicCallOp::DontLogAsDynamicCall,
2020 ClassExpr::Reified(instrs) => {
2021 let tmp = e.local_gen_mut().get_unnamed();
2023 InstrSeq::gather(vec![
2024 instr::nulluninit(),
2025 instr::nulluninit(),
2027 instr::popl(tmp.clone()),
2029 InstrSeq::gather(vec![
2030 instr::string(method_id.to_raw_string()),
2033 instr::fcallclsmethod(
2034 IsLogAsDynamicCallOp::LogAsDynamicCall,
2042 E_::ClassGet(c) => {
2044 // Case (Foo::$bar)(...).
2047 E_::ClassGet(Box::new((c.0.clone(), c.1.clone(), false))),
2049 emit_fcall_func(e, env, &expr, fcall_args)
2051 // Case Foo::bar(...).
2052 let (cid, cls_get_expr, _) = &**c;
2053 let mut cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
2054 if let ClassExpr::Id(ast_defs::Id(_, name)) = &cexpr {
2055 if let Some(reified_var_cexpr) = get_reified_var_cexpr(env, pos, &name)? {
2056 cexpr = reified_var_cexpr;
2059 let emit_meth_name = |e: &mut Emitter| match &cls_get_expr {
2060 tast::ClassGetExpr::CGstring((pos, id)) => Ok(emit_pos_then(
2062 instr::cgetl(local::Type::Named(id.clone())),
2064 tast::ClassGetExpr::CGexpr(expr) => emit_expr(e, env, expr),
2067 ClassExpr::Id(cid) => {
2068 let tmp = e.local_gen_mut().get_unnamed();
2070 InstrSeq::gather(vec![
2071 instr::nulluninit(),
2072 instr::nulluninit(),
2074 instr::popl(tmp.clone()),
2076 InstrSeq::gather(vec![
2078 emit_known_class_id(e, &cid),
2079 instr::fcallclsmethod(
2080 IsLogAsDynamicCallOp::LogAsDynamicCall,
2086 ClassExpr::Special(clsref) => {
2087 let tmp = e.local_gen_mut().get_unnamed();
2089 InstrSeq::gather(vec![
2090 instr::nulluninit(),
2091 instr::nulluninit(),
2093 instr::popl(tmp.clone()),
2095 InstrSeq::gather(vec![
2097 instr::fcallclsmethods(fcall_args, clsref),
2101 ClassExpr::Expr(expr) => {
2102 let cls = e.local_gen_mut().get_unnamed();
2103 let meth = e.local_gen_mut().get_unnamed();
2105 InstrSeq::gather(vec![
2106 instr::nulluninit(),
2107 instr::nulluninit(),
2108 emit_expr(e, env, &expr)?,
2109 instr::popl(cls.clone()),
2111 instr::popl(meth.clone()),
2113 InstrSeq::gather(vec![
2117 instr::fcallclsmethod(
2118 IsLogAsDynamicCallOp::LogAsDynamicCall,
2124 ClassExpr::Reified(instrs) => {
2125 let cls = e.local_gen_mut().get_unnamed();
2126 let meth = e.local_gen_mut().get_unnamed();
2128 InstrSeq::gather(vec![
2129 instr::nulluninit(),
2130 instr::nulluninit(),
2132 instr::popl(cls.clone()),
2134 instr::popl(meth.clone()),
2136 InstrSeq::gather(vec![
2140 instr::fcallclsmethod(
2141 IsLogAsDynamicCallOp::LogAsDynamicCall,
2151 let FcallArgs(flags, num_args, _, _, _, _) = fcall_args;
2152 let fq_id = match string_utils::strip_global_ns(&id.1) {
2153 "min" if num_args == 2 && !flags.contains(FcallFlags::HAS_UNPACK) => {
2154 function::Type::from_ast_name("__SystemLib\\min2")
2156 "max" if num_args == 2 && !flags.contains(FcallFlags::HAS_UNPACK) => {
2157 function::Type::from_ast_name("__SystemLib\\max2")
2160 //TODO(hrust): enable `function::Type::from_ast_name(&id.1)`
2161 string_utils::strip_global_ns(&id.1).to_string().into()
2164 let generics = emit_generics(e, env, &mut fcall_args)?;
2166 InstrSeq::gather(vec![instr::nulluninit(), instr::nulluninit()]),
2167 InstrSeq::gather(vec![generics, instr::fcallfuncd(fcall_args, fq_id)]),
2171 // TODO(hrust) should be able to accept `let fq_id = function::from_raw_string(s);`
2172 let fq_id = s.to_string().into();
2173 let generics = emit_generics(e, env, &mut fcall_args)?;
2175 InstrSeq::gather(vec![instr::nulluninit(), instr::nulluninit()]),
2176 InstrSeq::gather(vec![generics, instr::fcallfuncd(fcall_args, fq_id)]),
2179 _ => emit_fcall_func(e, env, expr, fcall_args),
2183 fn get_reified_var_cexpr(env: &Env, pos: &Pos, name: &str) -> Result<Option<ClassExpr>> {
2184 Ok(emit_reified_type_opt(env, pos, name)?.map(|instrs| {
2185 ClassExpr::Reified(InstrSeq::gather(vec![
2187 instr::basec(0, MemberOpMode::Warn),
2191 MemberKey::ET("classname".into(), ReadOnlyOp::Any),
2197 fn emit_args_inout_setters(
2200 args: &[tast::Expr],
2201 ) -> Result<(InstrSeq, InstrSeq)> {
2202 let aliases = if has_inout_arg(args) {
2203 inout_locals::collect_written_variables(env, args)
2205 inout_locals::AliasInfoMap::new()
2207 fn emit_arg_and_inout_setter(
2212 aliases: &inout_locals::AliasInfoMap,
2213 ) -> Result<(InstrSeq, InstrSeq)> {
2214 use tast::Expr_ as E_;
2216 E_::Callconv(cc) if (cc.0).is_pinout() => {
2220 let local = get_local(e, env, &l.0, local_id::get_name(&l.1))?;
2221 let move_instrs = if !env.flags.contains(env::Flags::IN_TRY)
2222 && inout_locals::should_move_local_value(&local, aliases)
2224 InstrSeq::gather(vec![instr::null(), instr::popl(local.clone())])
2229 InstrSeq::gather(vec![instr::cgetl(local.clone()), move_instrs]),
2233 // inout $arr[...][...]
2234 E_::ArrayGet(ag) => {
2235 let array_get_result = emit_array_get_(
2248 Ok(match array_get_result {
2249 ArrayGetInstr::Regular(instrs) => {
2250 let setter_base = emit_array_get(
2254 Some(MemberOpMode::Define),
2262 let (mk, warninstr) =
2263 get_elem_member_key(e, env, 0, ag.1.as_ref(), false)?;
2264 let setter = InstrSeq::gather(vec![
2272 ArrayGetInstr::Inout { load, store } => {
2273 let (mut ld, mut st) = (vec![], vec![store]);
2274 for (instr, local_kind_opt) in load.into_iter() {
2275 match local_kind_opt {
2276 None => ld.push(instr),
2277 Some((l, kind)) => {
2278 let unset = instr::unsetl(l.clone());
2279 let set = match kind {
2280 StoredValueKind::Expr => instr::setl(l),
2281 _ => instr::popl(l),
2289 (InstrSeq::gather(ld), InstrSeq::gather(st))
2293 _ => Err(unrecoverable(
2294 "emit_arg_and_inout_setter: Unexpected inout expression type",
2298 _ => Ok((emit_expr(e, env, arg)?, instr::empty())),
2301 let (instr_args, instr_setters): (Vec<InstrSeq>, Vec<InstrSeq>) = args
2304 .map(|(i, arg)| emit_arg_and_inout_setter(e, env, i, arg, &aliases))
2305 .collect::<Result<Vec<_>>>()?
2308 let instr_args = InstrSeq::gather(instr_args);
2309 let instr_setters = InstrSeq::gather(instr_setters);
2310 if has_inout_arg(args) {
2311 let retval = e.local_gen_mut().get_unnamed();
2314 InstrSeq::gather(vec![
2315 instr::popl(retval.clone()),
2317 instr::pushl(retval),
2321 Ok((instr_args, instr::empty()))
2326 args: &[tast::Expr],
2327 uarg: Option<&tast::Expr>,
2328 async_eager_label: Option<Label>,
2329 context: Option<String>,
2330 lock_while_unwinding: bool,
2332 let num_args = args.len();
2333 let num_rets = 1 + args.iter().filter(|x| is_inout_arg(*x)).count();
2334 let mut flags = FcallFlags::default();
2335 flags.set(FcallFlags::HAS_UNPACK, uarg.is_some());
2336 flags.set(FcallFlags::LOCK_WHILE_UNWINDING, lock_while_unwinding);
2337 let inouts: Vec<bool> = args.iter().map(is_inout_arg).collect();
2348 fn is_inout_arg(e: &tast::Expr) -> bool {
2349 e.1.as_callconv().map_or(false, |cc| cc.0.is_pinout())
2352 fn has_inout_arg(es: &[tast::Expr]) -> bool {
2353 es.iter().any(is_inout_arg)
2356 fn emit_special_function(
2360 args: &[tast::Expr],
2361 uarg: Option<&tast::Expr>,
2362 lower_fq_name: &str,
2363 ) -> Result<Option<InstrSeq>> {
2364 use tast::{Expr as E, Expr_ as E_};
2365 let nargs = args.len() + uarg.map_or(0, |_| 1);
2366 let fun_and_clsmeth_disabled = e
2371 .contains(LangFlags::DISALLOW_FUN_AND_CLS_METH_PSEUDO_FUNCS);
2372 match (lower_fq_name, args) {
2373 (id, _) if id == special_functions::ECHO => Ok(Some(InstrSeq::gather(
2377 Ok(InstrSeq::gather(vec![
2378 emit_expr(e, env, arg)?,
2388 .collect::<Result<_>>()?,
2390 ("HH\\invariant", args) if args.len() >= 2 => {
2391 let l = e.label_gen_mut().next_regular();
2392 let expr_id = tast::Expr(
2394 tast::Expr_::mk_id(ast_defs::Id(
2396 "\\hh\\invariant_violation".into(),
2399 let call = tast::Expr(
2401 tast::Expr_::mk_call(expr_id, vec![], args[1..].to_owned(), uarg.cloned()),
2403 let ignored_expr = emit_ignored_expr(e, env, &Pos::make_none(), &call)?;
2404 Ok(Some(InstrSeq::gather(vec![
2405 emit_expr(e, env, &args[0])?,
2406 instr::jmpnz(l.clone()),
2408 emit_fatal::emit_fatal_runtime(pos, "invariant_violation"),
2413 ("HH\\sequence", &[]) => Ok(Some(instr::null())),
2414 ("HH\\sequence", args) => Ok(Some(InstrSeq::gather(
2416 .map(|arg| emit_expr(e, env, arg))
2417 .collect::<Result<Vec<_>>>()?
2419 .intersperse(instr::popc())
2420 .collect::<Vec<_>>(),
2422 ("class_exists", &[ref arg1, ..])
2423 | ("trait_exists", &[ref arg1, ..])
2424 | ("interface_exists", &[ref arg1, ..])
2425 if nargs == 1 || nargs == 2 =>
2427 let class_kind = match lower_fq_name {
2428 "class_exists" => ClassKind::Class,
2429 "interface_exists" => ClassKind::Interface,
2430 "trait_exists" => ClassKind::Trait,
2431 _ => return Err(unrecoverable("emit_special_function: class_kind")),
2433 Ok(Some(InstrSeq::gather(vec![
2434 emit_expr(e, env, arg1)?,
2435 instr::cast_string(),
2439 InstrSeq::gather(vec![emit_expr(e, env, &args[1])?, instr::cast_bool()])
2441 instr::oodeclexists(class_kind),
2444 ("exit", _) | ("die", _) if nargs == 0 || nargs == 1 => {
2445 Ok(Some(emit_exit(e, env, args.first())?))
2448 if fun_and_clsmeth_disabled {
2450 [tast::Expr(_, tast::Expr_::String(func_name))] => {
2451 Err(emit_fatal::raise_fatal_parse(
2454 "`fun()` is disabled; switch to first-class references like `{}<>`",
2459 _ => Err(emit_fatal::raise_fatal_runtime(
2461 "Constant string expected in fun()",
2464 } else if nargs != 1 {
2465 Err(emit_fatal::raise_fatal_runtime(
2468 "fun() expects exactly 1 parameter, {} given",
2474 [tast::Expr(_, tast::Expr_::String(func_name))] => {
2475 Ok(Some(emit_hh_fun(
2479 &vec![ /* targs */ ],
2480 // FIXME: This is not safe--string literals are binary strings.
2481 // There's no guarantee that they're valid UTF-8.
2482 unsafe { std::str::from_utf8_unchecked(func_name.as_slice()) },
2485 _ => Err(emit_fatal::raise_fatal_runtime(
2487 "Constant string expected in fun()",
2492 ("__systemlib\\meth_caller", _) => {
2493 // used by meth_caller() to directly emit func ptr
2495 return Err(emit_fatal::raise_fatal_runtime(
2498 "fun() expects exactly 1 parameter, {} given",
2504 [E(_, E_::String(ref func_name))] => Ok(Some(instr::resolve_meth_caller(
2505 // TODO(hrust) should accept functions::from_raw_string(func_name)
2506 string_utils::strip_global_ns(
2507 // FIXME: This is not safe--string literals are binary strings.
2508 // There's no guarantee that they're valid UTF-8.
2509 unsafe { std::str::from_utf8_unchecked(func_name.as_slice().into()) },
2514 _ => Err(emit_fatal::raise_fatal_runtime(
2516 "Constant string expected in fun()",
2520 ("__systemlib\\__debugger_is_uninit", _) => {
2522 Err(emit_fatal::raise_fatal_runtime(
2525 "__debugger_is_uninit() expects exactly 1 parameter {} given",
2531 [E(_, E_::Lvar(id))] => {
2532 Ok(Some(instr::isunsetl(get_local(e, env, pos, id.name())?)))
2534 _ => Err(emit_fatal::raise_fatal_runtime(
2536 "Local variable expected in __debugger_is_uninit()",
2541 ("HH\\inst_meth", _) => match args {
2542 [obj_expr, method_name] => Ok(Some(emit_inst_meth(e, env, obj_expr, method_name)?)),
2543 _ => Err(emit_fatal::raise_fatal_runtime(
2546 "inst_meth() expects exactly 2 parameters, {} given",
2551 ("HH\\class_meth", _) if fun_and_clsmeth_disabled => Err(emit_fatal::raise_fatal_parse(
2553 "`class_meth()` is disabled; switch to first-class references like `C::bar<>`",
2555 ("HH\\class_meth", &[ref cls, ref meth, ..]) if nargs == 2 => {
2556 if meth.1.is_string() {
2557 if cls.1.is_string()
2561 .map_or(false, |(_, (_, id))| string_utils::is_class(id))
2565 .map_or(false, |ast_defs::Id(_, id)| id == pseudo_consts::G__CLASS__)
2567 return Ok(Some(emit_class_meth(e, env, cls, meth)?));
2570 Err(emit_fatal::raise_fatal_runtime(
2573 "class_meth() expects a literal class name or ::class constant, ",
2574 "followed by a constant string that refers to a static method ",
2579 ("HH\\class_meth", _) => Err(emit_fatal::raise_fatal_runtime(
2582 "class_meth() expects exactly 2 parameters, {} given",
2586 ("HH\\global_set", _) => match args {
2587 &[ref gkey, ref gvalue] => Ok(Some(InstrSeq::gather(vec![
2588 emit_expr(e, env, gkey)?,
2589 emit_expr(e, env, gvalue)?,
2595 _ => Err(emit_fatal::raise_fatal_runtime(
2598 "global_set() expects exactly 2 parameters, {} given",
2603 ("HH\\global_unset", _) => match args {
2604 &[ref gkey] => Ok(Some(InstrSeq::gather(vec![
2605 emit_expr(e, env, gkey)?,
2610 _ => Err(emit_fatal::raise_fatal_runtime(
2613 "global_unset() expects exactly 1 parameter, {} given",
2618 ("__hhvm_internal_whresult", &[E(_, E_::Lvar(ref param))]) if e.systemlib() => {
2619 Ok(Some(InstrSeq::gather(vec![
2620 instr::cgetl(local::Type::Named(local_id::get_name(¶m.1).into())),
2624 ("__hhvm_internal_getmemokeyl", &[E(_, E_::Lvar(ref param))]) if e.systemlib() => Ok(Some(
2625 instr::getmemokeyl(local::Type::Named(local_id::get_name(¶m.1).into())),
2627 ("HH\\array_mark_legacy", _) if args.len() == 1 || args.len() == 2 => {
2628 Ok(Some(emit_array_mark_legacy(e, env, pos, args, true)?))
2630 ("HH\\array_unmark_legacy", _) if args.len() == 1 || args.len() == 2 => {
2631 Ok(Some(emit_array_mark_legacy(e, env, pos, args, false)?))
2633 ("HH\\tag_provenance_here", _) if args.len() == 1 || args.len() == 2 => {
2634 Ok(Some(emit_tag_provenance_here(e, env, pos, args)?))
2639 istype_op(e.options(), lower_fq_name),
2640 is_isexp_op(lower_fq_name),
2642 (&[ref arg_expr], _, Some(ref h)) => {
2643 let is_expr = emit_is(e, env, pos, &h)?;
2644 Some(InstrSeq::gather(vec![
2645 emit_expr(e, env, &arg_expr)?,
2649 (&[E(_, E_::Lvar(ref arg_id))], Some(i), _)
2650 if superglobals::is_any_global(arg_id.name()) =>
2652 Some(InstrSeq::gather(vec![
2653 emit_local(e, env, BareThisOp::NoNotice, &arg_id)?,
2658 (&[E(_, E_::Lvar(ref arg_id))], Some(i), _) if !is_local_this(env, &arg_id.1) => {
2659 Some(instr::istypel(
2660 get_local(e, env, &arg_id.0, &(arg_id.1).1)?,
2664 (&[ref arg_expr], Some(i), _) => Some(InstrSeq::gather(vec![
2665 emit_expr(e, env, &arg_expr)?,
2669 _ => match get_call_builtin_func_info(e.options(), lower_fq_name) {
2670 Some((nargs, i)) if nargs == args.len() => {
2671 let inner = emit_exprs(e, env, args)?;
2672 let unmarked_inner = match lower_fq_name {
2673 "HH\\dict" | "HH\\vec" => wrap_array_unmark_legacy(e, inner),
2677 InstrSeq::gather(vec![unmarked_inner, emit_pos(pos), instr::instr(i)]);
2678 match lower_fq_name {
2679 "HH\\varray" | "HH\\darray" => Some(wrap_array_mark_legacy(e, instrs)),
2693 obj_expr: &tast::Expr,
2694 method_name: &tast::Expr,
2696 let instrs = InstrSeq::gather(vec![
2697 emit_expr(e, env, obj_expr)?,
2698 emit_expr(e, env, method_name)?,
2702 .contains(HhvmFlags::EMIT_INST_METH_POINTERS)
2704 instr::resolve_obj_method()
2705 } else if hack_arr_dv_arrs(e.options()) {
2706 instr::new_vec_array(2)
2708 instr::new_varray(2)
2714 .contains(HhvmFlags::EMIT_INST_METH_POINTERS)
2718 Ok(wrap_array_mark_legacy(e, instrs))
2722 fn emit_class_meth(e: &mut Emitter, env: &Env, cls: &tast::Expr, meth: &tast::Expr) -> Result {
2723 use tast::Expr_ as E_;
2727 .contains(HhvmFlags::EMIT_CLS_METH_POINTERS)
2729 let method_id = match &meth.1 {
2730 E_::String(method_name) => method_name.to_string().into(), // TODO(hrust) should accept method::from_raw_string(method_name),
2731 _ => return Err(unrecoverable("emit_class_meth: unhandled method")),
2733 if let Some((cid, (_, id))) = cls.1.as_class_const() {
2734 if string_utils::is_class(id) {
2735 return emit_class_meth_native(
2741 &vec![ /* targs */ ],
2745 if let Some(ast_defs::Id(_, s)) = cls.1.as_id() {
2746 if s == pseudo_consts::G__CLASS__ {
2747 return Ok(instr::resolveclsmethods(SpecialClsRef::Self_, method_id));
2750 if let Some(class_name) = cls.1.as_string() {
2751 return Ok(instr::resolveclsmethodd(
2752 // TODO(hrust) should accept class::Type::from_raw_string(class_name)
2753 string_utils::strip_global_ns(
2754 // FIXME: This is not safe--string literals are binary strings.
2755 // There's no guarantee that they're valid UTF-8.
2756 unsafe { std::str::from_utf8_unchecked(class_name.as_slice().into()) },
2763 Err(unrecoverable("emit_class_meth: unhandled method"))
2765 let instrs = InstrSeq::gather(vec![
2766 emit_expr(e, env, cls)?,
2767 emit_expr(e, env, meth)?,
2768 if hack_arr_dv_arrs(e.options()) {
2769 instr::new_vec_array(2)
2771 instr::new_varray(2)
2774 Ok(wrap_array_mark_legacy(e, instrs))
2778 fn emit_class_meth_native(
2782 cid: &tast::ClassId,
2783 method_id: MethodId,
2784 targs: &[tast::Targ],
2786 let mut cexpr = ClassExpr::class_id_to_class_expr(e, false, true, &env.scope, cid);
2787 if let ClassExpr::Id(ast_defs::Id(_, name)) = &cexpr {
2788 if let Some(reified_var_cexpr) = get_reified_var_cexpr(env, pos, &name)? {
2789 cexpr = reified_var_cexpr;
2792 let has_generics = has_non_tparam_generics_targs(env, targs);
2793 let mut emit_generics = || -> Result {
2798 &targs.iter().map(|targ| &targ.1).collect::<Vec<_>>(),
2802 ClassExpr::Id(ast_defs::Id(_, name)) if !has_generics => {
2803 instr::resolveclsmethodd(class::Type::from_ast_name_and_mangle(&name), method_id)
2805 ClassExpr::Id(ast_defs::Id(_, name)) => InstrSeq::gather(vec![
2807 instr::resolverclsmethodd(class::Type::from_ast_name_and_mangle(&name), method_id),
2809 ClassExpr::Special(clsref) if !has_generics => instr::resolveclsmethods(clsref, method_id),
2810 ClassExpr::Special(clsref) => InstrSeq::gather(vec![
2812 instr::resolverclsmethods(clsref, method_id),
2814 ClassExpr::Reified(instrs) if !has_generics => InstrSeq::gather(vec![
2817 instr::resolveclsmethod(method_id),
2819 ClassExpr::Reified(instrs) => InstrSeq::gather(vec![
2823 instr::resolverclsmethod(method_id),
2825 ClassExpr::Expr(_) => {
2826 return Err(unrecoverable(
2827 "emit_class_meth_native: ClassExpr::Expr should be impossible",
2833 fn get_call_builtin_func_info(opts: &Options, id: impl AsRef<str>) -> Option<(usize, Instruct)> {
2834 use {Instruct::*, InstructGet::*, InstructIsset::*, InstructMisc::*, InstructOperator::*};
2835 let hack_arr_dv_arrs = hack_arr_dv_arrs(opts);
2837 "array_key_exists" => Some((2, IMisc(AKExists))),
2838 "hphp_array_idx" => Some((3, IMisc(ArrayIdx))),
2839 "intval" => Some((1, IOp(CastInt))),
2840 "boolval" => Some((1, IOp(CastBool))),
2841 "strval" => Some((1, IOp(CastString))),
2842 "floatval" | "doubleval" => Some((1, IOp(CastDouble))),
2843 "HH\\vec" => Some((1, IOp(CastVec))),
2844 "HH\\keyset" => Some((1, IOp(CastKeyset))),
2845 "HH\\dict" => Some((1, IOp(CastDict))),
2846 "HH\\varray" => Some((
2848 IOp(if hack_arr_dv_arrs {
2854 "HH\\darray" => Some((
2856 IOp(if hack_arr_dv_arrs {
2862 "HH\\global_get" => Some((1, IGet(CGetG))),
2863 "HH\\global_isset" => Some((1, IIsset(IssetG))),
2868 fn emit_function_pointer(
2872 fpid: &tast::FunctionPtrId,
2873 targs: &[tast::Targ],
2875 let instrs = match fpid {
2876 // This is a function name. Equivalent to HH\fun('str')
2877 tast::FunctionPtrId::FPId(id) => emit_hh_fun(e, env, pos, targs, id.name())?,
2879 tast::FunctionPtrId::FPClassConst(cid, method_id) => {
2880 // TODO(hrust) should accept `let method_id = method::Type::from_ast_name(&(cc.1).1);`
2881 let method_id: method::Type = string_utils::strip_global_ns(&method_id.1)
2884 emit_class_meth_native(e, env, pos, cid, method_id, targs)?
2887 Ok(emit_pos_then(pos, instrs))
2894 targs: &[tast::Targ],
2896 ) -> Result<InstrSeq> {
2897 let fname = string_utils::strip_global_ns(fname);
2898 if has_non_tparam_generics_targs(env, targs) {
2899 let generics = emit_reified_targs(
2905 .map(|targ| &targ.1)
2906 .collect::<Vec<_>>()
2909 Ok(InstrSeq::gather(vec![
2911 instr::resolve_rfunc(fname.to_owned().into()),
2914 Ok(instr::resolve_func(fname.to_owned().into()))
2918 fn emit_is(e: &mut Emitter, env: &Env, pos: &Pos, h: &tast::Hint) -> Result {
2919 let (ts_instrs, is_static) = emit_reified_arg(e, env, pos, true, h)?;
2922 aast_defs::Hint_::Happly(ast_defs::Id(_, id), hs)
2923 if hs.is_empty() && string_utils::strip_hh_ns(&id) == typehints::THIS =>
2925 instr::islateboundcls()
2927 _ => InstrSeq::gather(vec![
2928 get_type_structure_for_hint(e, &[], &IndexSet::new(), h)?,
2929 instr::is_type_structc_resolve(),
2933 InstrSeq::gather(vec![ts_instrs, instr::is_type_structc_dontresolve()])
2937 fn istype_op(opts: &Options, id: impl AsRef<str>) -> Option<IstypeOp> {
2938 let hack_arr_dv_arrs = hack_arr_dv_arrs(opts);
2941 "is_int" | "is_integer" | "is_long" => Some(OpInt),
2942 "is_bool" => Some(OpBool),
2943 "is_float" | "is_real" | "is_double" => Some(OpDbl),
2944 "is_string" => Some(OpStr),
2945 "is_object" => Some(OpObj),
2946 "is_null" => Some(OpNull),
2947 "is_scalar" => Some(OpScalar),
2948 "HH\\is_keyset" => Some(OpKeyset),
2949 "HH\\is_dict" => Some(OpDict),
2950 "HH\\is_vec" => Some(OpVec),
2951 "HH\\is_varray" => Some(if hack_arr_dv_arrs { OpVec } else { OpVArray }),
2952 "HH\\is_darray" => Some(if hack_arr_dv_arrs { OpDict } else { OpDArray }),
2953 "HH\\is_any_array" => Some(OpArrLike),
2954 "HH\\is_class_meth" => Some(OpClsMeth),
2955 "HH\\is_fun" => Some(OpFunc),
2956 "HH\\is_php_array" => Some(if hack_arr_dv_arrs {
2961 "HH\\is_array_marked_legacy" => Some(OpLegacyArrLike),
2962 "HH\\is_class" => Some(OpClass),
2967 fn is_isexp_op(lower_fq_id: impl AsRef<str>) -> Option<tast::Hint> {
2969 Some(tast::Hint::new(
2971 tast::Hint_::mk_happly(tast::Id(Pos::make_none(), s.into()), vec![]),
2974 match lower_fq_id.as_ref() {
2975 "is_int" | "is_integer" | "is_long" => h("\\HH\\int"),
2976 "is_bool" => h("\\HH\\bool"),
2977 "is_float" | "is_real" | "is_double" => h("\\HH\\float"),
2978 "is_string" => h("\\HH\\string"),
2979 "is_null" => h("\\HH\\void"),
2980 "HH\\is_keyset" => h("\\HH\\keyset"),
2981 "HH\\is_dict" => h("\\HH\\dict"),
2982 "HH\\is_vec" => h("\\HH\\vec"),
2987 fn emit_eval(e: &mut Emitter, env: &Env, pos: &Pos, expr: &tast::Expr) -> Result {
2988 Ok(InstrSeq::gather(vec![
2989 emit_expr(e, env, expr)?,
2995 fn has_reified_types(env: &Env) -> bool {
2996 for param in env.scope.get_tparams() {
2997 match param.reified {
2998 oxidized::ast::ReifyKind::Reified => {
3011 async_eager_label: Option<Label>,
3012 (expr, targs, args, uarg): &(
3019 let jit_enable_rename_function = e
3023 .contains(HhvmFlags::JIT_ENABLE_RENAME_FUNCTION);
3024 use {tast::Expr as E, tast::Expr_ as E_};
3025 match (&expr.1, &args[..], uarg) {
3026 (E_::Id(id), [E(_, E_::String(data))], None) if id.1 == special_functions::HHAS_ADATA => {
3027 // FIXME: This is not safe--string literals are binary strings.
3028 // There's no guarantee that they're valid UTF-8.
3030 TypedValue::HhasAdata(unsafe { String::from_utf8_unchecked(data.clone().into()) });
3031 Ok(emit_pos_then(pos, instr::typedvalue(v)))
3033 (E_::Id(id), _, None) if id.1 == pseudo_functions::ISSET => {
3034 emit_call_isset_exprs(e, env, pos, args)
3036 (E_::Id(id), args, None)
3038 && !jit_enable_rename_function
3039 && (args.len() == 2 || args.len() == 3) =>
3041 emit_idx(e, env, pos, args)
3043 (E_::Id(id), [arg1], None) if id.1 == emitter_special_functions::EVAL => {
3044 emit_eval(e, env, pos, arg1)
3046 (E_::Id(id), [arg1], None) if id.1 == emitter_special_functions::SET_FRAME_METADATA => {
3047 Ok(InstrSeq::gather(vec![
3048 emit_expr(e, env, arg1)?,
3050 instr::popl(local::Type::Named("$86metadata".into())),
3054 (E_::Id(id), [], None)
3055 if id.1 == pseudo_functions::EXIT || id.1 == pseudo_functions::DIE =>
3057 let exit = emit_exit(e, env, None)?;
3058 Ok(emit_pos_then(pos, exit))
3060 (E_::Id(id), [arg1], None)
3061 if id.1 == pseudo_functions::EXIT || id.1 == pseudo_functions::DIE =>
3063 let exit = emit_exit(e, env, Some(arg1))?;
3064 Ok(emit_pos_then(pos, exit))
3067 if id.1 == emitter_special_functions::SYSTEMLIB_REIFIED_GENERICS
3069 && has_reified_types(env) =>
3071 // Rewrite __systemlib_reified_generics() to $0ReifiedGenerics,
3072 // but only in systemlib functions that take a reified generic.
3075 E_::Lvar(Box::new(tast::Lid(
3077 local_id::make_unscoped(string_utils::reified::GENERICS_LOCAL_NAME),
3080 emit_expr(e, env, &lvar)
3083 let instrs = emit_call(
3093 Ok(emit_pos_then(pos, instrs))
3098 pub fn emit_reified_generic_instrs(pos: &Pos, is_fun: bool, index: usize) -> Result {
3099 let base = if is_fun {
3101 local::Type::Named(string_utils::reified::GENERICS_LOCAL_NAME.into()),
3105 InstrSeq::gather(vec![
3109 prop::from_raw_string(string_utils::reified::PROP_NAME),
3116 InstrSeq::gather(vec![
3121 MemberKey::EI(index.try_into().unwrap(), ReadOnlyOp::Any),
3127 fn emit_reified_type(env: &Env, pos: &Pos, name: &str) -> Result<InstrSeq> {
3128 emit_reified_type_opt(env, pos, name)?
3129 .ok_or_else(|| emit_fatal::raise_fatal_runtime(&Pos::make_none(), "Invalid reified param"))
3132 fn emit_reified_type_opt(env: &Env, pos: &Pos, name: &str) -> Result<Option<InstrSeq>> {
3133 let is_in_lambda = env.scope.is_in_lambda();
3134 let cget_instr = |is_fun, i| {
3135 instr::cgetl(local::Type::Named(
3136 string_utils::reified::reified_generic_captured_name(is_fun, i),
3139 let check = |is_soft| -> Result<()> {
3141 Err(emit_fatal::raise_fatal_parse(
3144 "{} is annotated to be a soft reified generic, it cannot be used until the __Soft annotation is removed",
3152 let emit = |(i, is_soft), is_fun| {
3154 Ok(Some(if is_in_lambda {
3155 cget_instr(is_fun, i)
3157 emit_reified_generic_instrs(pos, is_fun, i)?
3160 match is_reified_tparam(env, true, name) {
3161 Some((i, is_soft)) => emit((i, is_soft), true),
3162 None => match is_reified_tparam(env, false, name) {
3163 Some((i, is_soft)) => emit((i, is_soft), false),
3169 fn emit_known_class_id(e: &mut Emitter, id: &ast_defs::Id) -> InstrSeq {
3170 let cid = class::Type::from_ast_name(&id.1);
3171 let cid_string = instr::string(cid.to_raw_string());
3172 emit_symbol_refs::State::add_class(e, cid);
3173 InstrSeq::gather(vec![cid_string, instr::classgetc()])
3176 fn emit_load_class_ref(e: &mut Emitter, env: &Env, pos: &Pos, cexpr: ClassExpr) -> Result {
3177 let instrs = match cexpr {
3178 ClassExpr::Special(SpecialClsRef::Self_) => instr::self_(),
3179 ClassExpr::Special(SpecialClsRef::Static) => instr::lateboundcls(),
3180 ClassExpr::Special(SpecialClsRef::Parent) => instr::parent(),
3181 ClassExpr::Id(id) => emit_known_class_id(e, &id),
3182 ClassExpr::Expr(expr) => InstrSeq::gather(vec![
3184 emit_expr(e, env, &expr)?,
3187 ClassExpr::Reified(instrs) => {
3188 InstrSeq::gather(vec![emit_pos(pos), instrs, instr::classgetc()])
3191 Ok(emit_pos_then(pos, instrs))
3198 (cid, targs, args, uarg, _): &(
3205 is_reflection_class_builtin: bool,
3207 if has_inout_arg(args) {
3208 return Err(unrecoverable("Unexpected inout arg in new expr"));
3210 let resolve_self = match &cid.1.as_ciexpr() {
3211 Some(ci_expr) => match ci_expr.as_id() {
3212 Some(ast_defs::Id(_, n)) if string_utils::is_self(n) => env
3214 .get_class_tparams()
3216 .all(|tp| tp.reified.is_erased()),
3217 Some(ast_defs::Id(_, n)) if string_utils::is_parent(n) => {
3220 .map_or(true, |cls| match cls.get_extends() {
3223 .map_or(true, |(_, l)| !has_non_tparam_generics(env, l))
3232 use HasGenericsOp as H;
3233 let cexpr = ClassExpr::class_id_to_class_expr(e, false, resolve_self, &env.scope, cid);
3234 let (cexpr, has_generics) = match &cexpr {
3235 ClassExpr::Id(ast_defs::Id(_, name)) => match emit_reified_type_opt(env, pos, name)? {
3237 if targs.is_empty() {
3238 (ClassExpr::Reified(instrs), H::MaybeGenerics)
3240 return Err(emit_fatal::raise_fatal_parse(
3242 "Cannot have higher kinded reified generics",
3246 None if !has_non_tparam_generics_targs(env, targs) => (cexpr, H::NoGenerics),
3247 None => (cexpr, H::HasGenerics),
3249 _ => (cexpr, H::NoGenerics),
3251 if is_reflection_class_builtin {
3252 scope::with_unnamed_locals(e, |e| {
3253 let (instr_args, _) = emit_args_inout_setters(e, env, args)?;
3254 let instr_uargs = match uarg {
3255 None => instr::empty(),
3256 Some(uarg) => emit_expr(e, env, uarg)?,
3260 InstrSeq::gather(vec![instr_args, instr_uargs]),
3265 let newobj_instrs = match cexpr {
3266 ClassExpr::Id(ast_defs::Id(_, cname)) => {
3267 let id = class::Type::from_ast_name_and_mangle(&cname);
3268 emit_symbol_refs::State::add_class(e, id.clone());
3269 match has_generics {
3270 H::NoGenerics => InstrSeq::gather(vec![emit_pos(pos), instr::newobjd(id)]),
3271 H::HasGenerics => InstrSeq::gather(vec![
3277 &targs.iter().map(|t| &t.1).collect::<Vec<_>>(),
3279 instr::newobjrd(id),
3281 H::MaybeGenerics => {
3282 return Err(unrecoverable(
3283 "Internal error: This case should have been transformed",
3288 ClassExpr::Special(cls_ref) => {
3289 InstrSeq::gather(vec![emit_pos(pos), instr::newobjs(cls_ref)])
3291 ClassExpr::Reified(instrs) if has_generics == H::MaybeGenerics => {
3292 InstrSeq::gather(vec![instrs, instr::classgetts(), instr::newobjr()])
3294 _ => InstrSeq::gather(vec![
3295 emit_load_class_ref(e, env, pos, cexpr)?,
3299 scope::with_unnamed_locals(e, |e| {
3300 let (instr_args, _) = emit_args_inout_setters(e, env, args)?;
3301 let instr_uargs = match uarg {
3302 None => instr::empty(),
3303 Some(uarg) => emit_expr(e, env, uarg)?,
3307 InstrSeq::gather(vec![
3310 instr::nulluninit(),
3314 instr::fcallctor(get_fcall_args(
3318 env.call_context.clone(),
3337 nullflavor: &ast_defs::OgNullFlavor,
3338 null_coalesce_assignment: bool,
3339 ) -> Result<(InstrSeq, Option<NumParams>)> {
3340 if let Some(tast::Lid(pos, id)) = expr.1.as_lvar() {
3341 if local_id::get_name(&id) == special_idents::THIS
3342 && nullflavor.eq(&ast_defs::OgNullFlavor::OGNullsafe)
3344 return Err(emit_fatal::raise_fatal_parse(
3346 "?-> is not allowed with $this",
3350 if let Some(ast_defs::Id(_, s)) = prop.1.as_id() {
3351 if string_utils::is_xhp(s) {
3352 return Ok((emit_xhp_obj_get(e, env, pos, &expr, s, nullflavor)?, None));
3355 let mode = if null_coalesce_assignment {
3358 get_querym_op_mode(&query_op)
3360 let prop_stack_size = emit_prop_expr(e, env, nullflavor, 0, prop, null_coalesce_assignment)?.2;
3362 base_expr_instrs_begin,
3363 base_expr_instrs_end,
3374 null_coalesce_assignment,
3378 let (mk, prop_instrs, _) = emit_prop_expr(
3384 null_coalesce_assignment,
3386 let total_stack_size = (prop_stack_size + base_stack_size + cls_stack_size) as usize;
3387 let num_params = if null_coalesce_assignment {
3392 let final_instr = instr::querym(num_params, query_op, mk);
3393 let querym_n_unpopped = if null_coalesce_assignment {
3394 Some(total_stack_size)
3398 let instr = InstrSeq::gather(vec![
3399 base_expr_instrs_begin,
3401 base_expr_instrs_end,
3406 Ok((instr, querym_n_unpopped))
3409 // Get the member key for a property, and return any instructions and
3410 // the size of the stack in the case that the property cannot be
3411 // placed inline in the instruction.
3415 nullflavor: &ast_defs::OgNullFlavor,
3416 stack_index: StackIndex,
3418 null_coalesce_assignment: bool,
3419 ) -> Result<(MemberKey, InstrSeq, StackIndex)> {
3420 let mk = match &prop.1 {
3421 tast::Expr_::Id(id) => {
3422 let ast_defs::Id(pos, name) = &**id;
3423 if name.starts_with("$") {
3424 MemberKey::PL(get_local(e, env, pos, name)?, ReadOnlyOp::Any)
3426 // Special case for known property name
3428 // TODO(hrust) enable `let pid = prop::Type::from_ast_name(name);`,
3429 // `from_ast_name` should be able to accpet Cow<str>
3430 let pid: prop::Type = string_utils::strip_global_ns(&name).to_string().into();
3432 ast_defs::OgNullFlavor::OGNullthrows => MemberKey::PT(pid, ReadOnlyOp::Any),
3433 ast_defs::OgNullFlavor::OGNullsafe => MemberKey::QT(pid, ReadOnlyOp::Any),
3437 // Special case for known property name
3438 tast::Expr_::String(name) => {
3439 // TODO(hrust) enable `let pid = prop::Type::from_ast_name(name);`,
3440 // `from_ast_name` should be able to accpet Cow<str>
3441 let pid: prop::Type = string_utils::strip_global_ns(
3442 // FIXME: This is not safe--string literals are binary strings.
3443 // There's no guarantee that they're valid UTF-8.
3444 unsafe { std::str::from_utf8_unchecked(name.as_slice().into()) },
3449 ast_defs::OgNullFlavor::OGNullthrows => MemberKey::PT(pid, ReadOnlyOp::Any),
3450 ast_defs::OgNullFlavor::OGNullsafe => MemberKey::QT(pid, ReadOnlyOp::Any),
3453 tast::Expr_::Lvar(lid) if !(is_local_this(env, &lid.1)) => MemberKey::PL(
3454 get_local(e, env, &lid.0, local_id::get_name(&lid.1))?,
3459 MemberKey::PC(stack_index, ReadOnlyOp::Any)
3462 // For nullsafe access, insist that property is known
3464 MemberKey::PL(_, _) | MemberKey::PC(_, _)
3465 if nullflavor.eq(&ast_defs::OgNullFlavor::OGNullsafe) =>
3467 return Err(emit_fatal::raise_fatal_parse(
3469 "?-> can only be used with scalar property names",
3472 MemberKey::PC(_, _) => (mk, emit_expr(e, env, prop)?, 1),
3473 MemberKey::PL(local, ReadOnlyOp::Any) if null_coalesce_assignment => (
3474 MemberKey::PC(stack_index, ReadOnlyOp::Any),
3475 instr::cgetl(local),
3478 _ => (mk, instr::empty(), 0),
3482 fn emit_xhp_obj_get(
3488 nullflavor: &ast_defs::OgNullFlavor,
3490 use tast::Expr as E;
3491 use tast::Expr_ as E_;
3498 E_::mk_id(ast_defs::Id(pos.clone(), "getAttribute".into())),
3504 let args = vec![E(pos.clone(), E_::mk_string(string_utils::clean(s).into()))];
3505 emit_call(e, env, pos, &f, &[], &args[..], None, None)
3512 mode: Option<MemberOpMode>,
3515 elem: Option<&tast::Expr>,
3517 null_coalesce_assignment: bool,
3518 ) -> Result<(InstrSeq, Option<usize>)> {
3519 let result = emit_array_get_(
3528 null_coalesce_assignment,
3532 (ArrayGetInstr::Regular(i), querym_n_unpopped) => Ok((i, querym_n_unpopped)),
3533 (ArrayGetInstr::Inout { .. }, _) => Err(unrecoverable("unexpected inout")),
3541 mode: Option<MemberOpMode>,
3543 base_expr: &tast::Expr,
3544 elem: Option<&tast::Expr>,
3546 null_coalesce_assignment: bool,
3547 inout_param_info: Option<(usize, &inout_locals::AliasInfoMap)>,
3548 ) -> Result<(ArrayGetInstr, Option<usize>)> {
3549 use tast::Expr as E;
3550 match (base_expr, elem) {
3551 (E(pos, _), None) if !env.flags.contains(env::Flags::ALLOWS_ARRAY_APPEND) => Err(
3552 emit_fatal::raise_fatal_runtime(pos, "Can't use [] for reading"),
3555 let local_temp_kind = get_local_temp_kind(env, false, inout_param_info, elem);
3556 let mode = if null_coalesce_assignment {
3559 mode.unwrap_or(get_querym_op_mode(&query_op))
3561 let (elem_instrs, elem_stack_size) =
3562 emit_elem(e, env, elem, local_temp_kind, null_coalesce_assignment)?;
3563 let base_result = emit_base_(
3570 QueryOp::Isset => BareThisOp::NoNotice,
3571 _ => BareThisOp::Notice,
3573 null_coalesce_assignment,
3578 let cls_stack_size = match &base_result {
3579 ArrayGetBase::Regular(base) => base.cls_stack_size,
3580 ArrayGetBase::Inout { load, .. } => load.cls_stack_size,
3582 let (memberkey, warninstr) =
3583 get_elem_member_key(e, env, cls_stack_size, elem, null_coalesce_assignment)?;
3584 let mut querym_n_unpopped = None;
3585 let mut make_final = |total_stack_size: StackIndex| -> InstrSeq {
3588 } else if null_coalesce_assignment {
3589 querym_n_unpopped = Some(total_stack_size as usize);
3590 instr::querym(0, query_op, memberkey.clone())
3592 instr::querym(total_stack_size as usize, query_op, memberkey.clone())
3595 let instr = match (base_result, local_temp_kind) {
3596 (ArrayGetBase::Regular(base), None) =>
3597 // neither base nor expression needs to store anything
3599 ArrayGetInstr::Regular(InstrSeq::gather(vec![
3604 emit_pos(outer_pos),
3606 make_final(base.base_stack_size + base.cls_stack_size + elem_stack_size),
3609 (ArrayGetBase::Regular(base), Some(local_kind)) => {
3610 // base does not need temp locals but index expression does
3611 let local = e.local_gen_mut().get_unnamed();
3613 // load base and indexer, value of indexer will be saved in local
3615 InstrSeq::gather(vec![base.base_instrs.clone(), elem_instrs]),
3616 Some((local.clone(), local_kind)),
3618 // finish loading the value
3620 InstrSeq::gather(vec![
3623 emit_pos(outer_pos),
3626 base.base_stack_size + base.cls_stack_size + elem_stack_size,
3632 let store = InstrSeq::gather(vec![
3633 emit_store_for_simple_base(
3644 ArrayGetInstr::Inout { load, store }
3647 ArrayGetBase::Inout {
3660 // base needs temp locals, indexer - does not,
3661 // simply concat two instruction sequences
3663 InstrSeq::gather(vec![
3667 emit_pos(outer_pos),
3669 make_final(base_stack_size + cls_stack_size + elem_stack_size),
3674 InstrSeq::gather(vec![store, instr::setm(0, memberkey), instr::popc()]);
3675 ArrayGetInstr::Inout {
3681 ArrayGetBase::Inout {
3694 // both base and index need temp locals,
3695 // create local for index value
3696 let local = e.local_gen_mut().get_unnamed();
3697 base_instrs.push((elem_instrs, Some((local.clone(), local_kind))));
3699 InstrSeq::gather(vec![
3702 emit_pos(outer_pos),
3704 make_final(base_stack_size + cls_stack_size + elem_stack_size),
3708 let store = InstrSeq::gather(vec![
3710 instr::setm(0, MemberKey::EL(local, ReadOnlyOp::Any)),
3713 ArrayGetInstr::Inout {
3719 Ok((instr, querym_n_unpopped))
3724 fn is_special_class_constant_accessed_with_class_id(cname: &tast::ClassId_, id: &str) -> bool {
3725 let is_self_parent_or_static = match cname {
3726 tast::ClassId_::CIexpr(tast::Expr(_, tast::Expr_::Id(id))) => {
3727 string_utils::is_self(&id.1)
3728 || string_utils::is_parent(&id.1)
3729 || string_utils::is_static(&id.1)
3733 string_utils::is_class(id) && !is_self_parent_or_static
3739 elem: Option<&tast::Expr>,
3740 local_temp_kind: Option<StoredValueKind>,
3741 null_coalesce_assignment: bool,
3742 ) -> Result<(InstrSeq, StackIndex)> {
3744 None => (instr::empty(), 0),
3745 Some(expr) if expr.1.is_int() || expr.1.is_string() => (instr::empty(), 0),
3746 Some(expr) => match &expr.1 {
3747 tast::Expr_::Lvar(x) if !is_local_this(env, &x.1) => {
3748 if local_temp_kind.is_some() {
3750 instr::cgetquietl(get_local(e, env, &x.0, local_id::get_name(&x.1))?),
3753 } else if null_coalesce_assignment {
3755 instr::cgetl(get_local(e, env, &x.0, local_id::get_name(&x.1))?),
3762 tast::Expr_::ClassConst(x)
3763 if is_special_class_constant_accessed_with_class_id(&(x.0).1, &(x.1).1) =>
3767 _ => (emit_expr(e, env, expr)?, 1),
3772 fn get_elem_member_key(
3775 stack_index: StackIndex,
3776 elem: Option<&tast::Expr>,
3777 null_coalesce_assignment: bool,
3778 ) -> Result<(MemberKey, InstrSeq)> {
3779 use tast::ClassId_ as CI_;
3780 use tast::Expr as E;
3781 use tast::Expr_ as E_;
3783 // ELement missing (so it's array append)
3784 None => Ok((MemberKey::W, instr::empty())),
3785 Some(elem_expr) => match &elem_expr.1 {
3786 // Special case for local
3787 E_::Lvar(x) if !is_local_this(env, &x.1) => Ok((
3789 if null_coalesce_assignment {
3790 MemberKey::EC(stack_index, ReadOnlyOp::Any)
3793 get_local(e, env, &x.0, local_id::get_name(&x.1))?,
3800 // Special case for literal integer
3802 match ast_constant_folder::expr_to_typed_value(e, &env.namespace, elem_expr) {
3803 Ok(TypedValue::Int(i)) => {
3804 Ok((MemberKey::EI(i, ReadOnlyOp::Any), instr::empty()))
3806 _ => Err(Unrecoverable(format!("{} is not a valid integer index", s))),
3809 // Special case for literal string
3812 // FIXME: This is not safe--string literals are binary strings.
3813 // There's no guarantee that they're valid UTF-8.
3815 unsafe { String::from_utf8_unchecked(s.clone().into()) },
3821 // Special case for class name
3823 if is_special_class_constant_accessed_with_class_id(&(x.0).1, &(x.1).1) =>
3826 match (&(x.0).1, env.scope.get_class()) {
3827 (CI_::CIself, Some(cd)) => string_utils::strip_global_ns(cd.get_name_str()),
3828 (CI_::CIexpr(E(_, E_::Id(id))), _) => string_utils::strip_global_ns(&id.1),
3829 (CI_::CI(id), _) => string_utils::strip_global_ns(&id.1),
3830 _ => return Err(Unrecoverable(
3831 "Unreachable due to is_special_class_constant_accessed_with_class_id"
3835 let fq_id = class::Type::from_ast_name(&cname).to_raw_string().into();
3836 if e.options().emit_class_pointers() > 0 {
3838 MemberKey::ET(fq_id, ReadOnlyOp::Any),
3839 instr::raise_class_string_conversion_warning(),
3842 Ok((MemberKey::ET(fq_id, ReadOnlyOp::Any), instr::empty()))
3847 Ok((MemberKey::EC(stack_index, ReadOnlyOp::Any), instr::empty()))
3853 fn emit_store_for_simple_base(
3857 elem_stack_size: isize,
3862 let (base_expr_instrs_begin, base_expr_instrs_end, base_setup_instrs, _, _) = emit_base(
3866 MemberOpMode::Define,
3873 let memberkey = MemberKey::EL(local, ReadOnlyOp::Any);
3874 Ok(InstrSeq::gather(vec![
3875 base_expr_instrs_begin,
3876 base_expr_instrs_end,
3880 instr::dim(MemberOpMode::Define, memberkey)
3882 instr::setm(0, memberkey)
3887 fn get_querym_op_mode(query_op: &QueryOp) -> MemberOpMode {
3889 QueryOp::InOut => MemberOpMode::InOut,
3890 QueryOp::CGet => MemberOpMode::Warn,
3891 _ => MemberOpMode::ModeNone,
3899 cid: &tast::ClassId,
3900 prop: &tast::ClassGetExpr,
3902 let cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
3903 Ok(InstrSeq::gather(vec![
3904 InstrSeq::from(emit_class_expr(e, env, cexpr, prop)?),
3906 QueryOp::CGet => instr::cgets(ReadOnlyOp::Any),
3907 QueryOp::Isset => instr::issets(),
3908 QueryOp::CGetQuiet => return Err(Unrecoverable("emit_class_get: CGetQuiet".into())),
3909 QueryOp::InOut => return Err(Unrecoverable("emit_class_get: InOut".into())),
3914 fn emit_conditional_expr(
3919 etrue: &Option<tast::Expr>,
3920 efalse: &tast::Expr,
3922 Ok(match etrue.as_ref() {
3924 let false_label = e.label_gen_mut().next_regular();
3925 let end_label = e.label_gen_mut().next_regular();
3926 let r = emit_jmpz(e, env, etest, &false_label)?;
3927 // only emit false branch if false_label is used
3928 let false_branch = if r.is_label_used {
3929 InstrSeq::gather(vec![instr::label(false_label), emit_expr(e, env, efalse)?])
3933 // only emit true branch if there is fallthrough from condition
3934 let true_branch = if r.is_fallthrough {
3935 InstrSeq::gather(vec![
3936 emit_expr(e, env, etrue)?,
3938 instr::jmp(end_label.clone()),
3943 InstrSeq::gather(vec![
3947 // end_label is used to jump out of true branch so they should be emitted together
3948 if r.is_fallthrough {
3949 instr::label(end_label)
3956 let end_label = e.label_gen_mut().next_regular();
3957 let efalse_instr = emit_expr(e, env, efalse)?;
3958 let etest_instr = emit_expr(e, env, etest)?;
3959 InstrSeq::gather(vec![
3962 instr::jmpnz(end_label.clone()),
3965 instr::label(end_label),
3971 fn emit_local(e: &mut Emitter, env: &Env, notice: BareThisOp, lid: &aast_defs::Lid) -> Result {
3972 let tast::Lid(pos, id) = lid;
3973 let id_name = local_id::get_name(id);
3974 if superglobals::is_superglobal(id_name) {
3975 Ok(InstrSeq::gather(vec![
3976 instr::string(string_utils::locals::strip_dollar(id_name)),
3981 let local = get_local(e, env, pos, id_name)?;
3983 if is_local_this(env, id) && !env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS) {
3984 emit_pos_then(pos, instr::barethis(notice))
3992 fn emit_class_const(
3996 cid: &tast::ClassId,
3997 id: &ast_defs::Pstring,
3999 let mut cexpr = ClassExpr::class_id_to_class_expr(e, false, true, &env.scope, cid);
4000 if let ClassExpr::Id(ast_defs::Id(_, name)) = &cexpr {
4001 if let Some(reified_var_cexpr) = get_reified_var_cexpr(env, pos, &name)? {
4002 cexpr = reified_var_cexpr;
4006 ClassExpr::Id(ast_defs::Id(pos, name)) => {
4007 let cid = class::Type::from_ast_name_and_mangle(&name);
4008 let cname = cid.to_raw_string();
4009 Ok(if string_utils::is_class(&id.1) {
4010 if e.options().emit_class_pointers() == 1 {
4011 emit_pos_then(&pos, instr::resolveclass(cid))
4012 } else if e.options().emit_class_pointers() == 2 {
4013 emit_pos_then(&pos, instr::lazyclass(cid))
4015 emit_pos_then(&pos, instr::string(cname))
4018 emit_symbol_refs::State::add_class(e, cid.clone());
4019 // TODO(hrust) enabel `let const_id = r#const::Type::from_ast_name(&id.1);`,
4020 // `from_ast_name` should be able to accpet Cow<str>
4021 let const_id: r#const::Type =
4022 string_utils::strip_global_ns(&id.1).to_string().into();
4023 emit_pos_then(&pos, instr::clscnsd(const_id, cid))
4027 let load_const = if string_utils::is_class(&id.1) {
4030 // TODO(hrust) enabel `let const_id = r#const::Type::from_ast_name(&id.1);`,
4031 // `from_ast_name` should be able to accpet Cow<str>
4032 let const_id: r#const::Type =
4033 string_utils::strip_global_ns(&id.1).to_string().into();
4034 instr::clscns(const_id)
4036 if string_utils::is_class(&id.1) && e.options().emit_class_pointers() > 0 {
4037 emit_load_class_ref(e, env, pos, cexpr)
4039 Ok(InstrSeq::gather(vec![
4040 emit_load_class_ref(e, env, pos, cexpr)?,
4052 (uop, expr): &(ast_defs::Uop, tast::Expr),
4054 use ast_defs::Uop as U;
4056 U::Utild | U::Unot => Ok(InstrSeq::gather(vec![
4057 emit_expr(e, env, expr)?,
4058 emit_pos_then(pos, from_unop(e.options(), uop)?),
4060 U::Uplus | U::Uminus => Ok(InstrSeq::gather(vec![
4063 emit_expr(e, env, expr)?,
4064 emit_pos_then(pos, from_unop(e.options(), uop)?),
4066 U::Uincr | U::Udecr | U::Upincr | U::Updecr => emit_lval_op(
4070 LValOp::IncDec(unop_to_incdec_op(e.options(), uop)?),
4075 U::Usilence => e.local_scope(|e| {
4076 let temp_local = e.local_gen_mut().get_unnamed();
4077 Ok(InstrSeq::gather(vec![
4079 instr::silence_start(temp_local.clone()),
4081 let try_instrs = emit_expr(e, env, expr)?;
4082 let catch_instrs = InstrSeq::gather(vec![
4084 instr::silence_end(temp_local.clone()),
4086 InstrSeq::create_try_catch(
4089 false, /* skip_throw */
4095 instr::silence_end(temp_local),
4101 fn unop_to_incdec_op(opts: &Options, op: &ast_defs::Uop) -> Result<IncdecOp> {
4102 let if_check_or = |op1, op2| Ok(if opts.check_int_overflow() { op1 } else { op2 });
4103 use {ast_defs::Uop as U, IncdecOp as I};
4105 U::Uincr => if_check_or(I::PreIncO, I::PreInc),
4106 U::Udecr => if_check_or(I::PreDecO, I::PreDec),
4107 U::Upincr => if_check_or(I::PostIncO, I::PostInc),
4108 U::Updecr => if_check_or(I::PostDecO, I::PostDec),
4109 _ => Err(Unrecoverable("invalid incdec op".into())),
4113 fn from_unop(opts: &Options, op: &ast_defs::Uop) -> Result {
4114 use ast_defs::Uop as U;
4116 U::Utild => instr::bitnot(),
4117 U::Unot => instr::not(),
4119 if opts.check_int_overflow() {
4126 if opts.check_int_overflow() {
4133 return Err(Unrecoverable(
4134 "this unary operation cannot be translated".into(),
4140 fn binop_to_eqop(opts: &Options, op: &ast_defs::Bop) -> Option<EqOp> {
4141 use {ast_defs::Bop as B, EqOp::*};
4143 B::Plus => Some(if opts.check_int_overflow() {
4148 B::Minus => Some(if opts.check_int_overflow() {
4153 B::Star => Some(if opts.check_int_overflow() {
4158 B::Slash => Some(DivEqual),
4159 B::Starstar => Some(PowEqual),
4160 B::Amp => Some(AndEqual),
4161 B::Bar => Some(OrEqual),
4162 B::Xor => Some(XorEqual),
4163 B::Ltlt => Some(SlEqual),
4164 B::Gtgt => Some(SrEqual),
4165 B::Percent => Some(ModEqual),
4166 B::Dot => Some(ConcatEqual),
4171 fn optimize_null_checks(e: &Emitter) -> bool {
4173 .hack_compiler_flags
4174 .contains(CompilerFlags::OPTIMIZE_NULL_CHECKS)
4177 fn from_binop(opts: &Options, op: &ast_defs::Bop) -> Result {
4178 use ast_defs::Bop as B;
4181 if opts.check_int_overflow() {
4188 if opts.check_int_overflow() {
4195 if opts.check_int_overflow() {
4201 B::Slash => instr::div(),
4202 B::Eqeq => instr::eq(),
4203 B::Eqeqeq => instr::same(),
4204 B::Starstar => instr::pow(),
4205 B::Diff => instr::neq(),
4206 B::Diff2 => instr::nsame(),
4207 B::Lt => instr::lt(),
4208 B::Lte => instr::lte(),
4209 B::Gt => instr::gt(),
4210 B::Gte => instr::gte(),
4211 B::Dot => instr::concat(),
4212 B::Amp => instr::bitand(),
4213 B::Bar => instr::bitor(),
4214 B::Ltlt => instr::shl(),
4215 B::Gtgt => instr::shr(),
4216 B::Cmp => instr::cmp(),
4217 B::Percent => instr::mod_(),
4218 B::Xor => instr::bitxor(),
4219 B::LogXor => instr::xor(),
4220 B::Eq(_) => return Err(Unrecoverable("assignment is emitted differently".into())),
4221 B::QuestionQuestion => {
4222 return Err(Unrecoverable(
4223 "null coalescence is emitted differently".into(),
4226 B::Barbar | B::Ampamp => {
4227 return Err(Unrecoverable(
4228 "short-circuiting operator cannot be generated as a simple binop".into(),
4234 fn emit_first_expr(e: &mut Emitter, env: &Env, expr: &tast::Expr) -> Result<(InstrSeq, bool)> {
4236 tast::Expr_::Lvar(l)
4237 if !((is_local_this(env, &l.1) && !env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS))
4238 || superglobals::is_any_global(local_id::get_name(&l.1))) =>
4241 instr::cgetl2(get_local(e, env, &l.0, local_id::get_name(&l.1))?),
4245 _ => (emit_expr(e, env, expr)?, false),
4249 pub fn emit_two_exprs(
4256 let (instrs1, is_under_top) = emit_first_expr(e, env, e1)?;
4257 let instrs2 = emit_expr(e, env, e2)?;
4258 let instrs2_is_var = e2.1.is_lvar();
4259 Ok(InstrSeq::gather(if is_under_top {
4261 vec![emit_pos(outer_pos), instrs2, instrs1]
4263 vec![instrs2, emit_pos(outer_pos), instrs1]
4265 } else if instrs2_is_var {
4266 vec![instrs1, emit_pos(outer_pos), instrs2]
4268 vec![instrs1, instrs2, emit_pos(outer_pos)]
4277 null_coalesce_assignment: bool,
4278 ) -> Result<(InstrSeq, Option<NumParams>)> {
4280 tast::Expr_::Lvar(lid) if !is_local_this(env, &lid.1) => Ok((
4281 instr::cgetquietl(get_local(e, env, pos, local_id::get_name(&lid.1))?),
4284 tast::Expr_::ArrayGet(x) => emit_array_get(
4293 null_coalesce_assignment,
4295 tast::Expr_::ObjGet(x) => {
4297 Ok((emit_expr(e, env, expr)?, None))
4307 null_coalesce_assignment,
4311 _ => Ok((emit_expr(e, env, expr)?, None)),
4315 fn emit_null_coalesce_assignment(
4322 let end_label = e.label_gen_mut().next_regular();
4323 let do_set_label = e.label_gen_mut().next_regular();
4324 let l_nonnull = e.local_gen_mut().get_unnamed();
4325 let (quiet_instr, querym_n_unpopped) = emit_quiet_expr(e, env, pos, e1, true)?;
4326 let emit_popc_n = |n_unpopped| match n_unpopped {
4327 Some(n) => InstrSeq::gather(iter::repeat(instr::popc()).take(n).collect::<Vec<_>>()),
4328 None => instr::empty(),
4330 Ok(InstrSeq::gather(vec![
4333 instr::istypec(IstypeOp::OpNull),
4334 instr::jmpnz(do_set_label.clone()),
4335 instr::popl(l_nonnull.clone()),
4336 emit_popc_n(querym_n_unpopped),
4337 instr::pushl(l_nonnull),
4338 instr::jmp(end_label.clone()),
4339 instr::label(do_set_label),
4341 emit_lval_op(e, env, pos, LValOp::Set, e1, Some(e2), true)?,
4342 instr::label(end_label),
4346 fn emit_short_circuit_op(e: &mut Emitter, env: &Env, pos: &Pos, expr: &tast::Expr) -> Result {
4347 let its_true = e.label_gen_mut().next_regular();
4348 let its_done = e.label_gen_mut().next_regular();
4349 let jmp_instrs = emit_jmpnz(e, env, expr, &its_true)?;
4350 Ok(if jmp_instrs.is_fallthrough {
4351 InstrSeq::gather(vec![
4355 instr::jmp(its_done.clone()),
4356 if jmp_instrs.is_label_used {
4357 InstrSeq::gather(vec![instr::label(its_true), emit_pos(pos), instr::true_()])
4361 instr::label(its_done),
4364 InstrSeq::gather(vec![
4366 if jmp_instrs.is_label_used {
4367 InstrSeq::gather(vec![instr::label(its_true), emit_pos(pos), instr::true_()])
4375 fn emit_binop(e: &mut Emitter, env: &Env, pos: &Pos, expr: &tast::Expr) -> Result {
4376 let (op, e1, e2) = expr.1.as_binop().unwrap();
4377 use ast_defs::Bop as B;
4379 B::Ampamp | B::Barbar => emit_short_circuit_op(e, env, pos, &expr),
4380 B::Eq(None) => emit_lval_op(e, env, pos, LValOp::Set, e1, Some(e2), false),
4381 B::Eq(Some(eop)) if eop.is_question_question() => {
4382 emit_null_coalesce_assignment(e, env, pos, e1, e2)
4384 B::Eq(Some(eop)) => match binop_to_eqop(e.options(), eop) {
4385 None => Err(Unrecoverable("illegal eq op".into())),
4386 Some(op) => emit_lval_op(e, env, pos, LValOp::SetOp(op), e1, Some(e2), false),
4388 B::QuestionQuestion => {
4389 let end_label = e.label_gen_mut().next_regular();
4390 let rhs = emit_expr(e, env, e2)?;
4391 Ok(InstrSeq::gather(vec![
4392 emit_quiet_expr(e, env, pos, e1, false)?.0,
4394 instr::istypec(IstypeOp::OpNull),
4396 instr::jmpnz(end_label.clone()),
4399 instr::label(end_label),
4403 let default = |e: &mut Emitter| {
4404 Ok(InstrSeq::gather(vec![
4405 emit_two_exprs(e, env, pos, e1, e2)?,
4406 from_binop(e.options(), op)?,
4409 if optimize_null_checks(e) {
4411 B::Eqeqeq if e2.1.is_null() => emit_is_null(e, env, e1),
4412 B::Eqeqeq if e1.1.is_null() => emit_is_null(e, env, e2),
4413 B::Diff2 if e2.1.is_null() => Ok(InstrSeq::gather(vec![
4414 emit_is_null(e, env, e1)?,
4417 B::Diff2 if e1.1.is_null() => Ok(InstrSeq::gather(vec![
4418 emit_is_null(e, env, e2)?,
4433 (_, e1, e2): &(aast_defs::Lid, tast::Expr, tast::Expr),
4435 let lhs_instrs = emit_expr(e, env, e1)?;
4436 scope::with_unnamed_local(e, |e, local| {
4437 // TODO(hrust) avoid cloning env
4438 let mut pipe_env = env.clone();
4439 pipe_env.with_pipe_var(local.clone());
4440 let rhs_instrs = emit_expr(e, &pipe_env, e2)?;
4442 InstrSeq::gather(vec![lhs_instrs, instr::popl(local.clone())]),
4444 instr::unsetl(local),
4453 (expr, h, is_nullable): &(tast::Expr, aast_defs::Hint, bool),
4456 let arg_local = e.local_gen_mut().get_unnamed();
4457 let type_struct_local = e.local_gen_mut().get_unnamed();
4458 let (ts_instrs, is_static) = emit_reified_arg(e, env, pos, true, h)?;
4459 let then_label = e.label_gen_mut().next_regular();
4460 let done_label = e.label_gen_mut().next_regular();
4461 let main_block = |ts_instrs, resolve| {
4462 InstrSeq::gather(vec![
4464 instr::setl(type_struct_local.clone()),
4466 TypestructResolveOp::Resolve => instr::is_type_structc_resolve(),
4467 TypestructResolveOp::DontResolve => instr::is_type_structc_dontresolve(),
4469 instr::jmpnz(then_label.clone()),
4471 InstrSeq::gather(vec![instr::null(), instr::jmp(done_label.clone())])
4473 InstrSeq::gather(vec![
4474 instr::pushl(arg_local.clone()),
4475 instr::pushl(type_struct_local.clone()),
4476 instr::throwastypestructexception(),
4481 let i2 = if is_static {
4483 get_type_structure_for_hint(e, &[], &IndexSet::new(), h)?,
4484 TypestructResolveOp::Resolve,
4487 main_block(ts_instrs, TypestructResolveOp::DontResolve)
4489 let i1 = emit_expr(e, env, expr)?;
4490 Ok(InstrSeq::gather(vec![
4492 instr::setl(arg_local.clone()),
4494 instr::label(then_label),
4495 instr::pushl(arg_local),
4496 instr::unsetl(type_struct_local),
4497 instr::label(done_label),
4506 hint: &aast_defs::Hint_,
4509 use aast_defs::Hint_ as H_;
4510 let op = match hint {
4511 H_::Happly(ast_defs::Id(_, id), hints) if hints.is_empty() => {
4512 let id = string_utils::strip_ns(id);
4513 match string_utils::strip_hh_ns(&id).as_ref() {
4514 typehints::INT => instr::cast_int(),
4515 typehints::BOOL => instr::cast_bool(),
4516 typehints::STRING => instr::cast_string(),
4517 typehints::FLOAT => instr::cast_double(),
4519 return Err(emit_fatal::raise_fatal_parse(
4521 format!("Invalid cast type: {}", id),
4526 _ => return Err(emit_fatal::raise_fatal_parse(pos, "Invalid cast type")),
4528 Ok(InstrSeq::gather(vec![
4529 emit_expr(e, env, expr)?,
4535 pub fn emit_unset_expr(e: &mut Emitter, env: &Env, expr: &tast::Expr) -> Result {
4536 emit_lval_op_nonlist(
4548 pub fn emit_set_range_expr(
4554 args: &[&tast::Expr],
4556 let raise_fatal = |msg: &str| {
4557 Err(emit_fatal::raise_fatal_parse(
4559 format!("{} {}", name, msg),
4563 let (base, offset, src, args) = if args.len() >= 3 {
4564 (&args[0], &args[1], &args[2], &args[3..])
4566 return raise_fatal("expects at least 3 arguments");
4569 let count_instrs = match (args, kind.vec) {
4570 ([c], true) => emit_expr(e, env, c)?,
4571 ([], _) => instr::int(-1),
4572 (_, false) => return raise_fatal("expects no more than 3 arguments"),
4573 (_, true) => return raise_fatal("expects no more than 4 arguments"),
4576 let (base_expr, cls_expr, base_setup, base_stack, cls_stack) = emit_base(
4580 MemberOpMode::Define,
4581 false, /* is_object */
4583 false, /*null_coalesce_assignment*/
4584 3, /* base_offset */
4585 3, /* rhs_stack_size */
4587 Ok(InstrSeq::gather(vec![
4590 emit_expr(e, env, offset)?,
4591 emit_expr(e, env, src)?,
4594 instr::instr(Instruct::IFinal(InstructFinal::SetRangeM(
4595 (base_stack + cls_stack)
4597 .expect("StackIndex overflow"),
4598 kind.size.try_into().expect("Setrange size overflow"),
4605 pub fn is_reified_tparam(env: &Env, is_fun: bool, name: &str) -> Option<(usize, bool)> {
4606 let is = |tparams: &[tast::Tparam]| {
4607 let is_soft = |ual: &Vec<tast::UserAttribute>| {
4608 ual.iter().any(|ua| user_attributes::is_soft(&ua.name.1))
4610 use tast::ReifyKind::*;
4611 tparams.iter().enumerate().find_map(|(i, tp)| {
4612 if (tp.reified == Reified || tp.reified == SoftReified) && tp.name.1 == name {
4613 Some((i, is_soft(&tp.user_attributes)))
4620 is(env.scope.get_fun_tparams())
4622 is(&env.scope.get_class_tparams()[..])
4626 /// Emit code for a base expression `expr` that forms part of
4627 /// an element access `expr[elem]` or field access `expr->fld`.
4628 /// The instructions are divided into three sections:
4629 /// 1. base and element/property expression instructions:
4630 /// push non-trivial base and key values on the stack
4631 /// 2. base selector instructions: a sequence of Base/Dim instructions that
4632 /// actually constructs the base address from "member keys" that are inlined
4633 /// in the instructions, or pulled from the key values that
4634 /// were pushed on the stack in section 1.
4635 /// 3. (constructed by the caller) a final accessor e.g. QueryM or setter
4636 /// e.g. SetOpM instruction that has the final key inlined in the
4637 /// instruction, or pulled from the key values that were pushed on the
4638 /// stack in section 1.
4639 /// The function returns a triple (base_instrs, base_setup_instrs, stack_size)
4640 /// where base_instrs is section 1 above, base_setup_instrs is section 2, and
4641 /// stack_size is the number of values pushed onto the stack by section 1.
4643 /// For example, the r-value expression $arr[3][$ix+2]
4645 /// # Section 1, pushing the value of $ix+2 on the stack
4649 /// # Section 2, constructing the base address of $arr[3]
4652 /// # Section 3, indexing the array using the value at stack position 0 (EC:0)
4653 /// QueryM 1 CGet EC:0
4662 null_coalesce_assignment: bool,
4663 base_offset: StackIndex,
4664 rhs_stack_size: StackIndex,
4665 ) -> Result<(InstrSeq, InstrSeq, InstrSeq, StackIndex, StackIndex)> {
4666 let result = emit_base_(
4673 null_coalesce_assignment,
4679 ArrayGetBase::Regular(i) => Ok((
4683 i.base_stack_size as isize,
4684 i.cls_stack_size as isize,
4686 ArrayGetBase::Inout { .. } => Err(unrecoverable("unexpected input")),
4690 fn is_trivial(env: &Env, is_base: bool, expr: &tast::Expr) -> bool {
4691 use tast::Expr_ as E_;
4693 E_::Int(_) | E_::String(_) => true,
4694 E_::Lvar(x) => !is_local_this(env, &x.1) || env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS),
4695 E_::ArrayGet(_) if !is_base => false,
4696 E_::ArrayGet(x) => {
4697 is_trivial(env, is_base, &x.0)
4700 .map_or(true, |e| is_trivial(env, is_base, &e))
4706 fn get_local_temp_kind(
4709 inout_param_info: Option<(usize, &inout_locals::AliasInfoMap)>,
4710 expr: Option<&tast::Expr>,
4711 ) -> Option<StoredValueKind> {
4712 match (expr, inout_param_info) {
4714 (Some(tast::Expr(_, tast::Expr_::Lvar(id))), Some((i, aliases)))
4715 if inout_locals::should_save_local_value(id.name(), i, aliases) =>
4717 Some(StoredValueKind::Local)
4720 if is_trivial(env, is_base, e) {
4723 Some(StoredValueKind::Expr)
4737 null_coalesce_assignment: bool,
4738 base_offset: StackIndex,
4739 rhs_stack_size: StackIndex,
4740 inout_param_info: Option<(usize, &inout_locals::AliasInfoMap)>,
4741 ) -> Result<ArrayGetBase> {
4743 let expr_ = &expr.1;
4744 let base_mode = if mode == MemberOpMode::InOut {
4749 let local_temp_kind = get_local_temp_kind(env, true, inout_param_info, Some(expr));
4750 let emit_default = |
4758 match local_temp_kind {
4759 Some(local_temp) => {
4760 let local = e.local_gen_mut().get_unnamed();
4761 ArrayGetBase::Inout {
4762 load: ArrayGetBaseData {
4763 base_instrs: vec![(base_instrs, Some((local.clone(), local_temp)))],
4769 store: instr::basel(local, MemberOpMode::Define),
4772 _ => ArrayGetBase::Regular(ArrayGetBaseData {
4782 let emit_expr_default = |e: &mut Emitter, env, expr: &tast::Expr| -> Result<ArrayGetBase> {
4783 let base_expr_instrs = emit_expr(e, env, expr)?;
4788 emit_pos_then(pos, instr::basec(base_offset, base_mode)),
4794 use tast::Expr_ as E_;
4796 E_::Lvar(x) if superglobals::is_superglobal(&(x.1).1) => {
4797 let base_instrs = emit_pos_then(
4799 instr::string(string_utils::locals::strip_dollar(&(x.1).1)),
4806 instr::basegc(base_offset, base_mode),
4811 E_::Lvar(x) if is_object && &(x.1).1 == special_idents::THIS => {
4812 let base_instrs = emit_pos_then(&x.0, instr::checkthis());
4823 if !is_local_this(env, &x.1) || env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS) =>
4825 let v = get_local(e, env, &x.0, &(x.1).1)?;
4826 let base_instr = if local_temp_kind.is_some() {
4827 instr::cgetquietl(v.clone())
4835 instr::basel(v, base_mode),
4841 let local = emit_local(e, env, notice, lid)?;
4846 instr::basec(base_offset, base_mode),
4851 E_::ArrayGet(x) => match (&(x.0).1, x.1.as_ref()) {
4852 // $a[] can not be used as the base of an array get unless as an lval
4853 (_, None) if !env.flags.contains(env::Flags::ALLOWS_ARRAY_APPEND) => {
4854 return Err(emit_fatal::raise_fatal_runtime(
4856 "Can't use [] for reading",
4859 // base is in turn array_get - do a specific handling for inout params
4861 (_, opt_elem_expr) => {
4862 let base_expr = &x.0;
4863 let local_temp_kind =
4864 get_local_temp_kind(env, false, inout_param_info, opt_elem_expr);
4865 let (elem_instrs, elem_stack_size) = emit_elem(
4870 null_coalesce_assignment,
4872 let base_result = emit_base_(
4879 null_coalesce_assignment,
4880 base_offset + elem_stack_size,
4884 let cls_stack_size = match &base_result {
4885 ArrayGetBase::Regular(base) => base.cls_stack_size,
4886 ArrayGetBase::Inout { load, .. } => load.cls_stack_size,
4888 let (mk, warninstr) = get_elem_member_key(
4891 base_offset + cls_stack_size,
4893 null_coalesce_assignment,
4895 let make_setup_instrs = |base_setup_instrs: InstrSeq| {
4896 InstrSeq::gather(vec![
4899 instr::dim(mode, mk.clone()),
4902 Ok(match (base_result, local_temp_kind) {
4903 // both base and index don't use temps - fallback to default handler
4904 (ArrayGetBase::Regular(base), None) => emit_default(
4906 InstrSeq::gather(vec![base.base_instrs, elem_instrs]),
4908 make_setup_instrs(base.setup_instrs),
4909 base.base_stack_size + elem_stack_size,
4910 base.cls_stack_size,
4912 // base does not need temps but index does
4913 (ArrayGetBase::Regular(base), Some(local_temp)) => {
4914 let local = e.local_gen_mut().get_unnamed();
4915 let base_instrs = InstrSeq::gather(vec![base.base_instrs, elem_instrs]);
4916 ArrayGetBase::Inout {
4917 load: ArrayGetBaseData {
4918 // store result of instr_begin to temp
4919 base_instrs: vec![(base_instrs, Some((local.clone(), local_temp)))],
4920 cls_instrs: base.cls_instrs,
4921 setup_instrs: make_setup_instrs(base.setup_instrs),
4922 base_stack_size: base.base_stack_size + elem_stack_size,
4923 cls_stack_size: base.cls_stack_size,
4925 store: emit_store_for_simple_base(
4936 // base needs temps, index - does not
4938 ArrayGetBase::Inout {
4951 base_instrs.push((elem_instrs, None));
4952 ArrayGetBase::Inout {
4953 load: ArrayGetBaseData {
4956 setup_instrs: make_setup_instrs(setup_instrs),
4957 base_stack_size: base_stack_size + elem_stack_size,
4960 store: InstrSeq::gather(vec![
4962 instr::dim(MemberOpMode::Define, mk),
4966 // both base and index needs locals
4968 ArrayGetBase::Inout {
4981 let local = e.local_gen_mut().get_unnamed();
4982 base_instrs.push((elem_instrs, Some((local.clone(), local_kind))));
4983 ArrayGetBase::Inout {
4984 load: ArrayGetBaseData {
4987 setup_instrs: make_setup_instrs(setup_instrs),
4988 base_stack_size: base_stack_size + elem_stack_size,
4991 store: InstrSeq::gather(vec![
4994 MemberOpMode::Define,
4995 MemberKey::EL(local, ReadOnlyOp::Any),
5005 emit_expr_default(e, env, expr)
5007 let (base_expr, prop_expr, null_flavor, _) = &**x;
5008 Ok(match prop_expr.1.as_id() {
5009 Some(ast_defs::Id(_, s)) if string_utils::is_xhp(&s) => {
5011 emit_xhp_obj_get(e, env, pos, base_expr, &s, null_flavor)?;
5016 instr::basec(base_offset, base_mode),
5022 let prop_stack_size = emit_prop_expr(
5028 null_coalesce_assignment,
5032 base_expr_instrs_begin,
5033 base_expr_instrs_end,
5044 null_coalesce_assignment,
5045 base_offset + prop_stack_size,
5048 let (mk, prop_instrs, _) = emit_prop_expr(
5052 base_offset + cls_stack_size,
5054 null_coalesce_assignment,
5056 let total_stack_size = prop_stack_size + base_stack_size;
5057 let final_instr = instr::dim(mode, mk);
5060 InstrSeq::gather(vec![base_expr_instrs_begin, prop_instrs]),
5061 base_expr_instrs_end,
5062 InstrSeq::gather(vec![base_setup_instrs, final_instr]),
5070 E_::ClassGet(x) => {
5072 emit_expr_default(e, env, expr)
5074 let (cid, prop, _) = &**x;
5075 let cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
5076 let (cexpr_begin, cexpr_end) = emit_class_expr(e, env, cexpr, prop)?;
5081 instr::basesc(base_offset + 1, rhs_stack_size, base_mode, ReadOnlyOp::Any),
5087 _ => emit_expr_default(e, env, expr),
5091 pub fn emit_ignored_exprs(
5092 emitter: &mut Emitter,
5095 exprs: &[tast::Expr],
5099 .map(|e| emit_ignored_expr(emitter, env, pos, e))
5100 .collect::<Result<Vec<_>>>()
5101 .map(InstrSeq::gather)
5104 // TODO(hrust): change pos from &Pos to Option<&Pos>, since Pos::make_none() still allocate mem.
5105 pub fn emit_ignored_expr(emitter: &mut Emitter, env: &Env, pos: &Pos, expr: &tast::Expr) -> Result {
5106 Ok(InstrSeq::gather(vec![
5107 emit_expr(emitter, env, expr)?,
5108 emit_pos_then(pos, instr::popc()),
5112 pub fn emit_lval_op(
5118 expr2: Option<&tast::Expr>,
5119 null_coalesce_assignment: bool,
5121 match (op, &expr1.1, expr2) {
5122 (LValOp::Set, tast::Expr_::List(l), Some(expr2)) => {
5123 let instr_rhs = emit_expr(e, env, expr2)?;
5124 let has_elements = l.iter().any(|e| !e.1.is_omitted());
5128 scope::with_unnamed_local(e, |e, local| {
5129 let loc = if can_use_as_rhs_in_list_assignment(&expr2.1)? {
5134 let (instr_lhs, instr_assign) =
5135 emit_lval_op_list(e, env, pos, loc, &[], expr1, false)?;
5137 InstrSeq::gather(vec![instr_lhs, instr_rhs, instr::popl(local.clone())]),
5139 instr::pushl(local),
5144 _ => e.local_scope(|e| {
5145 let (rhs_instrs, rhs_stack_size) = match expr2 {
5146 None => (instr::empty(), 0),
5147 Some(tast::Expr(_, tast::Expr_::Yield(af))) => {
5148 let temp = e.local_gen_mut().get_unnamed();
5150 InstrSeq::gather(vec![
5151 emit_yield(e, env, pos, af)?,
5152 instr::setl(temp.clone()),
5159 Some(expr) => (emit_expr(e, env, expr)?, 1),
5161 emit_lval_op_nonlist(
5169 null_coalesce_assignment,
5175 fn can_use_as_rhs_in_list_assignment(expr: &tast::Expr_) -> Result<bool> {
5176 use aast::Expr_ as E_;
5181 .map_or(false, |id| id.1 == special_functions::ECHO) =>
5185 E_::ObjGet(o) if !o.as_ref().3 => true,
5186 E_::ClassGet(c) if !c.as_ref().2 => true,
5190 | E_::FunctionPointer(_)
5203 | E_::ReadonlyExpr(_)
5204 | E_::ClassConst(_) => true,
5205 E_::Pipe(p) => can_use_as_rhs_in_list_assignment(&(p.2).1)?,
5207 if let ast_defs::Bop::Eq(None) = &b.0 {
5208 if (b.1).1.is_list() {
5209 return can_use_as_rhs_in_list_assignment(&(b.2).1);
5212 b.0.is_plus() || b.0.is_question_question() || b.0.is_any_eq()
5218 // Given a local $local and a list of integer array indices i_1, ..., i_n,
5219 // generate code to extract the value of $local[i_n]...[i_1]:
5220 // BaseL $local Warn
5221 // Dim Warn EI:i_n ...
5223 // QueryM 0 CGet EI:i_1
5224 fn emit_array_get_fixed(last_usage: bool, local: local::Type, indices: &[isize]) -> InstrSeq {
5225 let (base, stack_count) = if last_usage {
5227 InstrSeq::gather(vec![
5228 instr::pushl(local),
5229 instr::basec(0, MemberOpMode::Warn),
5234 (instr::basel(local, MemberOpMode::Warn), 0)
5236 let indices = InstrSeq::gather(
5242 let mk = MemberKey::EI(*ix as i64, ReadOnlyOp::Any);
5244 instr::querym(stack_count, QueryOp::CGet, mk)
5246 instr::dim(MemberOpMode::Warn, mk)
5251 InstrSeq::gather(vec![base, indices])
5254 // Generate code for each lvalue assignment in a list destructuring expression.
5255 // Lvalues are assigned right-to-left, regardless of the nesting structure. So
5256 // list($a, list($b, $c)) = $d
5257 // and list(list($a, $b), $c) = $d
5258 // will both assign to $c, $b and $a in that order.
5259 // Returns a pair of instructions:
5260 // 1. initialization part of the left hand side
5262 // this is necessary to handle cases like:
5263 // list($a[$f()]) = b();
5264 // here f() should be invoked before b()
5265 pub fn emit_lval_op_list(
5269 local: Option<&local::Type>,
5273 ) -> Result<(InstrSeq, InstrSeq)> {
5274 use options::Php7Flags;
5275 use tast::Expr_ as E_;
5276 let is_ltr = e.options().php7_flags.contains(Php7Flags::LTR_ASSIGN);
5278 E_::List(exprs) => {
5279 let last_non_omitted = if last_usage {
5280 // last usage of the local will happen when processing last non-omitted
5281 // element in the list - find it
5283 exprs.iter().rposition(|v| !v.1.is_omitted())
5285 // in right-to-left case result list will be reversed
5286 // so we need to find first non-omitted expression
5287 exprs.iter().rev().rposition(|v| !v.1.is_omitted())
5292 let (lhs_instrs, set_instrs): (Vec<InstrSeq>, Vec<InstrSeq>) = exprs
5296 let mut new_indices = vec![i as isize];
5297 new_indices.extend_from_slice(indices);
5305 last_non_omitted.map_or(false, |j| j == i),
5308 .collect::<Result<Vec<_>>>()?
5312 InstrSeq::gather(lhs_instrs),
5313 InstrSeq::gather(if !is_ltr {
5314 set_instrs.into_iter().rev().collect()
5320 E_::Omitted => Ok((instr::empty(), instr::empty())),
5322 // Generate code to access the element from the array
5323 let access_instrs = match (local, indices) {
5324 (Some(loc), [_, ..]) => emit_array_get_fixed(last_usage, loc.to_owned(), indices),
5325 (Some(loc), []) => {
5327 instr::pushl(loc.to_owned())
5329 instr::cgetl(loc.to_owned())
5332 (None, _) => instr::null(),
5334 // Generate code to assign to the lvalue *)
5335 // Return pair: side effects to initialize lhs + assignment
5336 let (lhs_instrs, rhs_instrs, set_op) = emit_lval_op_nonlist_steps(
5349 InstrSeq::gather(vec![lhs_instrs, rhs_instrs, set_op, instr::popc()]),
5354 InstrSeq::gather(vec![instr::empty(), rhs_instrs, set_op, instr::popc()]),
5361 pub fn emit_lval_op_nonlist(
5367 rhs_instrs: InstrSeq,
5368 rhs_stack_size: isize,
5369 null_coalesce_assignment: bool,
5371 emit_lval_op_nonlist_steps(
5379 null_coalesce_assignment,
5381 .map(|(lhs, rhs, setop)| InstrSeq::gather(vec![lhs, rhs, setop]))
5384 pub fn emit_final_global_op(pos: &Pos, op: LValOp) -> InstrSeq {
5387 L::Set => emit_pos_then(pos, instr::setg()),
5388 L::SetOp(op) => instr::setopg(op),
5389 L::IncDec(op) => instr::incdecg(op),
5390 L::Unset => emit_pos_then(pos, instr::unsetg()),
5394 pub fn emit_final_local_op(pos: &Pos, op: LValOp, lid: local::Type) -> InstrSeq {
5399 L::Set => instr::setl(lid),
5400 L::SetOp(op) => instr::setopl(lid, op),
5401 L::IncDec(op) => instr::incdecl(lid, op),
5402 L::Unset => instr::unsetl(lid),
5407 fn emit_final_member_op(stack_size: usize, op: LValOp, mk: MemberKey) -> InstrSeq {
5410 L::Set => instr::setm(stack_size, mk),
5411 L::SetOp(op) => instr::setopm(stack_size, op, mk),
5412 L::IncDec(op) => instr::incdecm(stack_size, op, mk),
5413 L::Unset => instr::unsetm(stack_size, mk),
5417 fn emit_final_static_op(cid: &tast::ClassId, prop: &tast::ClassGetExpr, op: LValOp) -> Result {
5420 L::Set => instr::sets(ReadOnlyOp::Any),
5421 L::SetOp(op) => instr::setops(op, ReadOnlyOp::Any),
5422 L::IncDec(op) => instr::incdecs(op, ReadOnlyOp::Any),
5424 let pos = match prop {
5425 tast::ClassGetExpr::CGstring((pos, _))
5426 | tast::ClassGetExpr::CGexpr(tast::Expr(pos, _)) => pos,
5428 let cid = text_of_class_id(cid);
5429 let id = text_of_prop(prop);
5430 emit_fatal::emit_fatal_runtime(
5433 "Attempt to unset static property {}::{}",
5434 string_utils::strip_ns(&cid),
5442 pub fn emit_lval_op_nonlist_steps(
5448 rhs_instrs: InstrSeq,
5449 rhs_stack_size: isize,
5450 null_coalesce_assignment: bool,
5451 ) -> Result<(InstrSeq, InstrSeq, InstrSeq)> {
5452 let f = |env: &mut Env| {
5453 use tast::Expr_ as E_;
5456 E_::Lvar(v) if superglobals::is_any_global(local_id::get_name(&v.1)) => (
5459 instr::string(string_utils::lstrip(local_id::get_name(&v.1), "$")),
5462 emit_final_global_op(outer_pos, op),
5464 E_::Lvar(v) if is_local_this(env, &v.1) && op.is_incdec() => (
5465 emit_local(e, env, BareThisOp::Notice, v)?,
5469 E_::Lvar(v) if !is_local_this(env, &v.1) || op == LValOp::Unset => {
5470 (instr::empty(), rhs_instrs, {
5471 let lid = get_local(e, env, &v.0, &(v.1).1)?;
5472 emit_final_local_op(outer_pos, op, lid)
5475 E_::ArrayGet(x) => match (&(x.0).1, x.1.as_ref()) {
5476 (_, None) if !env.flags.contains(env::Flags::ALLOWS_ARRAY_APPEND) => {
5477 return Err(emit_fatal::raise_fatal_runtime(
5479 "Can't use [] for reading",
5482 (_, opt_elem_expr) => {
5483 let mode = match op {
5484 LValOp::Unset => MemberOpMode::Unset,
5485 _ => MemberOpMode::Define,
5487 let (mut elem_instrs, elem_stack_size) =
5488 emit_elem(e, env, opt_elem_expr, None, null_coalesce_assignment)?;
5489 if null_coalesce_assignment {
5490 elem_instrs = instr::empty();
5492 let base_offset = elem_stack_size + rhs_stack_size;
5494 base_expr_instrs_begin,
5495 base_expr_instrs_end,
5506 null_coalesce_assignment,
5510 let (mk, warninstr) = get_elem_member_key(
5513 rhs_stack_size + cls_stack_size,
5515 null_coalesce_assignment,
5517 let total_stack_size = elem_stack_size + base_stack_size + cls_stack_size;
5519 emit_pos_then(pos, emit_final_member_op(total_stack_size as usize, op, mk));
5521 if null_coalesce_assignment {
5524 InstrSeq::gather(vec![
5525 base_expr_instrs_begin,
5527 base_expr_instrs_end,
5531 InstrSeq::gather(vec![
5540 E_::ObjGet(x) if !x.as_ref().3 => {
5541 let (e1, e2, nullflavor, _) = &**x;
5542 if nullflavor.eq(&ast_defs::OgNullFlavor::OGNullsafe) {
5543 return Err(emit_fatal::raise_fatal_parse(
5545 "?-> is not allowed in write context",
5548 let mode = match op {
5549 LValOp::Unset => MemberOpMode::Unset,
5550 _ => MemberOpMode::Define,
5552 let prop_stack_size =
5553 emit_prop_expr(e, env, nullflavor, 0, e2, null_coalesce_assignment)?.2;
5554 let base_offset = prop_stack_size + rhs_stack_size;
5556 base_expr_instrs_begin,
5557 base_expr_instrs_end,
5568 null_coalesce_assignment,
5572 let (mk, mut prop_instrs, _) = emit_prop_expr(
5576 rhs_stack_size + cls_stack_size,
5578 null_coalesce_assignment,
5580 if null_coalesce_assignment {
5581 prop_instrs = instr::empty();
5583 let total_stack_size = prop_stack_size + base_stack_size + cls_stack_size;
5585 emit_pos_then(pos, emit_final_member_op(total_stack_size as usize, op, mk));
5587 if null_coalesce_assignment {
5590 InstrSeq::gather(vec![
5591 base_expr_instrs_begin,
5593 base_expr_instrs_end,
5597 InstrSeq::gather(vec![base_setup_instrs, final_instr]),
5600 E_::ClassGet(x) if !x.as_ref().2 => {
5601 let (cid, prop, _) = &**x;
5602 let cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
5603 let final_instr_ = emit_final_static_op(cid, prop, op)?;
5604 let final_instr = emit_pos_then(pos, final_instr_);
5606 InstrSeq::from(emit_class_expr(e, env, cexpr, prop)?),
5614 InstrSeq::gather(vec![
5615 emit_lval_op_nonlist(
5625 from_unop(e.options(), &uop.0)?,
5629 return Err(emit_fatal::raise_fatal_parse(
5631 "Can't use return value in write context",
5636 // TODO(shiqicao): remove clone!
5637 let mut env = env.clone();
5639 LValOp::Set | LValOp::SetOp(_) | LValOp::IncDec(_) => env.with_allows_array_append(f),
5648 prop: &tast::ClassGetExpr,
5649 ) -> Result<(InstrSeq, InstrSeq)> {
5650 let load_prop = |e: &mut Emitter| match prop {
5651 tast::ClassGetExpr::CGstring((pos, id)) => Ok(emit_pos_then(
5653 instr::string(string_utils::locals::strip_dollar(id)),
5655 tast::ClassGetExpr::CGexpr(expr) => emit_expr(e, env, expr),
5658 ClassExpr::Expr(expr)
5660 || expr.1.is_binop()
5661 || expr.1.is_class_get()
5665 .map_or(false, |tast::Lid(_, id)| local_id::get_name(id) == "$this") =>
5667 let cexpr_local = emit_expr(e, env, expr)?;
5670 InstrSeq::gather(vec![
5672 scope::stash_top_in_unnamed_local(e, load_prop)?,
5678 let pos = match prop {
5679 tast::ClassGetExpr::CGstring((pos, _))
5680 | tast::ClassGetExpr::CGexpr(tast::Expr(pos, _)) => pos,
5682 (load_prop(e)?, emit_load_class_ref(e, env, pos, cexpr)?)
5687 pub fn fixup_type_arg<'a>(
5690 hint: &'a tast::Hint,
5691 ) -> Result<impl AsRef<tast::Hint> + 'a> {
5692 struct Checker<'s> {
5693 erased_tparams: &'s [&'s str],
5696 impl<'ast, 's> Visitor<'ast> for Checker<'s> {
5697 type P = AstParams<(), Option<Error>>;
5699 fn object(&mut self) -> &mut dyn Visitor<'ast, P = Self::P> {
5707 ) -> StdResult<(), Option<Error>> {
5708 hf.param_tys.accept(c, self.object())?;
5709 hf.return_ty.accept(c, self.object())
5712 fn visit_hint(&mut self, c: &mut (), h: &tast::Hint) -> StdResult<(), Option<Error>> {
5713 use tast::{Hint_ as H_, Id};
5714 match h.1.as_ref() {
5715 H_::Happly(Id(_, id), _)
5716 if self.erased_tparams.contains(&id.as_str()) && self.isas =>
5718 return Err(Some(emit_fatal::raise_fatal_parse(
5720 "Erased generics are not allowed in is/as expressions",
5723 H_::Haccess(_, _) => return Ok(()),
5726 h.recurse(c, self.object())
5729 fn visit_hint_(&mut self, c: &mut (), h: &tast::Hint_) -> StdResult<(), Option<Error>> {
5730 use tast::{Hint_ as H_, Id};
5732 H_::Happly(Id(_, id), _) if self.erased_tparams.contains(&id.as_str()) => Err(None),
5733 _ => h.recurse(c, self.object()),
5738 struct Updater<'s> {
5739 erased_tparams: &'s [&'s str],
5741 impl<'ast, 's> VisitorMut<'ast> for Updater<'s> {
5742 type P = AstParams<(), ()>;
5744 fn object(&mut self) -> &mut dyn VisitorMut<'ast, P = Self::P> {
5748 fn visit_hint_fun(&mut self, c: &mut (), hf: &mut tast::HintFun) -> StdResult<(), ()> {
5749 <Vec<tast::Hint> as NodeMut<Self::P>>::accept(&mut hf.param_tys, c, self.object())?;
5750 <tast::Hint as NodeMut<Self::P>>::accept(&mut hf.return_ty, c, self.object())
5753 fn visit_hint_(&mut self, c: &mut (), h: &mut tast::Hint_) -> StdResult<(), ()> {
5754 use tast::{Hint_ as H_, Id};
5756 H_::Happly(Id(_, id), _) if self.erased_tparams.contains(&id.as_str()) => {
5757 Ok(*id = "_".into())
5759 _ => h.recurse(c, self.object()),
5763 let erased_tparams = get_erased_tparams(env);
5764 let erased_tparams = erased_tparams.as_slice();
5765 let mut checker = Checker {
5769 match visit(&mut checker, &mut (), hint) {
5770 Ok(()) => Ok(Either::Left(hint)),
5771 Err(Some(error)) => Err(error),
5773 let mut updater = Updater { erased_tparams };
5774 let mut hint = hint.clone();
5775 visit_mut(&mut updater, &mut (), &mut hint).unwrap();
5776 Ok(Either::Right(hint))
5781 pub fn emit_reified_arg(
5787 ) -> Result<(InstrSeq, bool)> {
5788 struct Collector<'ast, 'a> {
5789 current_tags: &'a HashSet<&'a str>,
5790 acc: IndexSet<&'ast str>,
5793 impl<'ast, 'a> Collector<'ast, 'a> {
5794 fn add_name(&mut self, name: &'ast str) {
5795 if self.current_tags.contains(name) && !self.acc.contains(name) {
5796 self.acc.insert(name);
5801 impl<'ast, 'a> Visitor<'ast> for Collector<'ast, 'a> {
5802 type P = AstParams<(), ()>;
5804 fn object(&mut self) -> &mut dyn Visitor<'ast, P = Self::P> {
5808 fn visit_hint_(&mut self, c: &mut (), h_: &'ast tast::Hint_) -> StdResult<(), ()> {
5809 use tast::{Hint_ as H_, Id};
5811 H_::Haccess(_, sids) => Ok(sids.iter().for_each(|Id(_, name)| self.add_name(name))),
5812 H_::Habstr(name, h) | H_::Happly(Id(_, name), h) => {
5813 self.add_name(name);
5814 h.accept(c, self.object())
5816 _ => h_.recurse(c, self.object()),
5820 let hint = fixup_type_arg(env, isas, hint)?;
5821 let hint = hint.as_ref();
5822 fn f<'a>(mut acc: HashSet<&'a str>, tparam: &'a tast::Tparam) -> HashSet<&'a str> {
5823 if tparam.reified != tast::ReifyKind::Erased {
5824 acc.insert(&tparam.name.1);
5828 let current_tags = env
5832 .fold(HashSet::<&str>::new(), |acc, t| f(acc, &*t));
5833 let class_tparams = env.scope.get_class_tparams();
5834 let current_tags = class_tparams
5836 .fold(current_tags, |acc, t| f(acc, &*t));
5837 let mut collector = Collector {
5838 current_tags: ¤t_tags,
5839 acc: IndexSet::new(),
5841 visit(&mut collector, &mut (), hint).unwrap();
5842 match hint.1.as_ref() {
5843 tast::Hint_::Happly(tast::Id(_, name), hs)
5844 if hs.is_empty() && current_tags.contains(name.as_str()) =>
5846 Ok((emit_reified_type(env, pos, name)?, false))
5849 let ts = get_type_structure_for_hint(e, &[], &collector.acc, hint)?;
5850 let ts_list = if collector.acc.is_empty() {
5853 let values = collector
5856 .map(|v| emit_reified_type(env, pos, v))
5857 .collect::<Result<Vec<_>>>()?;
5858 InstrSeq::gather(vec![InstrSeq::gather(values), ts])
5861 InstrSeq::gather(vec![
5863 instr::combine_and_resolve_type_struct((collector.acc.len() + 1) as isize),
5865 collector.acc.is_empty(),
5871 pub fn get_local(e: &mut Emitter, env: &Env, pos: &Pos, s: &str) -> Result<local::Type> {
5872 if s == special_idents::DOLLAR_DOLLAR {
5873 match &env.pipe_var {
5874 None => Err(emit_fatal::raise_fatal_runtime(
5876 "Pipe variables must occur only in the RHS of pipe expressions",
5878 Some(var) => Ok(var.clone()),
5880 } else if special_idents::is_tmp_var(s) {
5881 Ok(e.local_gen().get_unnamed_for_tempname(s).clone())
5883 Ok(local::Type::Named(s.into()))
5887 pub fn emit_is_null(e: &mut Emitter, env: &Env, expr: &tast::Expr) -> Result {
5888 if let Some(tast::Lid(pos, id)) = expr.1.as_lvar() {
5889 if !is_local_this(env, id) {
5890 return Ok(instr::istypel(
5891 get_local(e, env, pos, local_id::get_name(id))?,
5897 Ok(InstrSeq::gather(vec![
5898 emit_expr(e, env, expr)?,
5899 instr::istypec(IstypeOp::OpNull),
5908 ) -> Result<EmitJmpResult> {
5909 let tast::Expr(pos, expr_) = expr;
5910 let opt = optimize_null_checks(e);
5912 match ast_constant_folder::expr_to_typed_value(e, &env.namespace, expr) {
5914 if Into::<bool>::into(tv) {
5916 instrs: emit_pos_then(pos, instr::jmp(label.clone())),
5917 is_fallthrough: false,
5918 is_label_used: true,
5922 instrs: emit_pos_then(pos, instr::empty()),
5923 is_fallthrough: true,
5924 is_label_used: false,
5929 use {ast_defs::Uop as U, tast::Expr_ as E};
5931 E::Unop(uo) if uo.0 == U::Unot => emit_jmpz(e, env, &uo.1, label)?,
5932 E::Binop(bo) if bo.0.is_barbar() => {
5933 let r1 = emit_jmpnz(e, env, &bo.1, label)?;
5934 if r1.is_fallthrough {
5935 let r2 = emit_jmpnz(e, env, &bo.2, label)?;
5937 instrs: emit_pos_then(
5939 InstrSeq::gather(vec![r1.instrs, r2.instrs]),
5941 is_fallthrough: r2.is_fallthrough,
5942 is_label_used: r1.is_label_used || r2.is_label_used,
5948 E::Binop(bo) if bo.0.is_ampamp() => {
5949 let skip_label = e.label_gen_mut().next_regular();
5950 let r1 = emit_jmpz(e, env, &bo.1, &skip_label)?;
5951 if !r1.is_fallthrough {
5953 instrs: emit_pos_then(
5955 InstrSeq::gather(vec![
5959 vec![instr::label(skip_label)],
5963 is_fallthrough: r1.is_label_used,
5964 is_label_used: false,
5967 let r2 = emit_jmpnz(e, env, &bo.2, label)?;
5969 instrs: emit_pos_then(
5971 InstrSeq::gather(vec![
5976 vec![instr::label(skip_label)],
5980 is_fallthrough: r2.is_fallthrough || r1.is_label_used,
5981 is_label_used: r2.is_label_used,
5987 && ((bo.1).1.is_null() || (bo.2).1.is_null())
5991 emit_is_null(e, env, if (bo.1).1.is_null() { &bo.2 } else { &bo.1 })?;
5993 instrs: emit_pos_then(
5995 InstrSeq::gather(vec![is_null, instr::jmpnz(label.clone())]),
5997 is_fallthrough: true,
5998 is_label_used: true,
6002 if bo.0.is_diff2() && ((bo.1).1.is_null() || (bo.2).1.is_null()) && opt =>
6005 emit_is_null(e, env, if (bo.1).1.is_null() { &bo.2 } else { &bo.1 })?;
6007 instrs: emit_pos_then(
6009 InstrSeq::gather(vec![is_null, instr::jmpz(label.clone())]),
6011 is_fallthrough: true,
6012 is_label_used: true,
6016 let instr = emit_expr(e, env, expr)?;
6018 instrs: emit_pos_then(
6020 InstrSeq::gather(vec![instr, instr::jmpnz(label.clone())]),
6022 is_fallthrough: true,
6023 is_label_used: true,
6037 ) -> Result<EmitJmpResult> {
6038 let tast::Expr(pos, expr_) = expr;
6039 let opt = optimize_null_checks(e);
6041 match ast_constant_folder::expr_to_typed_value(e, &env.namespace, expr) {
6043 let b: bool = v.into();
6046 instrs: emit_pos_then(pos, instr::empty()),
6047 is_fallthrough: true,
6048 is_label_used: false,
6052 instrs: emit_pos_then(pos, instr::jmp(label.clone())),
6053 is_fallthrough: false,
6054 is_label_used: true,
6059 use {ast_defs::Uop as U, tast::Expr_ as E};
6061 E::Unop(uo) if uo.0 == U::Unot => emit_jmpnz(e, env, &uo.1, label)?,
6062 E::Binop(bo) if bo.0.is_barbar() => {
6063 let skip_label = e.label_gen_mut().next_regular();
6064 let r1 = emit_jmpnz(e, env, &bo.1, &skip_label)?;
6065 if !r1.is_fallthrough {
6067 instrs: emit_pos_then(
6069 InstrSeq::gather(vec![
6073 vec![instr::label(skip_label)],
6077 is_fallthrough: r1.is_label_used,
6078 is_label_used: false,
6081 let r2 = emit_jmpz(e, env, &bo.2, label)?;
6083 instrs: emit_pos_then(
6085 InstrSeq::gather(vec![
6090 vec![instr::label(skip_label)],
6094 is_fallthrough: r2.is_fallthrough || r1.is_label_used,
6095 is_label_used: r2.is_label_used,
6099 E::Binop(bo) if bo.0.is_ampamp() => {
6100 let r1 = emit_jmpz(e, env, &bo.1, label)?;
6101 if r1.is_fallthrough {
6102 let r2 = emit_jmpz(e, env, &bo.2, label)?;
6104 instrs: emit_pos_then(
6106 InstrSeq::gather(vec![r1.instrs, r2.instrs]),
6108 is_fallthrough: r2.is_fallthrough,
6109 is_label_used: r1.is_label_used || r2.is_label_used,
6113 instrs: emit_pos_then(pos, r1.instrs),
6114 is_fallthrough: false,
6115 is_label_used: r1.is_label_used,
6121 && ((bo.1).1.is_null() || (bo.2).1.is_null())
6125 emit_is_null(e, env, if (bo.1).1.is_null() { &bo.2 } else { &bo.1 })?;
6127 instrs: emit_pos_then(
6129 InstrSeq::gather(vec![is_null, instr::jmpz(label.clone())]),
6131 is_fallthrough: true,
6132 is_label_used: true,
6136 if bo.0.is_diff2() && ((bo.1).1.is_null() || (bo.2).1.is_null()) && opt =>
6139 emit_is_null(e, env, if (bo.1).1.is_null() { &bo.2 } else { &bo.1 })?;
6141 instrs: emit_pos_then(
6143 InstrSeq::gather(vec![is_null, instr::jmpnz(label.clone())]),
6145 is_fallthrough: true,
6146 is_label_used: true,
6150 let instr = emit_expr(e, env, expr)?;
6152 instrs: emit_pos_then(
6154 InstrSeq::gather(vec![instr, instr::jmpz(label.clone())]),
6156 is_fallthrough: true,
6157 is_label_used: true,