use std::{assert_matches, fmt, iter};

use rustc_abi::{ExternAbi, FIRST_VARIANT, FieldIdx, VariantIdx};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_hir::lang_items::LangItem;
use rustc_index::{Idx, IndexVec};
use rustc_middle::mir::visit::{MutVisitor, PlaceContext};
use rustc_middle::mir::*;
use rustc_middle::query::Providers;
use rustc_middle::ty::{
    self, CoroutineArgs, CoroutineArgsExt, EarlyBinder, GenericArgs, Ty, TyCtxt,
};
use rustc_middle::{bug, span_bug};
use rustc_span::{DUMMY_SP, Span, Spanned, dummy_spanned};
use tracing::{debug, instrument};

use crate::deref_separator::deref_finder;
use crate::elaborate_drop::{DropElaborator, DropFlagMode, DropStyle, Unwind, elaborate_drop};
use crate::patch::MirPatch;
use crate::{
    abort_unwinding_calls, add_call_guards, add_moves_for_packed_drops, inline, instsimplify,
    mentioned_items, pass_manager as pm, remove_noop_landing_pads, run_optimization_passes,
    simplify,
};

mod async_destructor_ctor;

pub(super) fn provide(providers: &mut Providers) {
    providers.mir_shims = make_shim;
}

// Replace Pin<&mut ImplCoroutine> accesses (_1.0) into Pin<&mut ProxyCoroutine> accesses
struct FixProxyFutureDropVisitor<'tcx> {
    tcx: TyCtxt<'tcx>,
    replace_to: Local,
}

impl<'tcx> MutVisitor<'tcx> for FixProxyFutureDropVisitor<'tcx> {
    fn tcx(&self) -> TyCtxt<'tcx> {
        self.tcx
    }

    fn visit_place(
        &mut self,
        place: &mut Place<'tcx>,
        _context: PlaceContext,
        _location: Location,
    ) {
        if place.local == Local::from_u32(1) {
            if place.projection.len() == 1 {
                assert!(matches!(
                    place.projection.first(),
                    Some(ProjectionElem::Field(FieldIdx::ZERO, _))
                ));
                *place = Place::from(self.replace_to);
            } else if place.projection.len() == 2 {
                assert!(matches!(place.projection[0], ProjectionElem::Field(FieldIdx::ZERO, _)));
                assert!(matches!(place.projection[1], ProjectionElem::Deref));
                *place =
                    Place::from(self.replace_to).project_deeper(&[ProjectionElem::Deref], self.tcx);
            }
        }
    }
}

fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceKind<'tcx>) -> Body<'tcx> {
    debug!("make_shim({:?})", instance);

    let mut result = match instance {
        ty::InstanceKind::Item(..) => bug!("item {:?} passed to make_shim", instance),
        ty::InstanceKind::VTableShim(def_id) => {
            let adjustment = Adjustment::Deref { source: DerefSource::MutPtr };
            build_call_shim(tcx, instance, Some(adjustment), CallKind::Direct(def_id))
        }
        ty::InstanceKind::FnPtrShim(def_id, ty) => {
            let trait_ = tcx.parent(def_id);
            // Supports `Fn` or `async Fn` traits.
            let adjustment = match tcx
                .fn_trait_kind_from_def_id(trait_)
                .or_else(|| tcx.async_fn_trait_kind_from_def_id(trait_))
            {
                Some(ty::ClosureKind::FnOnce) => Adjustment::Identity,
                Some(ty::ClosureKind::Fn) => Adjustment::Deref { source: DerefSource::ImmRef },
                Some(ty::ClosureKind::FnMut) => Adjustment::Deref { source: DerefSource::MutRef },
                None => bug!("fn pointer {:?} is not an fn", ty),
            };

            build_call_shim(tcx, instance, Some(adjustment), CallKind::Indirect(ty))
        }
        // We are generating a call back to our def-id, which the
        // codegen backend knows to turn to an actual call, be it
        // a virtual call, or a direct call to a function for which
        // indirect calls must be codegen'd differently than direct ones
        // (such as `#[track_caller]`).
        ty::InstanceKind::ReifyShim(def_id, _) => {
            build_call_shim(tcx, instance, None, CallKind::Direct(def_id))
        }
        ty::InstanceKind::ClosureOnceShim { call_once: _, track_caller: _ } => {
            let fn_mut = tcx.require_lang_item(LangItem::FnMut, DUMMY_SP);
            let call_mut = tcx
                .associated_items(fn_mut)
                .in_definition_order()
                .find(|it| it.is_fn())
                .unwrap()
                .def_id;

            build_call_shim(tcx, instance, Some(Adjustment::RefMut), CallKind::Direct(call_mut))
        }

        ty::InstanceKind::ConstructCoroutineInClosureShim {
            coroutine_closure_def_id,
            receiver_by_ref,
        } => build_construct_coroutine_by_move_shim(tcx, coroutine_closure_def_id, receiver_by_ref),

        ty::InstanceKind::DropGlue(def_id, ty) => {
            // FIXME(#91576): Drop shims for coroutines aren't subject to the MIR passes at the end
            // of this function. Is this intentional?
            if let Some(&ty::Coroutine(coroutine_def_id, args)) = ty.map(Ty::kind) {
                let coroutine_body = tcx.optimized_mir(coroutine_def_id);

                let ty::Coroutine(_, id_args) = *tcx.type_of(coroutine_def_id).skip_binder().kind()
                else {
                    bug!()
                };

                // If this is a regular coroutine, grab its drop shim. If this is a coroutine
                // that comes from a coroutine-closure, and the kind ty differs from the "maximum"
                // kind that it supports, then grab the appropriate drop shim. This ensures that
                // the future returned by `<[coroutine-closure] as AsyncFnOnce>::call_once` will
                // drop the coroutine-closure's upvars.
                let body = if id_args.as_coroutine().kind_ty() == args.as_coroutine().kind_ty() {
                    coroutine_body.coroutine_drop().unwrap()
                } else {
                    assert_eq!(
                        args.as_coroutine().kind_ty().to_opt_closure_kind().unwrap(),
                        ty::ClosureKind::FnOnce
                    );
                    tcx.optimized_mir(tcx.coroutine_by_move_body_def_id(coroutine_def_id))
                        .coroutine_drop()
                        .unwrap()
                };

                let mut body = EarlyBinder::bind(body.clone()).instantiate(tcx, args);
                debug!("make_shim({:?}) = {:?}", instance, body);

                pm::run_passes(
                    tcx,
                    &mut body,
                    &[
                        &mentioned_items::MentionedItems,
                        &abort_unwinding_calls::AbortUnwindingCalls,
                        &add_call_guards::CriticalCallEdges,
                    ],
                    Some(MirPhase::Runtime(RuntimePhase::Optimized)),
                    pm::Optimizations::Allowed,
                );

                return body;
            }

            build_drop_shim(tcx, def_id, ty)
        }
        ty::InstanceKind::ThreadLocalShim(..) => build_thread_local_shim(tcx, instance),
        ty::InstanceKind::CloneShim(def_id, ty) => build_clone_shim(tcx, def_id, ty),
        ty::InstanceKind::FnPtrAddrShim(def_id, ty) => build_fn_ptr_addr_shim(tcx, def_id, ty),
        ty::InstanceKind::FutureDropPollShim(def_id, proxy_ty, impl_ty) => {
            let mut body =
                async_destructor_ctor::build_future_drop_poll_shim(tcx, def_id, proxy_ty, impl_ty);

            pm::run_passes(
                tcx,
                &mut body,
                &[
                    &mentioned_items::MentionedItems,
                    &abort_unwinding_calls::AbortUnwindingCalls,
                    &add_call_guards::CriticalCallEdges,
                ],
                Some(MirPhase::Runtime(RuntimePhase::PostCleanup)),
                pm::Optimizations::Allowed,
            );
            run_optimization_passes(tcx, &mut body);
            debug!("make_shim({:?}) = {:?}", instance, body);
            return body;
        }
        ty::InstanceKind::AsyncDropGlue(def_id, ty) => {
            let mut body = async_destructor_ctor::build_async_drop_shim(tcx, def_id, ty);

            // Main pass required here is StateTransform to convert sync drop ladder
            // into coroutine.
            // Others are minimal passes as for sync drop glue shim
            pm::run_passes(
                tcx,
                &mut body,
                &[
                    &mentioned_items::MentionedItems,
                    &abort_unwinding_calls::AbortUnwindingCalls,
                    &add_call_guards::CriticalCallEdges,
                    &simplify::SimplifyCfg::MakeShim,
                    &crate::coroutine::StateTransform,
                ],
                Some(MirPhase::Runtime(RuntimePhase::PostCleanup)),
                pm::Optimizations::Allowed,
            );
            run_optimization_passes(tcx, &mut body);
            debug!("make_shim({:?}) = {:?}", instance, body);
            return body;
        }

        ty::InstanceKind::AsyncDropGlueCtorShim(def_id, ty) => {
            let body = async_destructor_ctor::build_async_destructor_ctor_shim(tcx, def_id, ty);
            debug!("make_shim({:?}) = {:?}", instance, body);
            return body;
        }
        ty::InstanceKind::Virtual(..) => {
            bug!("InstanceKind::Virtual ({:?}) is for direct calls only", instance)
        }
        ty::InstanceKind::Intrinsic(_) => {
            bug!("creating shims from intrinsics ({:?}) is unsupported", instance)
        }
    };
    debug!("make_shim({:?}) = untransformed {:?}", instance, result);

    deref_finder(tcx, &mut result, false);

    // We don't validate MIR here because the shims may generate code that's
    // only valid in a `PostAnalysis` param-env. However, since we do initial
    // validation with the MirBuilt phase, which uses a user-facing param-env.
    // This causes validation errors when TAITs are involved.
    pm::run_passes_no_validate(
        tcx,
        &mut result,
        &[
            &mentioned_items::MentionedItems,
            &add_moves_for_packed_drops::AddMovesForPackedDrops,
            &remove_noop_landing_pads::RemoveNoopLandingPads,
            &simplify::SimplifyCfg::MakeShim,
            &instsimplify::InstSimplify::BeforeInline,
            // Perform inlining of `#[rustc_force_inline]`-annotated callees.
            &inline::ForceInline,
            &abort_unwinding_calls::AbortUnwindingCalls,
            &add_call_guards::CriticalCallEdges,
        ],
        Some(MirPhase::Runtime(RuntimePhase::Optimized)),
    );

    debug!("make_shim({:?}) = {:?}", instance, result);

    result
}

#[derive(Copy, Clone, Debug, PartialEq)]
enum DerefSource {
    /// `fn shim(&self) { inner(*self )}`.
    ImmRef,
    /// `fn shim(&mut self) { inner(*self )}`.
    MutRef,
    /// `fn shim(*mut self) { inner(*self )}`.
    MutPtr,
}

#[derive(Copy, Clone, Debug, PartialEq)]
enum Adjustment {
    /// Pass the receiver as-is.
    Identity,

    /// We get passed a reference or a raw pointer to `self` and call the target with `*self`.
    ///
    /// This either copies `self` (if `Self: Copy`, eg. for function items), or moves out of it
    /// (for `VTableShim`, which effectively is passed `&own Self`).
    Deref { source: DerefSource },

    /// We get passed `self: Self` and call the target with `&mut self`.
    ///
    /// In this case we need to ensure that the `Self` is dropped after the call, as the callee
    /// won't do it for us.
    RefMut,
}

#[derive(Copy, Clone, Debug, PartialEq)]
enum CallKind<'tcx> {
    /// Call the `FnPtr` that was passed as the receiver.
    Indirect(Ty<'tcx>),

    /// Call a known `FnDef`.
    Direct(DefId),
}

