use std::{cmp, fmt};

use rustc_abi as abi;
use rustc_abi::{
    AddressSpace, Align, ExternAbi, FieldIdx, FieldsShape, HasDataLayout, LayoutData, PointeeInfo,
    PointerKind, Primitive, ReprFlags, ReprOptions, Scalar, Size, TagEncoding, TargetDataLayout,
    TyAbiInterface, VariantIdx, Variants,
};
use rustc_errors::{
    Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, IntoDiagArg, Level,
};
use rustc_hir as hir;
use rustc_hir::LangItem;
use rustc_hir::def_id::DefId;
use rustc_macros::{StableHash, TyDecodable, TyEncodable, extension};
use rustc_session::config::OptLevel;
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, Symbol, sym};
use rustc_target::callconv::FnAbi;
use rustc_target::spec::{HasTargetSpec, HasX86AbiOpt, Target, X86Abi};
use tracing::debug;

use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use crate::query::TyCtxtAt;
use crate::traits::ObligationCause;
use crate::ty::normalize_erasing_regions::NormalizationError;
use crate::ty::{self, CoroutineArgsExt, Ty, TyCtxt, TypeVisitableExt, Unnormalized};

#[extension(pub trait IntegerExt)]
impl abi::Integer {
    #[inline]
    fn to_ty<'tcx>(&self, tcx: TyCtxt<'tcx>, signed: bool) -> Ty<'tcx> {
        use abi::Integer::{I8, I16, I32, I64, I128};
        match (*self, signed) {
            (I8, false) => tcx.types.u8,
            (I16, false) => tcx.types.u16,
            (I32, false) => tcx.types.u32,
            (I64, false) => tcx.types.u64,
            (I128, false) => tcx.types.u128,
            (I8, true) => tcx.types.i8,
            (I16, true) => tcx.types.i16,
            (I32, true) => tcx.types.i32,
            (I64, true) => tcx.types.i64,
            (I128, true) => tcx.types.i128,
        }
    }

    fn from_int_ty<C: HasDataLayout>(cx: &C, ity: ty::IntTy) -> abi::Integer {
        use abi::Integer::{I8, I16, I32, I64, I128};
        match ity {
            ty::IntTy::I8 => I8,
            ty::IntTy::I16 => I16,
            ty::IntTy::I32 => I32,
            ty::IntTy::I64 => I64,
            ty::IntTy::I128 => I128,
            ty::IntTy::Isize => cx.data_layout().ptr_sized_integer(),
        }
    }
    fn from_uint_ty<C: HasDataLayout>(cx: &C, ity: ty::UintTy) -> abi::Integer {
        use abi::Integer::{I8, I16, I32, I64, I128};
        match ity {
            ty::UintTy::U8 => I8,
            ty::UintTy::U16 => I16,
            ty::UintTy::U32 => I32,
            ty::UintTy::U64 => I64,
            ty::UintTy::U128 => I128,
            ty::UintTy::Usize => cx.data_layout().ptr_sized_integer(),
        }
    }

    /// Finds the appropriate Integer type and signedness for the given
    /// signed discriminant range and `#[repr]` attribute.
    /// N.B.: `u128` values above `i128::MAX` will be treated as signed, but
    /// that shouldn't affect anything, other than maybe debuginfo.
    ///
    /// This is the basis for computing the type of the *tag* of an enum (which can be smaller than
    /// the type of the *discriminant*, which is determined by [`ReprOptions::discr_type`]).
    fn discr_range_of_repr<'tcx>(
        tcx: TyCtxt<'tcx>,
        ty: Ty<'tcx>,
        repr: &ReprOptions,
        min: i128,
        max: i128,
    ) -> (abi::Integer, bool) {
        // Theoretically, negative values could be larger in unsigned representation
        // than the unsigned representation of the signed minimum. However, if there
        // are any negative values, the only valid unsigned representation is u128
        // which can fit all i128 values, so the result remains unaffected.
        let unsigned_fit = abi::Integer::fit_unsigned(cmp::max(min as u128, max as u128));
        let signed_fit = cmp::max(abi::Integer::fit_signed(min), abi::Integer::fit_signed(max));

        if let Some(ity) = repr.int {
            let discr = abi::Integer::from_attr(&tcx, ity);
            let fit = if ity.is_signed() { signed_fit } else { unsigned_fit };
            if discr < fit {
                bug!(
                    "Integer::repr_discr: `#[repr]` hint too small for \
                      discriminant range of enum `{}`",
                    ty
                )
            }
            return (discr, ity.is_signed());
        }

        let at_least = if repr.c() {
            // This is usually I32, however it can be different on some platforms,
            // notably hexagon and arm-none/thumb-none
            tcx.data_layout().c_enum_min_size
        } else {
            // repr(Rust) enums try to be as small as possible
            abi::Integer::I8
        };

        // Pick the smallest fit. Prefer unsigned; that matches clang in cases where this makes a
        // difference (https://godbolt.org/z/h4xEasW1d) so it is crucial for repr(C).
        if unsigned_fit <= signed_fit {
            (cmp::max(unsigned_fit, at_least), false)
        } else {
            (cmp::max(signed_fit, at_least), true)
        }
    }
}

#[extension(pub trait FloatExt)]
impl abi::Float {
    #[inline]
    fn to_ty<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
        use abi::Float::*;
        match *self {
            F16 => tcx.types.f16,
            F32 => tcx.types.f32,
            F64 => tcx.types.f64,
            F128 => tcx.types.f128,
        }
    }

    fn from_float_ty(fty: ty::FloatTy) -> Self {
        use abi::Float::*;
        match fty {
            ty::FloatTy::F16 => F16,
            ty::FloatTy::F32 => F32,
            ty::FloatTy::F64 => F64,
            ty::FloatTy::F128 => F128,
        }
    }
}

#[extension(pub trait PrimitiveExt)]
impl Primitive {
    #[inline]
    fn to_ty<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
        match *self {
            Primitive::Int(i, signed) => i.to_ty(tcx, signed),
            Primitive::Float(f) => f.to_ty(tcx),
            // FIXME(erikdesjardins): handle non-default addrspace ptr sizes
            Primitive::Pointer(_) => Ty::new_mut_ptr(tcx, tcx.types.unit),
        }
    }

    /// Return an *integer* type matching this primitive.
    /// Useful in particular when dealing with enum discriminants.
    #[inline]
    fn to_int_ty<'tcx>(&self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
        match *self {
            Primitive::Int(i, signed) => i.to_ty(tcx, signed),
            // FIXME(erikdesjardins): handle non-default addrspace ptr sizes
            Primitive::Pointer(_) => {
                let signed = false;
                tcx.data_layout().ptr_sized_integer().to_ty(tcx, signed)
            }
            Primitive::Float(_) => bug!("floats do not have an int type"),
        }
    }
}

/// The first half of a wide pointer.
///
/// - For a trait object, this is the address of the box.
/// - For a slice, this is the base address.
pub const WIDE_PTR_ADDR: usize = 0;

/// The second half of a wide pointer.
///
/// - For a trait object, this is the address of the vtable.
/// - For a slice, this is the length.
pub const WIDE_PTR_EXTRA: usize = 1;

pub const MAX_SIMD_LANES: u64 = rustc_abi::MAX_SIMD_LANES;

