/*
 * 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.
 */

//! Spelling suggestions.

use strsim::levenshtein;

/// Find a suggestion for a typo.
pub(crate) fn did_you_mean<'a>(
    value: &str,
    variants: impl IntoIterator<Item = &'a str>,
) -> Option<&'a str> {
    if value.is_empty() {
        return None;
    }

    let max_dist = if value.len() <= 2 {
        // we don't want to suggest "cd" for "a"
        1
    } else {
        2
    };

    variants
        .into_iter()
        .map(|v| (v, levenshtein(value, v)))
        .filter(|(_, dist)| *dist <= max_dist)
        .min_by_key(|(_v, sim)| *sim)
        .map(|(v, _)| v)
}

#[cfg(test)]
mod tests {
    use crate::errors::did_you_mean::did_you_mean;

    #[test]
    fn prefixes() {
        assert_eq!(
            Some("cxx_library"),
            did_you_mean("cxx_librar", vec!["cxx_library"])
        );
        assert_eq!(
            Some("cxx_library"),
            did_you_mean("cxx_libra", vec!["cxx_library"])
        );
        assert_eq!(None, did_you_mean("cxx_libr", vec!["cxx_library"]));
    }

    #[test]
    fn typos() {
        assert_eq!(
            Some("cxx_library"),
            did_you_mean("cxx_librarx", vec!["cxx_library"])
        );
        assert_eq!(
            Some("cxx_library"),
            did_you_mean("cxx_libraxx", vec!["cxx_library"])
        );
        assert_eq!(None, did_you_mean("cxx_librxxx", vec!["cxx_library"]));
    }

    #[test]
    fn best() {
        assert_eq!(Some("abc"), did_you_mean("abx", vec!["abc", "abcd"]));
    }

    #[test]
    fn very_short() {
        assert_eq!(Some("a"), did_you_mean("b", vec!["a"]));
        assert_eq!(Some("ab"), did_you_mean("b", vec!["ab"]));
        assert_eq!(None, did_you_mean("b", vec!["cd"]));

        assert_eq!(None, did_you_mean("bc", vec!["de"]));
    }

    #[test]
    fn earlier_variants_are_more_important() {
        assert_eq!(Some("aaaay"), did_you_mean("aaaax", vec!["aaaay", "aaaaz"]));
        assert_eq!(Some("aaaaz"), did_you_mean("aaaax", vec!["aaaaz", "aaaay"]));
    }
}


/*
 * 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.
 */

//! Error types used by Starlark, mostly [`Diagnostic`].

use std::error::Error;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;

use annotate_snippets::display_list::DisplayList;
use annotate_snippets::display_list::FormatOptions;
use annotate_snippets::snippet::Annotation;
use annotate_snippets::snippet::AnnotationType;
use annotate_snippets::snippet::Slice;
use annotate_snippets::snippet::Snippet;
use annotate_snippets::snippet::SourceAnnotation;

pub use crate::analysis::EvalMessage;
pub use crate::analysis::EvalSeverity;
pub use crate::analysis::Lint;
use crate::codemap::CodeMap;
use crate::codemap::FileSpan;
use crate::codemap::Span;
pub use crate::errors::frame::Frame;
use crate::eval::CallStack;
use crate::values::string::fast_string;

pub(crate) mod did_you_mean;
pub(crate) mod frame;

/// An error plus its origination location and call stack.
///
/// The underlying [`message`](Diagnostic::message) is an [`anyhow::Error`].
/// The [`Diagnostic`] structure itself usually stored within an [`anyhow::Error`].
#[derive(Debug)]
pub struct Diagnostic {
    /// Underlying error for the [`Diagnostic`].
    /// Should _never_ be of type [`Diagnostic`] itself.
    pub message: anyhow::Error,

    /// Location where the error originated.
    pub span: Option<FileSpan>,

    /// Call stack where the error originated.
    pub call_stack: CallStack,
}

impl Error for Diagnostic {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        // We do have an underlying source (namely `self.message`), but if we return
        // it then `anyhow` will print it with `{:#}`, and we already print it in our
        // `Display`, which would cause it to appear twice.
        // Therefore, we say we have no source.
        None
    }

    // TODO(nga): figure out how to do it with unstable rust.
    // fn backtrace(&self) -> Option<&std::backtrace::Backtrace> {
    //     Some(self.message.backtrace())
    // }
}

impl Diagnostic {
    /// Create a new [`Diagnostic`] containing an underlying error and span.
    /// If the given `message` is already a [`Diagnostic`] with a [`Span`],
    /// the new span will be ignored and the original `message` returned.
    pub(crate) fn new(
        message: impl Into<anyhow::Error>,
        span: Span,
        codemap: &CodeMap,
    ) -> anyhow::Error {
        Self::modify(message.into(), |d| d.set_span(span, codemap))
    }

    /// Modify an error by attaching diagnostic information to it - e.g. `span`/`call_stack`.
    /// If given an [`anyhow::Error`] which is a [`Diagnostic`], it will add the information to the
    /// existing [`Diagnostic`]. If not, it will wrap the error in [`Diagnostic`].
    pub fn modify(mut err: anyhow::Error, f: impl FnOnce(&mut Diagnostic)) -> anyhow::Error {
        match err.downcast_mut::<Diagnostic>() {
            Some(diag) => {
                f(diag);
                err
            }
            _ => {
                let mut err = Self {
                    message: err,
                    span: None,
                    call_stack: CallStack::default(),
                };
                f(&mut err);
                err.into()
            }
        }
    }

    /// Set the [`Diagnostic::span`] field, unless it's already been set.
    pub(crate) fn set_span(&mut self, span: Span, codemap: &CodeMap) {
        if self.span.is_none() {
            // We want the best span, which is likely the first person to set it
            self.span = Some(codemap.file_span(span));
        }
    }

    /// Set the [`Diagnostic::call_stack`] field, unless it's already been set.
    pub fn set_call_stack(&mut self, call_stack: impl FnOnce() -> CallStack) {
        if self.call_stack.is_empty() {
            // We want the best call stack, which is likely the first person to set it
            self.call_stack = call_stack();
        }
    }

    /// Print an error to the stderr stream. If the error is a [`Diagnostic`] it will use
    /// color-codes when printing.
    ///
    /// Note that this function doesn't print any context information if the error is a
    /// [`Diagnostic`], so you might prefer to use `eprintln!("{:#}"), err)`
    /// if you suspect there is useful context (although you won't get pretty colors).
    pub fn eprint(err: &anyhow::Error) {
        match err.downcast_ref::<Diagnostic>() {
            None => eprintln!("{:#}", err),
            Some(diag) => diagnostic_stderr(diag),
        }
    }

    /// Gets annotated snippets for a [`Diagnostic`].
    fn get_display_list<'a>(&'a self, annotation_label: &'a str, color: bool) -> DisplayList<'a> {
        fn convert_span_to_slice<'a>(span: &'a FileSpan) -> Slice<'a> {
            let region = span.resolve_span();

            // we want the source_span to capture any whitespace ahead of the diagnostic span to
            // get the column numbers correct in the DisplayList, and any trailing source code
            // on the last line for context.
            let first_line_span = span.file.line_span(region.begin_line);
            let last_line_span = span.file.line_span(region.end_line);
            let source_span = span.span.merge(first_line_span).merge(last_line_span);
            let source = span.file.source_span(source_span);

            // We want to highlight the span, which needs to be relative to source, and in
            // characters.
            // Our spans are in terms of bytes, but our resolved spans in terms of characters.
            let range_start_chars = region.begin_column;
            let range_len_chars = fast_string::len(span.source_span()).0;

            Slice {
                source,
                line_start: 1 + region.begin_line,
                origin: Some(span.file.filename()),
                fold: false,
                annotations: vec![SourceAnnotation {
                    label: "",
                    annotation_type: AnnotationType::Error,
                    range: (range_start_chars, range_start_chars + range_len_chars),
                }],
            }
        }

        let slice = self.span.as_ref().map(convert_span_to_slice);

        let snippet = Snippet {
            title: Some(Annotation {
                label: Some(annotation_label),
                id: None,
                annotation_type: AnnotationType::Error,
            }),
            footer: Vec::new(),
            slices: slice.map(|s| vec![s]).unwrap_or_default(),
            opt: FormatOptions {
                color,
                ..Default::default()
            },
        };

        DisplayList::from(snippet)
    }
}

impl Display for Diagnostic {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        diagnostic_display(self, false, f)
    }
}

/////////////////////////////////////////////////////////////////////
// DISPLAY RELATED UTILITIES
// Since formatting these types is difficult, we reuse the Rust compiler
// variants by doing a conversion using annotate-snippets
// (https://github.com/rust-lang/annotate-snippets-rs)

fn diagnostic_display(diagnostic: &Diagnostic, color: bool, f: &mut dyn fmt::Write) -> fmt::Result {
    write!(f, "{}", diagnostic.call_stack)?;
    let annotation_label = format!("{}", diagnostic.message);
    // I set color to false here to make the comparison easier with tests (coloring
    // adds in pretty strange unicode chars).
    let display_list = diagnostic.get_display_list(&annotation_label, color);
    writeln!(f, "{}", display_list)?;
    // Print out the `Caused by:` trace (if exists) and rust backtrace (if enabled).
    // The trace printed comes from an [`anyhow::Error`] that is not a [`Diagnostic`].
    if diagnostic.message.source().is_some() {
        writeln!(f, "\n\n{:?}", diagnostic.message)?;
    }

    Ok(())
}

fn diagnostic_stderr(diagnostic: &Diagnostic) {
    let mut stderr = String::new();
    diagnostic_display(diagnostic, true, &mut stderr).unwrap();
    eprint!("{}", stderr);
}


/*
 * Copyright 2018 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.
 */

//! Evaluate some code, typically done by creating an [`Evaluator`], then calling
//! [`eval_module`](Evaluator::eval_module).

pub(crate) mod bc;
pub(crate) mod compiler;
pub(crate) mod runtime;

use std::collections::HashMap;
use std::mem;
use std::time::Instant;

use dupe::Dupe;
pub use runtime::arguments::Arguments;
pub use runtime::before_stmt::BeforeStmtFuncDyn;
pub use runtime::call_stack::CallStack;
pub use runtime::evaluator::Evaluator;
pub use runtime::file_loader::FileLoader;
pub use runtime::file_loader::ReturnFileLoader;
pub use runtime::params::ParametersParser;
pub use runtime::params::ParametersSpec;
pub use runtime::params::ParametersSpecBuilder;
pub use runtime::profile::data::ProfileData;
pub use runtime::profile::ProfileMode;

