/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Instruction implementations.

use std::cmp::Ordering;
use std::marker;
use std::ptr;
use std::time::Instant;

use crate::coerce::coerce;
use crate::collections::symbol_map::Symbol;
use crate::collections::Hashed;
use crate::collections::SmallMap;
use crate::const_frozen_string;
use crate::environment::slots::ModuleSlotId;
use crate::eval::bc::addr::BcAddrOffset;
use crate::eval::bc::addr::BcAddrOffsetNeg;
use crate::eval::bc::addr::BcPtrAddr;
use crate::eval::bc::bytecode::Bc;
use crate::eval::bc::call::BcCallArgs;
use crate::eval::bc::call::BcCallArgsForDef;
use crate::eval::bc::call::BcCallArgsFull;
use crate::eval::bc::call::BcCallArgsPos;
use crate::eval::bc::for_loop::LoopDepth;
use crate::eval::bc::frame::BcFramePtr;
use crate::eval::bc::instr::BcInstr;
use crate::eval::bc::instr::InstrControl;
use crate::eval::bc::instr_arg::BcInstrArg;
use crate::eval::bc::native_function::BcNativeFunction;
use crate::eval::bc::slow_arg::BcInstrEndArg;
use crate::eval::bc::stack_ptr::BcSlotIn;
use crate::eval::bc::stack_ptr::BcSlotInRange;
use crate::eval::bc::stack_ptr::BcSlotInRangeFrom;
use crate::eval::bc::stack_ptr::BcSlotOut;
use crate::eval::compiler::add_span_to_expr_error;
use crate::eval::compiler::def::Def;
use crate::eval::compiler::def::FrozenDef;
use crate::eval::compiler::def::ParameterCompiled;
use crate::eval::compiler::def::ParametersCompiled;
use crate::eval::compiler::expr::get_attr_hashed_bind;
use crate::eval::compiler::expr::get_attr_hashed_raw;
use crate::eval::compiler::expr::EvalError;
use crate::eval::compiler::expr::MemberOrValue;
use crate::eval::compiler::expr_throw;
use crate::eval::compiler::stmt::add_assign;
use crate::eval::compiler::stmt::bit_or_assign;
use crate::eval::compiler::stmt::possible_gc;
use crate::eval::compiler::stmt::AssignError;
use crate::eval::compiler::EvalException;
use crate::eval::runtime::arguments::ResolvedArgName;
use crate::eval::runtime::frame_span::FrameSpan;
use crate::eval::runtime::slots::LocalCapturedSlotId;
use crate::eval::runtime::slots::LocalSlotId;
use crate::eval::Arguments;
use crate::eval::DefInfo;
use crate::eval::Evaluator;
use crate::eval::ParametersSpec;
use crate::values::dict::Dict;
use crate::values::int::PointerI32;
use crate::values::layout::value_not_special::FrozenValueNotSpecial;
use crate::values::string::dot_format::format_one;
use crate::values::string::interpolation::percent_s_one;
use crate::values::types::known_methods::KnownMethod;
use crate::values::types::list::value::ListData;
use crate::values::typing::TypeCompiled;
use crate::values::FrozenRef;
use crate::values::FrozenStringValue;
use crate::values::FrozenValue;
use crate::values::FrozenValueTyped;
use crate::values::Heap;
use crate::values::StarlarkValue;
use crate::values::StringValue;
use crate::values::StringValueLike;
use crate::values::Value;

/// Instructions which either fail or proceed to the following instruction,
/// and it returns error with span.
pub(crate) trait InstrNoFlowImpl: 'static {
    type Arg: BcInstrArg;

    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr,
        arg: &Self::Arg,
    ) -> anyhow::Result<()>;
}

pub(crate) struct InstrNoFlow<I: InstrNoFlowImpl>(marker::PhantomData<I>);

impl<I: InstrNoFlowImpl> BcInstr for InstrNoFlow<I> {
    type Arg = I::Arg;

    #[inline(always)]
    fn run<'v, 'b>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr<'b>,
        arg: &Self::Arg,
    ) -> InstrControl<'v, 'b> {
        match I::run_with_args(eval, frame, ip, arg) {
            Ok(()) => InstrControl::Next(ip.add_instr::<Self>()),
            Err(e) => InstrControl::Err(e),
        }
    }
}

pub(crate) struct InstrConstImpl;
pub(crate) type InstrConst = InstrNoFlow<InstrConstImpl>;

impl InstrNoFlowImpl for InstrConstImpl {
    type Arg = (FrozenValue, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (constant, target): &(FrozenValue, BcSlotOut),
    ) -> anyhow::Result<()> {
        frame.set_bc_slot(*target, constant.to_value());
        Ok(())
    }
}

pub(crate) struct InstrLoadLocalImpl;
pub(crate) struct InstrLoadLocalCapturedImpl;
pub(crate) struct InstrLoadModuleImpl;
pub(crate) struct InstrMovImpl;
pub(crate) struct InstrStoreLocalCapturedImpl;
pub(crate) struct InstrStoreModuleImpl;
pub(crate) struct InstrStoreModuleAndExportImpl;
pub(crate) struct InstrUnpackImpl;
pub(crate) struct InstrArrayIndexImpl;
pub(crate) struct InstrSetArrayIndexImpl;
pub(crate) struct InstrArrayIndexSetImpl;
pub(crate) struct InstrObjectFieldImpl;
pub(crate) struct InstrObjectFieldRawImpl;
pub(crate) struct InstrSetObjectFieldImpl;
pub(crate) struct InstrSliceImpl;
pub(crate) struct InstrArrayIndex2Impl;

pub(crate) type InstrLoadLocal = InstrNoFlow<InstrLoadLocalImpl>;
pub(crate) type InstrLoadLocalCaptured = InstrNoFlow<InstrLoadLocalCapturedImpl>;
pub(crate) type InstrLoadModule = InstrNoFlow<InstrLoadModuleImpl>;
pub(crate) type InstrMov = InstrNoFlow<InstrMovImpl>;
pub(crate) type InstrStoreLocalCaptured = InstrNoFlow<InstrStoreLocalCapturedImpl>;
pub(crate) type InstrStoreModule = InstrNoFlow<InstrStoreModuleImpl>;
pub(crate) type InstrStoreModuleAndExport = InstrNoFlow<InstrStoreModuleAndExportImpl>;
pub(crate) type InstrUnpack = InstrNoFlow<InstrUnpackImpl>;
pub(crate) type InstrArrayIndex = InstrNoFlow<InstrArrayIndexImpl>;
pub(crate) type InstrSetArrayIndex = InstrNoFlow<InstrSetArrayIndexImpl>;
pub(crate) type InstrArrayIndexSet = InstrNoFlow<InstrArrayIndexSetImpl>;
pub(crate) type InstrObjectField = InstrNoFlow<InstrObjectFieldImpl>;
pub(crate) type InstrObjectFieldRaw = InstrNoFlow<InstrObjectFieldRawImpl>;
pub(crate) type InstrSetObjectField = InstrNoFlow<InstrSetObjectFieldImpl>;
pub(crate) type InstrSlice = InstrNoFlow<InstrSliceImpl>;
pub(crate) type InstrArrayIndex2 = InstrNoFlow<InstrArrayIndex2Impl>;