fn local_decls_for_sig<'tcx>(
    sig: &ty::FnSig<'tcx>,
    span: Span,
) -> IndexVec<Local, LocalDecl<'tcx>> {
    iter::once(LocalDecl::new(sig.output(), span))
        .chain(sig.inputs().iter().map(|ity| LocalDecl::new(*ity, span).immutable()))
        .collect()
}

fn dropee_emit_retag<'tcx>(
    tcx: TyCtxt<'tcx>,
    body: &mut Body<'tcx>,
    mut dropee_ptr: Place<'tcx>,
    span: Span,
) -> Place<'tcx> {
    if tcx.sess.opts.unstable_opts.mir_emit_retag {
        let source_info = SourceInfo::outermost(span);
        // We want to treat the function argument as if it was passed by `&mut`. As such, we
        // generate
        // ```
        // temp = &mut *arg;
        // Retag(temp, FnEntry)
        // ```
        // It's important that we do this first, before anything that depends on `dropee_ptr`
        // has been put into the body.
        let reborrow = Rvalue::Ref(
            tcx.lifetimes.re_erased,
            BorrowKind::Mut { kind: MutBorrowKind::Default },
            tcx.mk_place_deref(dropee_ptr),
        );
        let ref_ty = reborrow.ty(body.local_decls(), tcx);
        dropee_ptr = body.local_decls.push(LocalDecl::new(ref_ty, span)).into();
        let new_statements = [
            StatementKind::Assign(Box::new((dropee_ptr, reborrow))),
            StatementKind::Retag(RetagKind::FnEntry, Box::new(dropee_ptr)),
        ];
        for s in new_statements {
            body.basic_blocks_mut()[START_BLOCK].statements.push(Statement::new(source_info, s));
        }
    }
    dropee_ptr
}

fn build_drop_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, ty: Option<Ty<'tcx>>) -> Body<'tcx> {
    debug!("build_drop_shim(def_id={:?}, ty={:?})", def_id, ty);

    assert!(!matches!(ty, Some(ty) if ty.is_coroutine()));

    let args = if let Some(ty) = ty {
        tcx.mk_args(&[ty.into()])
    } else {
        GenericArgs::identity_for_item(tcx, def_id)
    };
    let sig = tcx.fn_sig(def_id).instantiate(tcx, args);
    let sig = tcx.instantiate_bound_regions_with_erased(sig);
    let span = tcx.def_span(def_id);

    let source_info = SourceInfo::outermost(span);

    let return_block = BasicBlock::new(1);
    let mut blocks = IndexVec::with_capacity(2);
    let block = |blocks: &mut IndexVec<_, _>, kind| {
        blocks.push(BasicBlockData::new(Some(Terminator { source_info, kind }), false))
    };
    block(&mut blocks, TerminatorKind::Goto { target: return_block });
    block(&mut blocks, TerminatorKind::Return);

    let source = MirSource::from_instance(ty::InstanceKind::DropGlue(def_id, ty));
    let mut body =
        new_body(source, blocks, local_decls_for_sig(&sig, span), sig.inputs().len(), span);

    // The first argument (index 0), but add 1 for the return value.
    let dropee_ptr = Place::from(Local::new(1 + 0));
    let dropee_ptr = dropee_emit_retag(tcx, &mut body, dropee_ptr, span);

    if ty.is_some() {
        let patch = {
            let typing_env = ty::TypingEnv::post_analysis(tcx, def_id);
            let mut elaborator = DropShimElaborator {
                body: &body,
                patch: MirPatch::new(&body),
                tcx,
                typing_env,
                produce_async_drops: false,
            };
            let dropee = tcx.mk_place_deref(dropee_ptr);
            let resume_block = elaborator.patch.resume_block();
            elaborate_drop(
                &mut elaborator,
                source_info,
                dropee,
                (),
                return_block,
                Unwind::To(resume_block),
                START_BLOCK,
                None,
            );
            elaborator.patch
        };
        patch.apply(&mut body);
    }

    body
}

fn new_body<'tcx>(
    source: MirSource<'tcx>,
    basic_blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>,
    local_decls: IndexVec<Local, LocalDecl<'tcx>>,
    arg_count: usize,
    span: Span,
) -> Body<'tcx> {
    let mut body = Body::new(
        source,
        basic_blocks,
        IndexVec::from_elem_n(
            SourceScopeData {
                span,
                parent_scope: None,
                inlined: None,
                inlined_parent_scope: None,
                local_data: ClearCrossCrate::Clear,
            },
            1,
        ),
        local_decls,
        IndexVec::new(),
        arg_count,
        vec![],
        span,
        None,
        // FIXME(compiler-errors): is this correct?
        None,
    );
    // Shims do not directly mention any consts.
    body.set_required_consts(Vec::new());
    body
}

pub(super) struct DropShimElaborator<'a, 'tcx> {
    pub body: &'a Body<'tcx>,
    pub patch: MirPatch<'tcx>,
    pub tcx: TyCtxt<'tcx>,
    pub typing_env: ty::TypingEnv<'tcx>,
    pub produce_async_drops: bool,
}

impl fmt::Debug for DropShimElaborator<'_, '_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
        f.debug_struct("DropShimElaborator").finish_non_exhaustive()
    }
}

impl<'a, 'tcx> DropElaborator<'a, 'tcx> for DropShimElaborator<'a, 'tcx> {
    type Path = ();