use crate::collections::symbol_map::Symbol;
use crate::docs::DocString;
use crate::environment::Globals;
use crate::eval::compiler::def::DefInfo;
use crate::eval::compiler::scope::ModuleScopes;
use crate::eval::compiler::scope::ScopeId;
use crate::eval::compiler::scope::TopLevelStmtIndex;
use crate::eval::compiler::Compiler;
use crate::eval::compiler::EvalException;
use crate::eval::runtime::arguments::ArgNames;
use crate::eval::runtime::arguments::ArgumentsFull;
use crate::slice_vec_ext::SliceExt;
use crate::syntax::module::AstModule;
use crate::syntax::DialectTypes;
use crate::values::Value;

impl<'v, 'a> Evaluator<'v, 'a> {
    /// Evaluate an [`AstModule`] with this [`Evaluator`], modifying the in-scope
    /// [`Module`](crate::environment::Module) as appropriate.
    pub fn eval_module(&mut self, ast: AstModule, globals: &Globals) -> anyhow::Result<Value<'v>> {
        let start = Instant::now();

        let AstModule {
            codemap,
            statement,
            dialect,
        } = ast;

        let codemap = self
            .module_env
            .frozen_heap()
            .alloc_any_display_from_debug(codemap.dupe());

        let globals = self
            .module_env
            .frozen_heap()
            .alloc_any_display_from_type_name(globals.dupe());

        if let Some(docstring) = DocString::extract_raw_starlark_docstring(&statement) {
            self.module_env.set_docstring(docstring)
        }

        let ModuleScopes {
            cst,
            module_slot_count,
            scope_data,
            top_level_stmt_count,
            last_stmt_defining_type,
            ..
        } = ModuleScopes::check_module_err(
            self.module_env.mutable_names(),
            self.module_env.frozen_heap(),
            &HashMap::new(),
            statement,
            globals,
            codemap,
            &dialect,
        )?;

        let scope_names = scope_data.get_scope(ScopeId::module());
        let local_names = self
            .frozen_heap()
            .alloc_any_slice_display_from_debug(&scope_names.used);

        self.module_env.slots().ensure_slots(module_slot_count);
        let old_def_info = mem::replace(
            &mut self.module_def_info,
            self.module_env.frozen_heap().alloc_any(DefInfo::for_module(
                codemap,
                local_names,
                self.module_env
                    .frozen_heap()
                    .alloc_any_slice_display_from_debug(&scope_names.parent),
                globals,
            )),
        );

        // Set up the world to allow evaluation (do NOT use ? from now on)

        self.call_stack.push(Value::new_none(), None).unwrap();

        // Evaluation
        let mut compiler = Compiler {
            scope_data,
            locals: Vec::new(),
            globals,
            codemap,
            eval: self,
            check_types: dialect.enable_types == DialectTypes::Enable,
            top_level_stmt_count,
            last_stmt_defining_type,
            last_stmt_with_populated_types: TopLevelStmtIndex(0),
        };

        let res = compiler.eval_module(cst, local_names);

        // Clean up the world, putting everything back
        self.call_stack.pop();

        self.module_def_info = old_def_info;

        self.module_env.add_eval_duration(start.elapsed());

        // Return the result of evaluation
        res.map_err(EvalException::into_anyhow)
    }

    /// Evaluate a function stored in a [`Value`], passing in `positional` and `named` arguments.
    pub fn eval_function(
        &mut self,
        function: Value<'v>,
        positional: &[Value<'v>],
        named: &[(&str, Value<'v>)],
    ) -> anyhow::Result<Value<'v>> {
        let names = named.map(|(s, _)| (Symbol::new(s), self.heap().alloc_str(s)));
        let named = named.map(|x| x.1);
        let params = Arguments(ArgumentsFull {
            pos: positional,
            named: &named,
            names: ArgNames::new(&names),
            args: None,
            kwargs: None,
        });
        // eval_module pushes an "empty" call stack frame. other places expect that first frame to be ignorable, and
        // so we push an empty frame too (otherwise things would ignore this function's own frame).
        self.with_call_stack(Value::new_none(), None, |this| {
            function.invoke(&params, this)
        })
    }
}


/*
 * 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.
 */

//! Address types used in bytecode interpreter.

use std::marker;
use std::mem;
use std::ops::Add;
use std::ops::AddAssign;
use std::ops::Sub;

use derive_more::Display;
use dupe::Dupe;

use crate::eval::bc::if_debug::IfDebug;
use crate::eval::bc::instr::BcInstr;
use crate::eval::bc::opcode::BcOpcode;
use crate::eval::bc::repr::BcInstrHeader;
use crate::eval::bc::repr::BcInstrRepr;
use crate::eval::bc::repr::BC_INSTR_ALIGN;

/// Address relative to bytecode start.
#[derive(
    Eq, PartialEq, Copy, Clone, Dupe, Debug, PartialOrd, Ord, Display, Hash, Default
)]
#[display(fmt = "@{}", _0)]
pub(crate) struct BcAddr(pub(crate) u32);

impl BcAddr {
    #[inline]
    pub(crate) fn offset_from(self, start: BcAddr) -> BcAddrOffset {
        debug_assert!(self >= start);
        BcAddrOffset(self.0 - start.0)
    }

    #[inline]
    pub(crate) fn offset(self, offset: BcAddrOffset) -> BcAddr {
        BcAddr(self.0 + offset.0)
    }

    #[inline]
    pub(crate) fn offset_neg(self, offset: BcAddrOffsetNeg) -> BcAddr {
        BcAddr(self.0 - offset.0)
    }
}

impl Sub<u32> for BcAddr {
    type Output = BcAddr;

    fn sub(self, rhs: u32) -> BcAddr {
        BcAddr(self.0.checked_sub(rhs).unwrap())
    }
}

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

    fn add(self, rhs: u32) -> BcAddr {
        BcAddr(self.0 + rhs)
    }
}

impl AddAssign<u32> for BcAddr {
    fn add_assign(&mut self, rhs: u32) {
        self.0 += rhs;
    }
}

/// Valid pointer range of bytecode.
/// Used for debugging assertions. This objects is not created in release mode.
#[derive(Copy, Clone, Dupe, Debug, PartialEq)]
pub(crate) struct BcPtrRange {
    start: *const u8,
    /// Length in bytes.
    len: usize,
}

impl BcPtrRange {
    pub(crate) fn for_slice(slice: &[u64]) -> BcPtrRange {
        BcPtrRange {
            start: slice.as_ptr() as *const u8,
            len: mem::size_of_val::<[u64]>(slice),
        }
    }

    pub(crate) fn assert_in_range(&self, ptr: *const u8) {
        let offset = unsafe { ptr.offset_from(self.start) };
        assert!(offset >= 0);
        assert!(offset as usize <= self.len);
    }

    fn end(&self) -> *const u8 {
        unsafe { self.start.add(self.len) }
    }
}

/// Pointer to an instruction in memory.
#[derive(Copy, Clone, Dupe, PartialOrd, PartialEq, Debug)]
// Lifetime here is mostly for documentation, it does not guarantee anything.
pub(crate) struct BcPtrAddr<'b> {
    ptr: *const u8,
    /// When assertions enabled, we validate the pointer is in this range.
    range: IfDebug<BcPtrRange>,
    _marker: marker::PhantomData<&'b u8>,
}

impl<'b> BcPtrAddr<'b> {
    /// Constructor.
    unsafe fn new(ptr: *const u8, range: IfDebug<BcPtrRange>) -> BcPtrAddr<'b> {
        debug_assert!(ptr as usize % BC_INSTR_ALIGN == 0);
        range.if_debug(|range| range.assert_in_range(ptr));
        BcPtrAddr {
            ptr,
            range,
            _marker: marker::PhantomData,
        }
    }

    /// Create a pointer for the beginning of the slice.
    pub(crate) fn for_slice_start(slice: &'b [u64]) -> BcPtrAddr<'b> {
        unsafe {
            BcPtrAddr::new(
                slice.as_ptr() as *const u8,
                IfDebug::new(BcPtrRange::for_slice(slice)),
            )
        }
    }

    /// Create a pointer for the beginning of the slice.
    pub(crate) fn for_slice_end(slice: &'b [u64]) -> BcPtrAddr<'b> {
        unsafe {
            BcPtrAddr::new(
                slice.as_ptr().add(slice.len()) as *const u8,
                IfDebug::new(BcPtrRange::for_slice(slice)),
            )
        }
    }

    /// Distance from current ptr to the end of instructions.
    fn remaining_if_debug(self) -> usize {
        unsafe { self.range.get_ref_if_debug().end().offset_from(self.ptr) as usize }
    }

    pub(crate) fn get_instr<I: BcInstr>(self) -> &'b BcInstrRepr<I> {
        debug_assert!(self.remaining_if_debug() >= mem::size_of::<BcInstrRepr<I>>());
        let ptr = self.ptr as *const BcInstrRepr<I>;
        let repr = unsafe { &*ptr };
        debug_assert_eq!(repr.header.opcode, BcOpcode::for_instr::<I>());
        repr
    }

    pub(crate) fn get_instr_checked<I: BcInstr>(self) -> Option<&'b BcInstrRepr<I>> {
        if self.get_opcode() == BcOpcode::for_instr::<I>() {
            Some(self.get_instr())
        } else {
            None
        }
    }

    pub(crate) fn get_instr_mut<I: BcInstr>(self) -> *mut BcInstrRepr<I> {
        debug_assert!(
            self.remaining_if_debug() >= mem::size_of::<BcInstrRepr<I>>(),
            "remaining: {}, instr size: {}",
            self.remaining_if_debug(),
            mem::size_of::<BcInstrRepr<I>>()
        );
        self.ptr as *mut BcInstrRepr<I>
    }

    pub(crate) fn get_opcode(self) -> BcOpcode {
        debug_assert!(self.remaining_if_debug() >= mem::size_of::<BcInstrHeader>());
        let ptr = self.ptr as *const BcInstrHeader;
        unsafe { (*ptr).opcode }
    }

    pub(crate) fn offset_from(self, start: BcPtrAddr) -> BcAddr {
        debug_assert!(self.range.get_ref_if_debug() == start.range.get_ref_if_debug());
        unsafe {
            let offset = self.ptr.offset_from(start.ptr);
            debug_assert!(offset >= 0);
            debug_assert!(offset <= i32::MAX as isize);
            BcAddr(offset as u32)
        }
    }

    #[inline(always)]
    fn sub_usize(self, offset: usize) -> BcPtrAddr<'b> {
        unsafe { BcPtrAddr::new(self.ptr.sub(offset), self.range) }
    }

    #[inline(always)]
    pub(crate) fn sub(self, start: BcAddr) -> BcPtrAddr<'b> {
        self.sub_usize(start.0 as usize)
    }

    pub(crate) fn offset(self, addr: BcAddr) -> BcPtrAddr<'b> {
        self.add(addr.0 as usize)
    }

    #[inline(always)]
    pub(crate) fn add_rel(self, rel: BcAddrOffset) -> BcPtrAddr<'b> {
        self.add(rel.0 as usize)
    }

    #[inline(always)]
    pub(crate) fn add_rel_neg(self, rel: BcAddrOffsetNeg) -> BcPtrAddr<'b> {
        self.sub_usize(rel.0 as usize)
    }

    pub(crate) fn add(self, offset: usize) -> BcPtrAddr<'b> {
        unsafe { BcPtrAddr::new(self.ptr.add(offset), self.range) }
    }

    pub(crate) fn add_instr<I: BcInstr>(self) -> BcPtrAddr<'b> {
        self.add_rel(BcAddrOffset::for_instr::<I>())
    }
}