impl InstrNoFlowImpl for InstrLoadLocalImpl {
    type Arg = (LocalSlotId, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (source, target): &(LocalSlotId, BcSlotOut),
    ) -> anyhow::Result<()> {
        let value = eval.get_slot_local(frame, *source)?;
        frame.set_bc_slot(*target, value);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrLoadLocalCapturedImpl {
    type Arg = (LocalCapturedSlotId, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (source, target): &(LocalCapturedSlotId, BcSlotOut),
    ) -> anyhow::Result<()> {
        let value = eval.get_slot_local_captured(*source)?;
        frame.set_bc_slot(*target, value);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrLoadModuleImpl {
    type Arg = (ModuleSlotId, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (source, target): &(ModuleSlotId, BcSlotOut),
    ) -> anyhow::Result<()> {
        let value = eval.get_slot_module(*source)?;
        frame.set_bc_slot(*target, value);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrMovImpl {
    type Arg = (BcSlotIn, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (source, target): &(BcSlotIn, BcSlotOut),
    ) -> anyhow::Result<()> {
        let v = frame.get_bc_slot(*source);
        frame.set_bc_slot(*target, v);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrStoreLocalCapturedImpl {
    type Arg = (BcSlotIn, LocalCapturedSlotId);

    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (source, target): &(BcSlotIn, LocalCapturedSlotId),
    ) -> anyhow::Result<()> {
        let v = frame.get_bc_slot(*source);
        eval.set_slot_local_captured(*target, v);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrStoreModuleAndExportImpl {
    type Arg = (BcSlotIn, ModuleSlotId, String);

    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (source, slot, name): &(BcSlotIn, ModuleSlotId, String),
    ) -> anyhow::Result<()> {
        let v = frame.get_bc_slot(*source);
        v.export_as(name.as_str(), eval);
        eval.set_slot_module(*slot, v);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrStoreModuleImpl {
    type Arg = (BcSlotIn, ModuleSlotId);

    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (source, target): &(BcSlotIn, ModuleSlotId),
    ) -> anyhow::Result<()> {
        let v = frame.get_bc_slot(*source);
        eval.set_slot_module(*target, v);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrUnpackImpl {
    type Arg = (BcSlotIn, FrozenRef<'static, [BcSlotOut]>);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (source, target): &(BcSlotIn, FrozenRef<'static, [BcSlotOut]>),
    ) -> anyhow::Result<()> {
        let v = frame.get_bc_slot(*source);
        let nvl = v.length()?;
        if nvl != target.len() as i32 {
            return Err(
                AssignError::IncorrectNumberOfValueToUnpack(target.len() as i32, nvl).into(),
            );
        }
        let mut i = 0;
        for item in v.iterate(eval.heap())? {
            // Use unconditional assertion here because we cannot trust
            // user defined `length` and `with_iterator` consistently.
            assert!(i < target.len());
            frame.set_bc_slot(target[i], item);
            i += 1;
        }
        assert!(i == target.len());
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrArrayIndexImpl {
    type Arg = (BcSlotIn, BcSlotIn, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (array, index, target): &(BcSlotIn, BcSlotIn, BcSlotOut),
    ) -> anyhow::Result<()> {
        let array = frame.get_bc_slot(*array);
        let index = frame.get_bc_slot(*index);
        let value = array.at(index, eval.heap())?;
        frame.set_bc_slot(*target, value);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrSetArrayIndexImpl {
    type Arg = (BcSlotIn, BcSlotIn, BcSlotIn);

    #[inline(always)]
    fn run_with_args<'v>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (source, array, index): &(BcSlotIn, BcSlotIn, BcSlotIn),
    ) -> anyhow::Result<()> {
        let value = frame.get_bc_slot(*source);
        let array = frame.get_bc_slot(*array);
        let index = frame.get_bc_slot(*index);
        array.set_at(index, value)
    }
}

impl InstrNoFlowImpl for InstrArrayIndexSetImpl {
    type Arg = (BcSlotIn, BcSlotIn, BcSlotIn);

    #[inline(always)]
    fn run_with_args<'v>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (array, index, source): &(BcSlotIn, BcSlotIn, BcSlotIn),
    ) -> anyhow::Result<()> {
        let value = frame.get_bc_slot(*source);
        let array = frame.get_bc_slot(*array);
        let index = frame.get_bc_slot(*index);
        array.set_at(index, value)
    }
}

impl InstrNoFlowImpl for InstrObjectFieldImpl {
    type Arg = (BcSlotIn, Symbol, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (object, field, target): &(BcSlotIn, Symbol, BcSlotOut),
    ) -> anyhow::Result<()> {
        let object = frame.get_bc_slot(*object);
        let value = get_attr_hashed_bind(object, field, eval.heap())?;
        frame.set_bc_slot(*target, value);
        Ok(())
    }
}

/// Get raw field.
///
/// For regular field, get the field. For methods, get the raw unbound method.
///
/// This instruction is used for call profiling, where we don't need to bind the methods.
impl InstrNoFlowImpl for InstrObjectFieldRawImpl {
    type Arg = (BcSlotIn, Symbol, BcSlotOut);

    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (object, field, target): &(BcSlotIn, Symbol, BcSlotOut),
    ) -> anyhow::Result<()> {
        let object = frame.get_bc_slot(*object);
        let value = get_attr_hashed_raw(object, field, eval.heap())?;
        frame.set_bc_slot(*target, value.to_value());
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrSetObjectFieldImpl {
    type Arg = (BcSlotIn, BcSlotIn, Symbol);

    fn run_with_args<'v>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (source, object, field): &(BcSlotIn, BcSlotIn, Symbol),
    ) -> anyhow::Result<()> {
        let v = frame.get_bc_slot(*source);
        let object = frame.get_bc_slot(*object);
        object.set_attr(field.as_str(), v)
    }
}

impl InstrNoFlowImpl for InstrSliceImpl {
    type Arg = (
        BcSlotIn,
        Option<BcSlotIn>,
        Option<BcSlotIn>,
        Option<BcSlotIn>,
        BcSlotOut,
    );

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (list, start, stop, step, target): &(
            BcSlotIn,
            Option<BcSlotIn>,
            Option<BcSlotIn>,
            Option<BcSlotIn>,
            BcSlotOut,
        ),
    ) -> anyhow::Result<()> {
        let list = frame.get_bc_slot(*list);
        let start = start.map(|s| frame.get_bc_slot(s));
        let stop = stop.map(|s| frame.get_bc_slot(s));
        let step = step.map(|s| frame.get_bc_slot(s));
        let value = list.slice(start, stop, step, eval.heap())?;
        frame.set_bc_slot(*target, value);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrArrayIndex2Impl {
    type Arg = (BcSlotIn, BcSlotIn, BcSlotIn, BcSlotOut);

    #[cold]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (array, index0, index1, target): &(BcSlotIn, BcSlotIn, BcSlotIn, BcSlotOut),
    ) -> anyhow::Result<()> {
        let array = frame.get_bc_slot(*array);
        let index0 = frame.get_bc_slot(*index0);
        let index1 = frame.get_bc_slot(*index1);
        let value = array.get_ref().at2(index0, index1, eval.heap())?;
        frame.set_bc_slot(*target, value);
        Ok(())
    }
}

pub(crate) struct InstrEqImpl;
pub(crate) struct InstrEqConstImpl;
pub(crate) struct InstrEqPtrImpl;
pub(crate) struct InstrEqStrImpl;
pub(crate) struct InstrEqIntImpl;

pub(crate) type InstrEq = InstrBinOp<InstrEqImpl>;
pub(crate) type InstrEqConst = InstrNoFlow<InstrEqConstImpl>;
pub(crate) type InstrEqPtr = InstrNoFlow<InstrEqPtrImpl>;
pub(crate) type InstrEqStr = InstrNoFlow<InstrEqStrImpl>;
pub(crate) type InstrEqInt = InstrNoFlow<InstrEqIntImpl>;

impl InstrBinOpImpl for InstrEqImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v0.equals(v1).map(Value::new_bool)
    }
}

impl InstrNoFlowImpl for InstrEqConstImpl {
    type Arg = (BcSlotIn, FrozenValueNotSpecial, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (a, b, target): &(BcSlotIn, FrozenValueNotSpecial, BcSlotOut),
    ) -> anyhow::Result<()> {
        let a = frame.get_bc_slot(*a);
        let r = b.equals(a)?;
        frame.set_bc_slot(*target, Value::new_bool(r));
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrEqPtrImpl {
    type Arg = (BcSlotIn, FrozenValue, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (a, b, target): &(BcSlotIn, FrozenValue, BcSlotOut),
    ) -> anyhow::Result<()> {
        let a = frame.get_bc_slot(*a);
        let r = a.ptr_eq(b.to_value());
        frame.set_bc_slot(*target, Value::new_bool(r));
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrEqIntImpl {
    type Arg = (BcSlotIn, FrozenValueTyped<'static, PointerI32>, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (a, b, target): &(BcSlotIn, FrozenValueTyped<'static, PointerI32>, BcSlotOut),
    ) -> anyhow::Result<()> {
        let a = frame.get_bc_slot(*a);
        let r = if let Some(a) = a.unpack_int_value() {
            a.as_ref() == b.as_ref()
        } else {
            b.equals(a)?
        };
        frame.set_bc_slot(*target, Value::new_bool(r));
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrEqStrImpl {
    type Arg = (BcSlotIn, FrozenStringValue, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (a, b, target): &(BcSlotIn, FrozenStringValue, BcSlotOut),
    ) -> anyhow::Result<()> {
        let a = frame.get_bc_slot(*a);
        let r = if let Some(a) = StringValue::new(a) {
            a == b.to_string_value()
        } else {
            false
        };
        frame.set_bc_slot(*target, Value::new_bool(r));
        Ok(())
    }
}

pub(crate) struct InstrNotImpl;
pub(crate) struct InstrMinusImpl;
pub(crate) struct InstrPlusImpl;
pub(crate) struct InstrBitNotImpl;

pub(crate) type InstrNot = InstrUnOp<InstrNotImpl>;
pub(crate) type InstrMinus = InstrUnOp<InstrMinusImpl>;
pub(crate) type InstrPlus = InstrUnOp<InstrPlusImpl>;
pub(crate) type InstrBitNot = InstrUnOp<InstrBitNotImpl>;

impl InstrUnOpImpl for InstrNotImpl {
    #[inline(always)]
    fn eval<'v>(v: Value<'v>, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(Value::new_bool(!v.to_bool()))
    }
}

impl InstrUnOpImpl for InstrPlusImpl {
    #[inline(always)]
    fn eval<'v>(v: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v.plus(heap)
    }
}

impl InstrUnOpImpl for InstrMinusImpl {
    #[inline(always)]
    fn eval<'v>(v: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v.minus(heap)
    }
}

impl InstrUnOpImpl for InstrBitNotImpl {
    #[inline(always)]
    fn eval<'v>(v: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v.bit_not(heap)
    }
}

pub(crate) trait InstrBinOpImpl: 'static {
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>>;
}

pub(crate) trait InstrUnOpImpl: 'static {
    fn eval<'v>(v: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>>;
}

pub(crate) struct InstrBinOpWrapper<I: InstrBinOpImpl>(marker::PhantomData<I>);
pub(crate) struct InstrUnOpWrapper<I: InstrUnOpImpl>(marker::PhantomData<I>);
pub(crate) type InstrBinOp<I> = InstrNoFlow<InstrBinOpWrapper<I>>;
pub(crate) type InstrUnOp<I> = InstrNoFlow<InstrUnOpWrapper<I>>;

impl<I: InstrBinOpImpl> InstrNoFlowImpl for InstrBinOpWrapper<I> {
    type Arg = (BcSlotIn, BcSlotIn, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (v0, v1, target): &(BcSlotIn, BcSlotIn, BcSlotOut),
    ) -> anyhow::Result<()> {
        let v0 = frame.get_bc_slot(*v0);
        let v1 = frame.get_bc_slot(*v1);
        let v = I::eval(v0, v1, eval.heap())?;
        frame.set_bc_slot(*target, v);
        Ok(())
    }
}

impl<I: InstrUnOpImpl> InstrNoFlowImpl for InstrUnOpWrapper<I> {
    type Arg = (BcSlotIn, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (source, target): &(BcSlotIn, BcSlotOut),
    ) -> anyhow::Result<()> {
        let source = frame.get_bc_slot(*source);
        let value = I::eval(source, eval.heap())?;
        frame.set_bc_slot(*target, value);
        Ok(())
    }
}

pub(crate) struct InstrAddImpl;
pub(crate) struct InstrAddAssignImpl;
pub(crate) struct InstrSubImpl;
pub(crate) struct InstrMultiplyImpl;
pub(crate) struct InstrPercentImpl;
pub(crate) struct InstrDivideImpl;
pub(crate) struct InstrFloorDivideImpl;
pub(crate) struct InstrBitAndImpl;
pub(crate) struct InstrBitOrImpl;
pub(crate) struct InstrBitOrAssignImpl;
pub(crate) struct InstrBitXorImpl;
pub(crate) struct InstrLeftShiftImpl;
pub(crate) struct InstrRightShiftImpl;
pub(crate) struct InstrInImpl;

pub(crate) type InstrAdd = InstrBinOp<InstrAddImpl>;
pub(crate) type InstrAddAssign = InstrBinOp<InstrAddAssignImpl>;
pub(crate) type InstrSub = InstrBinOp<InstrSubImpl>;
pub(crate) type InstrMultiply = InstrBinOp<InstrMultiplyImpl>;
pub(crate) type InstrPercent = InstrBinOp<InstrPercentImpl>;
pub(crate) type InstrDivide = InstrBinOp<InstrDivideImpl>;
pub(crate) type InstrFloorDivide = InstrBinOp<InstrFloorDivideImpl>;
pub(crate) type InstrBitAnd = InstrBinOp<InstrBitAndImpl>;
pub(crate) type InstrBitOr = InstrBinOp<InstrBitOrImpl>;
pub(crate) type InstrBitOrAssign = InstrBinOp<InstrBitOrAssignImpl>;
pub(crate) type InstrBitXor = InstrBinOp<InstrBitXorImpl>;
pub(crate) type InstrLeftShift = InstrBinOp<InstrLeftShiftImpl>;
pub(crate) type InstrRightShift = InstrBinOp<InstrRightShiftImpl>;
pub(crate) type InstrIn = InstrBinOp<InstrInImpl>;

impl InstrBinOpImpl for InstrAddImpl {
    #[inline(always)]
    fn eval<'v>(l: Value<'v>, r: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        l.add(r, heap)
    }
}

impl InstrBinOpImpl for InstrAddAssignImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        add_assign(v0, v1, heap)
    }
}

impl InstrBinOpImpl for InstrSubImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v0.sub(v1, heap)
    }
}

impl InstrBinOpImpl for InstrMultiplyImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v0.mul(v1, heap)
    }
}

impl InstrBinOpImpl for InstrPercentImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v0.percent(v1, heap)
    }
}

impl InstrBinOpImpl for InstrFloorDivideImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v0.floor_div(v1, heap)
    }
}

impl InstrBinOpImpl for InstrDivideImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v0.div(v1, heap)
    }
}

impl InstrBinOpImpl for InstrBitAndImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v0.bit_and(v1, heap)
    }
}

impl InstrBinOpImpl for InstrBitOrImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v0.bit_or(v1, heap)
    }
}

impl InstrBinOpImpl for InstrBitOrAssignImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        bit_or_assign(v0, v1, heap)
    }
}

impl InstrBinOpImpl for InstrBitXorImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v0.bit_xor(v1, heap)
    }
}

impl InstrBinOpImpl for InstrLeftShiftImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v0.left_shift(v1, heap)
    }
}

impl InstrBinOpImpl for InstrRightShiftImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        v0.right_shift(v1, heap)
    }
}

impl InstrBinOpImpl for InstrInImpl {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(Value::new_bool(v1.is_in(v0)?))
    }
}

pub(crate) struct InstrPercentSOneImpl;
pub(crate) type InstrPercentSOne = InstrNoFlow<InstrPercentSOneImpl>;
pub(crate) struct InstrFormatOneImpl;
pub(crate) type InstrFormatOne = InstrNoFlow<InstrFormatOneImpl>;