/// Used in `check_validity_requirement` to indicate the kind of initialization
/// that is checked to be valid
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, StableHash)]
pub enum ValidityRequirement {
    Inhabited,
    Zero,
    /// The return value of mem::uninitialized, 0x01
    /// (unless -Zstrict-init-checks is on, in which case it's the same as Uninit).
    UninitMitigated0x01Fill,
    /// True uninitialized memory.
    Uninit,
}

impl ValidityRequirement {
    pub fn from_intrinsic(intrinsic: Symbol) -> Option<Self> {
        match intrinsic {
            sym::assert_inhabited => Some(Self::Inhabited),
            sym::assert_zero_valid => Some(Self::Zero),
            sym::assert_mem_uninitialized_valid => Some(Self::UninitMitigated0x01Fill),
            _ => None,
        }
    }
}

impl fmt::Display for ValidityRequirement {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Inhabited => f.write_str("is inhabited"),
            Self::Zero => f.write_str("allows being left zeroed"),
            Self::UninitMitigated0x01Fill => f.write_str("allows being filled with 0x01"),
            Self::Uninit => f.write_str("allows being left uninitialized"),
        }
    }
}

#[derive(Copy, Clone, Debug, StableHash, TyEncodable, TyDecodable)]
pub enum SimdLayoutError {
    /// The vector has 0 lanes.
    ZeroLength,
    /// The vector has more lanes than supported or permitted by
    /// #\[rustc_simd_monomorphize_lane_limit\].
    TooManyLanes(u64),
}

#[derive(Copy, Clone, Debug, StableHash, TyEncodable, TyDecodable)]
pub enum LayoutError<'tcx> {
    /// A type doesn't have a sensible layout.
    ///
    /// This variant is used for layout errors that don't necessarily cause
    /// compile errors.
    ///
    /// For example, this can happen if a struct contains an unsized type in a
    /// non-tail field, but has an unsatisfiable bound like `str: Sized`.
    Unknown(Ty<'tcx>),
    /// The size of a type exceeds [`TargetDataLayout::obj_size_bound`].
    SizeOverflow(Ty<'tcx>),
    /// A SIMD vector has invalid layout, such as zero-length or too many lanes.
    InvalidSimd { ty: Ty<'tcx>, kind: SimdLayoutError },
    /// The layout can vary due to a generic parameter.
    ///
    /// Unlike `Unknown`, this variant is a "soft" error and indicates that the layout
    /// may become computable after further instantiating the generic parameter(s).
    TooGeneric(Ty<'tcx>),
    /// An alias failed to normalize.
    ///
    /// This variant is necessary, because, due to trait solver incompleteness, it is
    /// possible than an alias that was rigid during analysis fails to normalize after
    /// revealing opaque types.
    ///
    /// See `tests/ui/layout/normalization-failure.rs` for an example.
    NormalizationFailure(Ty<'tcx>, NormalizationError<'tcx>),
    /// A non-layout error is reported elsewhere.
    ReferencesError(ErrorGuaranteed),
}

impl<'tcx> fmt::Display for LayoutError<'tcx> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            LayoutError::Unknown(ty) => write!(f, "the type `{ty}` has an unknown layout"),
            LayoutError::TooGeneric(ty) => {
                write!(f, "the type `{ty}` does not have a fixed layout")
            }
            LayoutError::SizeOverflow(ty) => {
                write!(f, "values of the type `{ty}` are too big for the target architecture")
            }
            LayoutError::InvalidSimd { ty, kind: SimdLayoutError::TooManyLanes(max_lanes) } => {
                write!(f, "the SIMD type `{ty}` has more elements than the limit {max_lanes}")
            }
            LayoutError::InvalidSimd { ty, kind: SimdLayoutError::ZeroLength } => {
                write!(f, "the SIMD type `{ty}` has zero elements")
            }
            LayoutError::NormalizationFailure(t, e) => write!(
                f,
                "unable to determine layout for `{}` because `{}` cannot be normalized",
                t,
                e.get_type_for_failure()
            ),
            LayoutError::ReferencesError(_) => write!(f, "the type has an unknown layout"),
        }
    }
}

impl<'tcx> IntoDiagArg for LayoutError<'tcx> {
    fn into_diag_arg(self, _: &mut Option<std::path::PathBuf>) -> DiagArgValue {
        self.to_string().into_diag_arg(&mut None)
    }
}

#[derive(Clone, Copy)]
pub struct LayoutCx<'tcx> {
    pub calc: abi::LayoutCalculator<TyCtxt<'tcx>>,
    pub typing_env: ty::TypingEnv<'tcx>,
}

impl<'tcx> LayoutCx<'tcx> {
    pub fn new(tcx: TyCtxt<'tcx>, typing_env: ty::TypingEnv<'tcx>) -> Self {
        Self { calc: abi::LayoutCalculator::new(tcx), typing_env }
    }
}

/// Type size "skeleton", i.e., the only information determining a type's size.
/// While this is conservative, (aside from constant sizes, only pointers,
/// newtypes thereof and null pointer optimized enums are allowed), it is
/// enough to statically check common use cases of transmute.
#[derive(Copy, Clone, Debug)]
pub enum SizeSkeleton<'tcx> {
    /// Any statically computable Layout.
    /// Alignment can be `None` if unknown.
    Known(Size, Option<Align>),

    /// A potentially-wide pointer.
    Pointer {
        /// If true, this pointer is never null.
        non_zero: bool,
        /// The type which determines the unsized metadata, if any,
        /// of this pointer. Either a type parameter or a projection
        /// depending on one, with regions erased.
        tail: Ty<'tcx>,
    },
}