/// Difference between addresses.
#[derive(Eq, PartialEq, Copy, Clone, Dupe, Debug, PartialOrd, Ord, Display)]
pub(crate) struct BcAddrOffset(pub(crate) u32);

impl BcAddrOffset {
    /// Pointer to not yet known address.
    pub(crate) const FORWARD: BcAddrOffset = BcAddrOffset(0xdeadbeef);

    /// Size of an instruction.
    fn for_instr<I: BcInstr>() -> BcAddrOffset {
        <BcInstrRepr<I>>::assert_align();
        BcAddrOffset(mem::size_of::<BcInstrRepr<I>>() as u32)
    }

    #[inline(always)]
    pub(crate) fn neg(self) -> BcAddrOffsetNeg {
        BcAddrOffsetNeg(self.0)
    }
}

/// Negative difference between addresses.
#[derive(Eq, PartialEq, Copy, Clone, Dupe, Debug, PartialOrd, Ord, Display)]
pub(crate) struct BcAddrOffsetNeg(pub(crate) 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.
 */

//! Unsorted/core interpreter stuff.

use std::fmt::Write;

use crate::eval::bc::addr::BcPtrAddr;
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_impl::InstrEnd;
use crate::eval::bc::instrs::BcInstrs;
use crate::eval::bc::opcode::BcOpcode;
use crate::eval::bc::opcode::BcOpcodeHandler;
use crate::eval::bc::slow_arg::BcInstrEndArg;
use crate::eval::bc::slow_arg::BcInstrSlowArg;
use crate::eval::compiler::add_span_to_expr_error;
use crate::eval::compiler::EvalException;
use crate::eval::runtime::evaluator::EvaluationCallbacks;
use crate::eval::Evaluator;
use crate::values::Value;

/// Ready to execute bytecode.
#[derive(Default)]
pub(crate) struct Bc {
    pub(crate) instrs: BcInstrs,
    /// Number of local variable slots.
    pub(crate) local_count: u32,
    /// Max stack size in values (`Value`).
    pub(crate) max_stack_size: u32,
    /// Max depth of loops.
    pub(crate) max_loop_depth: LoopDepth,
}

impl Bc {
    /// Find span for instruction.
    #[cold]
    #[inline(never)]
    pub(crate) fn slow_arg_at_ptr(addr_ptr: BcPtrAddr) -> &BcInstrSlowArg {
        let mut ptr = addr_ptr;
        loop {
            let opcode = ptr.get_opcode();
            if opcode == BcOpcode::End {
                let end_of_bc = ptr.get_instr::<InstrEnd>();
                let BcInstrEndArg {
                    slow_args,
                    end_addr,
                    ..
                } = &end_of_bc.arg;
                let code_start_ptr = ptr.sub(*end_addr);
                let addr = addr_ptr.offset_from(code_start_ptr);
                for (next_addr, next_span) in slow_args {
                    if *next_addr == addr {
                        return next_span;
                    }
                }
                panic!("span not found for addr: {}", addr);
            }
            ptr = ptr.add(opcode.size_of_repr());
        }
    }

    #[cold]
    #[inline(never)]
    pub(crate) fn wrap_error_for_instr_ptr(
        ptr: BcPtrAddr,
        e: anyhow::Error,
        eval: &Evaluator,
    ) -> EvalException {
        let span = Self::slow_arg_at_ptr(ptr).span;
        add_span_to_expr_error(e, span, eval)
    }

    /// Run the bytecode in the current frame allocated in the evaluator.
    ///
    /// Frame must be allocated properly, otherwise it will likely result in memory corruption.
    #[inline(always)]
    pub(crate) fn run<'v, EC: EvaluationCallbacks>(
        &self,
        eval: &mut Evaluator<'v, '_>,
        ec: &mut EC,
    ) -> Result<Value<'v>, EvalException> {
        debug_assert!(eval.current_frame.is_inititalized());
        debug_assert_eq!(self.max_stack_size, eval.current_frame.max_stack_size());
        run_block(eval, ec, self.instrs.start_ptr())
    }

    pub(crate) fn dump_debug(&self) -> String {
        let mut w = String::new();
        writeln!(w, "Max stack size: {}", self.max_stack_size).unwrap();
        writeln!(w, "Instructions:").unwrap();
        self.instrs
            .dump_debug()
            .lines()
            .for_each(|line| writeln!(w, "  {}", line).unwrap());
        w
    }
}

/// Execute one instruction.
#[cfg_attr(not(debug_assertions), inline(always))]
fn step<'v, 'b, EC: EvaluationCallbacks>(
    eval: &mut Evaluator<'v, '_>,
    ec: &mut EC,
    frame: BcFramePtr<'v>,
    ip: BcPtrAddr<'b>,
) -> InstrControl<'v, 'b> {
    let opcode = ip.get_opcode();
    // println!("{}: {:?}", self.current_ip, opcode);

    struct HandlerImpl<'v, 'a, 'y, 'b> {
        eval: &'y mut Evaluator<'v, 'a>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr<'b>,
    }

    impl<'v, 'a, 'y, 'b> BcOpcodeHandler<InstrControl<'v, 'b>> for HandlerImpl<'v, 'a, 'y, 'b> {
        #[cfg_attr(not(debug_assertions), inline(always))]
        fn handle<I: BcInstr>(self) -> InstrControl<'v, 'b> {
            let HandlerImpl { eval, frame, ip } = self;
            let repr = ip.get_instr::<I>();
            I::run(eval, frame, ip, &repr.arg)
        }
    }

    ec.before_instr(eval, ip, opcode);
    opcode.dispatch(HandlerImpl { eval, frame, ip })
}

/// Execute the code block, either a module, a function body or a loop body.
// Do not inline this function because it is called from two places: function and loop.
pub(crate) fn run_block<'v, EC: EvaluationCallbacks>(
    eval: &mut Evaluator<'v, '_>,
    ec: &mut EC,
    mut ip: BcPtrAddr,
) -> Result<Value<'v>, EvalException> {
    // Copy frame pointer to local variable to generate more efficient code.
    let frame = eval.current_frame;

    loop {
        // Note most functions called from here must be carefully annotated
        // as `#[inline(always)]` otherwise LLVM considers them too large to inline.
        //
        // We do inline always only in release mode because otherwise
        // generated stack frame is too large which leads to C stack overflow in debug more.
        ip = match step(eval, ec, frame, ip) {
            InstrControl::Next(ip) => ip,
            InstrControl::Return(v) => return Ok(v),
            InstrControl::Err(e) => return Err(Bc::wrap_error_for_instr_ptr(ip, e, eval)),
        }
    }
}


/*
 * 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.
 */

//! Call-related bytecode interpreter code.

use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::marker::PhantomData;

use crate::coerce::coerce;
use crate::collections::symbol_map::Symbol;
use crate::eval::bc::frame::BcFramePtr;
use crate::eval::bc::instr_arg::BcInstrArg;
use crate::eval::bc::stack_ptr::BcSlotIn;
use crate::eval::bc::stack_ptr::BcSlotInRange;
use crate::eval::compiler::def::FrozenDef;
use crate::eval::runtime::arguments::ArgNames;
use crate::eval::runtime::arguments::ArgSymbol;
use crate::eval::runtime::arguments::ArgumentsFull;
use crate::eval::runtime::arguments::ArgumentsImpl;
use crate::eval::runtime::arguments::ArgumentsPos;
use crate::eval::runtime::arguments::ResolvedArgName;
use crate::slice_vec_ext::VecExt;
use crate::values::FrozenStringValue;

/// Call arguments.
pub(crate) trait BcCallArgs<S: ArgSymbol>: BcInstrArg {
    fn pop_from_stack<'a, 'v>(&'a self, frame: BcFramePtr<'v>) -> ArgumentsFull<'v, 'a, S>;
}

/// Call arguments for `def` call.
pub(crate) trait BcCallArgsForDef: BcInstrArg {
    type Args<'v, 'a>: ArgumentsImpl<'v, 'a, ArgSymbol = ResolvedArgName>
    where
        'v: 'a;

    fn pop_from_stack<'a, 'v>(&'a self, stack: BcFramePtr<'v>) -> Self::Args<'v, 'a>;
}

/// Full call arguments: positional, named, star and star-star. All taken from the stack.
#[derive(Debug)]
pub(crate) struct BcCallArgsFull<S: ArgSymbol> {
    pub(crate) pos_named: BcSlotInRange,
    pub(crate) names: Box<[(S, FrozenStringValue)]>,
    pub(crate) args: Option<BcSlotIn>,
    pub(crate) kwargs: Option<BcSlotIn>,
}

/// Positional-only call arguments, from stack.
#[derive(Debug)]
pub(crate) struct BcCallArgsPos {
    /// Range of positional arguments.
    pub(crate) pos: BcSlotInRange,
}