impl InstrNoFlowImpl for InstrPercentSOneImpl {
    type Arg = (FrozenStringValue, BcSlotIn, FrozenStringValue, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (before, arg, after, target): &(FrozenStringValue, BcSlotIn, FrozenStringValue, BcSlotOut),
    ) -> anyhow::Result<()> {
        let arg = frame.get_bc_slot(*arg);
        let r = percent_s_one(before.as_str(), arg, after.as_str(), eval.heap())?;
        frame.set_bc_slot(*target, r.to_value());
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrFormatOneImpl {
    type Arg = (FrozenStringValue, BcSlotIn, FrozenStringValue, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (before, arg, after, target): &(FrozenStringValue, BcSlotIn, FrozenStringValue, BcSlotOut),
    ) -> anyhow::Result<()> {
        let arg = frame.get_bc_slot(*arg);
        let r = format_one(before.as_str(), arg, after.as_str(), eval.heap());
        frame.set_bc_slot(*target, r.to_value());
        Ok(())
    }
}

pub(crate) trait InstrCompareImpl: 'static {
    fn eval_compare(ordering: Ordering) -> bool;
}

pub(crate) struct InstrCompare<I: InstrCompareImpl>(marker::PhantomData<I>);

impl<I: InstrCompareImpl> InstrBinOpImpl for InstrCompare<I> {
    #[inline(always)]
    fn eval<'v>(v0: Value<'v>, v1: Value<'v>, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(Value::new_bool(I::eval_compare(v0.compare(v1)?)))
    }
}

pub(crate) struct InstrLessImpl;
pub(crate) struct InstrGreaterImpl;
pub(crate) struct InstrLessOrEqualImpl;
pub(crate) struct InstrGreaterOrEqualImpl;

pub(crate) type InstrLess = InstrBinOp<InstrCompare<InstrLessImpl>>;
pub(crate) type InstrGreater = InstrBinOp<InstrCompare<InstrGreaterImpl>>;
pub(crate) type InstrLessOrEqual = InstrBinOp<InstrCompare<InstrLessOrEqualImpl>>;
pub(crate) type InstrGreaterOrEqual = InstrBinOp<InstrCompare<InstrGreaterOrEqualImpl>>;

impl InstrCompareImpl for InstrLessImpl {
    #[inline(always)]
    fn eval_compare(ordering: Ordering) -> bool {
        ordering == Ordering::Less
    }
}

impl InstrCompareImpl for InstrGreaterImpl {
    #[inline(always)]
    fn eval_compare(ordering: Ordering) -> bool {
        ordering == Ordering::Greater
    }
}

impl InstrCompareImpl for InstrLessOrEqualImpl {
    #[inline(always)]
    fn eval_compare(ordering: Ordering) -> bool {
        ordering != Ordering::Greater
    }
}

impl InstrCompareImpl for InstrGreaterOrEqualImpl {
    #[inline(always)]
    fn eval_compare(ordering: Ordering) -> bool {
        ordering != Ordering::Less
    }
}

pub(crate) struct InstrTypeImpl;
pub(crate) type InstrType = InstrUnOp<InstrTypeImpl>;

impl InstrUnOpImpl for InstrTypeImpl {
    #[inline(always)]
    fn eval<'v>(v: Value<'v>, _heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(v.get_type_value().to_frozen_value().to_value())
    }
}

pub(crate) struct InstrTypeIsImpl;
pub(crate) type InstrTypeIs = InstrNoFlow<InstrTypeIsImpl>;

impl InstrNoFlowImpl for InstrTypeIsImpl {
    type Arg = (BcSlotIn, FrozenStringValue, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _: BcPtrAddr,
        (arg, t, target): &(BcSlotIn, FrozenStringValue, BcSlotOut),
    ) -> anyhow::Result<()> {
        let arg = frame.get_bc_slot(*arg);
        let r = arg.get_type_value() == *t;
        frame.set_bc_slot(*target, Value::new_bool(r));
        Ok(())
    }
}

pub(crate) struct InstrLenImpl;
pub(crate) type InstrLen = InstrUnOp<InstrLenImpl>;

impl InstrUnOpImpl for InstrLenImpl {
    #[inline(always)]
    fn eval<'v>(v: Value<'v>, heap: &'v Heap) -> anyhow::Result<Value<'v>> {
        Ok(heap.alloc(v.length()?))
    }
}

pub(crate) struct InstrTupleNPopImpl;
pub(crate) struct InstrListNPopImpl;
pub(crate) struct InstrListOfConstsImpl;
pub(crate) struct InstrDictOfConstsImpl;
pub(crate) struct InstrDictConstKeysImpl;
pub(crate) struct InstrDictNPopImpl;
pub(crate) struct InstrListNewImpl;
pub(crate) struct InstrDictNewImpl;

pub(crate) type InstrTupleNPop = InstrNoFlow<InstrTupleNPopImpl>;
pub(crate) type InstrListNew = InstrNoFlow<InstrListNewImpl>;
pub(crate) type InstrListNPop = InstrNoFlow<InstrListNPopImpl>;
pub(crate) type InstrListOfConsts = InstrNoFlow<InstrListOfConstsImpl>;
pub(crate) type InstrDictNew = InstrNoFlow<InstrDictNewImpl>;
pub(crate) type InstrDictOfConsts = InstrNoFlow<InstrDictOfConstsImpl>;
pub(crate) type InstrDictConstKeys = InstrNoFlow<InstrDictConstKeysImpl>;
pub(crate) type InstrDictNPop = InstrNoFlow<InstrDictNPopImpl>;

impl InstrNoFlowImpl for InstrTupleNPopImpl {
    type Arg = (BcSlotInRange, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _: BcPtrAddr,
        (values, target): &(BcSlotInRange, BcSlotOut),
    ) -> anyhow::Result<()> {
        let items = frame.get_bc_slot_range(*values);
        let value = eval.heap().alloc_tuple(items);
        frame.set_bc_slot(*target, value);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrListNPopImpl {
    type Arg = (BcSlotInRange, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _: BcPtrAddr,
        (values, target): &(BcSlotInRange, BcSlotOut),
    ) -> anyhow::Result<()> {
        let items = frame.get_bc_slot_range(*values);
        let value = eval.heap().alloc_list(items);
        frame.set_bc_slot(*target, value);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrListOfConstsImpl {
    type Arg = (Box<[FrozenValue]>, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _: BcPtrAddr,
        (values, target): &(Box<[FrozenValue]>, BcSlotOut),
    ) -> anyhow::Result<()> {
        let list = eval.heap().alloc_list(coerce(&values));
        frame.set_bc_slot(*target, list);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrDictOfConstsImpl {
    type Arg = (SmallMap<FrozenValue, FrozenValue>, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _: BcPtrAddr,
        (values, target): &(SmallMap<FrozenValue, FrozenValue>, BcSlotOut),
    ) -> anyhow::Result<()> {
        let dict = eval.heap().alloc(Dict::new((*coerce(values)).clone()));
        frame.set_bc_slot(*target, dict);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrDictNPopImpl {
    type Arg = (BcSlotInRange, BcSlotOut);

    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr,
        (npops, target): &(BcSlotInRange, BcSlotOut),
    ) -> anyhow::Result<()> {
        let items = frame.get_bc_slot_range(*npops);
        debug_assert!(items.len() % 2 == 0);
        let mut dict = SmallMap::with_capacity(items.len() / 2);
        for i in 0..items.len() / 2 {
            let k = items[i * 2];
            let v = items[i * 2 + 1];
            let k = match k.get_hashed() {
                Ok(k) => k,
                Err(e) => {
                    let spans = &Bc::slow_arg_at_ptr(ip).spans;
                    return Err(add_span_to_expr_error(e, spans[i], eval).into_anyhow());
                }
            };
            let prev = dict.insert_hashed(k, v);
            if prev.is_some() {
                let e = EvalError::DuplicateDictionaryKey(k.key().to_string()).into();
                let spans = &Bc::slow_arg_at_ptr(ip).spans;
                return Err(add_span_to_expr_error(e, spans[i], eval).into_anyhow());
            }
        }
        let dict = eval.heap().alloc(Dict::new(dict));
        frame.set_bc_slot(*target, dict);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrDictConstKeysImpl {
    type Arg = (Box<[Hashed<FrozenValue>]>, BcSlotInRangeFrom, BcSlotOut);

    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _: BcPtrAddr,
        (keys, values, target): &(Box<[Hashed<FrozenValue>]>, BcSlotInRangeFrom, BcSlotOut),
    ) -> anyhow::Result<()> {
        let values = frame.get_bc_slot_range(values.to_range(keys.len() as u32));
        let mut dict = SmallMap::with_capacity(keys.len());
        for (k, v) in keys.iter().zip(values) {
            let prev = dict.insert_hashed(*k, *v);
            debug_assert!(prev.is_none());
        }
        let dict = eval.heap().alloc(Dict::new(coerce(dict)));
        frame.set_bc_slot(*target, dict);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrListNewImpl {
    type Arg = BcSlotOut;

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _: BcPtrAddr,
        target: &BcSlotOut,
    ) -> anyhow::Result<()> {
        let list = eval.heap().alloc_list(&[]);
        frame.set_bc_slot(*target, list);
        Ok(())
    }
}

impl InstrNoFlowImpl for InstrDictNewImpl {
    type Arg = BcSlotOut;

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _: BcPtrAddr,
        target: &BcSlotOut,
    ) -> anyhow::Result<()> {
        let dict = eval.heap().alloc(Dict::default());
        frame.set_bc_slot(*target, dict);
        Ok(())
    }
}

pub(crate) struct InstrComprListAppend;
pub(crate) struct InstrComprDictInsert;

impl BcInstr for InstrComprListAppend {
    type Arg = (BcSlotIn, BcSlotIn);

    #[inline(always)]
    fn run<'v, 'b>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr<'b>,
        (list, item): &(BcSlotIn, BcSlotIn),
    ) -> InstrControl<'v, 'b> {
        let list = frame.get_bc_slot(*list);
        let item = frame.get_bc_slot(*item);
        // SAFETY: in generated bytecode this slot can be only occupied by a mutable list.
        let list = unsafe { ListData::from_value_unchecked_mut(list) };
        list.push(item, eval.heap());
        // TODO(nga): call continue routine here.
        InstrControl::Next(ip.add_instr::<Self>())
    }
}

impl BcInstr for InstrComprDictInsert {
    type Arg = (BcSlotIn, BcSlotIn, BcSlotIn);

    #[inline(always)]
    fn run<'v, 'b>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr<'b>,
        (dict, key, value): &(BcSlotIn, BcSlotIn, BcSlotIn),
    ) -> InstrControl<'v, 'b> {
        let dict = frame.get_bc_slot(*dict);
        let key = frame.get_bc_slot(*key);
        let value = frame.get_bc_slot(*value);
        let key = match key.get_hashed() {
            Ok(key) => key,
            Err(e) => return InstrControl::Err(e),
        };
        // SAFETY: in generated bytecode this slot can be only occupied by a mutable dict.
        let mut dict = unsafe { Dict::from_value_unchecked_mut(dict) };
        dict.insert_hashed(key, value);
        // TODO(nga): call continue routine here.
        InstrControl::Next(_ip.add_instr::<Self>())
    }
}

pub(crate) struct InstrCheckTypeImpl;
pub(crate) type InstrCheckType = InstrNoFlow<InstrCheckTypeImpl>;

impl InstrNoFlowImpl for InstrCheckTypeImpl {
    type Arg = (BcSlotIn, TypeCompiled<FrozenValue>);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (expr, ty): &(BcSlotIn, TypeCompiled<FrozenValue>),
    ) -> anyhow::Result<()> {
        let expr = frame.get_bc_slot(*expr);
        let start = if eval.typecheck_profile.enabled {
            Some(Instant::now())
        } else {
            None
        };
        let res = ty.check_type(expr, None);
        if let Some(start) = start {
            let name = const_frozen_string!("assignment");
            eval.typecheck_profile.add(name, start.elapsed());
        }
        res
    }
}

pub(crate) struct InstrBr;
pub(crate) struct InstrIfBr;
pub(crate) struct InstrIfNotBr;

impl BcInstr for InstrBr {
    type Arg = BcAddrOffset;

    #[inline(always)]
    fn run<'v, 'b>(
        _eval: &mut Evaluator<'v, '_>,
        _frame: BcFramePtr<'v>,
        ip: BcPtrAddr<'b>,
        target: &BcAddrOffset,
    ) -> InstrControl<'v, 'b> {
        InstrControl::Next(ip.add_rel(*target))
    }
}

impl BcInstr for InstrIfBr {
    type Arg = (BcSlotIn, BcAddrOffset);

    #[inline(always)]
    fn run<'v, 'b>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr<'b>,
        (cond, target): &(BcSlotIn, BcAddrOffset),
    ) -> InstrControl<'v, 'b> {
        let cond = frame.get_bc_slot(*cond);
        if cond.to_bool() {
            InstrControl::Next(ip.add_rel(*target))
        } else {
            InstrControl::Next(ip.add_instr::<Self>())
        }
    }
}

impl BcInstr for InstrIfNotBr {
    type Arg = (BcSlotIn, BcAddrOffset);

    #[inline(always)]
    fn run<'v, 'b>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr<'b>,
        (cond, target): &(BcSlotIn, BcAddrOffset),
    ) -> InstrControl<'v, 'b> {
        let cond = frame.get_bc_slot(*cond);
        if !cond.to_bool() {
            InstrControl::Next(ip.add_rel(*target))
        } else {
            InstrControl::Next(ip.add_instr::<Self>())
        }
    }
}

/// Setup `for` loop.
pub(crate) struct InstrIter;
/// `continue` statement.
pub(crate) struct InstrContinue;
/// `break` statement.
pub(crate) struct InstrBreak;
/// Stop all the iterations to release mutation locks before `return`.
pub(crate) struct InstrIterStop;

impl BcInstr for InstrIter {
    type Arg = (BcSlotIn, LoopDepth, BcSlotOut, BcSlotOut, BcAddrOffset);

    #[inline(always)]
    fn run<'v, 'b>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr<'b>,
        (over, loop_depth, iter_slot, var, end): &(
            BcSlotIn,
            LoopDepth,
            BcSlotOut,
            BcSlotOut,
            BcAddrOffset,
        ),
    ) -> InstrControl<'v, 'b> {
        let over = frame.get_bc_slot(*over);
        let iter = match over.get_ref().iterate(over, eval.heap()) {
            Ok(iter) => iter,
            Err(e) => return InstrControl::Err(e),
        };
        match iter.get_ref().iter_next(0, eval.heap()) {
            Some(next) => {
                frame.set_bc_slot(*iter_slot, iter);
                frame.set_bc_slot(*var, next);
                frame.set_iter_index(*loop_depth, 1);
                InstrControl::Next(ip.add_instr::<Self>())
            }
            None => {
                iter.get_ref().iter_stop();
                InstrControl::Next(ip.add_rel(*end))
            }
        }
    }
}

impl BcInstr for InstrContinue {
    type Arg = (
        BcSlotIn,
        LoopDepth,
        BcSlotOut,
        BcAddrOffsetNeg,
        BcAddrOffset,
    );

    #[inline(always)]
    fn run<'v, 'b>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr<'b>,
        (iter, loop_depth, var, begin, end): &(
            BcSlotIn,
            LoopDepth,
            BcSlotOut,
            BcAddrOffsetNeg,
            BcAddrOffset,
        ),
    ) -> InstrControl<'v, 'b> {
        let iter = frame.get_bc_slot(*iter);
        let loop_depth = *loop_depth;
        let i = frame.get_iter_index(loop_depth);
        match iter.get_ref().iter_next(i, eval.heap()) {
            Some(next) => {
                frame.set_iter_index(loop_depth, i + 1);
                frame.set_bc_slot(*var, next);
                InstrControl::Next(ip.add_rel_neg(*begin))
            }
            None => {
                iter.get_ref().iter_stop();
                InstrControl::Next(ip.add_rel(*end))
            }
        }
    }
}

impl BcInstr for InstrBreak {
    type Arg = (BcSlotIn, BcAddrOffset);