    fn patch_ref(&self) -> &MirPatch<'tcx> {
        &self.patch
    }
    fn patch(&mut self) -> &mut MirPatch<'tcx> {
        &mut self.patch
    }
    fn body(&self) -> &'a Body<'tcx> {
        self.body
    }
    fn tcx(&self) -> TyCtxt<'tcx> {
        self.tcx
    }
    fn typing_env(&self) -> ty::TypingEnv<'tcx> {
        self.typing_env
    }

    fn terminator_loc(&self, bb: BasicBlock) -> Location {
        self.patch.terminator_loc(self.body, bb)
    }
    fn allow_async_drops(&self) -> bool {
        self.produce_async_drops
    }

    fn drop_style(&self, _path: Self::Path, mode: DropFlagMode) -> DropStyle {
        match mode {
            DropFlagMode::Shallow => {
                // Drops for the contained fields are "shallow" and "static" - they will simply call
                // the field's own drop glue.
                DropStyle::Static
            }
            DropFlagMode::Deep => {
                // The top-level drop is "deep" and "open" - it will be elaborated to a drop ladder
                // dropping each field contained in the value.
                DropStyle::Open
            }
        }
    }

    fn get_drop_flag(&mut self, _path: Self::Path) -> Option<Operand<'tcx>> {
        None
    }

    fn clear_drop_flag(&mut self, _location: Location, _path: Self::Path, _mode: DropFlagMode) {}

    fn field_subpath(&self, _path: Self::Path, _field: FieldIdx) -> Option<Self::Path> {
        None
    }
    fn deref_subpath(&self, _path: Self::Path) -> Option<Self::Path> {
        None
    }
    fn downcast_subpath(&self, _path: Self::Path, _variant: VariantIdx) -> Option<Self::Path> {
        Some(())
    }
    fn array_subpath(&self, _path: Self::Path, _index: u64, _size: u64) -> Option<Self::Path> {
        None
    }
}

fn build_thread_local_shim<'tcx>(
    tcx: TyCtxt<'tcx>,
    instance: ty::InstanceKind<'tcx>,
) -> Body<'tcx> {
    let def_id = instance.def_id();

    let span = tcx.def_span(def_id);
    let source_info = SourceInfo::outermost(span);

    let blocks = IndexVec::from_raw(vec![BasicBlockData::new_stmts(
        vec![Statement::new(
            source_info,
            StatementKind::Assign(Box::new((
                Place::return_place(),
                Rvalue::ThreadLocalRef(def_id),
            ))),
        )],
        Some(Terminator { source_info, kind: TerminatorKind::Return }),
        false,
    )]);

    new_body(
        MirSource::from_instance(instance),
        blocks,
        IndexVec::from_raw(vec![LocalDecl::new(tcx.thread_local_ptr_ty(def_id), span)]),
        0,
        span,
    )
}

/// Builds a `Clone::clone` shim for `self_ty`. Here, `def_id` is `Clone::clone`.
fn build_clone_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'tcx>) -> Body<'tcx> {
    debug!("build_clone_shim(def_id={:?})", def_id);

    let mut builder = CloneShimBuilder::new(tcx, def_id, self_ty);

    let dest = Place::return_place();
    let src = tcx.mk_place_deref(Place::from(Local::new(1 + 0)));

    match self_ty.kind() {
        ty::FnDef(..) | ty::FnPtr(..) => builder.copy_shim(),
        ty::Closure(_, args) => builder.tuple_like_shim(dest, src, args.as_closure().upvar_tys()),
        ty::CoroutineClosure(_, args) => {
            builder.tuple_like_shim(dest, src, args.as_coroutine_closure().upvar_tys())
        }
        ty::Tuple(..) => builder.tuple_like_shim(dest, src, self_ty.tuple_fields()),
        ty::Coroutine(coroutine_def_id, args) => {
            assert_eq!(tcx.coroutine_movability(*coroutine_def_id), hir::Movability::Movable);
            builder.coroutine_shim(dest, src, *coroutine_def_id, args.as_coroutine())
        }
        _ => bug!("clone shim for `{:?}` which is not `Copy` and is not an aggregate", self_ty),
    };

    builder.into_mir()
}

struct CloneShimBuilder<'tcx> {
    tcx: TyCtxt<'tcx>,
    def_id: DefId,
    local_decls: IndexVec<Local, LocalDecl<'tcx>>,
    blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>,
    span: Span,
    sig: ty::FnSig<'tcx>,
}

impl<'tcx> CloneShimBuilder<'tcx> {
    fn new(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'tcx>) -> Self {
        // we must instantiate the self_ty because it's
        // otherwise going to be TySelf and we can't index
        // or access fields of a Place of type TySelf.
        let sig = tcx.fn_sig(def_id).instantiate(tcx, &[self_ty.into()]);
        let sig = tcx.instantiate_bound_regions_with_erased(sig);
        let span = tcx.def_span(def_id);

        CloneShimBuilder {
            tcx,
            def_id,
            local_decls: local_decls_for_sig(&sig, span),
            blocks: IndexVec::new(),
            span,
            sig,
        }
    }