impl<S: ArgSymbol> BcCallArgsFull<S> {
    /// Number of positional arguments.
    fn pos(&self) -> u32 {
        assert!(self.pos_named.len() >= (self.names.len() as u32));
        self.pos_named.len() - (self.names.len() as u32)
    }
}

impl BcCallArgsFull<Symbol> {
    pub(crate) fn resolve(self, def: &FrozenDef) -> BcCallArgsFull<ResolvedArgName> {
        let BcCallArgsFull {
            pos_named,
            names,
            args,
            kwargs,
        } = self;
        BcCallArgsFull {
            pos_named,
            names: names
                .into_vec()
                .into_map(|(name, value)| (def.resolve_arg_name(name.as_str_hashed()), value))
                .into_boxed_slice(),
            args,
            kwargs,
        }
    }
}

impl<S: ArgSymbol> Display for BcCallArgsFull<S> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let mut first = true;
        let mut write_sep = |f: &mut Formatter| {
            if !first {
                write!(f, " ")?;
            }
            first = false;
            Ok(())
        };
        // Number of positional arguments.
        if self.pos() != 0 {
            write_sep(f)?;
            write!(f, "{}", self.pos())?;
        }
        // Named arguments.
        for (_, name) in &*self.names {
            write_sep(f)?;
            write!(f, "{}", name.as_str())?;
        }
        // Star argument?
        if self.args.is_some() {
            write_sep(f)?;
            write!(f, "*")?;
        }
        // Star-star argument?
        if self.kwargs.is_some() {
            write_sep(f)?;
            write!(f, "**")?;
        }
        Ok(())
    }
}

impl<S: ArgSymbol> BcCallArgs<S> for BcCallArgsFull<S> {
    #[inline]
    fn pop_from_stack<'a, 'v>(&'a self, stack: BcFramePtr<'v>) -> ArgumentsFull<'v, 'a, S> {
        let pos_named = stack.get_bc_slot_range(self.pos_named);
        let (pos, named) = pos_named.split_at(pos_named.len() - self.names.len());
        let args = self.args.map(|slot| stack.get_bc_slot(slot));
        let kwargs = self.kwargs.map(|slot| stack.get_bc_slot(slot));
        ArgumentsFull {
            pos,
            named,
            names: ArgNames::new(coerce(&self.names)),
            args,
            kwargs,
        }
    }
}

impl<S: ArgSymbol> BcCallArgs<S> for BcCallArgsPos {
    #[inline]
    fn pop_from_stack<'a, 'v>(&'a self, stack: BcFramePtr<'v>) -> ArgumentsFull<'v, 'a, S> {
        let pos = stack.get_bc_slot_range(self.pos);
        ArgumentsFull {
            pos,
            named: &[],
            names: ArgNames::new(&[]),
            args: None,
            kwargs: None,
        }
    }
}

impl BcCallArgsForDef for BcCallArgsFull<ResolvedArgName> {
    type Args<'v, 'a>
    = ArgumentsFull<'v, 'a, ResolvedArgName> where
        'v: 'a,
    ;

    #[inline]
    fn pop_from_stack<'a, 'v>(
        &'a self,
        stack: BcFramePtr<'v>,
    ) -> ArgumentsFull<'v, 'a, ResolvedArgName> {
        let pos_named = stack.get_bc_slot_range(self.pos_named);
        let (pos, named) = pos_named.split_at(pos_named.len() - self.names.len());
        let args = self.args.map(|slot| stack.get_bc_slot(slot));
        let kwargs = self.kwargs.map(|slot| stack.get_bc_slot(slot));
        ArgumentsFull {
            pos,
            named,
            names: ArgNames::new(coerce(&self.names)),
            args,
            kwargs,
        }
    }
}

impl BcCallArgsForDef for BcCallArgsPos {
    type Args<'v, 'a> = ArgumentsPos<'v, 'a, ResolvedArgName> where 'v: 'a;

    #[inline]
    fn pop_from_stack<'a, 'v>(
        &'a self,
        stack: BcFramePtr<'v>,
    ) -> ArgumentsPos<'v, 'a, ResolvedArgName> {
        let pos = stack.get_bc_slot_range(self.pos);
        ArgumentsPos {
            pos,
            names: PhantomData,
        }
    }
}


/*
 * 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::runtime::slots::LocalSlotId;

/// Tracker for local variables which are definitely assigned.
///
/// For example, when compiling an program like:
///
/// ```ignore
/// foo(x)
/// bar(x)
/// ```
///
/// To access `x` variable first time, when evaluating `foo(x)` we need to check
/// that `x` is assigned, and return an error if it is not.
/// But when evaluating `bar(x)` we don't need to check `x` is assigned,
/// because we know for sure it is assigned: we checked that when evaluating `foo(x)`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct BcDefinitelyAssigned {
    /// Map from local variable slot to flag indicating whether it is definitely assigned
    /// at the current program point.
    // TODO(nga): most functions have less than 64 locals, we could use a bitmap instead.
    definitely_assigned: Vec<bool>,
}

impl BcDefinitelyAssigned {
    pub(crate) fn new(local_count: u32) -> BcDefinitelyAssigned {
        BcDefinitelyAssigned {
            definitely_assigned: vec![false; local_count as usize],
        }
    }

    /// Is local variable definitely assigned at given program point?
    pub(crate) fn is_definitely_assigned(&self, local: LocalSlotId) -> bool {
        self.definitely_assigned[local.0 as usize]
    }

    /// Mark variable definitely assigned.
    ///
    /// For example, after execution of:
    ///
    /// ```ignore
    /// foo(x)
    /// ```
    ///
    /// both `foo` and `x` are definitely assigned.
    pub(crate) fn mark_definitely_assigned(&mut self, local: LocalSlotId) {
        self.definitely_assigned[local.0 as usize] = true;
    }

    /// Assert that each variable definitely assigned in self,
    /// also definitely assigned in other.
    pub(crate) fn assert_smaller_then(&self, other: &BcDefinitelyAssigned) {
        assert_eq!(
            self.definitely_assigned.len(),
            other.definitely_assigned.len()
        );
        for (&a, &b) in self
            .definitely_assigned
            .iter()
            .zip(other.definitely_assigned.iter())
        {
            assert!(b || !a)
        }
    }
}


/*
 * 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 dupe::Dupe;

/// Depth of the loop. For example,
///
/// ```text
/// def foo():
///   for i in range(10): # depth 0
///     for j in range(20): # depth 1
///       pass
/// ```
#[derive(
    Default,
    Debug,
    Copy,
    Clone,
    Dupe,
    derive_more::Display,
    Eq,
    PartialEq,
    Ord,
    PartialOrd
)]
pub(crate) struct LoopDepth(pub(crate) 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.
 */

//! Local variables and stack, in single allocation.

use std::cell::Cell;
use std::mem;
use std::mem::MaybeUninit;
use std::ptr;
use std::slice;

use dupe::Dupe;

use crate::eval::bc::for_loop::LoopDepth;
use crate::eval::bc::stack_ptr::BcSlotIn;
use crate::eval::bc::stack_ptr::BcSlotInRange;
use crate::eval::bc::stack_ptr::BcSlotOut;
use crate::eval::runtime::slots::LocalSlotIdCapturedOrNot;
use crate::eval::Evaluator;
use crate::values::Trace;
use crate::values::Tracer;
use crate::values::Value;

/// Current `def` frame (but not native function frame).
///
/// Frame memory layout:
///
/// ```text
/// [ loop_indices | BcFrame | locals | stack ]
///   BcFramePtr points here ^
/// ```
#[repr(C)]
struct BcFrame<'v> {
    /// Number of local slots.
    local_count: u32,
    /// Number of stack slots.
    max_stack_size: u32,
    /// Max number of nested for loops.
    max_loop_depth: LoopDepth,
    /// `local_count` local slots followed by `max_stack_size` stack slots.
    slots: [Option<Value<'v>>; 0],
}

const _: () = assert!(mem::size_of::<BcFrame>() % mem::size_of::<usize>() == 0);
const _: () = assert!(mem::align_of::<BcFrame>() == mem::align_of::<usize>());

#[derive(Copy, Clone, Dupe)]
pub(crate) struct BcFramePtr<'v> {
    /// Pointer to the `slots` field of `BcFrame`.
    ///
    /// We could store `BcFrame` pointer here, but since the most common
    /// data accessed is slots, storing `slots` pointer is slightly more efficient:
    /// no need to add a constant when accessing the field.
    slots_ptr: *mut Option<Value<'v>>,
}

impl<'v> PartialEq for BcFramePtr<'v> {
    fn eq(&self, other: &Self) -> bool {
        ptr::eq(self.slots_ptr, other.slots_ptr)
    }
}

impl<'v> BcFramePtr<'v> {
    pub(crate) fn null() -> BcFramePtr<'v> {
        BcFramePtr {
            slots_ptr: ptr::null_mut(),
        }
    }

    /// Is this frame allocated or constructed empty?
    pub(crate) fn is_inititalized(self) -> bool {
        !self.slots_ptr.is_null()
    }

    #[inline(always)]
    fn frame<'a>(self) -> &'a BcFrame<'v> {
        debug_assert!(self.is_inititalized());
        unsafe {
            let frame = (self.slots_ptr as *mut u8).sub(BcFrame::offset_of_slots()) as *mut BcFrame;
            &*frame
        }
    }

    #[inline(always)]
    fn frame_mut(&mut self) -> &mut BcFrame<'v> {
        debug_assert!(self.is_inititalized());
        unsafe {
            let frame = (self.slots_ptr as *mut u8).sub(BcFrame::offset_of_slots()) as *mut BcFrame;
            &mut *frame
        }
    }

    pub(crate) fn get_slot_slow(self, slot: LocalSlotIdCapturedOrNot) -> Option<Value<'v>> {
        assert!(slot.0 < self.frame().local_count);
        self.get_slot(slot)
    }

    #[inline(always)]
    pub(crate) fn get_slot(self, slot: LocalSlotIdCapturedOrNot) -> Option<Value<'v>> {
        self.frame().get_slot(slot)
    }

    pub(crate) fn set_slot_slow(self, slot: LocalSlotIdCapturedOrNot, value: Value<'v>) {
        assert!(slot.0 < self.frame().local_count);
        self.set_slot(slot, value);
    }

    #[inline(always)]
    pub(crate) fn set_slot(mut self, slot: LocalSlotIdCapturedOrNot, value: Value<'v>) {
        self.frame_mut().set_slot(slot, value)
    }

    #[inline(always)]
    pub(crate) fn get_bc_slot(self, slot: BcSlotIn) -> Value<'v> {
        self.frame().get_bc_slot(slot)
    }

    #[inline(always)]
    pub(crate) fn set_bc_slot(mut self, slot: BcSlotOut, value: Value<'v>) {
        self.frame_mut().set_bc_slot(slot, value)
    }

    #[inline(always)]
    pub(crate) fn get_bc_slot_range<'a>(self, slots: BcSlotInRange) -> &'a [Value<'v>] {
        self.frame().get_bc_slot_range(slots)
    }

    #[inline(always)]
    pub(crate) fn get_iter_index(self, loop_depth: LoopDepth) -> usize {
        self.frame().get_iter_index(loop_depth)
    }

    #[inline(always)]
    pub(crate) fn set_iter_index(mut self, loop_depth: LoopDepth, index: usize) {
        self.frame_mut().set_iter_index(loop_depth, index)
    }

    pub(crate) fn max_stack_size(self) -> u32 {
        self.frame().max_stack_size
    }

    #[inline(always)]
    pub(crate) fn locals(&self) -> &[Cell<Option<Value<'v>>>] {
        self.frame().locals()
    }
}

