fn emit_base_()

in hphp/hack/src/hackc/emitter/emit_expression.rs [5247:5648]


fn emit_base_<'a, 'arena, 'decl>(
    e: &mut Emitter<'arena, 'decl>,
    env: &Env<'a, 'arena>,
    expr: &ast::Expr,
    mode: MOpMode,
    is_object: bool,
    notice: BareThisOp,
    null_coalesce_assignment: bool,
    base_offset: StackIndex,
    rhs_stack_size: StackIndex,
    inout_param_info: Option<(usize, &inout_locals::AliasInfoMap<'_>)>,
    readonly_op: ReadonlyOp,
) -> Result<ArrayGetBase<'arena>> {
    let alloc = env.arena;
    let pos = &expr.1;
    let expr_ = &expr.2;
    let base_mode = if mode == MOpMode::InOut {
        MOpMode::Warn
    } else {
        mode
    };
    let local_temp_kind = get_local_temp_kind(env, true, inout_param_info, Some(expr));
    let emit_default = |e: &mut Emitter<'arena, 'decl>,
                        base_instrs,
                        cls_instrs,
                        setup_instrs,
                        base_stack_size,
                        cls_stack_size| {
        match local_temp_kind {
            Some(local_temp) => {
                let local = e.local_gen_mut().get_unnamed();
                ArrayGetBase::Inout {
                    load: ArrayGetBaseData {
                        base_instrs: vec![(base_instrs, Some((local, local_temp)))],
                        cls_instrs,
                        setup_instrs,
                        base_stack_size,
                        cls_stack_size,
                    },
                    store: instr::basel(local, MOpMode::Define, ReadonlyOp::Any),
                }
            }
            _ => ArrayGetBase::Regular(ArrayGetBaseData {
                base_instrs,
                cls_instrs,
                setup_instrs,
                base_stack_size,
                cls_stack_size,
            }),
        }
    };
    // Called when emitting a base with MOpMode::Define on a Readonly expression
    let emit_readonly_lval_base = |
        e: &mut Emitter<'arena, 'decl>,
        env: &Env<'a, 'arena>,
        inner_expr: &ast::Expr, // expression inside of readonly expression
    | -> Option<Result<ArrayGetBase<'_>>> {
        // Readonly local variable requires a CheckROCOW
        if let aast::Expr(_, _, Expr_::Lvar(x)) = &*inner_expr {
            if !is_local_this(env, &x.1) || env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS) {
                match get_local(e, env, &x.0, &(x.1).1) {
                    Ok(v) => {
                    let base_instr = if local_temp_kind.is_some() {
                        instr::cgetquietl(v)
                    } else {
                        instr::empty()
                    };
                    Some(Ok(emit_default(
                        e,
                        base_instr,
                        instr::empty(),
                        instr::basel( v, MOpMode::Define, ReadonlyOp::CheckROCOW),
                        0,
                        0,
                    )))
                 }
                    Err(e) => Some(Err(e))
                }
            } else {
                None // Found a local variable case that does not work
            }
        } else {
            // The only other reasonable case is if the inner expression
            // is a ClassGet, in which case we can use the default emit_base_ logic to handle
            None // Otherwise, ignore readonly
        }
    };

    let emit_expr_default =
        |e: &mut Emitter<'arena, 'decl>, env, expr: &ast::Expr| -> Result<ArrayGetBase<'_>> {
            let base_expr_instrs = emit_expr(e, env, expr)?;
            Ok(emit_default(
                e,
                base_expr_instrs,
                instr::empty(),
                emit_pos_then(pos, instr::basec(base_offset, base_mode)),
                1,
                0,
            ))
        };

    use ast::Expr_;
    match expr_ {
        // Readonly expression in assignment
        Expr_::ReadonlyExpr(r) if base_mode == MOpMode::Define => {
            if let Some(result) = emit_readonly_lval_base(e, env, r) {
                result
            } else {
                // If we're not able to emit special readonly expression instructions, emit code as if readonly
                // expression does not exist
                emit_base_(
                    e,
                    env,
                    r,
                    mode,
                    is_object,
                    notice,
                    null_coalesce_assignment,
                    base_offset,
                    rhs_stack_size,
                    inout_param_info,
                    readonly_op,
                )
            }
        }
        Expr_::Lvar(x) if superglobals::is_superglobal(&(x.1).1) => {
            let base_instrs = emit_pos_then(
                &x.0,
                instr::string(alloc, string_utils::locals::strip_dollar(&(x.1).1)),
            );

            Ok(emit_default(
                e,
                base_instrs,
                instr::empty(),
                instr::basegc(base_offset, base_mode),
                1,
                0,
            ))
        }
        Expr_::Lvar(x) if is_object && (x.1).1 == special_idents::THIS => {
            let base_instrs = emit_pos_then(&x.0, instr::checkthis());
            Ok(emit_default(
                e,
                base_instrs,
                instr::empty(),
                instr::baseh(),
                0,
                0,
            ))
        }
        Expr_::Lvar(x)
            if !is_local_this(env, &x.1) || env.flags.contains(EnvFlags::NEEDS_LOCAL_THIS) =>
        {
            let v = get_local(e, env, &x.0, &(x.1).1)?;
            let base_instr = if local_temp_kind.is_some() {
                instr::cgetquietl(v)
            } else {
                instr::empty()
            };
            Ok(emit_default(
                e,
                base_instr,
                instr::empty(),
                instr::basel(v, base_mode, ReadonlyOp::Any),
                0,
                0,
            ))
        }
        Expr_::Lvar(lid) => {
            let local = emit_local(e, env, notice, lid)?;
            Ok(emit_default(
                e,
                local,
                instr::empty(),
                instr::basec(base_offset, base_mode),
                1,
                0,
            ))
        }
        Expr_::ArrayGet(x) => match (&(x.0).1, x.1.as_ref()) {
            // $a[] can not be used as the base of an array get unless as an lval
            (_, None) if !env.flags.contains(env::Flags::ALLOWS_ARRAY_APPEND) => {
                Err(Error::fatal_runtime(pos, "Can't use [] for reading"))
            }
            // base is in turn array_get - do a specific handling for inout params
            // if necessary
            (_, opt_elem_expr) => {
                let base_expr = &x.0;
                let local_temp_kind =
                    get_local_temp_kind(env, false, inout_param_info, opt_elem_expr);
                let (elem_instrs, elem_stack_size) = emit_elem(
                    e,
                    env,
                    opt_elem_expr,
                    local_temp_kind,
                    null_coalesce_assignment,
                )?;
                let base_result = emit_base_(
                    e,
                    env,
                    base_expr,
                    mode,
                    false,
                    notice,
                    null_coalesce_assignment,
                    base_offset + elem_stack_size,
                    rhs_stack_size,
                    inout_param_info,
                    readonly_op, // continue passing readonly enforcement up
                )?;
                let cls_stack_size = match &base_result {
                    ArrayGetBase::Regular(base) => base.cls_stack_size,
                    ArrayGetBase::Inout { load, .. } => load.cls_stack_size,
                };
                let (mk, warninstr) = get_elem_member_key(
                    e,
                    env,
                    base_offset + cls_stack_size,
                    opt_elem_expr,
                    null_coalesce_assignment,
                )?;
                let make_setup_instrs = |base_setup_instrs: InstrSeq<'arena>| {
                    InstrSeq::gather(vec![warninstr, base_setup_instrs, instr::dim(mode, mk)])
                };
                Ok(match (base_result, local_temp_kind) {
                    // both base and index don't use temps - fallback to default handler
                    (ArrayGetBase::Regular(base), None) => emit_default(
                        e,
                        InstrSeq::gather(vec![base.base_instrs, elem_instrs]),
                        base.cls_instrs,
                        make_setup_instrs(base.setup_instrs),
                        base.base_stack_size + elem_stack_size,
                        base.cls_stack_size,
                    ),
                    // base does not need temps but index does
                    (ArrayGetBase::Regular(base), Some(local_temp)) => {
                        let local = e.local_gen_mut().get_unnamed();
                        let base_instrs = InstrSeq::gather(vec![base.base_instrs, elem_instrs]);
                        ArrayGetBase::Inout {
                            load: ArrayGetBaseData {
                                // store result of instr_begin to temp
                                base_instrs: vec![(base_instrs, Some((local, local_temp)))],
                                cls_instrs: base.cls_instrs,
                                setup_instrs: make_setup_instrs(base.setup_instrs),
                                base_stack_size: base.base_stack_size + elem_stack_size,
                                cls_stack_size: base.cls_stack_size,
                            },
                            store: emit_store_for_simple_base(
                                e,
                                env,
                                pos,
                                elem_stack_size,
                                base_expr,
                                local,
                                true,
                                readonly_op,
                            )?,
                        }
                    }
                    // base needs temps, index - does not
                    (
                        ArrayGetBase::Inout {
                            load:
                                ArrayGetBaseData {
                                    mut base_instrs,
                                    cls_instrs,
                                    setup_instrs,
                                    base_stack_size,
                                    cls_stack_size,
                                },
                            store,
                        },
                        None,
                    ) => {
                        base_instrs.push((elem_instrs, None));
                        ArrayGetBase::Inout {
                            load: ArrayGetBaseData {
                                base_instrs,
                                cls_instrs,
                                setup_instrs: make_setup_instrs(setup_instrs),
                                base_stack_size: base_stack_size + elem_stack_size,
                                cls_stack_size,
                            },
                            store: InstrSeq::gather(vec![store, instr::dim(MOpMode::Define, mk)]),
                        }
                    }
                    // both base and index needs locals
                    (
                        ArrayGetBase::Inout {
                            load:
                                ArrayGetBaseData {
                                    mut base_instrs,
                                    cls_instrs,
                                    setup_instrs,
                                    base_stack_size,
                                    cls_stack_size,
                                },
                            store,
                        },
                        Some(local_kind),
                    ) => {
                        let local = e.local_gen_mut().get_unnamed();
                        base_instrs.push((elem_instrs, Some((local, local_kind))));
                        ArrayGetBase::Inout {
                            load: ArrayGetBaseData {
                                base_instrs,
                                cls_instrs,
                                setup_instrs: make_setup_instrs(setup_instrs),
                                base_stack_size: base_stack_size + elem_stack_size,
                                cls_stack_size,
                            },
                            store: InstrSeq::gather(vec![
                                store,
                                instr::dim(MOpMode::Define, MemberKey::EL(local, ReadonlyOp::Any)),
                            ]),
                        }
                    }
                })
            }
        },
        Expr_::ObjGet(x) if x.as_ref().3 == ast::PropOrMethod::IsProp => {
            let (base_expr, prop_expr, null_flavor, _) = &**x;
            Ok(match prop_expr.2.as_id() {
                Some(ast_defs::Id(_, s)) if string_utils::is_xhp(s) => {
                    let base_instrs = emit_xhp_obj_get(e, env, pos, base_expr, s, null_flavor)?;
                    emit_default(
                        e,
                        base_instrs,
                        instr::empty(),
                        instr::basec(base_offset, base_mode),
                        1,
                        0,
                    )
                }
                _ => {
                    let prop_stack_size = emit_prop_expr(
                        e,
                        env,
                        null_flavor,
                        0,
                        prop_expr,
                        null_coalesce_assignment,
                        ReadonlyOp::Any, // just getting stack size here
                    )?
                    .2;
                    let (
                        base_expr_instrs_begin,
                        base_expr_instrs_end,
                        base_setup_instrs,
                        base_stack_size,
                        cls_stack_size,
                    ) = emit_base(
                        e,
                        env,
                        base_expr,
                        mode,
                        true,
                        BareThisOp::Notice,
                        null_coalesce_assignment,
                        base_offset + prop_stack_size,
                        rhs_stack_size,
                        ReadonlyOp::Mutable, // the rest of the base must be completely mutable
                    )?;
                    let (mk, prop_instrs, _) = emit_prop_expr(
                        e,
                        env,
                        null_flavor,
                        base_offset + cls_stack_size,
                        prop_expr,
                        null_coalesce_assignment,
                        readonly_op, // use the current enforcement
                    )?;
                    let total_stack_size = prop_stack_size + base_stack_size;
                    let final_instr = instr::dim(mode, mk);
                    emit_default(
                        e,
                        InstrSeq::gather(vec![base_expr_instrs_begin, prop_instrs]),
                        base_expr_instrs_end,
                        InstrSeq::gather(vec![base_setup_instrs, final_instr]),
                        total_stack_size,
                        cls_stack_size,
                    )
                }
            })
        }
        Expr_::ClassGet(x) => {
            let (cid, prop, _) = &**x;
            let cexpr = ClassExpr::class_id_to_class_expr(e, false, false, &env.scope, cid);
            let (cexpr_begin, cexpr_end) = emit_class_expr(e, env, cexpr, prop)?;
            Ok(emit_default(
                e,
                cexpr_begin,
                cexpr_end,
                instr::basesc(base_offset + 1, rhs_stack_size, base_mode, readonly_op),
                1,
                1,
            ))
        }
        _ => emit_expr_default(e, env, expr),
    }
}