    #[inline(always)]
    fn run<'v, 'b>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr<'b>,
        (iter, end): &(BcSlotIn, BcAddrOffset),
    ) -> InstrControl<'v, 'b> {
        let iter = frame.get_bc_slot(*iter);
        iter.get_ref().iter_stop();
        InstrControl::Next(ip.add_rel(*end))
    }
}

impl BcInstr for InstrIterStop {
    type Arg = BcSlotIn;

    #[inline(always)]
    fn run<'v, 'b>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr<'b>,
        iter: &BcSlotIn,
    ) -> InstrControl<'v, 'b> {
        let iter = frame.get_bc_slot(*iter);
        iter.get_ref().iter_stop();
        InstrControl::Next(ip.add_instr::<Self>())
    }
}

pub(crate) struct InstrReturnConst;
pub(crate) struct InstrReturn;
pub(crate) struct InstrReturnCheckType;

impl BcInstr for InstrReturnConst {
    type Arg = FrozenValue;

    fn run<'v, 'b>(
        _eval: &mut Evaluator<'v, '_>,
        _frame: BcFramePtr<'v>,
        _ip: BcPtrAddr<'b>,
        value: &FrozenValue,
    ) -> InstrControl<'v, 'b> {
        InstrControl::Return(value.to_value())
    }
}

impl BcInstr for InstrReturn {
    type Arg = BcSlotIn;

    #[inline(always)]
    fn run<'v, 'b>(
        _eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr<'b>,
        &slot: &BcSlotIn,
    ) -> InstrControl<'v, 'b> {
        let v = frame.get_bc_slot(slot);
        InstrControl::Return(v)
    }
}

impl BcInstr for InstrReturnCheckType {
    type Arg = BcSlotIn;

    #[inline(always)]
    fn run<'v, 'b>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr<'b>,
        &slot: &BcSlotIn,
    ) -> InstrControl<'v, 'b> {
        let v = frame.get_bc_slot(slot);
        if let Err(e) = eval.check_return_type(v) {
            return InstrControl::Err(e);
        }
        InstrControl::Return(v)
    }
}

pub(crate) struct InstrDefImpl;
pub(crate) type InstrDef = InstrNoFlow<InstrDefImpl>;

#[derive(Debug)]
pub(crate) struct InstrDefData {
    pub(crate) function_name: String,
    pub(crate) params: ParametersCompiled<u32>,
    pub(crate) return_type: Option<TypeCompiled<FrozenValue>>,
    pub(crate) info: FrozenRef<'static, DefInfo>,
}

impl InstrNoFlowImpl for InstrDefImpl {
    type Arg = (BcSlotInRange, InstrDefData, BcSlotOut);

    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (pops, def_data, target): &(BcSlotInRange, InstrDefData, BcSlotOut),
    ) -> anyhow::Result<()> {
        let pop = frame.get_bc_slot_range(*pops);

        let mut parameters = ParametersSpec::with_capacity(
            def_data.function_name.clone(),
            def_data.params.params.len(),
        );
        parameters.no_more_positional_only_args();
        let mut parameter_types = Vec::new();

        let mut pop_index = 0;

        for (i, x) in def_data.params.params.iter().enumerate() {
            let i = i as u32;

            if i == def_data.params.num_positional && !x.is_star_or_star_star() {
                parameters.no_more_positional_args();
            }

            if let (name, Some(t)) = x.name_ty() {
                parameter_types.push((LocalSlotId(i), name.name.clone(), t));
            }

            match &x.node {
                ParameterCompiled::Normal(n, _) => parameters.required(&n.name),
                ParameterCompiled::WithDefaultValue(n, ty, v) => {
                    assert!(*v == pop_index);
                    let value = pop[pop_index as usize];
                    pop_index += 1;

                    if ty.is_some() {
                        // Check the type of the default
                        let (_, _, ty_compiled) = parameter_types.last().unwrap();
                        expr_throw(ty_compiled.check_type(value, Some(&n.name)), x.span, eval)
                            .map_err(EvalException::into_anyhow)?;
                    }
                    parameters.defaulted(&n.name, value);
                }
                ParameterCompiled::Args(_, _) => parameters.args(),
                ParameterCompiled::KwArgs(_, _) => parameters.kwargs(),
            };
        }
        let return_type = def_data.return_type;
        assert!(pop_index as usize == pop.len());
        let def = eval.heap().alloc(Def::new(
            parameters.finish(),
            parameter_types,
            return_type,
            def_data.info,
            eval,
        ));
        frame.set_bc_slot(*target, def);
        Ok(())
    }
}

/// A frozen function argument to a call instruction.
pub(crate) trait BcFrozenCallable: BcInstrArg + Copy {
    fn bc_invoke<'v>(
        self,
        location: FrozenRef<'static, FrameSpan>,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>>;
}

impl BcFrozenCallable for FrozenValue {
    #[inline(always)]
    fn bc_invoke<'v>(
        self,
        location: FrozenRef<'static, FrameSpan>,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        self.to_value().invoke_with_loc(Some(location), args, eval)
    }
}

impl BcFrozenCallable for FrozenValueTyped<'static, FrozenDef> {
    #[inline(always)]
    fn bc_invoke<'v>(
        self,
        location: FrozenRef<'static, FrameSpan>,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        eval.with_call_stack(self.to_value(), Some(location), |eval| {
            self.as_ref().invoke(self.to_value(), args, eval)
        })
    }
}

impl BcFrozenCallable for BcNativeFunction {
    #[inline(always)]
    fn bc_invoke<'v>(
        self,
        location: FrozenRef<'static, FrameSpan>,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        eval.with_call_stack(self.to_value(), Some(location), |eval| {
            self.invoke(args, eval)
        })
    }
}

pub(crate) struct InstrCallImpl<A: BcCallArgs<Symbol>>(marker::PhantomData<fn(A)>);
pub(crate) struct InstrCallFrozenGenericImpl<F: BcFrozenCallable, A: BcCallArgs<Symbol>>(
    marker::PhantomData<(F, A)>,
);
pub(crate) struct InstrCallFrozenDefImpl<A: BcCallArgsForDef>(marker::PhantomData<A>);
pub(crate) struct InstrCallMethodImpl<A: BcCallArgs<Symbol>>(marker::PhantomData<A>);
pub(crate) struct InstrCallMaybeKnownMethodImpl<A: BcCallArgs<Symbol>>(marker::PhantomData<A>);

pub(crate) type InstrCall = InstrNoFlow<InstrCallImpl<BcCallArgsFull<Symbol>>>;
pub(crate) type InstrCallPos = InstrNoFlow<InstrCallImpl<BcCallArgsPos>>;
pub(crate) type InstrCallFrozenDef =
    InstrNoFlow<InstrCallFrozenDefImpl<BcCallArgsFull<ResolvedArgName>>>;
pub(crate) type InstrCallFrozenDefPos = InstrNoFlow<InstrCallFrozenDefImpl<BcCallArgsPos>>;
pub(crate) type InstrCallFrozenNative =
    InstrNoFlow<InstrCallFrozenGenericImpl<BcNativeFunction, BcCallArgsFull<Symbol>>>;
pub(crate) type InstrCallFrozenNativePos =
    InstrNoFlow<InstrCallFrozenGenericImpl<BcNativeFunction, BcCallArgsPos>>;
pub(crate) type InstrCallFrozen =
    InstrNoFlow<InstrCallFrozenGenericImpl<FrozenValue, BcCallArgsFull<Symbol>>>;
pub(crate) type InstrCallFrozenPos =
    InstrNoFlow<InstrCallFrozenGenericImpl<FrozenValue, BcCallArgsPos>>;
pub(crate) type InstrCallMethod = InstrNoFlow<InstrCallMethodImpl<BcCallArgsFull<Symbol>>>;
pub(crate) type InstrCallMethodPos = InstrNoFlow<InstrCallMethodImpl<BcCallArgsPos>>;
pub(crate) type InstrCallMaybeKnownMethod =
    InstrNoFlow<InstrCallMaybeKnownMethodImpl<BcCallArgsFull<Symbol>>>;
pub(crate) type InstrCallMaybeKnownMethodPos =
    InstrNoFlow<InstrCallMaybeKnownMethodImpl<BcCallArgsPos>>;