impl<'v> BcFrame<'v> {
    #[inline(always)]
    fn offset_of_slots() -> usize {
        let offset = memoffset::offset_of!(BcFrame<'v>, slots);
        assert_eq!(mem::size_of::<BcFrame<'v>>(), offset);
        offset
    }

    #[inline(always)]
    fn frame_ptr(&mut self) -> BcFramePtr<'v> {
        unsafe {
            BcFramePtr {
                slots_ptr: (self as *mut _ as *mut u8).add(Self::offset_of_slots()) as *mut _,
            }
        }
    }

    #[inline(always)]
    fn locals(&self) -> &[Cell<Option<Value<'v>>>] {
        unsafe {
            slice::from_raw_parts(
                self.slots.as_ptr() as *const Cell<Option<Value>>,
                self.local_count as usize,
            )
        }
    }

    #[inline(always)]
    fn locals_mut(&mut self) -> &mut [Option<Value<'v>>] {
        unsafe { slice::from_raw_parts_mut(self.slots.as_mut_ptr(), self.local_count as usize) }
    }

    #[inline(always)]
    fn locals_uninit(&mut self) -> &mut [MaybeUninit<Option<Value<'v>>>] {
        unsafe {
            slice::from_raw_parts_mut(self.slots.as_mut_ptr() as *mut _, self.local_count as usize)
        }
    }

    #[inline(always)]
    fn stack_uninit(&mut self) -> &mut [MaybeUninit<Value<'v>>] {
        unsafe {
            slice::from_raw_parts_mut(
                self.slots.as_mut_ptr().add(self.local_count as usize) as *mut _,
                self.max_stack_size as usize,
            )
        }
    }

    /// Initialize frame after it was allocated.
    #[inline(always)]
    fn init(&mut self) {
        self.locals_uninit().fill(MaybeUninit::new(None));

        if cfg!(debug_assertions) {
            // Write junk to the stack to trigger memory error if the stack is used incorrectly.
            unsafe {
                // Any bit pattern would do except
                // * a bit pattern which would make valid int tag (0b_xxxx_x010)
                // * zeros, which would be interpreted as uninitialized if copied to a local
                let byte = 0xef;

                let start = self.stack_uninit().as_ptr() as *mut u8;
                ptr::write_bytes(
                    start,
                    byte,
                    (self.max_stack_size as usize) * mem::size_of::<Value>(),
                );
            }
        }
    }

    /// Gets a local variable. Returns None to indicate the variable is not yet assigned.
    #[inline(always)]
    pub(crate) fn get_slot(&self, slot: LocalSlotIdCapturedOrNot) -> Option<Value<'v>> {
        debug_assert!(slot.0 < self.local_count);
        unsafe { self.slots.as_ptr().add(slot.0 as usize).read() }
    }

    /// Get a stack slot.
    #[inline(always)]
    pub(crate) fn get_bc_slot(&self, slot: BcSlotIn) -> Value<'v> {
        debug_assert!(slot.get().0 < self.local_count + self.max_stack_size);
        unsafe {
            let option = self.slots.as_ptr().add(slot.get().0 as usize).read();
            // Slot must be always initialized.
            option.unwrap_unchecked()
        }
    }

    #[inline(always)]
    pub(crate) fn set_slot(&mut self, slot: LocalSlotIdCapturedOrNot, value: Value<'v>) {
        debug_assert!(slot.0 < self.local_count);
        unsafe {
            self.slots
                .as_mut_ptr()
                .add(slot.0 as usize)
                .write(Some(value))
        }
    }

    #[inline(always)]
    pub(crate) fn get_bc_slot_range(&self, slots: BcSlotInRange) -> &[Value<'v>] {
        debug_assert!(slots.end.get().0 <= self.local_count + self.max_stack_size);
        unsafe {
            let start = self.slots.as_ptr().add(slots.start.get().0 as usize);
            let start = start as *const Value;
            slice::from_raw_parts(start, slots.len() as usize)
        }
    }

    #[inline(always)]
    pub(crate) fn set_bc_slot(&mut self, slot: BcSlotOut, value: Value<'v>) {
        debug_assert!(slot.get().0 < self.local_count + self.max_stack_size);
        unsafe {
            self.slots
                .as_mut_ptr()
                .add(slot.get().0 as usize)
                .write(Some(value))
        }
    }

    #[inline(always)]
    pub(crate) fn set_iter_index(&mut self, iter_index: LoopDepth, index: usize) {
        debug_assert!(iter_index < self.max_loop_depth);
        unsafe {
            (self as *mut BcFrame as *mut usize)
                .sub(iter_index.0 as usize)
                .sub(1)
                .write(index);
        }
    }

    #[inline(always)]
    pub(crate) fn get_iter_index(&self, iter_index: LoopDepth) -> usize {
        debug_assert!(iter_index < self.max_loop_depth);
        unsafe {
            (self as *const BcFrame as *const usize)
                .sub(iter_index.0 as usize)
                .sub(1)
                .read()
        }
    }
}

unsafe impl<'v> Trace<'v> for BcFrame<'v> {
    fn trace(&mut self, tracer: &Tracer<'v>) {
        self.locals_mut().trace(tracer);
        // Note this does not trace the stack.
        // GC can be performed only when the stack is empty.
    }
}

unsafe impl<'v> Trace<'v> for BcFramePtr<'v> {
    fn trace(&mut self, tracer: &Tracer<'v>) {
        self.frame_mut().trace(tracer);
    }
}