    fn into_mir(self) -> Body<'tcx> {
        let source = MirSource::from_instance(ty::InstanceKind::CloneShim(
            self.def_id,
            self.sig.inputs_and_output[0],
        ));
        new_body(source, self.blocks, self.local_decls, self.sig.inputs().len(), self.span)
    }

    fn source_info(&self) -> SourceInfo {
        SourceInfo::outermost(self.span)
    }

    fn block(
        &mut self,
        statements: Vec<Statement<'tcx>>,
        kind: TerminatorKind<'tcx>,
        is_cleanup: bool,
    ) -> BasicBlock {
        let source_info = self.source_info();
        self.blocks.push(BasicBlockData::new_stmts(
            statements,
            Some(Terminator { source_info, kind }),
            is_cleanup,
        ))
    }

    /// Gives the index of an upcoming BasicBlock, with an offset.
    /// offset=0 will give you the index of the next BasicBlock,
    /// offset=1 will give the index of the next-to-next block,
    /// offset=-1 will give you the index of the last-created block
    fn block_index_offset(&self, offset: usize) -> BasicBlock {
        BasicBlock::new(self.blocks.len() + offset)
    }

    fn make_statement(&self, kind: StatementKind<'tcx>) -> Statement<'tcx> {
        Statement::new(self.source_info(), kind)
    }

    fn copy_shim(&mut self) {
        let rcvr = self.tcx.mk_place_deref(Place::from(Local::new(1 + 0)));
        let ret_statement = self.make_statement(StatementKind::Assign(Box::new((
            Place::return_place(),
            Rvalue::Use(Operand::Copy(rcvr)),
        ))));
        self.block(vec![ret_statement], TerminatorKind::Return, false);
    }

    fn make_place(&mut self, mutability: Mutability, ty: Ty<'tcx>) -> Place<'tcx> {
        let span = self.span;
        let mut local = LocalDecl::new(ty, span);
        if mutability.is_not() {
            local = local.immutable();
        }
        Place::from(self.local_decls.push(local))
    }

    fn make_clone_call(
        &mut self,
        dest: Place<'tcx>,
        src: Place<'tcx>,
        ty: Ty<'tcx>,
        next: BasicBlock,
        cleanup: BasicBlock,
    ) {
        let tcx = self.tcx;

        // `func == Clone::clone(&ty) -> ty`
        let func_ty = Ty::new_fn_def(tcx, self.def_id, [ty]);
        let func = Operand::Constant(Box::new(ConstOperand {
            span: self.span,
            user_ty: None,
            const_: Const::zero_sized(func_ty),
        }));

        let ref_loc =
            self.make_place(Mutability::Not, Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, ty));

        // `let ref_loc: &ty = &src;`
        let statement = self.make_statement(StatementKind::Assign(Box::new((
            ref_loc,
            Rvalue::Ref(tcx.lifetimes.re_erased, BorrowKind::Shared, src),
        ))));

        // `let loc = Clone::clone(ref_loc);`
        self.block(
            vec![statement],
            TerminatorKind::Call {
                func,
                args: [Spanned { node: Operand::Move(ref_loc), span: DUMMY_SP }].into(),
                destination: dest,
                target: Some(next),
                unwind: UnwindAction::Cleanup(cleanup),
                call_source: CallSource::Normal,
                fn_span: self.span,
            },
            false,
        );
    }

    fn clone_fields<I>(
        &mut self,
        dest: Place<'tcx>,
        src: Place<'tcx>,
        target: BasicBlock,
        mut unwind: BasicBlock,
        tys: I,
    ) -> BasicBlock
    where
        I: IntoIterator<Item = Ty<'tcx>>,
    {
        // For an iterator of length n, create 2*n + 1 blocks.
        for (i, ity) in tys.into_iter().enumerate() {
            // Each iteration creates two blocks, referred to here as block 2*i and block 2*i + 1.
            //
            // Block 2*i attempts to clone the field. If successful it branches to 2*i + 2 (the
            // next clone block). If unsuccessful it branches to the previous unwind block, which
            // is initially the `unwind` argument passed to this function.
            //
            // Block 2*i + 1 is the unwind block for this iteration. It drops the cloned value
            // created by block 2*i. We store this block in `unwind` so that the next clone block
            // will unwind to it if cloning fails.

            let field = FieldIdx::new(i);
            let src_field = self.tcx.mk_place_field(src, field, ity);

            let dest_field = self.tcx.mk_place_field(dest, field, ity);

            let next_unwind = self.block_index_offset(1);
            let next_block = self.block_index_offset(2);
            self.make_clone_call(dest_field, src_field, ity, next_block, unwind);
            self.block(
                vec![],
                TerminatorKind::Drop {
                    place: dest_field,
                    target: unwind,
                    unwind: UnwindAction::Terminate(UnwindTerminateReason::InCleanup),
                    replace: false,
                    drop: None,
                    async_fut: None,
                },
                /* is_cleanup */ true,
            );
            unwind = next_unwind;
        }
        // If all clones succeed then we end up here.
        self.block(vec![], TerminatorKind::Goto { target }, false);
        unwind
    }

    fn tuple_like_shim<I>(&mut self, dest: Place<'tcx>, src: Place<'tcx>, tys: I)
    where
        I: IntoIterator<Item = Ty<'tcx>>,
    {
        self.block(vec![], TerminatorKind::Goto { target: self.block_index_offset(3) }, false);
        let unwind = self.block(vec![], TerminatorKind::UnwindResume, true);
        let target = self.block(vec![], TerminatorKind::Return, false);

        let _final_cleanup_block = self.clone_fields(dest, src, target, unwind, tys);
    }

    fn coroutine_shim(
        &mut self,
        dest: Place<'tcx>,
        src: Place<'tcx>,
        coroutine_def_id: DefId,
        args: CoroutineArgs<TyCtxt<'tcx>>,
    ) {
        self.block(vec![], TerminatorKind::Goto { target: self.block_index_offset(3) }, false);
        let unwind = self.block(vec![], TerminatorKind::UnwindResume, true);
        // This will get overwritten with a switch once we know the target blocks
        let switch = self.block(vec![], TerminatorKind::Unreachable, false);
        let unwind = self.clone_fields(dest, src, switch, unwind, args.upvar_tys());
        let target = self.block(vec![], TerminatorKind::Return, false);
        let unreachable = self.block(vec![], TerminatorKind::Unreachable, false);
        let mut cases = Vec::with_capacity(args.state_tys(coroutine_def_id, self.tcx).count());
        for (index, state_tys) in args.state_tys(coroutine_def_id, self.tcx).enumerate() {
            let variant_index = VariantIdx::new(index);
            let dest = self.tcx.mk_place_downcast_unnamed(dest, variant_index);
            let src = self.tcx.mk_place_downcast_unnamed(src, variant_index);
            let clone_block = self.block_index_offset(1);
            let start_block = self.block(
                vec![self.make_statement(StatementKind::SetDiscriminant {
                    place: Box::new(Place::return_place()),
                    variant_index,
                })],
                TerminatorKind::Goto { target: clone_block },
                false,
            );
            cases.push((index as u128, start_block));
            let _final_cleanup_block = self.clone_fields(dest, src, target, unwind, state_tys);
        }
        let discr_ty = args.discr_ty(self.tcx);
        let temp = self.make_place(Mutability::Mut, discr_ty);
        let rvalue = Rvalue::Discriminant(src);
        let statement = self.make_statement(StatementKind::Assign(Box::new((temp, rvalue))));
        match &mut self.blocks[switch] {
            BasicBlockData { statements, terminator: Some(Terminator { kind, .. }), .. } => {
                statements.push(statement);
                *kind = TerminatorKind::SwitchInt {
                    discr: Operand::Move(temp),
                    targets: SwitchTargets::new(cases.into_iter(), unreachable),
                };
            }
            BasicBlockData { terminator: None, .. } => unreachable!(),
        }
    }
}