impl<A: BcCallArgs<Symbol>> InstrNoFlowImpl for InstrCallImpl<A> {
    type Arg = (BcSlotIn, A, FrozenRef<'static, FrameSpan>, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (this, args, span, target): &(BcSlotIn, A, FrozenRef<'static, FrameSpan>, BcSlotOut),
    ) -> anyhow::Result<()> {
        let f = frame.get_bc_slot(*this);
        let arguments = Arguments(args.pop_from_stack(frame));
        let r = f.invoke_with_loc(Some(*span), &arguments, eval)?;
        frame.set_bc_slot(*target, r);
        Ok(())
    }
}

impl<F: BcFrozenCallable, A: BcCallArgs<Symbol>> InstrNoFlowImpl
    for InstrCallFrozenGenericImpl<F, A>
{
    type Arg = (F, A, FrozenRef<'static, FrameSpan>, BcSlotOut);

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (fun, args, span, target): &(F, A, FrozenRef<'static, FrameSpan>, BcSlotOut),
    ) -> anyhow::Result<()> {
        let arguments = Arguments(args.pop_from_stack(frame));
        let r = fun.bc_invoke(*span, &arguments, eval)?;
        frame.set_bc_slot(*target, r);
        Ok(())
    }
}

impl<A: BcCallArgsForDef> InstrNoFlowImpl for InstrCallFrozenDefImpl<A> {
    type Arg = (
        FrozenValueTyped<'static, FrozenDef>,
        A,
        FrozenRef<'static, FrameSpan>,
        BcSlotOut,
    );

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (fun, args, span, target): &(
            FrozenValueTyped<'static, FrozenDef>,
            A,
            FrozenRef<'static, FrameSpan>,
            BcSlotOut,
        ),
    ) -> anyhow::Result<()> {
        let arguments = args.pop_from_stack(frame);
        let r = eval.with_call_stack(fun.to_value(), Some(*span), |eval| {
            fun.as_ref()
                .invoke_with_args(fun.to_value(), &arguments, eval)
        })?;
        frame.set_bc_slot(*target, r);
        Ok(())
    }
}

/// Common of method invocation instructions.
#[inline(always)]
fn call_method_common<'v>(
    eval: &mut Evaluator<'v, '_>,
    frame: BcFramePtr<'v>,
    this: Value<'v>,
    symbol: &Symbol,
    arguments: &Arguments<'v, '_>,
    span: FrozenRef<'static, FrameSpan>,
    target: BcSlotOut,
) -> anyhow::Result<()> {
    // TODO: wrong span: should be span of `object.method`, not of the whole expression
    let method = get_attr_hashed_raw(this, symbol, eval.heap())?;
    let r = match method {
        MemberOrValue::Member(member) => member.invoke_method(this, span, arguments, eval)?,
        MemberOrValue::Value(value) => value.invoke_with_loc(Some(span), arguments, eval)?,
    };
    frame.set_bc_slot(target, r);
    Ok(())
}

/// Common of method invocation instructions where a method is likely stdlib method.
#[inline(always)]
fn call_maybe_known_method_common<'v>(
    eval: &mut Evaluator<'v, '_>,
    frame: BcFramePtr<'v>,
    this: Value<'v>,
    symbol: &Symbol,
    known_method: &KnownMethod,
    arguments: &Arguments<'v, '_>,
    span: FrozenRef<'static, FrameSpan>,
    target: BcSlotOut,
) -> anyhow::Result<()> {
    if let Some(methods) = this.get_ref().get_methods() {
        // Instead of method lookup by name, we compare `Methods` pointers.
        // If pointers are equal, getattr would return the same method
        // we already have.
        if ptr::eq(methods, known_method.type_methods) {
            let r = eval.with_call_stack(known_method.to_value(), Some(span), |eval| {
                known_method.invoke_method(this, arguments, eval)
            })?;
            frame.set_bc_slot(target, r);
            return Ok(());
        }
    }

    call_method_common(eval, frame, this, symbol, arguments, span, target)
}

impl<A: BcCallArgs<Symbol>> InstrNoFlowImpl for InstrCallMethodImpl<A> {
    type Arg = (
        BcSlotIn,
        Symbol,
        A,
        FrozenRef<'static, FrameSpan>,
        BcSlotOut,
    );

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (this, symbol, args, span, target): &(
            BcSlotIn,
            Symbol,
            A,
            FrozenRef<'static, FrameSpan>,
            BcSlotOut,
        ),
    ) -> anyhow::Result<()> {
        let this = frame.get_bc_slot(*this);
        let arguments = Arguments(args.pop_from_stack(frame));
        call_method_common(eval, frame, this, symbol, &arguments, *span, *target)
    }
}

impl<A: BcCallArgs<Symbol>> InstrNoFlowImpl for InstrCallMaybeKnownMethodImpl<A> {
    type Arg = (
        BcSlotIn,
        Symbol,
        KnownMethod,
        A,
        FrozenRef<'static, FrameSpan>,
        BcSlotOut,
    );

    #[inline(always)]
    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (this, symbol, known_method, args, span, target): &(
            BcSlotIn,
            Symbol,
            KnownMethod,
            A,
            FrozenRef<'static, FrameSpan>,
            BcSlotOut,
        ),
    ) -> anyhow::Result<()> {
        let this = frame.get_bc_slot(*this);
        let arguments = Arguments(args.pop_from_stack(frame));
        call_maybe_known_method_common(
            eval,
            frame,
            this,
            symbol,
            known_method,
            &arguments,
            *span,
            *target,
        )
    }
}

pub(crate) struct InstrPossibleGcImpl;

pub(crate) type InstrPossibleGc = InstrNoFlow<InstrPossibleGcImpl>;

impl InstrNoFlowImpl for InstrPossibleGcImpl {
    type Arg = ();

    fn run_with_args<'v>(
        eval: &mut Evaluator<'v, '_>,
        _frame: BcFramePtr<'v>,
        _ip: BcPtrAddr,
        (): &(),
    ) -> anyhow::Result<()> {
        possible_gc(eval);
        Ok(())
    }
}

/// Pseudo-instruction:
/// * to store bytecode metadata (i.e. spans): when bytecode is evaluated, we only have IP,
///   we don't have a pointer to bytecode object. To obtain spans by IP, we scroll
///   through the instruction until we encounter this pseudo-instruction.
/// * as a safety against memory overruns. Function block must terminate with return instruction,
///  but if return was missed, this instruction is executed and it panics.
pub(crate) struct InstrEnd;

impl BcInstr for InstrEnd {
    type Arg = BcInstrEndArg;