#[inline(always)]
fn alloca_raw<'v, 'a, R>(
    eval: &mut Evaluator<'v, 'a>,
    local_count: u32,
    max_stack_size: u32,
    max_loop_depth: LoopDepth,
    k: impl FnOnce(&mut Evaluator<'v, 'a>, BcFramePtr<'v>) -> R,
) -> R {
    assert_eq!(mem::align_of::<BcFrame>() % mem::size_of::<usize>(), 0);
    assert_eq!(mem::size_of::<Value>(), mem::size_of::<usize>());
    let alloca_size_in_words = mem::size_of::<BcFrame>() / mem::size_of::<usize>()
        + (local_count as usize)
        + (max_stack_size as usize)
        + (max_loop_depth.0 as usize);
    eval.alloca_uninit::<usize, _, _>(alloca_size_in_words, |slice, eval| unsafe {
        let frame_ptr = slice.as_mut_ptr().add(max_loop_depth.0 as usize) as *mut BcFrame;
        *(frame_ptr) = BcFrame {
            local_count,
            max_stack_size,
            max_loop_depth,
            slots: [],
        };

        k(eval, (*frame_ptr).frame_ptr())
    })
}

/// Allocate a frame and store it in the evaluator.
///
/// After callback finishes, previous frame is restored.
#[inline(always)]
pub(crate) fn alloca_frame<'v, 'a, R>(
    eval: &mut Evaluator<'v, 'a>,
    local_count: u32,
    max_stack_size: u32,
    loop_depth: LoopDepth,
    k: impl FnOnce(&mut Evaluator<'v, 'a>) -> R,
) -> R {
    alloca_raw(
        eval,
        local_count,
        max_stack_size,
        loop_depth,
        |eval, mut frame| {
            // TODO(nga): no need to fill the slots for parameters.
            frame.frame_mut().init();
            let old_frame = mem::replace(&mut eval.current_frame, frame);
            let r = k(eval);
            eval.current_frame = old_frame;
            r
        },
    )
}


/*
 * 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.
 */

//! Utility to make stateful debug assertions easier.

use std::cmp::Ordering;
use std::marker;

use dupe::Dupe;

/// Store `T` is debug assertions enabled, ZST otherwise.
///
/// This object is easier to work with than explicitly marking code with
/// `#[cfg(debug_assertions)]`: cfg attributes are not easy to write
/// without mistakes where code works, and no warnings in both debug and release.
///
/// In other words, this type converts cfg to types.
///
/// This type implements [`Eq`] which always returns `true`,
/// and [`Ord`] which always returns [`Ordering::Equal`], so
/// * this type is easy to include in structs which do derives
/// * behavior of this object does not depend on `<T>`
/// * behavior does not depend on whether debugging assertions enabled or not
#[derive(Debug, Default, Copy, Clone, Dupe)]
// In release build this structure is DST,
// so gazebo suggests implementing `Dupe` for any `<T>`. T102920913.
#[cfg_attr(feature = "gazebo_lint", allow(gazebo_lint_impl_dupe))]
pub(crate) struct IfDebug<T> {
    #[cfg(debug_assertions)]
    value: T,
    _marker: marker::PhantomData<T>,
}

impl<T> IfDebug<T> {
    /// Store a value if debug assertions enabled, drop otherwise.
    pub(crate) fn new(value: T) -> IfDebug<T> {
        Self::new_if_debug(|| value)
    }

    /// Store a value if debug assertions enabled, drop otherwise.
    pub(crate) fn new_if_debug(init: impl FnOnce() -> T) -> IfDebug<T> {
        #[cfg(not(debug_assertions))]
        drop(init);
        IfDebug {
            #[cfg(debug_assertions)]
            value: { init() },
            _marker: marker::PhantomData,
        }
    }

    /// Get a reference to stored value is assertions enabled, `None` otherwise.
    pub(crate) fn get_ref(&self) -> Option<&T> {
        #[cfg(debug_assertions)]
        return Some(&self.value);
        #[cfg(not(debug_assertions))]
        return None;
    }

    /// Get a reference to stored value is assertions enabled, panic otherwise.
    pub(crate) fn get_ref_if_debug(&self) -> &T {
        self.get_ref().expect("assertions disabled")
    }

    /// Invoke a function if debug enabled.
    pub(crate) fn if_debug(&self, f: impl FnOnce(&T)) {
        if let Some(value) = self.get_ref() {
            f(value);
        }
    }
}

impl<T> PartialEq for IfDebug<T> {
    fn eq(&self, _other: &Self) -> bool {
        true
    }
}

impl<T> Eq for IfDebug<T> {}

impl<T> PartialOrd for IfDebug<T> {
    fn partial_cmp(&self, _other: &Self) -> Option<Ordering> {
        Some(Ordering::Equal)
    }
}

impl<T> Ord for IfDebug<T> {
    fn cmp(&self, _other: &Self) -> Ordering {
        Ordering::Equal
    }
}


/*
 * 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.
 */

//! Define the bytecode instruction.

use crate::eval::bc::addr::BcPtrAddr;
use crate::eval::bc::frame::BcFramePtr;
use crate::eval::bc::instr_arg::BcInstrArg;
use crate::eval::Evaluator;
use crate::values::Value;

/// Result of instruction evaluation.
// This is more efficient than `Result<R, EvalException>`,
// see in compiler explorer: https://rust.godbolt.org/z/81rj4jb4M
#[must_use]
pub(crate) enum InstrControl<'v, 'b> {
    /// Go to address.
    Next(BcPtrAddr<'b>),
    /// Return from the function.
    Return(Value<'v>),
    /// Error. This can be either any `anyhow::Error` or `Diagnostics`.
    /// If it is the former, error span will be added from instruction metadata.
    Err(anyhow::Error),
}

pub(crate) trait BcInstr: Sized + 'static {
    /// Fixed instruction argument (which may encode additional arguments
    /// pushed or popped from the stack by the instruction implementation).
    type Arg: BcInstrArg;

    /// Execute the instruction.
    fn run<'v, 'b>(
        eval: &mut Evaluator<'v, '_>,
        frame: BcFramePtr<'v>,
        ip: BcPtrAddr<'b>,
        arg: &Self::Arg,
    ) -> InstrControl<'v, 'b>;
}


/*
 * 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.
 */

//! Instructions serialized in byte array.

use std::collections::HashSet;
use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Write;
use std::iter;
use std::mem;
use std::ptr;
use std::slice;

use either::Either;

use crate::eval::bc::addr::BcAddr;
use crate::eval::bc::addr::BcAddrOffset;
use crate::eval::bc::addr::BcPtrAddr;
use crate::eval::bc::instr::BcInstr;
use crate::eval::bc::instr_impl::InstrEnd;
use crate::eval::bc::instr_impl::InstrIter;
use crate::eval::bc::opcode::BcOpcode;
use crate::eval::bc::opcode::BcOpcodeHandler;
use crate::eval::bc::repr::BcInstrHeader;
use crate::eval::bc::repr::BcInstrRepr;
use crate::eval::bc::repr::BC_INSTR_ALIGN;
use crate::eval::bc::slow_arg::BcInstrEndArg;
use crate::eval::bc::slow_arg::BcInstrSlowArg;
use crate::eval::bc::writer::BcStatementLocations;
use crate::values::FrozenRef;
use crate::values::FrozenStringValue;

impl BcOpcode {
    /// Drop instruction at given address.
    unsafe fn drop_in_place(self, ptr: BcPtrAddr) {
        struct HandlerImpl<'b> {
            ptr: BcPtrAddr<'b>,
        }

        impl BcOpcodeHandler<()> for HandlerImpl<'_> {
            fn handle<I: BcInstr>(self) {
                let HandlerImpl { ptr } = self;
                let instr = ptr.get_instr_mut::<I>();
                unsafe {
                    ptr::drop_in_place(instr);
                }
            }
        }

        self.dispatch(HandlerImpl { ptr });
    }
}

/// Invoke drop for instructions in the buffer.
unsafe fn drop_instrs(instrs: &[u64]) {
    let end = BcPtrAddr::for_slice_end(instrs);
    let mut ptr = BcPtrAddr::for_slice_start(instrs);
    while ptr != end {
        assert!(ptr < end);
        let opcode = ptr.get_opcode();
        opcode.drop_in_place(ptr);
        ptr = ptr.add(opcode.size_of_repr());
    }
}

/// Statically allocate a valid instruction buffer micro-optimization.
///
/// Valid bytecode must end with `EndOfBc` instruction, otherwise evaluation overruns
/// the instruction buffer.
///
/// `BcInstrs` type need to have `Default` (it is convenient).
///
/// Allocating a vec in `BcInstrs::default` is non-free.
///
/// Assertion that `BcInstrs::instrs` is not empty is cheap but not free.
///
/// But if `BcInstrs::instrs` is `Either` allocated instructions or a pointer to statically
/// allocated instructions, then both `BcInstrs::default` is free
/// and evaluation start [is free](https://rust.godbolt.org/z/3nEhWGo4Y).
fn empty_instrs() -> &'static [u64] {
    static END_OF_BC: BcInstrRepr<InstrEnd> = BcInstrRepr {
        header: BcInstrHeader::for_opcode(BcOpcode::End),
        arg: BcInstrEndArg {
            end_addr: BcAddr(0),
            slow_args: Vec::new(),
            local_names: FrozenRef::new(&[]),
        },
        _align: [],
    };
    unsafe {
        slice::from_raw_parts(
            &END_OF_BC as *const BcInstrRepr<_> as *const u64,
            mem::size_of_val(&END_OF_BC) / mem::size_of::<u64>(),
        )
    }
}

pub(crate) struct BcInstrs {
    // We use `usize` here to guarantee the buffer is properly aligned
    // to store `BcInstrLayout`.
    instrs: Either<Box<[u64]>, &'static [u64]>,
    pub(crate) stmt_locs: BcStatementLocations,
}

/// Raw instructions writer.
///
/// Higher level wrapper is `BcWriter`.
pub(crate) struct BcInstrsWriter {
    pub(crate) instrs: Vec<u64>,
}

impl Default for BcInstrs {
    fn default() -> Self {
        Self::for_instrs(Either::Right(empty_instrs()), BcStatementLocations::new())
    }
}

impl Drop for BcInstrs {
    fn drop(&mut self) {
        match &self.instrs {
            Either::Left(heap_allocated) => unsafe {
                drop_instrs(heap_allocated);
            },
            Either::Right(_statically_allocated) => {}
        }
    }
}

impl Drop for BcInstrsWriter {
    fn drop(&mut self) {
        unsafe {
            drop_instrs(&self.instrs);
        }
    }
}

pub(crate) struct PatchAddr {
    pub(crate) instr_start: BcAddr,
    pub(crate) arg: BcAddr,
}

impl BcInstrs {
    pub(crate) fn start_ptr(&self) -> BcPtrAddr {
        BcPtrAddr::for_slice_start(&self.instrs)
    }

    pub(crate) fn for_instrs(
        instrs: Either<Box<[u64]>, &'static [u64]>,
        stmt_locs: BcStatementLocations,
    ) -> Self {
        Self { instrs, stmt_locs }
    }

    pub(crate) fn end(&self) -> BcAddr {
        BcAddr(
            self.instrs
                .len()
                .checked_mul(mem::size_of::<u64>())
                .unwrap()
                .try_into()
                .unwrap(),
        )
    }

    pub(crate) fn end_ptr(&self) -> BcPtrAddr {
        self.start_ptr().offset(self.end())
    }

    #[cfg(test)]
    pub(crate) fn opcodes(&self) -> Vec<BcOpcode> {
        let mut opcodes = Vec::new();
        let end = BcPtrAddr::for_slice_end(&self.instrs);
        let mut ptr = BcPtrAddr::for_slice_start(&self.instrs);
        while ptr != end {
            assert!(ptr < end);
            let opcode = ptr.get_opcode();
            opcodes.push(opcode);
            ptr = ptr.add(opcode.size_of_repr());
        }
        opcodes
    }

    fn iter(&self) -> impl Iterator<Item = (BcPtrAddr, BcAddr)> {
        let mut next_ptr = self.start_ptr();
        iter::from_fn(move || {
            assert!(next_ptr <= self.end_ptr());
            if next_ptr == self.end_ptr() {
                None
            } else {
                let ptr = next_ptr;
                let ip = ptr.offset_from(self.start_ptr());
                next_ptr = next_ptr.add(ptr.get_opcode().size_of_repr());
                Some((ptr, ip))
            }
        })
    }

    fn end_arg(&self) -> Option<&BcInstrEndArg> {
        self.iter()
            .find_map(|(ptr, _ip)| ptr.get_instr_checked::<InstrEnd>().map(|i| &i.arg))
    }

    pub(crate) fn fmt_impl(&self, f: &mut dyn Write, newline: bool) -> fmt::Result {
        let end_arg = self.end_arg();

        let mut loop_ends = Vec::new();
        let mut jump_targets = HashSet::new();
        for (ptr, ip) in self.iter() {
            ptr.get_opcode().visit_jump_addr(ptr, ip, &mut |jump_addr| {
                jump_targets.insert(jump_addr);
            });
        }
        for (ptr, ip) in self.iter() {
            if ptr != self.start_ptr() && !newline {
                write!(f, "; ")?;
            }

            if loop_ends.last() == Some(&ip) {
                loop_ends.pop().unwrap();
            }
            let opcode = ptr.get_opcode();
            if !jump_targets.is_empty() {
                if jump_targets.contains(&ip) {
                    write!(f, ">")?;
                } else if newline {
                    write!(f, " ")?;
                }
            }
            if newline {
                for _ in &loop_ends {
                    write!(f, "  ")?;
                }
            }
            write!(f, "{}: {:?}", ip.0, opcode)?;
            if opcode != BcOpcode::End {
                // `End` args are too verbose and not really instruction args.
                opcode.fmt_append_arg(ptr, ip, end_arg, f)?;
            }
            if newline {
                writeln!(f)?;
            }
            if opcode == BcOpcode::Iter {
                let for_loop = ptr.get_instr::<InstrIter>();
                loop_ends.push(ip.offset(for_loop.arg.4));
            }
        }
        Ok(())
    }