impl<'tcx> SizeSkeleton<'tcx> {
    pub fn compute(
        ty: Ty<'tcx>,
        tcx: TyCtxt<'tcx>,
        typing_env: ty::TypingEnv<'tcx>,
        span: Span,
    ) -> Result<SizeSkeleton<'tcx>, &'tcx LayoutError<'tcx>> {
        Self::compute_inner(ty, tcx, typing_env, span, 0)
    }

    fn compute_inner(
        ty: Ty<'tcx>,
        tcx: TyCtxt<'tcx>,
        typing_env: ty::TypingEnv<'tcx>,
        span: Span,
        depth: usize,
    ) -> Result<SizeSkeleton<'tcx>, &'tcx LayoutError<'tcx>> {
        debug_assert!(!ty.has_non_region_infer());

        // Bail out if we've recursed too deeply (issue #156137); a cyclic type
        // alias can otherwise blow the stack here. Using `>=` rather than `>`
        // means we fire exactly at the limit, which lets us report the
        // cycle-root type (`Thing<T>`) instead of an innocent field type.
        let recursion_limit = tcx.recursion_limit();
        if depth >= recursion_limit.0 {
            let suggested_limit = match recursion_limit {
                hir::limit::Limit(0) => hir::limit::Limit(2),
                limit => limit * 2,
            };
            let reported = tcx.dcx().emit_err(crate::error::RecursionLimitReachedSizeSkeleton {
                span,
                ty,
                suggested_limit,
            });
            return Err(tcx.arena.alloc(LayoutError::ReferencesError(reported)));
        }

        // First try computing a static layout.
        let err = match tcx.layout_of(typing_env.as_query_input(ty)) {
            Ok(layout) => {
                if layout.is_sized() {
                    return Ok(SizeSkeleton::Known(layout.size, Some(layout.align.abi)));
                } else {
                    // Just to be safe, don't claim a known layout for unsized types.
                    return Err(tcx.arena.alloc(LayoutError::Unknown(ty)));
                }
            }
            Err(err @ LayoutError::TooGeneric(_)) => err,
            // We can't extract SizeSkeleton info from other layout errors
            Err(
                e @ LayoutError::Unknown(_)
                | e @ LayoutError::SizeOverflow(_)
                | e @ LayoutError::InvalidSimd { .. }
                | e @ LayoutError::NormalizationFailure(..)
                | e @ LayoutError::ReferencesError(_),
            ) => return Err(e),
        };

        match *ty.kind() {
            ty::Ref(_, pointee, _) | ty::RawPtr(pointee, _) => {
                let non_zero = !ty.is_raw_ptr();

                tcx.assert_fully_normalized(typing_env, pointee);
                let tail = tcx.struct_tail_raw(
                    pointee,
                    &ObligationCause::dummy(),
                    |ty| match tcx.try_normalize_erasing_regions(typing_env, ty) {
                        Ok(ty) => ty,
                        Err(e) => Ty::new_error_with_message(
                            tcx,
                            DUMMY_SP,
                            format!(
                                "normalization failed for {} but no errors reported",
                                e.get_type_for_failure()
                            ),
                        ),
                    },
                    || {},
                );

                match tail.kind() {
                    ty::Param(_)
                    | ty::Alias(ty::AliasTy {
                        kind: ty::Projection { .. } | ty::Inherent { .. },
                        ..
                    }) => {
                        debug_assert!(tail.has_non_region_param());
                        Ok(SizeSkeleton::Pointer {
                            non_zero,
                            tail: tcx.erase_and_anonymize_regions(tail),
                        })
                    }
                    ty::Error(guar) => {
                        // Fixes ICE #124031
                        return Err(tcx.arena.alloc(LayoutError::ReferencesError(*guar)));
                    }
                    _ => bug!(
                        "SizeSkeleton::compute({ty}): layout errored ({err:?}), yet \
                              tail `{tail}` is not a type parameter or a projection",
                    ),
                }
            }
            ty::Array(inner, len) if tcx.features().transmute_generic_consts() => {
                let len_eval = len.try_to_target_usize(tcx);
                if len_eval == Some(0) {
                    return Ok(SizeSkeleton::Known(Size::from_bytes(0), None));
                }

                match SizeSkeleton::compute_inner(inner, tcx, typing_env, span, depth + 1)? {
                    // This may succeed because the multiplication of two types may overflow
                    // but a single size of a nested array will not.
                    SizeSkeleton::Known(s, a) => {
                        if let Some(c) = len_eval {
                            let size = s
                                .bytes()
                                .checked_mul(c)
                                .ok_or_else(|| &*tcx.arena.alloc(LayoutError::SizeOverflow(ty)))?;
                            // Alignment is unchanged by arrays.
                            return Ok(SizeSkeleton::Known(Size::from_bytes(size), a));
                        }
                        Err(err)
                    }
                    SizeSkeleton::Pointer { .. } => Err(err),
                }
            }

            ty::Adt(def, args) => {
                // Only newtypes and enums w/ nullable pointer optimization (NPO).
                if def.is_union() || def.variants().is_empty() || def.variants().len() > 2 {
                    return Err(err);
                }
                // Only default repr types.
                {
                    // We can ignore the seed and some particular flags that can never affect the
                    // layout of newtypes / NPO types, but we have to check everything else.
                    // If you are adding a new field to `ReprOptions`, make sure to extend the check
                    // below so that we bail out if it is not at its default value!
                    let ReprOptions { int, align, pack, flags, scalable, field_shuffle_seed: _ } =
                        def.repr();
                    let mut ignored_flags = ReprFlags::IS_TRANSPARENT
                        | ReprFlags::IS_LINEAR
                        | ReprFlags::RANDOMIZE_LAYOUT;
                    if def.is_struct() {
                        // `repr(C)` is only okay for structs, not for enums.
                        // Below, the *only* thing we do for structs is propagating
                        // `SizeSkeleton::Pointer`. We do *not* assume that `repr(C)` preserved
                        // ZST-ness (which might stop being true eventually).
                        ignored_flags |= ReprFlags::IS_C;
                    }
                    if int.is_some()
                        || align.is_some()
                        || pack.is_some()
                        || flags.difference(ignored_flags) != ReprFlags::default()
                        || scalable.is_some()
                    {
                        return Err(err);
                    }
                }

                // Get a zero-sized variant or a pointer newtype.
                // Returns `Ok(None)` for 1-ZST types, `Ok(Some)` if (ignoring all 1-ZST fields)
                // there's just a single pointer, and `Err` otherwise.
                let zero_or_ptr_variant = |i| -> Result<Option<SizeSkeleton<'tcx>>, _> {
                    let i = VariantIdx::from_usize(i);
                    let fields = def.variant(i).fields.iter().map(|field| {
                        SizeSkeleton::compute_inner(
                            field.ty(tcx, args).skip_norm_wip(),
                            tcx,
                            typing_env,
                            span,
                            depth + 1,
                        )
                    });
                    let mut ptr = None;
                    for field in fields {
                        let field = field?;
                        match field {
                            SizeSkeleton::Known(size, align) => {
                                let is_1zst = size.bytes() == 0
                                    && align.is_some_and(|align| align.bytes() == 1);
                                if !is_1zst {
                                    return Err(err);
                                }
                            }
                            SizeSkeleton::Pointer { .. } => {
                                if ptr.is_some() {
                                    return Err(err);
                                }
                                ptr = Some(field);
                            }
                        }
                    }
                    Ok(ptr)
                };

                let v0 = zero_or_ptr_variant(0)?;
                // Single-variant case: Check if this is a newtype around a pointer.
                // Such types are themselves pointer-sized.
                if def.variants().len() == 1 {
                    if let Some(SizeSkeleton::Pointer { non_zero, tail }) = v0 {
                        return Ok(SizeSkeleton::Pointer { non_zero, tail });
                    } else {
                        return Err(err);
                    }
                }

                let v1 = zero_or_ptr_variant(1)?;
                // 2-variant case: Check if one variant is a *non-zero* pointer and the other a
                // 1-ZST. Such types are eligible to for the nullable pointer enum optimization, so
                // they are themselves pointer-sized.
                match (v0, v1) {
                    (Some(SizeSkeleton::Pointer { non_zero: true, tail }), None)
                    | (None, Some(SizeSkeleton::Pointer { non_zero: true, tail })) => {
                        Ok(SizeSkeleton::Pointer { non_zero: false, tail })
                    }
                    _ => Err(err),
                }
            }

            ty::Alias(..) => {
                let normalized =
                    tcx.normalize_erasing_regions(typing_env, Unnormalized::new_wip(ty));
                if ty == normalized {
                    Err(err)
                } else {
                    SizeSkeleton::compute_inner(normalized, tcx, typing_env, span, depth + 1)
                }
            }

            ty::Pat(base, pat) => {
                // Pattern types are always the same size as their base.
                let base = SizeSkeleton::compute_inner(base, tcx, typing_env, span, depth + 1);
                match *pat {
                    ty::PatternKind::Range { .. } | ty::PatternKind::Or(_) => base,
                    // But in the case of `!null` patterns we need to note that in the
                    // raw pointer.
                    ty::PatternKind::NotNull => match base? {
                        SizeSkeleton::Known(..) => base,
                        SizeSkeleton::Pointer { non_zero: _, tail } => {
                            Ok(SizeSkeleton::Pointer { non_zero: true, tail })
                        }
                    },
                }
            }

            _ => Err(err),
        }
    }

    pub fn same_size(self, other: SizeSkeleton<'tcx>) -> bool {
        match (self, other) {
            (SizeSkeleton::Known(a, _), SizeSkeleton::Known(b, _)) => a == b,
            (SizeSkeleton::Pointer { tail: a, .. }, SizeSkeleton::Pointer { tail: b, .. }) => {
                a == b
            }
            _ => false,
        }
    }
}