    fn run<'v, 'b>(
        _eval: &mut Evaluator<'v, '_>,
        _frame: BcFramePtr<'v>,
        _ip: BcPtrAddr<'b>,
        _: &Self::Arg,
    ) -> InstrControl<'v, 'b> {
        unreachable!("this instruction is not meant to be executed");
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Bytecode interpreter.

pub(crate) mod addr;
pub(crate) mod bytecode;
pub(crate) mod call;
pub(crate) mod compiler;
pub(crate) mod definitely_assigned;
pub(crate) mod for_loop;
pub(crate) mod frame;
pub(crate) mod if_debug;
pub(crate) mod instr;
pub(crate) mod instr_arg;
pub(crate) mod instr_impl;
pub(crate) mod instrs;
pub(crate) mod native_function;
pub(crate) mod opcode;
pub(crate) mod repr;
pub(crate) mod slow_arg;
pub(crate) mod stack_ptr;
pub(crate) mod stack_values;
pub(crate) mod writer;


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use allocative::Allocative;
use dupe::Dupe;

use crate::eval::Arguments;
use crate::eval::Evaluator;
use crate::values::function::NativeFunc;
use crate::values::function::NativeFunction;
use crate::values::FrozenRef;
use crate::values::FrozenValueTyped;
use crate::values::Value;

/// Pointer to a native function optimized for bytecode execution.
#[derive(Copy, Clone, Dupe, Allocative)]
pub(crate) struct BcNativeFunction {
    fun: FrozenValueTyped<'static, NativeFunction>,
    /// Copy function here from `fun` to avoid extra dereference when calling.
    imp: FrozenRef<'static, dyn NativeFunc>,
}

impl BcNativeFunction {
    pub(crate) fn new(fun: FrozenValueTyped<'static, NativeFunction>) -> BcNativeFunction {
        let imp = fun.as_frozen_ref().map(|f| &*f.function);
        BcNativeFunction { fun, imp }
    }

    #[inline]
    pub(crate) fn fun(&self) -> FrozenValueTyped<'static, NativeFunction> {
        self.fun
    }

    #[inline]
    pub(crate) fn to_value<'v>(&self) -> Value<'v> {
        self.fun.to_value()
    }

    #[inline]
    pub(crate) fn invoke<'v>(
        &self,
        args: &Arguments<'v, '_>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        self.imp.invoke(eval, args)
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Instruction opcode.

use std::any;
use std::any::TypeId;
use std::marker;

use dupe::Dupe;
use starlark_derive::starlark_internal_bc;

use crate::eval::bc::instr::BcInstr;
use crate::eval::bc::instr_impl::*;

/// Bytecode instruction opcode.
#[starlark_internal_bc]
#[derive(Debug, Copy, Clone, Dupe, Eq, PartialEq, PartialOrd, Ord, Hash)]
#[repr(u32)]
pub(crate) enum BcOpcode {
    Const,
    LoadLocal,
    LoadLocalCaptured,
    LoadModule,
    Mov,
    StoreLocalCaptured,
    StoreModule,
    StoreModuleAndExport,
    Unpack,
    ArrayIndex,
    SetArrayIndex,
    ArrayIndexSet,
    Slice,
    ObjectField,
    ObjectFieldRaw,
    SetObjectField,
    Eq,
    EqConst,
    EqPtr,
    EqStr,
    EqInt,
    Not,
    Minus,
    Plus,
    BitNot,
    Less,
    Greater,
    LessOrEqual,
    GreaterOrEqual,
    In,
    Add,
    AddAssign,
    Sub,
    Multiply,
    Percent,
    PercentSOne,
    FormatOne,
    Divide,
    FloorDivide,
    BitAnd,
    BitOr,
    BitOrAssign,
    BitXor,
    LeftShift,
    RightShift,
    Len,
    Type,
    TypeIs,
    TupleNPop,
    ListNew,
    ListNPop,
    ListOfConsts,
    DictNew,
    DictNPop,
    DictOfConsts,
    DictConstKeys,
    ComprListAppend,
    ComprDictInsert,
    CheckType,
    Br,
    IfBr,
    IfNotBr,
    Iter,
    Continue,
    Break,
    IterStop,
    Return,
    ReturnConst,
    ReturnCheckType,
    Call,
    CallPos,
    CallFrozenDef,
    CallFrozenDefPos,
    CallFrozenNative,
    CallFrozenNativePos,
    CallFrozen,
    CallFrozenPos,
    CallMethod,
    CallMethodPos,
    CallMaybeKnownMethod,
    CallMaybeKnownMethodPos,
    Def,
    ArrayIndex2,
    PossibleGc,
    End,
}

/// Callback for the `dispatch` function.
pub(crate) trait BcOpcodeHandler<R> {
    fn handle<I: BcInstr>(self) -> R;
}

/// Callback for the `dispatch_all` function.
pub(crate) trait BcOpcodeAllHandler {
    fn handle<I: BcInstr>(&mut self, opcode: BcOpcode);
}

impl BcOpcode {
    /// Opcode count.
    pub(crate) const COUNT: usize = (BcOpcode::End as usize) + 1;

    /// Invoke a callback parameterized by instruction type depending on
    /// this opcode.
    #[cfg_attr(not(debug_assertions), inline(always))]
    pub(crate) fn dispatch<R>(self, handler: impl BcOpcodeHandler<R>) -> R {
        // Call a function generated by proc macro.
        self.do_dispatch(handler)
    }

    /// Invoke a callback parameterized by instruction type for each opcode.
    #[inline(always)]
    pub(crate) fn dispatch_all(handler: &mut impl BcOpcodeAllHandler) {
        // Call a function generated by proc macro.
        BcOpcode::do_dispatch_all(handler)
    }

    /// Get opcode by opcode number.
    pub(crate) fn by_number(n: u32) -> Option<BcOpcode> {
        struct ByNumber {
            i: u32,
            n: u32,
            opcode: Option<BcOpcode>,
        }
        impl BcOpcodeAllHandler for ByNumber {
            fn handle<I: BcInstr>(&mut self, opcode: BcOpcode) {
                if self.i == self.n {
                    self.opcode = Some(opcode);
                }
                self.i += 1;
            }
        }
        let mut by_number = ByNumber {
            i: 0,
            n,
            opcode: None,
        };
        BcOpcode::do_dispatch_all(&mut by_number);
        by_number.opcode
    }

    /// Get bytecode opcode for the instruction.
    pub(crate) fn for_instr<I: BcInstr>() -> BcOpcode {
        struct FindOpcode<I: BcInstr> {
            opcode: Option<BcOpcode>,
            _marker: marker::PhantomData<I>,
        }

        impl<I: BcInstr> BcOpcodeAllHandler for FindOpcode<I> {
            fn handle<J: BcInstr>(&mut self, opcode: BcOpcode) {
                if TypeId::of::<I>() == TypeId::of::<J>() {
                    assert!(self.opcode.is_none());
                    self.opcode = Some(opcode);
                }
            }
        }

        let mut find_opcode = FindOpcode::<I> {
            opcode: None,
            _marker: marker::PhantomData,
        };
        Self::dispatch_all(&mut find_opcode);
        find_opcode
            .opcode
            .unwrap_or_else(|| panic!("No opcode for instruction {:?}", any::type_name::<I>()))
    }
}

#[cfg(test)]
mod tests {
    use crate::eval::bc::opcode::BcOpcode;

    #[test]
    fn opcode_count() {
        for i in 0..10000 {
            if i < (BcOpcode::COUNT as u32) {
                assert!(BcOpcode::by_number(i).is_some());
            } else {
                assert!(BcOpcode::by_number(i).is_none());
            }
        }
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Instruction representation in memory.

use std::any;
use std::mem;

use crate::eval::bc::instr::BcInstr;
use crate::eval::bc::opcode::BcOpcode;
use crate::eval::bc::opcode::BcOpcodeHandler;

/// Instructions are be aligned to store `u64` even on 32-bit machines.
pub(crate) const BC_INSTR_ALIGN: usize = 8;

/// Instruction header.
#[derive(Clone, Copy)]
#[repr(C)]
pub(crate) struct BcInstrHeader {
    pub(crate) opcode: BcOpcode,
}
impl BcInstrHeader {
    fn for_instr<I: BcInstr>() -> BcInstrHeader {
        Self {
            opcode: BcOpcode::for_instr::<I>(),
        }
    }

    pub(crate) const fn for_opcode(opcode: BcOpcode) -> Self {
        Self { opcode }
    }
}

/// How instructions are stored in memory.
#[repr(C, align(8))]
pub(crate) struct BcInstrRepr<I: BcInstr> {
    pub(crate) header: BcInstrHeader,
    pub(crate) arg: I::Arg,
    // Align all instructions to make IP increment simple.
    pub(crate) _align: [u64; 0],
}

impl<I: BcInstr> BcInstrRepr<I> {
    pub(crate) fn new(arg: I::Arg) -> BcInstrRepr<I> {
        BcInstrRepr::<I>::assert_align();
        BcInstrRepr {
            header: BcInstrHeader::for_instr::<I>(),
            arg,
            _align: [],
        }
    }

    pub(crate) fn assert_align() {
        // If alignment is different, we do not compute addresses properly.
        // Practically everything has `usize` alignment.
        // This would break if we had some types like `__m128` in instruction arguments,
        // but we don't.
        assert!(
            mem::align_of::<BcInstrRepr<I>>() == BC_INSTR_ALIGN,
            "align: {}, required align: {}, type: {}",
            mem::align_of::<BcInstrRepr<I>>(),
            BC_INSTR_ALIGN,
            any::type_name::<BcInstrRepr<I>>()
        );
        assert!(
            mem::size_of::<BcInstrRepr<I>>() % BC_INSTR_ALIGN == 0,
            "{}",
            any::type_name::<BcInstrRepr<I>>()
        );
    }
}

impl BcOpcode {
    /// Size of instruction.
    pub(crate) fn size_of_repr(self) -> usize {
        struct HandlerImpl;

        impl BcOpcodeHandler<usize> for HandlerImpl {
            fn handle<I: BcInstr>(self) -> usize {
                <BcInstrRepr<I>>::assert_align();

                mem::size_of::<BcInstrRepr<I>>()
            }
        }

        self.dispatch(HandlerImpl)
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use crate::eval::bc::addr::BcAddr;
use crate::eval::runtime::frame_span::FrameSpan;
use crate::values::FrozenRef;
use crate::values::FrozenStringValue;

/// Slow instruction arg: stored in the end of bytecode,
/// expensive to access. Used to implement errors.
#[derive(Default, Debug)]
pub(crate) struct BcInstrSlowArg {
    /// Instruction code span.
    pub(crate) span: FrameSpan,
    /// Spans when an instruction needs multiple spans.
    pub(crate) spans: Vec<FrameSpan>,
}

#[derive(Debug, Default)]
pub(crate) struct BcInstrEndArg {
    /// Offset of end instruction.
    pub(crate) end_addr: BcAddr,
    /// Spans of all instructions.
    pub(crate) slow_args: Vec<(BcAddr, BcInstrSlowArg)>,
    /// Frame local names.
    pub(crate) local_names: FrozenRef<'static, [FrozenStringValue]>,
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Stack pointer.

use std::ops::Add;

use dupe::Dupe;

/// Index of the slot in the function frame.
/// This can be both a local variable or a temporary.
/// When reading local variable, it must be definitely initialized (e.g. function parameter).
#[derive(
    Copy,
    Clone,
    Dupe,
    Debug,
    PartialOrd,
    Ord,
    PartialEq,
    Eq,
    Hash,
    derive_more::Display
)]
#[display(fmt = "&{}", _0)]
pub(crate) struct BcSlot(pub(crate) u32);

impl BcSlot {
    pub(crate) fn to_in(self) -> BcSlotIn {
        BcSlotIn(self)
    }

    pub(crate) fn to_out(self) -> BcSlotOut {
        BcSlotOut(self)
    }
}

impl Add<u32> for BcSlot {
    type Output = BcSlot;

    #[inline]
    fn add(self, rhs: u32) -> BcSlot {
        BcSlot(self.0 + rhs)
    }
}

/// `N` slots starting with given number.
#[derive(Copy, Clone, Dupe, Debug)]
pub(crate) struct BcSlotsN<const N: usize> {
    /// `N` slots starting with given slot.
    pub(crate) start: BcSlot,
}

impl<const N: usize> BcSlotsN<N> {
    #[inline]
    pub(crate) fn get<const I: u32>(self) -> BcSlot {
        assert!((I as usize) < N);
        self.start + I
    }

    pub(crate) fn from_range(range: BcSlotRange) -> BcSlotsN<N> {
        assert_eq!(N, range.len() as usize);
        BcSlotsN { start: range.start }
    }
}

#[derive(Copy, Clone, Dupe, Debug, derive_more::Display)]
#[display(fmt = "{}..{}", start, end)]
pub(crate) struct BcSlotRange {
    pub(crate) start: BcSlot,
    pub(crate) end: BcSlot,
}

impl BcSlotRange {
    #[inline]
    pub(crate) fn len(self) -> u32 {
        self.end.0 - self.start.0
    }

    pub(crate) fn iter(self) -> impl Iterator<Item = BcSlot> {
        (self.start.0..self.end.0).map(BcSlot)
    }

    pub(crate) fn to_in(self) -> BcSlotInRange {
        BcSlotInRange {
            start: self.start.to_in(),
            end: self.end.to_in(),
        }
    }
}

#[derive(Copy, Clone, Dupe, Debug)]
pub(crate) struct BcSlotRangeFrom(pub(crate) BcSlot);

/// Slot containing a value.
///
/// The slot may be a local variable, so this slot cannot be used to store a temporary value.
#[derive(Debug, Copy, Clone, Dupe, derive_more::Display, PartialEq, Eq)]
pub(crate) struct BcSlotIn(BcSlot);

impl Add<u32> for BcSlotIn {
    type Output = BcSlotIn;

    #[inline]
    fn add(self, rhs: u32) -> BcSlotIn {
        BcSlotIn(self.0 + rhs)
    }
}

impl BcSlotIn {
    /// Take the slot.
    ///
    /// This operation need to be used carefully: this slot cannot be used for writing.
    #[inline]
    pub(crate) fn get(self) -> BcSlot {
        self.0
    }
}

#[derive(Copy, Clone, Dupe, Debug, derive_more::Display)]
#[display(fmt = "{}..{}", start, end)]
pub(crate) struct BcSlotInRange {
    pub(crate) start: BcSlotIn,
    pub(crate) end: BcSlotIn,
}

impl Default for BcSlotInRange {
    fn default() -> Self {
        BcSlotInRange {
            start: BcSlotIn(BcSlot(0)),
            end: BcSlotIn(BcSlot(0)),
        }
    }
}

impl BcSlotInRange {
    #[inline]
    pub(crate) fn len(self) -> u32 {
        self.end.0.0 - self.start.0.0
    }

    pub(crate) fn to_range_from(self) -> BcSlotInRangeFrom {
        BcSlotInRangeFrom(self.start)
    }

    pub(crate) fn iter(self) -> impl Iterator<Item = BcSlotIn> {
        (self.start.0.0..self.end.0.0).map(|s| BcSlotIn(BcSlot(s)))
    }

    /// Add an element to the slot range if possible.
    pub(crate) fn try_push(&mut self, slot: BcSlotIn) -> bool {
        if self.len() == 0 {
            *self = BcSlotInRange {
                start: slot,
                end: slot + 1,
            };
            true
        } else if self.end == slot {
            self.end = slot + 1;
            true
        } else {
            false
        }
    }
}

#[derive(Copy, Clone, Dupe, Debug)]
pub(crate) struct BcSlotInRangeFrom(pub(crate) BcSlotIn);

impl BcSlotInRangeFrom {
    #[inline]
    pub(crate) fn to_range(self, len: u32) -> BcSlotInRange {
        BcSlotInRange {
            start: self.0,
            end: self.0 + len,
        }
    }
}

/// Slot where the value should be stored.
///
/// The slot may be a local variable, so this slot cannot be used to store a temporary value.
#[derive(Debug, Copy, Clone, Dupe, derive_more::Display)]
pub(crate) struct BcSlotOut(BcSlot);

impl BcSlotOut {
    #[inline]
    pub(crate) fn get(self) -> BcSlot {
        self.0
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Instruction arguments for working with stack.

use crate::values::Value;

/// Instruction parameter for values popped from stack or pushed to stack.
pub(crate) trait BcStackValues<'v>: Copy {
    /// How many values.
    const N: u32;
}

impl<'v> BcStackValues<'v> for () {
    const N: u32 = 0;
}

impl<'v> BcStackValues<'v> for Value<'v> {
    const N: u32 = 1;
}

impl<'v, const N: usize> BcStackValues<'v> for [Value<'v>; N] {
    const N: u32 = N as u32;
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Bytecode writer.

use std::cmp;

use crate::cast::transmute;
use crate::eval::bc::addr::BcAddr;
use crate::eval::bc::addr::BcAddrOffset;
use crate::eval::bc::bytecode::Bc;
use crate::eval::bc::definitely_assigned::BcDefinitelyAssigned;
use crate::eval::bc::for_loop::LoopDepth;
use crate::eval::bc::instr::BcInstr;
use crate::eval::bc::instr_impl::InstrBr;
use crate::eval::bc::instr_impl::InstrBreak;
use crate::eval::bc::instr_impl::InstrConst;
use crate::eval::bc::instr_impl::InstrContinue;
use crate::eval::bc::instr_impl::InstrIfBr;
use crate::eval::bc::instr_impl::InstrIfNotBr;
use crate::eval::bc::instr_impl::InstrIter;
use crate::eval::bc::instr_impl::InstrIterStop;
use crate::eval::bc::instr_impl::InstrLoadLocal;
use crate::eval::bc::instr_impl::InstrLoadLocalCaptured;
use crate::eval::bc::instr_impl::InstrMov;
use crate::eval::bc::instr_impl::InstrStoreLocalCaptured;
use crate::eval::bc::instrs::BcInstrsWriter;
use crate::eval::bc::instrs::PatchAddr;
use crate::eval::bc::repr::BC_INSTR_ALIGN;
use crate::eval::bc::slow_arg::BcInstrSlowArg;
use crate::eval::bc::stack_ptr::BcSlot;
use crate::eval::bc::stack_ptr::BcSlotIn;
use crate::eval::bc::stack_ptr::BcSlotInRange;
use crate::eval::bc::stack_ptr::BcSlotOut;
use crate::eval::bc::stack_ptr::BcSlotRange;
use crate::eval::bc::stack_ptr::BcSlotsN;
use crate::eval::compiler::expr::MaybeNot;
use crate::eval::runtime::frame_span::FrameSpan;
use crate::eval::runtime::slots::LocalCapturedSlotId;
use crate::eval::runtime::slots::LocalSlotId;
use crate::values::FrozenHeap;
use crate::values::FrozenRef;
use crate::values::FrozenStringValue;
use crate::values::FrozenValue;

#[derive(Debug)]
pub(crate) struct BcStmtLoc {
    pub(crate) span: FrameSpan,
}

/// This records the locations of the first instruction for each starlark statement. It's effectively
/// Map<BcAddr, BcStmtLoc>. This is very performance sensitive (when profiling/debugging are enabled we
/// do a lookup for every instruction) and so it's implemented as a vec of statements and then a vec of
/// statement indexes for each possible BcAddr in a bytecode Bc.
pub(crate) struct BcStatementLocations {
    pub(crate) locs: Vec<BcStmtLoc>,
    /// Map bytecode offset to index in `locs`.
    pub(crate) stmts: Vec<u32>,
}

impl BcStatementLocations {
    pub(crate) fn new() -> Self {
        Self {
            locs: Vec::new(),
            stmts: Vec::new(),
        }
    }

    fn idx_for(addr: BcAddr) -> usize {
        let addr = addr.0 as usize;
        debug_assert!(addr % BC_INSTR_ALIGN == 0);
        addr / BC_INSTR_ALIGN
    }

    fn push(&mut self, addr: BcAddr, span: BcStmtLoc) {
        let idx = Self::idx_for(addr);
        let stmt_idx = self.locs.len().try_into().unwrap();
        self.locs.push(span);
        while self.stmts.len() <= idx {
            self.stmts.push(u32::MAX);
        }
        // we could use .push() to get this in place, but doing by index just makes it clearer that we're doing it correctly.
        self.stmts[idx] = stmt_idx;
    }

    pub(crate) fn stmt_at(&self, offset: BcAddr) -> Option<&BcStmtLoc> {
        match self.stmts.get(Self::idx_for(offset)) {
            None | Some(&u32::MAX) => None,
            Some(v) => Some(&self.locs[*v as usize]),
        }
    }
}

/// For loop during bytecode write.
struct BcWriterForLoop {
    /// Iterator variable.
    iter: BcSlotIn,
    /// Variable to store the next value in.
    var: BcSlotOut,
    /// Address of the first instruction in the loop body.
    inner_addr: BcAddr,
    /// Addresses to patch with the address of the instruction after the loop.
    end_addrs_to_patch: Vec<PatchAddr>,
}

/// Write bytecode here.
pub(crate) struct BcWriter<'f> {
    /// Serialized instructions.
    instrs: BcInstrsWriter,
    /// Instruction spans, used for errors.
    slow_args: Vec<(BcAddr, BcInstrSlowArg)>,
    /// For each statement, will store the span and the BcAddr for the first instruction.
    stmt_locs: BcStatementLocations,
    /// Current stack size.
    stack_size: u32,
    /// Local slot count.
    local_names: FrozenRef<'f, [FrozenStringValue]>,
    /// Local variables which are known to be definitely assigned at current program point.
    definitely_assigned: BcDefinitelyAssigned,
    /// Max observed stack size.
    max_stack_size: u32,
    /// Current loop depth.
    for_loops: Vec<BcWriterForLoop>,
    /// Max observed loop depth.
    max_loop_depth: LoopDepth,

    /// Allocate various objects here.
    pub(crate) heap: &'f FrozenHeap,
}

impl<'f> BcWriter<'f> {
    /// Empty.
    pub(crate) fn new(
        local_names: FrozenRef<'f, [FrozenStringValue]>,
        param_count: u32,
        heap: &'f FrozenHeap,
    ) -> BcWriter<'f> {
        assert!(param_count as usize <= local_names.len());
        let mut definitely_assigned =
            BcDefinitelyAssigned::new(local_names.len().try_into().unwrap());
        for i in 0..param_count {
            definitely_assigned.mark_definitely_assigned(LocalSlotId(i));
        }
        BcWriter {
            instrs: BcInstrsWriter::new(),
            slow_args: Vec::new(),
            stmt_locs: BcStatementLocations::new(),
            stack_size: 0,
            local_names,
            definitely_assigned,
            max_stack_size: 0,
            heap,
            for_loops: Vec::new(),
            max_loop_depth: LoopDepth(0),
        }
    }

    /// Finish writing the bytecode.
    #[allow(let_underscore_drop)]
    pub(crate) fn finish(self) -> Bc {
        let BcWriter {
            instrs,
            slow_args: spans,
            stmt_locs,
            stack_size,
            local_names,
            definitely_assigned,
            max_stack_size,
            heap,
            for_loops,
            max_loop_depth,
        } = self;
        let _ = heap;
        let _ = definitely_assigned;
        assert_eq!(stack_size, 0);
        assert!(for_loops.is_empty());
        // Drop lifetime.
        let local_names = unsafe {
            transmute!(
                FrozenRef<[FrozenStringValue]>,
                FrozenRef<[FrozenStringValue]>,
                local_names
            )
        };
        Bc {
            instrs: instrs.finish(spans, stmt_locs, local_names),
            local_count: local_names.len().try_into().unwrap(),
            max_stack_size,
            max_loop_depth,
        }
    }

    fn local_count(&self) -> u32 {
        self.local_names.len().try_into().unwrap()
    }

    /// Current offset.
    fn ip(&self) -> BcAddr {
        self.instrs.ip()
    }

    /// Version of instruction write with explicit slow arg arg.
    fn do_write_generic_explicit<I: BcInstr>(
        &mut self,
        slow_arg: BcInstrSlowArg,
        arg: I::Arg,
    ) -> (BcAddr, *const I::Arg) {
        self.slow_args.push((self.ip(), slow_arg));
        self.instrs.write::<I>(arg)
    }

    pub(crate) fn mark_before_stmt(&mut self, span: FrameSpan) {
        self.stmt_locs.push(self.ip(), BcStmtLoc { span })
    }

    /// Write an instruction, return address and argument.
    fn write_instr_ret_arg_explicit<I: BcInstr>(
        &mut self,
        slow_arg: BcInstrSlowArg,
        arg: I::Arg,
    ) -> (BcAddr, *const I::Arg) {
        self.do_write_generic_explicit::<I>(slow_arg, arg)
    }

    fn write_instr_ret_arg<I: BcInstr>(
        &mut self,
        span: FrameSpan,
        arg: I::Arg,
    ) -> (BcAddr, *const I::Arg) {
        self.write_instr_ret_arg_explicit::<I>(
            BcInstrSlowArg {
                span,
                ..Default::default()
            },
            arg,
        )
    }

    pub(crate) fn write_instr_explicit<I: BcInstr>(
        &mut self,
        slow_arg: BcInstrSlowArg,
        arg: I::Arg,
    ) {
        self.write_instr_ret_arg_explicit::<I>(slow_arg, arg);
    }

    /// Write an instruction.
    pub(crate) fn write_instr<I: BcInstr>(&mut self, span: FrameSpan, arg: I::Arg) {
        self.write_instr_explicit::<I>(
            BcInstrSlowArg {
                span,
                ..Default::default()
            },
            arg,
        );
    }

    /// Write load constant instruction.
    pub(crate) fn write_const(&mut self, span: FrameSpan, value: FrozenValue, slot: BcSlotOut) {
        assert!(slot.get().0 < self.local_count() + self.stack_size);

        self.write_instr::<InstrConst>(span, (value, slot));
    }

    /// Write load local instruction.
    pub(crate) fn write_load_local(
        &mut self,
        span: FrameSpan,
        slot: LocalSlotId,
        target: BcSlotOut,
    ) {
        assert!(slot.0 < self.local_count());

        if let Some(slot) = self.try_definitely_assigned(slot) {
            self.write_mov(span, slot, target);
        } else {
            self.write_instr::<InstrLoadLocal>(span, (slot, target));
        }
    }

    pub(crate) fn write_load_local_captured(
        &mut self,
        span: FrameSpan,
        source: LocalCapturedSlotId,
        target: BcSlotOut,
    ) {
        assert!(source.0 < self.local_count());
        assert!(target.get().0 < self.local_count() + self.stack_size);
        self.write_instr_ret_arg::<InstrLoadLocalCaptured>(span, (source, target));
    }

    pub(crate) fn write_mov(&mut self, span: FrameSpan, source: BcSlotIn, target: BcSlotOut) {
        assert!(source.get().0 < self.local_count() + self.stack_size);
        assert!(target.get().0 < self.local_count() + self.stack_size);

        // Do not emit no-op `Mov`.
        // It can occur when compiling code like `x = x`.
        // Currently we do not erase these no-op assignments at IR.
        if source.get() == target.get() {
            return;
        }

        self.write_instr_ret_arg::<InstrMov>(span, (source, target));
    }

    pub(crate) fn write_store_local_captured(
        &mut self,
        span: FrameSpan,
        source: BcSlotIn,
        target: LocalCapturedSlotId,
    ) {
        assert!(source.get().0 < self.local_count() + self.stack_size);
        assert!(target.0 < self.local_count());
        self.write_instr_ret_arg::<InstrStoreLocalCaptured>(span, (source, target));
    }

    /// Patch previously writted address with current IP.
    pub(crate) fn patch_addr(&mut self, addr: PatchAddr) {
        self.instrs.patch_addr(addr);
    }

    pub(crate) fn patch_addrs(&mut self, addrs: Vec<PatchAddr>) {
        for adds in addrs {
            self.patch_addr(adds);
        }
    }

    /// Write branch.
    pub(crate) fn write_br(&mut self, span: FrameSpan) -> PatchAddr {
        let (addr, arg) = self.write_instr_ret_arg::<InstrBr>(span, BcAddrOffset::FORWARD);
        self.instrs.addr_to_patch(addr, arg)
    }

    /// Write conditional branch.
    pub(crate) fn write_if_not_br(&mut self, cond: BcSlotIn, span: FrameSpan) -> PatchAddr {
        let (addr, arg) =
            self.write_instr_ret_arg::<InstrIfNotBr>(span, (cond, BcAddrOffset::FORWARD));
        self.instrs.addr_to_patch(addr, unsafe { &(*arg).1 })
    }

    /// Write conditional branch.
    pub(crate) fn write_if_br(&mut self, cond: BcSlotIn, span: FrameSpan) -> PatchAddr {
        let (addr, arg) =
            self.write_instr_ret_arg::<InstrIfBr>(span, (cond, BcAddrOffset::FORWARD));
        self.instrs.addr_to_patch(addr, unsafe { &(*arg).1 })
    }

    fn write_if_else_impl(
        &mut self,
        cond: BcSlotIn,
        span: FrameSpan,
        then_block: impl FnOnce(&mut Self),
        else_block: impl FnOnce(&mut Self),
    ) {
        let definitely_assigned = self.save_definitely_assigned();

        let else_target = self.write_if_not_br(cond, span);
        then_block(self);
        let end_target = self.write_br(span);

        self.restore_definitely_assigned(definitely_assigned.clone());

        self.patch_addr(else_target);
        else_block(self);
        self.patch_addr(end_target);

        self.restore_definitely_assigned(definitely_assigned);
    }

    /// Write if-else block.
    pub(crate) fn write_if_else(
        &mut self,
        cond: BcSlotIn,
        maybe_not: MaybeNot,
        span: FrameSpan,
        then_block: impl FnOnce(&mut Self),
        else_block: impl FnOnce(&mut Self),
    ) {
        match maybe_not {
            MaybeNot::Id => self.write_if_else_impl(cond, span, then_block, else_block),
            MaybeNot::Not => self.write_if_else_impl(cond, span, else_block, then_block),
        }
    }

    pub(crate) fn write_continue(&mut self, span: FrameSpan) {
        let loop_depth = LoopDepth(self.for_loops.len().checked_sub(1).unwrap() as u32);
        let for_loop = self.for_loops.last().unwrap();
        let jump_back = self.ip().offset_from(for_loop.inner_addr).neg();
        let var = for_loop.var;
        let (addr, arg) = self.write_instr_ret_arg::<InstrContinue>(
            span,
            (
                for_loop.iter,
                loop_depth,
                var,
                jump_back,
                BcAddrOffset::FORWARD,
            ),
        );
        let end_patch = self.instrs.addr_to_patch(addr, unsafe { &(*arg).4 });
        let for_loop = self.for_loops.last_mut().unwrap();
        for_loop.end_addrs_to_patch.push(end_patch);
    }

    pub(crate) fn write_break(&mut self, span: FrameSpan) {
        let for_loop = self.for_loops.last().unwrap();
        let (addr, arg) =
            self.write_instr_ret_arg::<InstrBreak>(span, (for_loop.iter, BcAddrOffset::FORWARD));
        let end_patch = self.instrs.addr_to_patch(addr, unsafe { &(*arg).1 });
        let for_loop = self.for_loops.last_mut().unwrap();
        for_loop.end_addrs_to_patch.push(end_patch);
    }

    /// Write for loop.
    pub(crate) fn write_for(
        &mut self,
        over: BcSlotIn,
        var: BcSlotOut,
        span: FrameSpan,
        body: impl FnOnce(&mut BcWriter),
    ) {
        // Allocate a slot to store the iterator.
        self.alloc_slot(|iter, bc| {
            // Definitely assigned save/restore is redundant here, it is performed more precisely
            // by the caller. But it is safer to do it here anyway.
            let definitely_assigned = bc.save_definitely_assigned();

            let loop_depth = LoopDepth(bc.for_loops.len() as u32);
            let (addr, arg) = bc.write_instr_ret_arg::<InstrIter>(
                span,
                (over, loop_depth, iter.to_out(), var, BcAddrOffset::FORWARD),
            );
            let end_patch = bc.instrs.addr_to_patch(addr, unsafe { &(*arg).4 });
            bc.for_loops.push(BcWriterForLoop {
                inner_addr: bc.ip(),
                end_addrs_to_patch: vec![end_patch],
                var,
                iter: iter.to_in(),
            });
            bc.max_loop_depth = cmp::max(bc.max_loop_depth, LoopDepth(bc.for_loops.len() as u32));
            body(bc);
            bc.write_continue(span);
            let for_loop = bc.for_loops.pop().unwrap();
            for addr_to_patch in for_loop.end_addrs_to_patch {
                bc.patch_addr(addr_to_patch);
            }

            bc.restore_definitely_assigned(definitely_assigned);
        })
    }

    /// Write instructions to stop all current iterations.
    /// This is done before `return`.
    pub(crate) fn write_iter_stop(&mut self, span: FrameSpan) {
        // We can stop iteration in any order, but let's for consistency stop them in reverse order.
        for depth in (0..self.for_loops.len()).rev() {
            let iter = self.for_loops[depth].iter;
            self.write_instr::<InstrIterStop>(span, iter);
        }
    }

    fn stack_add(&mut self, add: u32) {
        self.stack_size += add;
        self.max_stack_size = cmp::max(self.max_stack_size, self.stack_size);
    }

    fn stack_sub(&mut self, sub: u32) {
        assert!(self.stack_size >= sub);
        self.stack_size -= sub;
    }

    /// Convert local variable to BC slot if it is known to be definitely assigned
    /// at this execution point.
    pub(crate) fn try_definitely_assigned(&self, local: LocalSlotId) -> Option<BcSlotIn> {
        assert!(local.0 < self.local_count());
        if self.definitely_assigned.is_definitely_assigned(local) {
            Some(local.to_bc_slot().to_in())
        } else {
            None
        }
    }

    pub(crate) fn mark_definitely_assigned(&mut self, local: LocalSlotId) {
        self.definitely_assigned.mark_definitely_assigned(local);
    }

    pub(crate) fn save_definitely_assigned(&self) -> BcDefinitelyAssigned {
        self.definitely_assigned.clone()
    }

    pub(crate) fn restore_definitely_assigned(&mut self, saved: BcDefinitelyAssigned) {
        saved.assert_smaller_then(&self.definitely_assigned);
        self.definitely_assigned = saved;
    }

    /// Allocate a temporary slot, and call a callback.
    ///
    /// The slot is valid during the callback run, and can be reused later.
    pub(crate) fn alloc_slot<R>(&mut self, k: impl FnOnce(BcSlot, &mut BcWriter) -> R) -> R {
        let slot = BcSlot(self.local_count() + self.stack_size);
        self.stack_add(1);
        let r = k(slot, self);
        self.stack_sub(1);
        r
    }

    /// Allocate several slots for the duration of callback run.
    pub(crate) fn alloc_slots<R>(
        &mut self,
        count: u32,
        k: impl FnOnce(BcSlotRange, &mut BcWriter) -> R,
    ) -> R {
        let slots = BcSlotRange {
            start: BcSlot(self.local_count() + self.stack_size),
            end: BcSlot(self.local_count() + self.stack_size + count),
        };
        self.stack_add(count);
        let r = k(slots, self);
        self.stack_sub(count);
        r
    }

    /// Allocate several slots.
    pub(crate) fn alloc_slots_c<const N: usize, R>(
        &mut self,
        k: impl FnOnce(BcSlotsN<N>, &mut BcWriter) -> R,
    ) -> R {
        self.alloc_slots(N as u32, |slots, bc| k(BcSlotsN::from_range(slots), bc))
    }

    /// Allocate several slots for typical compilation of several expressions.
    pub(crate) fn alloc_slots_for_exprs<K, R>(
        &mut self,
        // Iterate over the elements.
        exprs: impl IntoIterator<Item = K>,
        // Invoke a callback which fills the slots.
        mut expr: impl FnMut(BcSlot, K, &mut BcWriter),
        // And then invoke a callback which consumes all the slots again together.
        k: impl FnOnce(BcSlotInRange, &mut BcWriter) -> R,
    ) -> R {
        let start = BcSlot(self.local_count() + self.stack_size);
        let mut end = start;
        for item in exprs {
            self.stack_add(1);
            // `expr` callback may allocate more temporary slots,
            // but they are released after the callback returns.
            // So resulting slots are sequential.
            expr(end, item, self);
            end = BcSlot(end.0 + 1);
        }
        let range = if end == start {
            // This is not really necessary, empty range is equally valid
            // with any starting point, but this makes bytecode output
            // (in particular, in golden tests) more readable.
            BcSlotInRange::default()
        } else {
            BcSlotRange { start, end }.to_in()
        };
        let r = k(range, self);
        self.stack_sub(end.0 - start.0);
        r
    }

    pub(crate) fn alloc_file_span(&self, span: FrameSpan) -> FrozenRef<'static, FrameSpan> {
        self.heap.alloc_any(span)
    }
}


/*
 * Copyright 2019 The Starlark in Rust Authors.
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Compile assignment lhs.

use crate::collections::symbol_map::Symbol;
use crate::eval::bc::compiler::expr::write_n_exprs;
use crate::eval::bc::instr_impl::InstrSetArrayIndex;
use crate::eval::bc::instr_impl::InstrSetObjectField;
use crate::eval::bc::instr_impl::InstrStoreModuleAndExport;
use crate::eval::bc::instr_impl::InstrUnpack;
use crate::eval::bc::stack_ptr::BcSlotIn;
use crate::eval::bc::stack_ptr::BcSlotOut;
use crate::eval::bc::writer::BcWriter;
use crate::eval::compiler::span::IrSpanned;
use crate::eval::compiler::stmt::AssignCompiledValue;
use crate::slice_vec_ext::SliceExt;

impl AssignCompiledValue {
    /// After evaluation of `(x, y[z]) = ...`, variables `x`, `y` and `z` as definitely assigned.
    pub(crate) fn mark_definitely_assigned_after(&self, bc: &mut BcWriter) {
        match self {
            AssignCompiledValue::Dot(object, field) => {
                object.mark_definitely_assigned_after(bc);
                let _ = field;
            }
            AssignCompiledValue::Module(..) => {}
            AssignCompiledValue::Index(array, index) => {
                array.mark_definitely_assigned_after(bc);
                index.mark_definitely_assigned_after(bc);
            }
            AssignCompiledValue::LocalCaptured(_slot) => {}
            AssignCompiledValue::Local(slot) => {
                bc.mark_definitely_assigned(*slot);
            }
            AssignCompiledValue::Tuple(xs) => {
                for x in xs {
                    x.mark_definitely_assigned_after(bc);
                }
            }
        }
    }
}

impl IrSpanned<AssignCompiledValue> {
    pub(crate) fn write_bc(&self, value: BcSlotIn, bc: &mut BcWriter) {
        let span = self.span;
        match self.node {
            AssignCompiledValue::Dot(ref object, ref field) => {
                object.write_bc_cb(bc, |object, bc| {
                    let symbol = Symbol::new(field.as_str());
                    bc.write_instr::<InstrSetObjectField>(span, (value, object, symbol));
                });
            }
            AssignCompiledValue::Index(ref array, ref index) => {
                write_n_exprs([array, index], bc, |[array, index], bc| {
                    bc.write_instr::<InstrSetArrayIndex>(span, (value, array, index));
                });
            }
            AssignCompiledValue::Tuple(ref xs) => {
                // All assignments are to local variables, e. g.
                // ```
                // (x, y, z) = ...
                // ```
                // so we can avoid using intermediate register.
                let all_local = xs
                    .try_map(|x| {
                        x.as_local_non_captured()
                            .map(|l| l.to_bc_slot().to_out())
                            .ok_or(())
                    })
                    .ok();
                if let Some(all_local) = all_local {
                    let args = bc.heap.alloc_any_slice_display_from_debug(&all_local);
                    bc.write_instr::<InstrUnpack>(span, (value, args));
                } else {
                    bc.alloc_slots(xs.len() as u32, |slots, bc| {
                        let args: Vec<BcSlotOut> = slots.iter().map(|s| s.to_out()).collect();
                        let args = bc.heap.alloc_any_slice_display_from_debug(&args);
                        bc.write_instr::<InstrUnpack>(span, (value, args));

                        for (x, slot) in xs.iter().zip(slots.iter()) {
                            x.write_bc(slot.to_in(), bc);
                        }
                    });
                }
            }
            AssignCompiledValue::Local(slot) => {
                bc.write_mov(span, value, slot.to_bc_slot().to_out());
            }
            AssignCompiledValue::LocalCaptured(slot) => {
                bc.write_store_local_captured(span, value, slot);
            }
            AssignCompiledValue::Module(slot, ref name) => {
                bc.write_instr::<InstrStoreModuleAndExport>(span, (value, slot, name.clone()));
            }
        }
    }
}