    pub(crate) fn dump_debug(&self) -> String {
        let mut w = String::new();
        self.fmt_impl(&mut w, true).unwrap();
        w
    }
}

impl Display for BcInstrs {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        self.fmt_impl(f, false)
    }
}

impl BcInstrsWriter {
    pub(crate) fn new() -> BcInstrsWriter {
        BcInstrsWriter { instrs: Vec::new() }
    }

    fn instrs_len_bytes(&self) -> usize {
        self.instrs
            .len()
            .checked_mul(mem::size_of::<u64>())
            .unwrap()
    }

    pub(crate) fn ip(&self) -> BcAddr {
        BcAddr(self.instrs_len_bytes().try_into().unwrap())
    }

    pub(crate) fn write<I: BcInstr>(&mut self, arg: I::Arg) -> (BcAddr, *const I::Arg) {
        let repr = BcInstrRepr::<I>::new(arg);
        assert!(mem::size_of_val(&repr) % mem::size_of::<u64>() == 0);

        let ip = self.ip();

        let offset_bytes = self.instrs_len_bytes();
        self.instrs.resize(
            self.instrs.len() + mem::size_of_val(&repr) / mem::size_of::<u64>(),
            0,
        );
        unsafe {
            let ptr =
                (self.instrs.as_mut_ptr() as *mut u8).add(offset_bytes) as *mut BcInstrRepr<I>;
            ptr::write(ptr, repr);
            (ip, &(*ptr).arg)
        }
    }

    pub(crate) fn addr_to_patch(
        &self,
        instr_start: BcAddr,
        addr: *const BcAddrOffset,
    ) -> PatchAddr {
        unsafe { assert_eq!(*addr, BcAddrOffset::FORWARD) };
        let offset_bytes =
            unsafe { (addr as *const u8).offset_from(self.instrs.as_ptr() as *const u8) };
        assert!((offset_bytes as usize) < self.instrs_len_bytes());
        PatchAddr {
            instr_start,
            arg: BcAddr(offset_bytes as u32),
        }
    }

    pub(crate) fn patch_addr(&mut self, addr: PatchAddr) {
        unsafe {
            let mem_addr =
                (self.instrs.as_mut_ptr() as *mut u8).add(addr.arg.0 as usize) as *mut BcAddrOffset;
            assert!(*mem_addr == BcAddrOffset::FORWARD);
            *mem_addr = self.ip().offset_from(addr.instr_start);
            debug_assert!(((*mem_addr).0 as usize) % BC_INSTR_ALIGN == 0);
        }
    }

    pub(crate) fn finish(
        mut self,
        slow_args: Vec<(BcAddr, BcInstrSlowArg)>,
        stmt_locs: BcStatementLocations,
        local_names: FrozenRef<'static, [FrozenStringValue]>,
    ) -> BcInstrs {
        self.write::<InstrEnd>(BcInstrEndArg {
            end_addr: self.ip(),
            slow_args,
            local_names,
        });
        // We cannot destructure `self` to fetch `instrs` because `Self` has `drop,
        // so we `mem::take`.
        let instrs = mem::take(&mut self.instrs);
        let instrs = instrs.into_boxed_slice();
        assert!((instrs.as_ptr() as usize) % BC_INSTR_ALIGN == 0);
        BcInstrs::for_instrs(Either::Left(instrs), stmt_locs)
    }
}

#[cfg(test)]
mod tests {
    use std::mem;

    use crate::const_frozen_string;
    use crate::eval::bc::instr_impl::InstrConst;
    use crate::eval::bc::instr_impl::InstrPossibleGc;
    use crate::eval::bc::instr_impl::InstrReturn;
    use crate::eval::bc::instrs::BcInstrs;
    use crate::eval::bc::instrs::BcInstrsWriter;
    use crate::eval::bc::stack_ptr::BcSlot;
    use crate::eval::bc::writer::BcStatementLocations;
    use crate::values::FrozenHeap;
    use crate::values::FrozenValue;

    #[test]
    fn write() {
        let mut bc = BcInstrsWriter::new();
        bc.write::<InstrPossibleGc>(());
        assert_eq!(1, bc.instrs.len());
        bc.write::<InstrPossibleGc>(());
        assert_eq!(2, bc.instrs.len());
    }

    /// Test `BcInstrs::default()` produces something valid.
    #[test]
    fn default() {
        assert_eq!("0: End", BcInstrs::default().to_string());
    }

    #[test]
    fn display() {
        let heap = FrozenHeap::new();
        let local_names = heap
            .alloc_any_display_from_debug(vec![const_frozen_string!("abc")])
            .map(|s| s.as_slice());
        let mut bc = BcInstrsWriter::new();
        bc.write::<InstrConst>((FrozenValue::new_bool(true), BcSlot(0).to_out()));
        bc.write::<InstrReturn>(BcSlot(0).to_in());
        let bc = bc.finish(Vec::new(), BcStatementLocations::new(), local_names);
        if mem::size_of::<usize>() == 8 {
            assert_eq!(
                "0: Const True &abc; 24: Return &abc; 32: End",
                bc.to_string()
            );
            assert_eq!(
                "0: Const True &abc\n24: Return &abc\n32: End\n",
                bc.dump_debug()
            );
        } else if mem::size_of::<usize>() == 4 {
            // Starlark doesn't work now on 32-bit CPU
        } else {
            panic!("unknown word size: {}", mem::size_of::<usize>());
        }
    }
}