/// Builds a "call" shim for `instance`. The shim calls the function specified by `call_kind`,
/// first adjusting its first argument according to `rcvr_adjustment`.
#[instrument(level = "debug", skip(tcx), ret)]
fn build_call_shim<'tcx>(
    tcx: TyCtxt<'tcx>,
    instance: ty::InstanceKind<'tcx>,
    rcvr_adjustment: Option<Adjustment>,
    call_kind: CallKind<'tcx>,
) -> Body<'tcx> {
    // `FnPtrShim` contains the fn pointer type that a call shim is being built for - this is used
    // to instantiate into the signature of the shim. It is not necessary for users of this
    // MIR body to perform further instantiations (see `InstanceKind::has_polymorphic_mir_body`).
    let (sig_args, untuple_args) = if let ty::InstanceKind::FnPtrShim(_, ty) = instance {
        let sig = tcx.instantiate_bound_regions_with_erased(ty.fn_sig(tcx));

        let untuple_args = sig.inputs();

        // Create substitutions for the `Self` and `Args` generic parameters of the shim body.
        let arg_tup = Ty::new_tup(tcx, untuple_args);

        (Some([ty.into(), arg_tup.into()]), Some(untuple_args))
    } else {
        (None, None)
    };

    let def_id = instance.def_id();

    let sig = tcx.fn_sig(def_id);
    let sig = sig.map_bound(|sig| tcx.instantiate_bound_regions_with_erased(sig));

    assert_eq!(sig_args.is_some(), !instance.has_polymorphic_mir_body());
    let mut sig = if let Some(sig_args) = sig_args {
        sig.instantiate(tcx, &sig_args)
    } else {
        sig.instantiate_identity()
    };

    if let CallKind::Indirect(fnty) = call_kind {
        // `sig` determines our local decls, and thus the callee type in the `Call` terminator. This
        // can only be an `FnDef` or `FnPtr`, but currently will be `Self` since the types come from
        // the implemented `FnX` trait.

        // Apply the opposite adjustment to the MIR input.
        let mut inputs_and_output = sig.inputs_and_output.to_vec();

        // Initial signature is `fn(&? Self, Args) -> Self::Output` where `Args` is a tuple of the
        // fn arguments. `Self` may be passed via (im)mutable reference or by-value.
        assert_eq!(inputs_and_output.len(), 3);

        // `Self` is always the original fn type `ty`. The MIR call terminator is only defined for
        // `FnDef` and `FnPtr` callees, not the `Self` type param.
        let self_arg = &mut inputs_and_output[0];
        *self_arg = match rcvr_adjustment.unwrap() {
            Adjustment::Identity => fnty,
            Adjustment::Deref { source } => match source {
                DerefSource::ImmRef => Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, fnty),
                DerefSource::MutRef => Ty::new_mut_ref(tcx, tcx.lifetimes.re_erased, fnty),
                DerefSource::MutPtr => Ty::new_mut_ptr(tcx, fnty),
            },
            Adjustment::RefMut => bug!("`RefMut` is never used with indirect calls: {instance:?}"),
        };
        sig.inputs_and_output = tcx.mk_type_list(&inputs_and_output);
    }

    // FIXME: Avoid having to adjust the signature both here and in
    // `fn_sig_for_fn_abi`.
    if let ty::InstanceKind::VTableShim(..) = instance {
        // Modify fn(self, ...) to fn(self: *mut Self, ...)
        let mut inputs_and_output = sig.inputs_and_output.to_vec();
        let self_arg = &mut inputs_and_output[0];
        debug_assert!(tcx.generics_of(def_id).has_self && *self_arg == tcx.types.self_param);
        *self_arg = Ty::new_mut_ptr(tcx, *self_arg);
        sig.inputs_and_output = tcx.mk_type_list(&inputs_and_output);
    }

    let span = tcx.def_span(def_id);

    debug!(?sig);

    let mut local_decls = local_decls_for_sig(&sig, span);
    let source_info = SourceInfo::outermost(span);

    let destination = Place::return_place();

    let rcvr_place = || {
        assert!(rcvr_adjustment.is_some());
        Place::from(Local::new(1))
    };
    let mut statements = vec![];

    let rcvr = rcvr_adjustment.map(|rcvr_adjustment| match rcvr_adjustment {
        Adjustment::Identity => Operand::Move(rcvr_place()),
        Adjustment::Deref { source: _ } => Operand::Move(tcx.mk_place_deref(rcvr_place())),
        Adjustment::RefMut => {
            // let rcvr = &mut rcvr;
            let ref_rcvr = local_decls.push(
                LocalDecl::new(
                    Ty::new_mut_ref(tcx, tcx.lifetimes.re_erased, sig.inputs()[0]),
                    span,
                )
                .immutable(),
            );
            let borrow_kind = BorrowKind::Mut { kind: MutBorrowKind::Default };
            statements.push(Statement::new(
                source_info,
                StatementKind::Assign(Box::new((
                    Place::from(ref_rcvr),
                    Rvalue::Ref(tcx.lifetimes.re_erased, borrow_kind, rcvr_place()),
                ))),
            ));
            Operand::Move(Place::from(ref_rcvr))
        }
    });

    let (callee, mut args) = match call_kind {
        // `FnPtr` call has no receiver. Args are untupled below.
        CallKind::Indirect(_) => (rcvr.unwrap(), vec![]),

        // `FnDef` call with optional receiver.
        CallKind::Direct(def_id) => {
            let ty = tcx.type_of(def_id).instantiate_identity();
            (
                Operand::Constant(Box::new(ConstOperand {
                    span,
                    user_ty: None,
                    const_: Const::zero_sized(ty),
                })),
                rcvr.into_iter().collect::<Vec<_>>(),
            )
        }
    };

    let mut arg_range = 0..sig.inputs().len();

    // Take the `self` ("receiver") argument out of the range (it's adjusted above).
    if rcvr_adjustment.is_some() {
        arg_range.start += 1;
    }

    // Take the last argument, if we need to untuple it (handled below).
    if untuple_args.is_some() {
        arg_range.end -= 1;
    }

    // Pass all of the non-special arguments directly.
    args.extend(arg_range.map(|i| Operand::Move(Place::from(Local::new(1 + i)))));

    // Untuple the last argument, if we have to.
    if let Some(untuple_args) = untuple_args {
        let tuple_arg = Local::new(1 + (sig.inputs().len() - 1));
        args.extend(untuple_args.iter().enumerate().map(|(i, ity)| {
            Operand::Move(tcx.mk_place_field(Place::from(tuple_arg), FieldIdx::new(i), *ity))
        }));
    }

    let n_blocks = if let Some(Adjustment::RefMut) = rcvr_adjustment { 5 } else { 2 };
    let mut blocks = IndexVec::with_capacity(n_blocks);
    let block = |blocks: &mut IndexVec<_, _>, statements, kind, is_cleanup| {
        blocks.push(BasicBlockData::new_stmts(
            statements,
            Some(Terminator { source_info, kind }),
            is_cleanup,
        ))
    };

    // BB #0
    let args = args.into_iter().map(|a| Spanned { node: a, span: DUMMY_SP }).collect();
    block(
        &mut blocks,
        statements,
        TerminatorKind::Call {
            func: callee,
            args,
            destination,
            target: Some(BasicBlock::new(1)),
            unwind: if let Some(Adjustment::RefMut) = rcvr_adjustment {
                UnwindAction::Cleanup(BasicBlock::new(3))
            } else {
                UnwindAction::Continue
            },
            call_source: CallSource::Misc,
            fn_span: span,
        },
        false,
    );

    if let Some(Adjustment::RefMut) = rcvr_adjustment {
        // BB #1 - drop for Self
        block(
            &mut blocks,
            vec![],
            TerminatorKind::Drop {
                place: rcvr_place(),
                target: BasicBlock::new(2),
                unwind: UnwindAction::Continue,
                replace: false,
                drop: None,
                async_fut: None,
            },
            false,
        );
    }
    // BB #1/#2 - return
    let stmts = vec![];
    block(&mut blocks, stmts, TerminatorKind::Return, false);
    if let Some(Adjustment::RefMut) = rcvr_adjustment {
        // BB #3 - drop if closure panics
        block(
            &mut blocks,
            vec![],
            TerminatorKind::Drop {
                place: rcvr_place(),
                target: BasicBlock::new(4),
                unwind: UnwindAction::Terminate(UnwindTerminateReason::InCleanup),
                replace: false,
                drop: None,
                async_fut: None,
            },
            /* is_cleanup */ true,
        );

        // BB #4 - resume
        block(&mut blocks, vec![], TerminatorKind::UnwindResume, true);
    }

    let mut body =
        new_body(MirSource::from_instance(instance), blocks, local_decls, sig.inputs().len(), span);

    if let ExternAbi::RustCall = sig.abi {
        body.spread_arg = Some(Local::new(sig.inputs().len()));
    }

    body
}