pub trait HasTyCtxt<'tcx>: HasDataLayout {
    fn tcx(&self) -> TyCtxt<'tcx>;
}

pub trait HasTypingEnv<'tcx> {
    fn typing_env(&self) -> ty::TypingEnv<'tcx>;
}

impl<'tcx> HasDataLayout for TyCtxt<'tcx> {
    #[inline]
    fn data_layout(&self) -> &TargetDataLayout {
        &self.data_layout
    }
}

impl<'tcx> HasTargetSpec for TyCtxt<'tcx> {
    fn target_spec(&self) -> &Target {
        &self.sess.target
    }
}

impl<'tcx> HasX86AbiOpt for TyCtxt<'tcx> {
    fn x86_abi_opt(&self) -> X86Abi {
        X86Abi {
            regparm: self.sess.opts.unstable_opts.regparm,
            reg_struct_return: self.sess.opts.unstable_opts.reg_struct_return,
        }
    }
}

impl<'tcx> HasTyCtxt<'tcx> for TyCtxt<'tcx> {
    #[inline]
    fn tcx(&self) -> TyCtxt<'tcx> {
        *self
    }
}

impl<'tcx> HasDataLayout for TyCtxtAt<'tcx> {
    #[inline]
    fn data_layout(&self) -> &TargetDataLayout {
        &self.data_layout
    }
}

impl<'tcx> HasTargetSpec for TyCtxtAt<'tcx> {
    fn target_spec(&self) -> &Target {
        &self.sess.target
    }
}

impl<'tcx> HasTyCtxt<'tcx> for TyCtxtAt<'tcx> {
    #[inline]
    fn tcx(&self) -> TyCtxt<'tcx> {
        **self
    }
}

impl<'tcx> HasTypingEnv<'tcx> for LayoutCx<'tcx> {
    fn typing_env(&self) -> ty::TypingEnv<'tcx> {
        self.typing_env
    }
}

impl<'tcx> HasDataLayout for LayoutCx<'tcx> {
    fn data_layout(&self) -> &TargetDataLayout {
        self.calc.cx.data_layout()
    }
}

impl<'tcx> HasTargetSpec for LayoutCx<'tcx> {
    fn target_spec(&self) -> &Target {
        self.calc.cx.target_spec()
    }
}

impl<'tcx> HasX86AbiOpt for LayoutCx<'tcx> {
    fn x86_abi_opt(&self) -> X86Abi {
        self.calc.cx.x86_abi_opt()
    }
}

impl<'tcx> HasTyCtxt<'tcx> for LayoutCx<'tcx> {
    fn tcx(&self) -> TyCtxt<'tcx> {
        self.calc.cx
    }
}

pub trait MaybeResult<T> {
    type Error;

    fn from(x: Result<T, Self::Error>) -> Self;
    fn to_result(self) -> Result<T, Self::Error>;
}

impl<T> MaybeResult<T> for T {
    type Error = !;

    fn from(Ok(x): Result<T, Self::Error>) -> Self {
        x
    }
    fn to_result(self) -> Result<T, Self::Error> {
        Ok(self)
    }
}

impl<T, E> MaybeResult<T> for Result<T, E> {
    type Error = E;

    fn from(x: Result<T, Self::Error>) -> Self {
        x
    }
    fn to_result(self) -> Result<T, Self::Error> {
        self
    }
}

pub type TyAndLayout<'tcx> = rustc_abi::TyAndLayout<'tcx, Ty<'tcx>>;

/// Trait for contexts that want to be able to compute layouts of types.
/// This automatically gives access to `LayoutOf`, through a blanket `impl`.
pub trait LayoutOfHelpers<'tcx>: HasDataLayout + HasTyCtxt<'tcx> + HasTypingEnv<'tcx> {
    /// The `TyAndLayout`-wrapping type (or `TyAndLayout` itself), which will be
    /// returned from `layout_of` (see also `handle_layout_err`).
    type LayoutOfResult: MaybeResult<TyAndLayout<'tcx>> = TyAndLayout<'tcx>;

    /// `Span` to use for `tcx.at(span)`, from `layout_of`.
    // FIXME(eddyb) perhaps make this mandatory to get contexts to track it better?
    #[inline]
    fn layout_tcx_at_span(&self) -> Span {
        DUMMY_SP
    }

    /// Helper used for `layout_of`, to adapt `tcx.layout_of(...)` into a
    /// `Self::LayoutOfResult` (which does not need to be a `Result<...>`).
    ///
    /// Most `impl`s, which propagate `LayoutError`s, should simply return `err`,
    /// but this hook allows e.g. codegen to return only `TyAndLayout` from its
    /// `cx.layout_of(...)`, without any `Result<...>` around it to deal with
    /// (and any `LayoutError`s are turned into fatal errors or ICEs).
    fn handle_layout_err(
        &self,
        err: LayoutError<'tcx>,
        span: Span,
        ty: Ty<'tcx>,
    ) -> <Self::LayoutOfResult as MaybeResult<TyAndLayout<'tcx>>>::Error;
}

/// Blanket extension trait for contexts that can compute layouts of types.
pub trait LayoutOf<'tcx>: LayoutOfHelpers<'tcx> {
    /// Computes the layout of a type. Note that this implicitly
    /// executes in `TypingMode::PostAnalysis`, and will normalize the input type.
    #[inline]
    fn layout_of(&self, ty: Ty<'tcx>) -> Self::LayoutOfResult {
        self.spanned_layout_of(ty, DUMMY_SP)
    }

    /// Computes the layout of a type, at `span`. Note that this implicitly
    /// executes in `TypingMode::PostAnalysis`, and will normalize the input type.
    // FIXME(eddyb) avoid passing information like this, and instead add more
    // `TyCtxt::at`-like APIs to be able to do e.g. `cx.at(span).layout_of(ty)`.
    #[inline]
    fn spanned_layout_of(&self, ty: Ty<'tcx>, span: Span) -> Self::LayoutOfResult {
        let span = if !span.is_dummy() { span } else { self.layout_tcx_at_span() };
        let tcx = self.tcx().at(span);

        MaybeResult::from(
            tcx.layout_of(self.typing_env().as_query_input(ty))
                .map_err(|err| self.handle_layout_err(*err, span, ty)),
        )
    }
}

impl<'tcx, C: LayoutOfHelpers<'tcx>> LayoutOf<'tcx> for C {}