/*
 * 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.

use std::fmt;
use std::fmt::Display;
use std::fmt::Formatter;
use std::fmt::Write;

use itertools::Itertools;

use crate::collections::symbol_map::Symbol;
use crate::collections::Hashed;
use crate::collections::SmallMap;
use crate::environment::slots::ModuleSlotId;
use crate::eval::bc::addr::BcAddr;
use crate::eval::bc::addr::BcAddrOffset;
use crate::eval::bc::addr::BcAddrOffsetNeg;
use crate::eval::bc::addr::BcPtrAddr;
use crate::eval::bc::call::BcCallArgsFull;
use crate::eval::bc::call::BcCallArgsPos;
use crate::eval::bc::for_loop::LoopDepth;
use crate::eval::bc::instr::BcInstr;
use crate::eval::bc::instr_impl::InstrDefData;
use crate::eval::bc::native_function::BcNativeFunction;
use crate::eval::bc::opcode::BcOpcode;
use crate::eval::bc::opcode::BcOpcodeHandler;
use crate::eval::bc::slow_arg::BcInstrEndArg;
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::BcSlotInRangeFrom;
use crate::eval::bc::stack_ptr::BcSlotOut;
use crate::eval::runtime::arguments::ArgSymbol;
use crate::eval::runtime::frame_span::FrameSpan;
use crate::eval::runtime::slots::LocalCapturedSlotId;
use crate::eval::runtime::slots::LocalSlotId;
use crate::values::layout::value_not_special::FrozenValueNotSpecial;
use crate::values::types::known_methods::KnownMethod;
use crate::values::typing::TypeCompiled;
use crate::values::FrozenRef;
use crate::values::FrozenValue;
use crate::values::FrozenValueTyped;
use crate::values::StarlarkValue;

/// Truncate value if it is too long.
struct TruncateValueRepr(FrozenValue);

impl Display for TruncateValueRepr {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let repr = self.0.to_value().to_repr();
        // Truncate too long constants (like dicts with hundreds of elements).
        if repr.len() > 100 {
            write!(f, "<{}>", self.0.to_value().get_type())
        } else {
            write!(f, "{}", repr)
        }
    }
}

/// Instruction fixed argument.
pub(crate) trait BcInstrArg: 'static {
    /// Append space then append the argument, or append nothing if the argument is empty.
    fn fmt_append(
        param: &Self,
        ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result;
    /// Collect instruction jump addresses.
    fn visit_jump_addr(param: &Self, ip: BcAddr, consumer: &mut dyn FnMut(BcAddr));
}

impl BcInstrArg for () {
    fn fmt_append(
        _param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        _f: &mut dyn Write,
    ) -> fmt::Result {
        Ok(())
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for u32 {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", param)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for i32 {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", param)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl<A: BcInstrArg, B: BcInstrArg> BcInstrArg for (A, B) {
    fn fmt_append(
        (a, b): &Self,
        ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        A::fmt_append(a, ip, end_arg, f)?;
        B::fmt_append(b, ip, end_arg, f)?;
        Ok(())
    }

    fn visit_jump_addr((a, b): &Self, ip: BcAddr, consumer: &mut dyn FnMut(BcAddr)) {
        BcInstrArg::visit_jump_addr(a, ip, consumer);
        BcInstrArg::visit_jump_addr(b, ip, consumer);
    }
}

impl<A: BcInstrArg, B: BcInstrArg, C: BcInstrArg> BcInstrArg for (A, B, C) {
    fn fmt_append(
        (a, b, c): &Self,
        ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        A::fmt_append(a, ip, end_arg, f)?;
        B::fmt_append(b, ip, end_arg, f)?;
        C::fmt_append(c, ip, end_arg, f)?;
        Ok(())
    }

    fn visit_jump_addr((a, b, c): &Self, ip: BcAddr, consumer: &mut dyn FnMut(BcAddr)) {
        BcInstrArg::visit_jump_addr(a, ip, consumer);
        BcInstrArg::visit_jump_addr(b, ip, consumer);
        BcInstrArg::visit_jump_addr(c, ip, consumer);
    }
}

#[allow(clippy::many_single_char_names)]
impl<A: BcInstrArg, B: BcInstrArg, C: BcInstrArg, D: BcInstrArg> BcInstrArg for (A, B, C, D) {
    fn fmt_append(
        (a, b, c, d): &Self,
        ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        A::fmt_append(a, ip, end_arg, f)?;
        B::fmt_append(b, ip, end_arg, f)?;
        C::fmt_append(c, ip, end_arg, f)?;
        D::fmt_append(d, ip, end_arg, f)?;
        Ok(())
    }

    fn visit_jump_addr((a, b, c, d): &Self, ip: BcAddr, consumer: &mut dyn FnMut(BcAddr)) {
        BcInstrArg::visit_jump_addr(a, ip, consumer);
        BcInstrArg::visit_jump_addr(b, ip, consumer);
        BcInstrArg::visit_jump_addr(c, ip, consumer);
        BcInstrArg::visit_jump_addr(d, ip, consumer);
    }
}

#[allow(clippy::many_single_char_names)]
impl<A: BcInstrArg, B: BcInstrArg, C: BcInstrArg, D: BcInstrArg, E: BcInstrArg> BcInstrArg
    for (A, B, C, D, E)
{
    fn fmt_append(
        (a, b, c, d, e): &Self,
        ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        A::fmt_append(a, ip, end_arg, f)?;
        B::fmt_append(b, ip, end_arg, f)?;
        C::fmt_append(c, ip, end_arg, f)?;
        D::fmt_append(d, ip, end_arg, f)?;
        E::fmt_append(e, ip, end_arg, f)?;
        Ok(())
    }

    fn visit_jump_addr((a, b, c, d, e): &Self, ip: BcAddr, consumer: &mut dyn FnMut(BcAddr)) {
        BcInstrArg::visit_jump_addr(a, ip, consumer);
        BcInstrArg::visit_jump_addr(b, ip, consumer);
        BcInstrArg::visit_jump_addr(c, ip, consumer);
        BcInstrArg::visit_jump_addr(d, ip, consumer);
        BcInstrArg::visit_jump_addr(e, ip, consumer);
    }
}

#[allow(clippy::many_single_char_names)]
impl<A: BcInstrArg, B: BcInstrArg, C: BcInstrArg, D: BcInstrArg, E: BcInstrArg, F: BcInstrArg>
    BcInstrArg for (A, B, C, D, E, F)
{
    fn fmt_append(
        (a, b, c, d, e, f): &Self,
        ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        w: &mut dyn Write,
    ) -> fmt::Result {
        A::fmt_append(a, ip, end_arg, w)?;
        B::fmt_append(b, ip, end_arg, w)?;
        C::fmt_append(c, ip, end_arg, w)?;
        D::fmt_append(d, ip, end_arg, w)?;
        E::fmt_append(e, ip, end_arg, w)?;
        F::fmt_append(f, ip, end_arg, w)?;
        Ok(())
    }

    fn visit_jump_addr((a, b, c, d, e, f): &Self, ip: BcAddr, consumer: &mut dyn FnMut(BcAddr)) {
        BcInstrArg::visit_jump_addr(a, ip, consumer);
        BcInstrArg::visit_jump_addr(b, ip, consumer);
        BcInstrArg::visit_jump_addr(c, ip, consumer);
        BcInstrArg::visit_jump_addr(d, ip, consumer);
        BcInstrArg::visit_jump_addr(e, ip, consumer);
        BcInstrArg::visit_jump_addr(f, ip, consumer);
    }
}

impl<A: BcInstrArg, const N: usize> BcInstrArg for [A; N] {
    fn fmt_append(
        param: &Self,
        ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        for a in param {
            A::fmt_append(a, ip, end_arg, f)?;
        }
        Ok(())
    }

    fn visit_jump_addr(param: &Self, ip: BcAddr, consumer: &mut dyn FnMut(BcAddr)) {
        for a in param {
            BcInstrArg::visit_jump_addr(a, ip, consumer);
        }
    }
}

impl BcInstrArg for BcAddrOffset {
    fn fmt_append(
        param: &Self,
        ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", ip.offset(*param).0)
    }

    fn visit_jump_addr(param: &Self, ip: BcAddr, consumer: &mut dyn FnMut(BcAddr)) {
        consumer(ip.offset(*param));
    }
}

impl BcInstrArg for BcAddrOffsetNeg {
    fn fmt_append(
        param: &Self,
        ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", ip.offset_neg(*param).0)
    }

    fn visit_jump_addr(param: &Self, ip: BcAddr, consumer: &mut dyn FnMut(BcAddr)) {
        consumer(ip.offset_neg(*param));
    }
}

impl BcInstrArg for FrozenValue {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", TruncateValueRepr(*param))
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for FrozenValueNotSpecial {
    fn fmt_append(
        param: &Self,
        ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        FrozenValue::fmt_append(&param.to_frozen_value(), ip, end_arg, f)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for TypeCompiled<FrozenValue> {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", param)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl<T: BcInstrArg> BcInstrArg for Option<T> {
    fn fmt_append(
        param: &Self,
        ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        match param {
            None => write!(f, " ()"),
            Some(v) => T::fmt_append(v, ip, end_arg, f),
        }
    }

    fn visit_jump_addr(param: &Self, ip: BcAddr, consumer: &mut dyn FnMut(BcAddr)) {
        if let Some(param) = param {
            T::visit_jump_addr(param, ip, consumer);
        }
    }
}

impl BcInstrArg for String {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, "{:?}", param)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl<T: Display> BcInstrArg for FrozenRef<'static, T>
where
    FrozenRef<'static, T>: Copy,
{
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", param.as_ref())
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl<T: Display> BcInstrArg for FrozenRef<'static, [T]>
where
    FrozenRef<'static, T>: Copy,
{
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(
            f,
            " [{}]",
            param.iter().map(|v| format!("{}", v)).join(", ")
        )
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl<T: StarlarkValue<'static>> BcInstrArg for FrozenValueTyped<'static, T> {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", TruncateValueRepr(param.to_frozen_value()))
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for BcNativeFunction {
    fn fmt_append(
        param: &Self,
        ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        BcInstrArg::fmt_append(&param.fun(), ip, end_arg, f)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

struct BcSlotDisplay<'a>(BcSlot, Option<&'a BcInstrEndArg>);

impl<'a> Display for BcSlotDisplay<'a> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let name = self
            .1
            .and_then(|end_arg| end_arg.local_names.get(self.0.0 as usize));
        match name {
            Some(name) => write!(f, "&{}", name.as_str()),
            None => write!(f, "&{}", self.0.0),
        }
    }
}

impl BcInstrArg for LocalSlotId {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", BcSlotDisplay(param.to_bc_slot(), end_arg))
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for LocalCapturedSlotId {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", BcSlotDisplay(param.to_bc_slot(), end_arg))
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for BcSlotIn {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", BcSlotDisplay(param.get(), end_arg))
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for BcSlotOut {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", BcSlotDisplay(param.get(), end_arg))
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for BcSlotInRange {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(
            f,
            " [{}]",
            param
                .iter()
                .map(|s| BcSlotDisplay(s.get(), end_arg).to_string())
                .join(", ")
        )
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for BcSlotInRangeFrom {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}..", param.0)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for ModuleSlotId {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " m{}", param.0)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for FrameSpan {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", param)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

/// Opcode as instruction argument.
impl BcInstrArg for BcOpcode {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {:?}", param)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for LoopDepth {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", param)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for KnownMethod {
    fn fmt_append(
        _param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " <m>")
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for Symbol {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", param.as_str())
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for Box<[FrozenValue]> {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " [")?;
        for (i, v) in param.iter().enumerate() {
            if i != 0 {
                write!(f, ", ")?;
            }
            write!(f, "{}", TruncateValueRepr(*v))?;
        }
        write!(f, "]")?;
        Ok(())
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for Box<[Hashed<FrozenValue>]> {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " [")?;
        for (i, v) in param.iter().enumerate() {
            if i != 0 {
                write!(f, ", ")?;
            }
            write!(f, "{}", TruncateValueRepr(*v.key()))?;
        }
        write!(f, "]")?;
        Ok(())
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for SmallMap<FrozenValue, FrozenValue> {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {{")?;
        for (i, (k, v)) in param.iter().enumerate() {
            if i != 0 {
                write!(f, ", ")?;
            }
            write!(f, "{}: {}", TruncateValueRepr(*k), TruncateValueRepr(*v))?;
        }
        write!(f, "}}")?;
        Ok(())
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for InstrDefData {
    fn fmt_append(
        _param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " InstrDefData")
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl<S: ArgSymbol> BcInstrArg for BcCallArgsFull<S> {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {{{}}}", param)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for BcCallArgsPos {
    fn fmt_append(
        param: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " {}", param.pos)
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcInstrArg for BcInstrEndArg {
    fn fmt_append(
        _: &Self,
        _ip: BcAddr,
        _end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        write!(f, " BcInstrEndArg")
    }

    fn visit_jump_addr(_param: &Self, _ip: BcAddr, _consumer: &mut dyn FnMut(BcAddr)) {}
}

impl BcOpcode {
    /// Format instruction argument.
    pub(crate) fn fmt_append_arg(
        self,
        ptr: BcPtrAddr,
        ip: BcAddr,
        end_arg: Option<&BcInstrEndArg>,
        f: &mut dyn Write,
    ) -> fmt::Result {
        struct HandlerImpl<'b, 'g> {
            ptr: BcPtrAddr<'b>,
            ip: BcAddr,
            end_arg: Option<&'b BcInstrEndArg>,
            f: &'g mut dyn Write,
        }

        impl BcOpcodeHandler<fmt::Result> for HandlerImpl<'_, '_> {
            fn handle<I: BcInstr>(self) -> fmt::Result {
                let HandlerImpl {
                    ptr,
                    ip,
                    end_arg,
                    f,
                } = self;
                let instr = ptr.get_instr::<I>();
                I::Arg::fmt_append(&instr.arg, ip, end_arg, f)
            }
        }

        self.dispatch(HandlerImpl {
            ptr,
            ip,
            end_arg,
            f,
        })
    }

    pub(crate) fn visit_jump_addr(
        self,
        ptr: BcPtrAddr,
        addr: BcAddr,
        consumer: &mut dyn FnMut(BcAddr),
    ) {
        struct HandlerImpl<'b, 'c> {
            ptr: BcPtrAddr<'b>,
            addr: BcAddr,
            consumer: &'c mut dyn FnMut(BcAddr),
        }

        impl BcOpcodeHandler<()> for HandlerImpl<'_, '_> {
            fn handle<I: BcInstr>(self) {
                let HandlerImpl {
                    ptr,
                    addr,
                    consumer,
                } = self;
                let instr = ptr.get_instr::<I>();
                I::Arg::visit_jump_addr(&instr.arg, addr, consumer);
            }
        }

        self.dispatch(HandlerImpl {
            ptr,
            addr,
            consumer,
        });
    }
}