pub(super) fn build_adt_ctor(tcx: TyCtxt<'_>, ctor_id: DefId) -> Body<'_> {
    debug_assert!(tcx.is_constructor(ctor_id));

    let typing_env = ty::TypingEnv::post_analysis(tcx, ctor_id);

    // Normalize the sig.
    let sig = tcx
        .fn_sig(ctor_id)
        .instantiate_identity()
        .no_bound_vars()
        .expect("LBR in ADT constructor signature");
    let sig = tcx.normalize_erasing_regions(typing_env, sig);

    let ty::Adt(adt_def, args) = sig.output().kind() else {
        bug!("unexpected type for ADT ctor {:?}", sig.output());
    };

    debug!("build_ctor: ctor_id={:?} sig={:?}", ctor_id, sig);

    let span = tcx.def_span(ctor_id);

    let local_decls = local_decls_for_sig(&sig, span);

    let source_info = SourceInfo::outermost(span);

    let variant_index =
        if adt_def.is_enum() { adt_def.variant_index_with_ctor_id(ctor_id) } else { FIRST_VARIANT };

    // Generate the following MIR:
    //
    // (return as Variant).field0 = arg0;
    // (return as Variant).field1 = arg1;
    //
    // return;
    debug!("build_ctor: variant_index={:?}", variant_index);

    let kind = AggregateKind::Adt(adt_def.did(), variant_index, args, None, None);
    let variant = adt_def.variant(variant_index);
    let statement = Statement::new(
        source_info,
        StatementKind::Assign(Box::new((
            Place::return_place(),
            Rvalue::Aggregate(
                Box::new(kind),
                (0..variant.fields.len())
                    .map(|idx| Operand::Move(Place::from(Local::new(idx + 1))))
                    .collect(),
            ),
        ))),
    );

    let start_block = BasicBlockData::new_stmts(
        vec![statement],
        Some(Terminator { source_info, kind: TerminatorKind::Return }),
        false,
    );

    let source = MirSource::item(ctor_id);
    let mut body = new_body(
        source,
        IndexVec::from_elem_n(start_block, 1),
        local_decls,
        sig.inputs().len(),
        span,
    );
    // A constructor doesn't mention any other items (and we don't run the usual optimization passes
    // so this would otherwise not get filled).
    body.set_mentioned_items(Vec::new());

    crate::pass_manager::dump_mir_for_phase_change(tcx, &body);

    body
}

/// ```ignore (pseudo-impl)
/// impl FnPtr for fn(u32) {
///     fn addr(self) -> usize {
///         self as usize
///     }
/// }
/// ```
fn build_fn_ptr_addr_shim<'tcx>(tcx: TyCtxt<'tcx>, def_id: DefId, self_ty: Ty<'tcx>) -> Body<'tcx> {
    assert_matches!(self_ty.kind(), ty::FnPtr(..), "expected fn ptr, found {self_ty}");
    let span = tcx.def_span(def_id);
    let Some(sig) = tcx.fn_sig(def_id).instantiate(tcx, &[self_ty.into()]).no_bound_vars() else {
        span_bug!(span, "FnPtr::addr with bound vars for `{self_ty}`");
    };
    let locals = local_decls_for_sig(&sig, span);

    let source_info = SourceInfo::outermost(span);
    // FIXME: use `expose_provenance` once we figure out whether function pointers have meaningful
    // provenance.
    let rvalue = Rvalue::Cast(
        CastKind::FnPtrToPtr,
        Operand::Move(Place::from(Local::new(1))),
        Ty::new_imm_ptr(tcx, tcx.types.unit),
    );
    let stmt = Statement::new(
        source_info,
        StatementKind::Assign(Box::new((Place::return_place(), rvalue))),
    );
    let statements = vec![stmt];
    let start_block = BasicBlockData::new_stmts(
        statements,
        Some(Terminator { source_info, kind: TerminatorKind::Return }),
        false,
    );
    let source = MirSource::from_instance(ty::InstanceKind::FnPtrAddrShim(def_id, self_ty));
    new_body(source, IndexVec::from_elem_n(start_block, 1), locals, sig.inputs().len(), span)
}

fn build_construct_coroutine_by_move_shim<'tcx>(
    tcx: TyCtxt<'tcx>,
    coroutine_closure_def_id: DefId,
    receiver_by_ref: bool,
) -> Body<'tcx> {
    let mut self_ty = tcx.type_of(coroutine_closure_def_id).instantiate_identity();
    let mut self_local: Place<'tcx> = Local::from_usize(1).into();
    let ty::CoroutineClosure(_, args) = *self_ty.kind() else {
        bug!();
    };

    // We use `&Self` here because we only need to emit an ABI-compatible shim body,
    // rather than match the signature exactly (which might take `&mut self` instead).
    //
    // We adjust the `self_local` to be a deref since we want to copy fields out of
    // a reference to the closure.
    if receiver_by_ref {
        self_local = tcx.mk_place_deref(self_local);
        self_ty = Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, self_ty);
    }

    let poly_sig = args.as_coroutine_closure().coroutine_closure_sig().map_bound(|sig| {
        tcx.mk_fn_sig(
            [self_ty].into_iter().chain(sig.tupled_inputs_ty.tuple_fields()),
            sig.to_coroutine_given_kind_and_upvars(
                tcx,
                args.as_coroutine_closure().parent_args(),
                tcx.coroutine_for_closure(coroutine_closure_def_id),
                ty::ClosureKind::FnOnce,
                tcx.lifetimes.re_erased,
                args.as_coroutine_closure().tupled_upvars_ty(),
                args.as_coroutine_closure().coroutine_captures_by_ref_ty(),
            ),
            sig.c_variadic,
            sig.safety,
            sig.abi,
        )
    });
    let sig = tcx.liberate_late_bound_regions(coroutine_closure_def_id, poly_sig);
    let ty::Coroutine(coroutine_def_id, coroutine_args) = *sig.output().kind() else {
        bug!();
    };

    let span = tcx.def_span(coroutine_closure_def_id);
    let locals = local_decls_for_sig(&sig, span);

    let mut fields = vec![];

    // Move all of the closure args.
    for idx in 1..sig.inputs().len() {
        fields.push(Operand::Move(Local::from_usize(idx + 1).into()));
    }

    for (idx, ty) in args.as_coroutine_closure().upvar_tys().iter().enumerate() {
        if receiver_by_ref {
            // The only situation where it's possible is when we capture immuatable references,
            // since those don't need to be reborrowed with the closure's env lifetime. Since
            // references are always `Copy`, just emit a copy.
            if !matches!(ty.kind(), ty::Ref(_, _, hir::Mutability::Not)) {
                // This copy is only sound if it's a `&T`. This may be
                // reachable e.g. when eagerly computing the `Fn` instance
                // of an async closure that doesn't borrowck.
                tcx.dcx().delayed_bug(format!(
                    "field should be captured by immutable ref if we have \
                    an `Fn` instance, but it was: {ty}"
                ));
            }
            fields.push(Operand::Copy(tcx.mk_place_field(
                self_local,
                FieldIdx::from_usize(idx),
                ty,
            )));
        } else {
            fields.push(Operand::Move(tcx.mk_place_field(
                self_local,
                FieldIdx::from_usize(idx),
                ty,
            )));
        }
    }

    let source_info = SourceInfo::outermost(span);
    let rvalue = Rvalue::Aggregate(
        Box::new(AggregateKind::Coroutine(coroutine_def_id, coroutine_args)),
        IndexVec::from_raw(fields),
    );
    let stmt = Statement::new(
        source_info,
        StatementKind::Assign(Box::new((Place::return_place(), rvalue))),
    );
    let statements = vec![stmt];
    let start_block = BasicBlockData::new_stmts(
        statements,
        Some(Terminator { source_info, kind: TerminatorKind::Return }),
        false,
    );

    let source = MirSource::from_instance(ty::InstanceKind::ConstructCoroutineInClosureShim {
        coroutine_closure_def_id,
        receiver_by_ref,
    });

    let body =
        new_body(source, IndexVec::from_elem_n(start_block, 1), locals, sig.inputs().len(), span);

    let pass_name =
        if receiver_by_ref { "coroutine_closure_by_ref" } else { "coroutine_closure_by_move" };
    if let Some(dumper) = MirDumper::new(tcx, pass_name, &body) {
        dumper.dump_mir(&body);
    }

    body
}