impl<'tcx> LayoutOfHelpers<'tcx> for LayoutCx<'tcx> {
    type LayoutOfResult = Result<TyAndLayout<'tcx>, &'tcx LayoutError<'tcx>>;

    #[inline]
    fn handle_layout_err(
        &self,
        err: LayoutError<'tcx>,
        _: Span,
        _: Ty<'tcx>,
    ) -> &'tcx LayoutError<'tcx> {
        self.tcx().arena.alloc(err)
    }
}

impl<'tcx, C> TyAbiInterface<'tcx, C> for Ty<'tcx>
where
    C: HasTyCtxt<'tcx> + HasTypingEnv<'tcx>,
{
    fn ty_and_layout_for_variant(
        this: TyAndLayout<'tcx>,
        cx: &C,
        variant_index: VariantIdx,
    ) -> TyAndLayout<'tcx> {
        let layout = match this.variants {
            // If all variants but one are uninhabited, the variant layout is the enum layout.
            Variants::Single { index } if index == variant_index => {
                return this;
            }

            Variants::Single { .. } | Variants::Empty => {
                // Single-variant and no-variant enums *can* have other variants, but those are
                // uninhabited. Produce a layout that has the right fields for that variant, so that
                // the rest of the compiler can project fields etc as usual.

                let tcx = cx.tcx();
                let typing_env = cx.typing_env();

                // Deny calling for_variant more than once for non-Single enums.
                if let Ok(original_layout) = tcx.layout_of(typing_env.as_query_input(this.ty)) {
                    assert_eq!(original_layout.variants, this.variants);
                }

                let fields = match this.ty.kind() {
                    ty::Adt(def, _) if def.variants().is_empty() => {
                        bug!("for_variant called on zero-variant enum {}", this.ty)
                    }
                    ty::Adt(def, _) => def.variant(variant_index).fields.len(),
                    _ => bug!("`ty_and_layout_for_variant` on unexpected type {}", this.ty),
                };
                tcx.mk_layout(LayoutData::uninhabited_variant(cx, variant_index, fields))
            }

            Variants::Multiple { .. } => {
                cx.tcx().mk_layout(LayoutData::for_variant(&this, variant_index))
            }
        };

        assert_eq!(*layout.variants(), Variants::Single { index: variant_index });

        TyAndLayout { ty: this.ty, layout }
    }

    fn ty_and_layout_field(this: TyAndLayout<'tcx>, cx: &C, i: usize) -> TyAndLayout<'tcx> {
        enum TyMaybeWithLayout<'tcx> {
            Ty(Ty<'tcx>),
            TyAndLayout(TyAndLayout<'tcx>),
        }

        fn field_ty_or_layout<'tcx>(
            this: TyAndLayout<'tcx>,
            cx: &(impl HasTyCtxt<'tcx> + HasTypingEnv<'tcx>),
            i: usize,
        ) -> TyMaybeWithLayout<'tcx> {
            let tcx = cx.tcx();
            let tag_layout = |tag: Scalar| -> TyAndLayout<'tcx> {
                TyAndLayout {
                    layout: tcx.mk_layout(LayoutData::scalar(cx, tag)),
                    ty: tag.primitive().to_ty(tcx),
                }
            };

            match *this.ty.kind() {
                ty::Bool
                | ty::Char
                | ty::Int(_)
                | ty::Uint(_)
                | ty::Float(_)
                | ty::FnPtr(..)
                | ty::Never
                | ty::FnDef(..)
                | ty::CoroutineWitness(..)
                | ty::Foreign(..)
                | ty::Dynamic(_, _) => {
                    bug!("TyAndLayout::field({:?}): not applicable", this)
                }

                ty::Pat(base, _) => {
                    assert_eq!(i, 0);
                    TyMaybeWithLayout::Ty(base)
                }

                ty::UnsafeBinder(bound_ty) => {
                    let ty = tcx.instantiate_bound_regions_with_erased(bound_ty.into());
                    field_ty_or_layout(TyAndLayout { ty, ..this }, cx, i)
                }

                // Potentially-wide pointers.
                ty::Ref(_, pointee, _) | ty::RawPtr(pointee, _) => {
                    assert!(i < this.fields.count());

                    // Reuse the wide `*T` type as its own thin pointer data field.
                    // This provides information about, e.g., DST struct pointees
                    // (which may have no non-DST form), and will work as long
                    // as the `Abi` or `FieldsShape` is checked by users.
                    if i == 0 {
                        let nil = tcx.types.unit;
                        let unit_ptr_ty = if this.ty.is_raw_ptr() {
                            Ty::new_mut_ptr(tcx, nil)
                        } else {
                            Ty::new_mut_ref(tcx, tcx.lifetimes.re_static, nil)
                        };

                        // NOTE: using an fully monomorphized typing env and `unwrap`-ing
                        // the `Result` should always work because the type is always either
                        // `*mut ()` or `&'static mut ()`.
                        let typing_env = ty::TypingEnv::fully_monomorphized();
                        return TyMaybeWithLayout::TyAndLayout(TyAndLayout {
                            ty: this.ty,
                            ..tcx.layout_of(typing_env.as_query_input(unit_ptr_ty)).unwrap()
                        });
                    }

                    let mk_dyn_vtable = |principal: Option<ty::PolyExistentialTraitRef<'tcx>>| {
                        let min_count = ty::vtable_min_entries(
                            tcx,
                            principal.map(|principal| {
                                tcx.instantiate_bound_regions_with_erased(principal)
                            }),
                        );
                        Ty::new_imm_ref(
                            tcx,
                            tcx.lifetimes.re_static,
                            // FIXME: properly type (e.g. usize and fn pointers) the fields.
                            Ty::new_array(tcx, tcx.types.usize, min_count.try_into().unwrap()),
                        )
                    };

                    let metadata = if let Some(metadata_def_id) = tcx.lang_items().metadata_type()
                        // Projection eagerly bails out when the pointee references errors,
                        // fall back to structurally deducing metadata.
                        && !pointee.references_error()
                    {
                        let metadata = tcx.normalize_erasing_regions(
                            cx.typing_env(),
                            Unnormalized::new(Ty::new_projection(tcx, metadata_def_id, [pointee])),
                        );

                        // Map `Metadata = DynMetadata<dyn Trait>` back to a vtable, since it
                        // offers better information than `std::ptr::metadata::VTable`,
                        // and we rely on this layout information to trigger a panic in
                        // `std::mem::uninitialized::<&dyn Trait>()`, for example.
                        if let ty::Adt(def, args) = metadata.kind()
                            && tcx.is_lang_item(def.did(), LangItem::DynMetadata)
                            && let ty::Dynamic(data, _) = args.type_at(0).kind()
                        {
                            mk_dyn_vtable(data.principal())
                        } else {
                            metadata
                        }
                    } else {
                        match tcx.struct_tail_for_codegen(pointee, cx.typing_env()).kind() {
                            ty::Slice(_) | ty::Str => tcx.types.usize,
                            ty::Dynamic(data, _) => mk_dyn_vtable(data.principal()),
                            _ => bug!("TyAndLayout::field({:?}): not applicable", this),
                        }
                    };

                    TyMaybeWithLayout::Ty(metadata)
                }

                // Arrays and slices.
                ty::Array(element, _) | ty::Slice(element) => TyMaybeWithLayout::Ty(element),
                ty::Str => TyMaybeWithLayout::Ty(tcx.types.u8),

                // Tuples, coroutines and closures.
                ty::Closure(_, args) => field_ty_or_layout(
                    TyAndLayout { ty: args.as_closure().tupled_upvars_ty(), ..this },
                    cx,
                    i,
                ),

                ty::CoroutineClosure(_, args) => field_ty_or_layout(
                    TyAndLayout { ty: args.as_coroutine_closure().tupled_upvars_ty(), ..this },
                    cx,
                    i,
                ),

                ty::Coroutine(def_id, args) => match this.variants {
                    Variants::Empty => unreachable!(),
                    Variants::Single { index } => TyMaybeWithLayout::Ty(
                        args.as_coroutine()
                            .state_tys(def_id, tcx)
                            .nth(index.as_usize())
                            .unwrap()
                            .nth(i)
                            .unwrap(),
                    ),
                    Variants::Multiple { tag, tag_field, .. } => {
                        if FieldIdx::from_usize(i) == tag_field {
                            return TyMaybeWithLayout::TyAndLayout(tag_layout(tag));
                        }
                        TyMaybeWithLayout::Ty(args.as_coroutine().prefix_tys()[i])
                    }
                },

                ty::Tuple(tys) => TyMaybeWithLayout::Ty(tys[i]),

                // ADTs.
                ty::Adt(def, args) => {
                    match this.variants {
                        Variants::Single { index } => {
                            let field = &def.variant(index).fields[FieldIdx::from_usize(i)];
                            TyMaybeWithLayout::Ty(field.ty(tcx, args).skip_norm_wip())
                        }
                        Variants::Empty => panic!("there is no field in Variants::Empty types"),

                        // Discriminant field for enums (where applicable).
                        Variants::Multiple { tag, .. } => {
                            assert_eq!(i, 0);
                            return TyMaybeWithLayout::TyAndLayout(tag_layout(tag));
                        }
                    }
                }

                ty::Alias(..)
                | ty::Bound(..)
                | ty::Placeholder(..)
                | ty::Param(_)
                | ty::Infer(_)
                | ty::Error(_) => bug!("TyAndLayout::field: unexpected type `{}`", this.ty),
            }
        }

        match field_ty_or_layout(this, cx, i) {
            TyMaybeWithLayout::Ty(field_ty) => {
                cx.tcx().layout_of(cx.typing_env().as_query_input(field_ty)).unwrap_or_else(|e| {
                    bug!(
                        "failed to get layout for `{field_ty}`: {e:?},\n\
                         despite it being a field (#{i}) of an existing layout: {this:#?}",
                    )
                })
            }
            TyMaybeWithLayout::TyAndLayout(field_layout) => field_layout,
        }
    }

    /// Compute the information for the pointer stored at the given offset inside this type.
    /// This will recurse into fields of ADTs to find the inner pointer.
    fn ty_and_layout_pointee_info_at(
        this: TyAndLayout<'tcx>,
        cx: &C,
        offset: Size,
    ) -> Option<PointeeInfo> {
        let tcx = cx.tcx();
        let typing_env = cx.typing_env();

        // Use conservative pointer kind if not optimizing. This saves us the
        // Freeze/Unpin queries, and can save time in the codegen backend (noalias
        // attributes in LLVM have compile-time cost even in unoptimized builds).
        let optimize = tcx.sess.opts.optimize != OptLevel::No;

        let pointee_info = match *this.ty.kind() {
            ty::RawPtr(_, _) | ty::FnPtr(..) if offset.bytes() == 0 => {
                Some(PointeeInfo { safe: None, size: Size::ZERO, align: Align::ONE })
            }
            ty::Ref(_, ty, mt) if offset.bytes() == 0 => {
                tcx.layout_of(typing_env.as_query_input(ty)).ok().map(|layout| {
                    let (size, kind);
                    match mt {
                        hir::Mutability::Not => {
                            let frozen = optimize && ty.is_freeze(tcx, typing_env);

                            // Non-frozen shared references are not necessarily dereferenceable for the entire duration of the function
                            // (see <https://github.com/rust-lang/rust/pull/98017>)
                            // (if we had "dereferenceable on entry", we could support this)
                            size = if frozen { layout.size } else { Size::ZERO };

                            kind = PointerKind::SharedRef { frozen };
                        }
                        hir::Mutability::Mut => {
                            let unpin = optimize
                                && ty.is_unpin(tcx, typing_env)
                                && ty.is_unsafe_unpin(tcx, typing_env);

                            // Mutable references to potentially self-referential types are not
                            // necessarily dereferenceable for the entire duration of the function
                            // (see <https://github.com/rust-lang/unsafe-code-guidelines/issues/381>)
                            // (if we had "dereferenceable on entry", we could support this)
                            size = if unpin { layout.size } else { Size::ZERO };

                            kind = PointerKind::MutableRef { unpin };
                        }
                    };
                    PointeeInfo { safe: Some(kind), size, align: layout.align.abi }
                })
            }

            ty::Adt(..)
                if offset.bytes() == 0
                    && let Some(pointee) = this.ty.boxed_ty() =>
            {
                tcx.layout_of(typing_env.as_query_input(pointee)).ok().map(|layout| PointeeInfo {
                    safe: Some(PointerKind::Box {
                        // Same logic as for mutable references above.
                        unpin: optimize
                            && pointee.is_unpin(tcx, typing_env)
                            && pointee.is_unsafe_unpin(tcx, typing_env),
                        global: this.ty.is_box_global(tcx),
                    }),

                    // `Box` are not necessarily dereferenceable for the entire duration of the function as
                    // they can be deallocated at any time.
                    // (if we had "dereferenceable on entry", we could support this)
                    size: Size::ZERO,

                    align: layout.align.abi,
                })
            }

            ty::Adt(adt_def, ..) if adt_def.is_maybe_dangling() => {
                Self::ty_and_layout_pointee_info_at(this.field(cx, 0), cx, offset).map(|info| {
                    PointeeInfo {
                        // Mark the pointer as raw
                        // (thus removing noalias/readonly/etc in case of the llvm backend)
                        safe: None,
                        // Make sure we don't assert dereferenceability of the pointer.
                        size: Size::ZERO,
                        // Preserve the alignment assertion! That is required even inside `MaybeDangling`.
                        align: info.align,
                    }
                })
            }

            _ => {
                let mut data_variant = match &this.variants {
                    // Within the discriminant field, only the niche itself is
                    // always initialized, so we only check for a pointer at its
                    // offset.
                    //
                    // Our goal here is to check whether this represents a
                    // "dereferenceable or null" pointer, so we need to ensure
                    // that there is only one other variant, and it must be null.
                    // Below, we will then check whether the pointer is indeed
                    // dereferenceable.
                    Variants::Multiple {
                        tag_encoding:
                            TagEncoding::Niche { untagged_variant, niche_variants, niche_start },
                        tag_field,
                        variants,
                        ..
                    } if variants.len() == 2
                        && this.fields.offset(tag_field.as_usize()) == offset =>
                    {
                        let tagged_variant = if *untagged_variant == VariantIdx::ZERO {
                            VariantIdx::from_u32(1)
                        } else {
                            VariantIdx::from_u32(0)
                        };
                        assert_eq!(tagged_variant, niche_variants.start);
                        if *niche_start == 0 {
                            // The other variant is encoded as "null", so we can recurse searching for
                            // a pointer here. This relies on the fact that the codegen backend
                            // only adds "dereferenceable" if there's also a "nonnull" proof,
                            // and that null is aligned for all alignments so it's okay to forward
                            // the pointer's alignment.
                            Some(this.for_variant(cx, *untagged_variant))
                        } else {
                            None
                        }
                    }
                    Variants::Multiple { .. } => None,
                    Variants::Empty | Variants::Single { .. } => Some(this),
                };

                if let Some(variant) = data_variant
                    // We're not interested in any unions.
                    && let FieldsShape::Union(_) = variant.fields
                {
                    data_variant = None;
                }

                let mut result = None;

                if let Some(variant) = data_variant {
                    // FIXME(erikdesjardins): handle non-default addrspace ptr sizes
                    // (requires passing in the expected address space from the caller)
                    let ptr_end = offset + Primitive::Pointer(AddressSpace::ZERO).size(cx);
                    for i in 0..variant.fields.count() {
                        let field_start = variant.fields.offset(i);
                        if field_start <= offset {
                            let field = variant.field(cx, i);
                            result = field.to_result().ok().and_then(|field| {
                                if ptr_end <= field_start + field.size {
                                    // We found the right field, look inside it.
                                    let field_info =
                                        field.pointee_info_at(cx, offset - field_start);
                                    field_info
                                } else {
                                    None
                                }
                            });
                            if result.is_some() {
                                break;
                            }
                        }
                    }
                }

                result
            }
        };

        debug!(
            "pointee_info_at (offset={:?}, type kind: {:?}) => {:?}",
            offset,
            this.ty.kind(),
            pointee_info
        );

        pointee_info
    }

    fn is_adt(this: TyAndLayout<'tcx>) -> bool {
        matches!(this.ty.kind(), ty::Adt(..))
    }

    fn is_never(this: TyAndLayout<'tcx>) -> bool {
        matches!(this.ty.kind(), ty::Never)
    }

    fn is_tuple(this: TyAndLayout<'tcx>) -> bool {
        matches!(this.ty.kind(), ty::Tuple(..))
    }

    fn is_unit(this: TyAndLayout<'tcx>) -> bool {
        matches!(this.ty.kind(), ty::Tuple(list) if list.len() == 0)
    }

    fn is_transparent(this: TyAndLayout<'tcx>) -> bool {
        matches!(this.ty.kind(), ty::Adt(def, _) if def.repr().transparent())
    }

    fn is_scalable_vector(this: TyAndLayout<'tcx>) -> bool {
        this.ty.is_scalable_vector()
    }

    /// See [`TyAndLayout::pass_indirectly_in_non_rustic_abis`] for details.
    fn is_pass_indirectly_in_non_rustic_abis_flag_set(this: TyAndLayout<'tcx>) -> bool {
        matches!(this.ty.kind(), ty::Adt(def, _) if def.repr().flags.contains(ReprFlags::PASS_INDIRECTLY_IN_NON_RUSTIC_ABIS))
    }
}

/// Calculates whether a function's ABI can unwind or not.
///
/// This takes two primary parameters:
///
/// * `fn_def_id` - the `DefId` of the function. If this is provided then we can
///   determine more precisely if the function can unwind. If this is not provided
///   then we will only infer whether the function can unwind or not based on the
///   ABI of the function. For example, a function marked with `#[rustc_nounwind]`
///   is known to not unwind even if it's using Rust ABI.
///
/// * `abi` - this is the ABI that the function is defined with. This is the
///   primary factor for determining whether a function can unwind or not.
///
/// Note that in this case unwinding is not necessarily panicking in Rust. Rust
/// panics are implemented with unwinds on most platform (when
/// `-Cpanic=unwind`), but this also accounts for `-Cpanic=abort` build modes.
/// Notably unwinding is disallowed for more non-Rust ABIs unless it's
/// specifically in the name (e.g. `"C-unwind"`). Unwinding within each ABI is
/// defined for each ABI individually, but it always corresponds to some form of
/// stack-based unwinding (the exact mechanism of which varies
/// platform-by-platform).
///
/// Rust functions are classified whether or not they can unwind based on the
/// active "panic strategy". In other words Rust functions are considered to
/// unwind in `-Cpanic=unwind` mode and cannot unwind in `-Cpanic=abort` mode.
/// Note that Rust supports intermingling panic=abort and panic=unwind code, but
/// only if the final panic mode is panic=abort. In this scenario any code
/// previously compiled assuming that a function can unwind is still correct, it
/// just never happens to actually unwind at runtime.
///
/// This function's answer to whether or not a function can unwind is quite
/// impactful throughout the compiler. This affects things like:
///
/// * Calling a function which can't unwind means codegen simply ignores any
///   associated unwinding cleanup.
/// * Calling a function which can unwind from a function which can't unwind
///   causes the `abort_unwinding_calls` MIR pass to insert a landing pad that
///   aborts the process.
/// * This affects whether functions have the LLVM `nounwind` attribute, which
///   affects various optimizations and codegen.
#[inline]
#[tracing::instrument(level = "debug", skip(tcx))]
pub fn fn_can_unwind(tcx: TyCtxt<'_>, fn_def_id: Option<DefId>, abi: ExternAbi) -> bool {
    if let Some(did) = fn_def_id {
        // Special attribute for functions which can't unwind.
        if tcx.codegen_fn_attrs(did).flags.contains(CodegenFnAttrFlags::NEVER_UNWIND) {
            return false;
        }

        // With `-C panic=abort`, all non-FFI functions are required to not unwind.
        //
        // Note that this is true regardless ABI specified on the function -- a `extern "C-unwind"`
        // function defined in Rust is also required to abort.
        if !tcx.sess.panic_strategy().unwinds() && !tcx.is_foreign_item(did) {
            return false;
        }

        // With -Z panic-in-drop=abort, `drop_glue` never unwinds.
        //
        // This is not part of `codegen_fn_attrs` as it can differ between crates
        // and therefore cannot be computed in core.
        if !tcx.sess.opts.unstable_opts.panic_in_drop.unwinds()
            && tcx.is_lang_item(did, LangItem::DropGlue)
        {
            return false;
        }
    }

    // Otherwise if this isn't special then unwinding is generally determined by
    // the ABI of the itself. ABIs like `C` have variants which also
    // specifically allow unwinding (`C-unwind`), but not all platform-specific
    // ABIs have such an option. Otherwise the only other thing here is Rust
    // itself, and those ABIs are determined by the panic strategy configured
    // for this compilation.
    use ExternAbi::*;
    match abi {
        C { unwind }
        | System { unwind }
        | Cdecl { unwind }
        | Stdcall { unwind }
        | Fastcall { unwind }
        | Vectorcall { unwind }
        | Thiscall { unwind }
        | Aapcs { unwind }
        | Win64 { unwind }
        | SysV64 { unwind } => unwind,
        PtxKernel
        | Msp430Interrupt
        | X86Interrupt
        | GpuKernel
        | EfiApi
        | AvrInterrupt
        | AvrNonBlockingInterrupt
        | CmseNonSecureCall
        | CmseNonSecureEntry
        | Custom
        | RiscvInterruptM
        | RiscvInterruptS
        | RustInvalid
        | Swift
        | Unadjusted => false,
        Rust | RustCall | RustCold | RustPreserveNone => tcx.sess.panic_strategy().unwinds(),
    }
}

/// Error produced by attempting to compute or adjust a `FnAbi`.
#[derive(Copy, Clone, Debug, StableHash)]
pub enum FnAbiError<'tcx> {
    /// Error produced by a `layout_of` call, while computing `FnAbi` initially.
    Layout(LayoutError<'tcx>),
}

impl<'a, 'b, G: EmissionGuarantee> Diagnostic<'a, G> for FnAbiError<'b> {
    fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> {
        match self {
            Self::Layout(e) => Diag::new(dcx, level, e.to_string()),
        }
    }
}

// FIXME(eddyb) maybe use something like this for an unified `fn_abi_of`, not
// just for error handling.
#[derive(Debug)]
pub enum FnAbiRequest<'tcx> {
    OfFnPtr { sig: ty::PolyFnSig<'tcx>, extra_args: &'tcx ty::List<Ty<'tcx>> },
    OfInstance { instance: ty::Instance<'tcx>, extra_args: &'tcx ty::List<Ty<'tcx>> },
}

/// Trait for contexts that want to be able to compute `FnAbi`s.
/// This automatically gives access to `FnAbiOf`, through a blanket `impl`.
pub trait FnAbiOfHelpers<'tcx>: LayoutOfHelpers<'tcx> {
    /// The `&FnAbi`-wrapping type (or `&FnAbi` itself), which will be
    /// returned from `fn_abi_of_*` (see also `handle_fn_abi_err`).
    type FnAbiOfResult: MaybeResult<&'tcx FnAbi<'tcx, Ty<'tcx>>> = &'tcx FnAbi<'tcx, Ty<'tcx>>;

    /// Helper used for `fn_abi_of_*`, to adapt `tcx.fn_abi_of_*(...)` into a
    /// `Self::FnAbiOfResult` (which does not need to be a `Result<...>`).
    ///
    /// Most `impl`s, which propagate `FnAbiError`s, should simply return `err`,
    /// but this hook allows e.g. codegen to return only `&FnAbi` from its
    /// `cx.fn_abi_of_*(...)`, without any `Result<...>` around it to deal with
    /// (and any `FnAbiError`s are turned into fatal errors or ICEs).
    fn handle_fn_abi_err(
        &self,
        err: FnAbiError<'tcx>,
        span: Span,
        fn_abi_request: FnAbiRequest<'tcx>,
    ) -> <Self::FnAbiOfResult as MaybeResult<&'tcx FnAbi<'tcx, Ty<'tcx>>>>::Error;
}

/// Blanket extension trait for contexts that can compute `FnAbi`s.
pub trait FnAbiOf<'tcx>: FnAbiOfHelpers<'tcx> {
    /// Compute a `FnAbi` suitable for indirect calls, i.e. to `fn` pointers.
    ///
    /// NB: this doesn't handle virtual calls - those should use `fn_abi_of_instance`
    /// instead, where the instance is an `InstanceKind::Virtual`.
    #[inline]
    fn fn_abi_of_fn_ptr(
        &self,
        sig: ty::PolyFnSig<'tcx>,
        extra_args: &'tcx ty::List<Ty<'tcx>>,
    ) -> Self::FnAbiOfResult {
        // FIXME(eddyb) get a better `span` here.
        let span = self.layout_tcx_at_span();
        let tcx = self.tcx().at(span);

        MaybeResult::from(
            tcx.fn_abi_of_fn_ptr(self.typing_env().as_query_input((sig, extra_args))).map_err(
                |err| self.handle_fn_abi_err(*err, span, FnAbiRequest::OfFnPtr { sig, extra_args }),
            ),
        )
    }

    /// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for direct calls*
    /// to an `fn`. Indirectly-passed parameters in the returned ABI might not include all possible
    /// codegen optimization attributes (such as `ReadOnly` or `CapturesNone`), as deducing these
    /// requires inspection of function bodies that can lead to cycles when performed during typeck.
    /// Post typeck, you should prefer the optimized ABI returned by `fn_abi_of_instance`.
    ///
    /// NB: the ABI returned by this query must not differ from that returned by
    ///     `fn_abi_of_instance` in any other way.
    ///
    /// * that includes virtual calls, which are represented by "direct calls" to an
    ///   `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::fn`).
    #[inline]
    #[tracing::instrument(level = "debug", skip(self))]
    fn fn_abi_of_instance_no_deduced_attrs(
        &self,
        instance: ty::Instance<'tcx>,
        extra_args: &'tcx ty::List<Ty<'tcx>>,
    ) -> Self::FnAbiOfResult {
        // FIXME(eddyb) get a better `span` here.
        let span = self.layout_tcx_at_span();
        let tcx = self.tcx().at(span);

        MaybeResult::from(
            tcx.fn_abi_of_instance_no_deduced_attrs(
                self.typing_env().as_query_input((instance, extra_args)),
            )
            .map_err(|err| {
                // HACK(eddyb) at least for definitions of/calls to `Instance`s,
                // we can get some kind of span even if one wasn't provided.
                // However, we don't do this early in order to avoid calling
                // `def_span` unconditionally (which may have a perf penalty).
                let span = if !span.is_dummy() { span } else { tcx.def_span(instance.def_id()) };
                self.handle_fn_abi_err(
                    *err,
                    span,
                    FnAbiRequest::OfInstance { instance, extra_args },
                )
            }),
        )
    }

    /// Compute a `FnAbi` suitable for declaring/defining an `fn` instance, and for direct calls*
    /// to an `fn`. Indirectly-passed parameters in the returned ABI will include applicable
    /// codegen optimization attributes, including `ReadOnly` and `CapturesNone` -- deduction of
    /// which requires inspection of function bodies that can lead to cycles when performed during
    /// typeck. During typeck, you should therefore use instead the unoptimized ABI returned by
    /// `fn_abi_of_instance_no_deduced_attrs`.
    ///
    /// * that includes virtual calls, which are represented by "direct calls" to an
    ///   `InstanceKind::Virtual` instance (of `<dyn Trait as Trait>::fn`).
    #[inline]
    #[tracing::instrument(level = "debug", skip(self))]
    fn fn_abi_of_instance(
        &self,
        instance: ty::Instance<'tcx>,
        extra_args: &'tcx ty::List<Ty<'tcx>>,
    ) -> Self::FnAbiOfResult {
        // FIXME(eddyb) get a better `span` here.
        let span = self.layout_tcx_at_span();
        let tcx = self.tcx().at(span);

        MaybeResult::from(
            tcx.fn_abi_of_instance(self.typing_env().as_query_input((instance, extra_args)))
                .map_err(|err| {
                    // HACK(eddyb) at least for definitions of/calls to `Instance`s,
                    // we can get some kind of span even if one wasn't provided.
                    // However, we don't do this early in order to avoid calling
                    // `def_span` unconditionally (which may have a perf penalty).
                    let span =
                        if !span.is_dummy() { span } else { tcx.def_span(instance.def_id()) };
                    self.handle_fn_abi_err(
                        *err,
                        span,
                        FnAbiRequest::OfInstance { instance, extra_args },
                    )
                }),
        )
    }
}

impl<'tcx, C: FnAbiOfHelpers<'tcx>> FnAbiOf<'tcx> for C {}
