use std::fmt::{self, Debug}; use std::num::NonZero; use std::ops::RangeInclusive; use serde::Serialize; use crate::compiler_interface::with; use crate::mir::FieldIdx; use crate::target::{MachineInfo, MachineSize as Size}; use crate::ty::{Align, IndexedVal, Ty, VariantIdx}; use crate::{Error, Opaque, error}; /// A function ABI definition. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct FnAbi { /// The types of each argument. pub args: Vec, /// The expected return type. pub ret: ArgAbi, /// The count of non-variadic arguments. /// /// Should only be different from `args.len()` when a function is a C variadic function. pub fixed_count: u32, /// The ABI convention. pub conv: CallConvention, /// Whether this is a variadic C function, pub c_variadic: bool, } /// Information about the ABI of a function's argument, or return value. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct ArgAbi { pub ty: Ty, pub layout: Layout, pub mode: PassMode, } /// How a function argument should be passed in to the target function. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub enum PassMode { /// Ignore the argument. /// /// The argument is either uninhabited or a ZST. Ignore, /// Pass the argument directly. /// /// The argument has a layout abi of `Scalar` or `Vector`. Direct(Opaque), /// Pass a pair's elements directly in two arguments. /// /// The argument has a layout abi of `ScalarPair`. Pair(Opaque, Opaque), /// Pass the argument after casting it. Cast { pad_i32: bool, cast: Opaque }, /// Pass the argument indirectly via a hidden pointer. Indirect { attrs: Opaque, meta_attrs: Opaque, on_stack: bool }, } /// The layout of a type, alongside the type itself. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct TyAndLayout { pub ty: Ty, pub layout: Layout, } /// The layout of a type in memory. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct LayoutShape { /// The fields location within the layout pub fields: FieldsShape, /// Encodes information about multi-variant layouts. /// Even with `Multiple` variants, a layout still has its own fields! Those are then /// shared between all variants. /// /// To access all fields of this layout, both `fields` and the fields of the active variant /// must be taken into account. pub variants: VariantsShape, /// The `abi` defines how this data is passed between functions. pub abi: ValueAbi, /// The ABI mandated alignment in bytes. pub abi_align: Align, /// The size of this layout in bytes. pub size: Size, } impl LayoutShape { /// Returns `true` if the layout corresponds to an unsized type. #[inline] pub fn is_unsized(&self) -> bool { self.abi.is_unsized() } #[inline] pub fn is_sized(&self) -> bool { !self.abi.is_unsized() } /// Returns `true` if the type is sized and a 1-ZST (meaning it has size 0 and alignment 1). pub fn is_1zst(&self) -> bool { self.is_sized() && self.size.bits() == 0 && self.abi_align == 1 } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct Layout(usize); impl Layout { pub fn shape(self) -> LayoutShape { with(|cx| cx.layout_shape(self)) } } impl IndexedVal for Layout { fn to_val(index: usize) -> Self { Layout(index) } fn to_index(&self) -> usize { self.0 } } /// Describes how the fields of a type are shaped in memory. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub enum FieldsShape { /// Scalar primitives and `!`, which never have fields. Primitive, /// All fields start at no offset. The `usize` is the field count. Union(NonZero), /// Array/vector-like placement, with all fields of identical types. Array { stride: Size, count: u64 }, /// Struct-like placement, with precomputed offsets. /// /// Fields are guaranteed to not overlap, but note that gaps /// before, between and after all the fields are NOT always /// padding, and as such their contents may not be discarded. /// For example, enum variants leave a gap at the start, /// where the discriminant field in the enum layout goes. Arbitrary { /// Offsets for the first byte of each field, /// ordered to match the source definition order. /// I.e.: It follows the same order as [crate::ty::VariantDef::fields()]. /// This vector does not go in increasing order. offsets: Vec, }, } impl FieldsShape { pub fn fields_by_offset_order(&self) -> Vec { match self { FieldsShape::Primitive => vec![], FieldsShape::Union(_) | FieldsShape::Array { .. } => (0..self.count()).collect(), FieldsShape::Arbitrary { offsets, .. } => { let mut indices = (0..offsets.len()).collect::>(); indices.sort_by_key(|idx| offsets[*idx]); indices } } } pub fn count(&self) -> usize { match self { FieldsShape::Primitive => 0, FieldsShape::Union(count) => count.get(), FieldsShape::Array { count, .. } => *count as usize, FieldsShape::Arbitrary { offsets, .. } => offsets.len(), } } } #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub enum VariantsShape { /// Single enum variants, structs/tuples, unions, and all non-ADTs. Single { index: VariantIdx }, /// Enum-likes with more than one inhabited variant: each variant comes with /// a *discriminant* (usually the same as the variant index but the user can /// assign explicit discriminant values). That discriminant is encoded /// as a *tag* on the machine. The layout of each variant is /// a struct, and they all have space reserved for the tag. /// For enums, the tag is the sole field of the layout. Multiple { tag: Scalar, tag_encoding: TagEncoding, tag_field: usize, variants: Vec, }, } #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub enum TagEncoding { /// The tag directly stores the discriminant, but possibly with a smaller layout /// (so converting the tag to the discriminant can require sign extension). Direct, /// Niche (values invalid for a type) encoding the discriminant: /// Discriminant and variant index coincide. /// The variant `untagged_variant` contains a niche at an arbitrary /// offset (field `tag_field` of the enum), which for a variant with /// discriminant `d` is set to /// `(d - niche_variants.start).wrapping_add(niche_start)`. /// /// For example, `Option<(usize, &T)>` is represented such that /// `None` has a null pointer for the second tuple field, and /// `Some` is the identity function (with a non-null reference). Niche { untagged_variant: VariantIdx, niche_variants: RangeInclusive, niche_start: u128, }, } /// Describes how values of the type are passed by target ABIs, /// in terms of categories of C types there are ABI rules for. #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub enum ValueAbi { Uninhabited, Scalar(Scalar), ScalarPair(Scalar, Scalar), Vector { element: Scalar, count: u64, }, Aggregate { /// If true, the size is exact, otherwise it's only a lower bound. sized: bool, }, } impl ValueAbi { /// Returns `true` if the layout corresponds to an unsized type. pub fn is_unsized(&self) -> bool { match *self { ValueAbi::Uninhabited | ValueAbi::Scalar(_) | ValueAbi::ScalarPair(..) | ValueAbi::Vector { .. } => false, ValueAbi::Aggregate { sized } => !sized, } } } /// Information about one scalar component of a Rust type. #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize)] pub enum Scalar { Initialized { /// The primitive type used to represent this value. value: Primitive, /// The range that represents valid values. /// The range must be valid for the `primitive` size. valid_range: WrappingRange, }, Union { /// Unions never have niches, so there is no `valid_range`. /// Even for unions, we need to use the correct registers for the kind of /// values inside the union, so we keep the `Primitive` type around. /// It is also used to compute the size of the scalar. value: Primitive, }, } impl Scalar { pub fn has_niche(&self, target: &MachineInfo) -> bool { match self { Scalar::Initialized { value, valid_range } => { !valid_range.is_full(value.size(target)).unwrap() } Scalar::Union { .. } => false, } } } /// Fundamental unit of memory access and layout. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Serialize)] pub enum Primitive { /// The `bool` is the signedness of the `Integer` type. /// /// One would think we would not care about such details this low down, /// but some ABIs are described in terms of C types and ISAs where the /// integer arithmetic is done on {sign,zero}-extended registers, e.g. /// a negative integer passed by zero-extension will appear positive in /// the callee, and most operations on it will produce the wrong values. Int { length: IntegerLength, signed: bool, }, Float { length: FloatLength, }, Pointer(AddressSpace), } impl Primitive { pub fn size(self, target: &MachineInfo) -> Size { match self { Primitive::Int { length, .. } => Size::from_bits(length.bits()), Primitive::Float { length } => Size::from_bits(length.bits()), Primitive::Pointer(_) => target.pointer_width, } } } /// Enum representing the existing integer lengths. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize)] pub enum IntegerLength { I8, I16, I32, I64, I128, } /// Enum representing the existing float lengths. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize)] pub enum FloatLength { F16, F32, F64, F128, } impl IntegerLength { pub fn bits(self) -> usize { match self { IntegerLength::I8 => 8, IntegerLength::I16 => 16, IntegerLength::I32 => 32, IntegerLength::I64 => 64, IntegerLength::I128 => 128, } } } impl FloatLength { pub fn bits(self) -> usize { match self { FloatLength::F16 => 16, FloatLength::F32 => 32, FloatLength::F64 => 64, FloatLength::F128 => 128, } } } /// An identifier that specifies the address space that some operation /// should operate on. Special address spaces have an effect on code generation, /// depending on the target and the address spaces it implements. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] pub struct AddressSpace(pub u32); impl AddressSpace { /// The default address space, corresponding to data space. pub const DATA: Self = AddressSpace(0); } /// Inclusive wrap-around range of valid values (bitwise representation), that is, if /// start > end, it represents `start..=MAX`, followed by `0..=end`. /// /// That is, for an i8 primitive, a range of `254..=2` means following /// sequence: /// /// 254 (-2), 255 (-1), 0, 1, 2 #[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize)] pub struct WrappingRange { pub start: u128, pub end: u128, } impl WrappingRange { /// Returns `true` if `size` completely fills the range. #[inline] pub fn is_full(&self, size: Size) -> Result { let Some(max_value) = size.unsigned_int_max() else { return Err(error!("Expected size <= 128 bits, but found {} instead", size.bits())); }; if self.start <= max_value && self.end <= max_value { Ok(self.start == (self.end.wrapping_add(1) & max_value)) } else { Err(error!("Range `{self:?}` out of bounds for size `{}` bits.", size.bits())) } } /// Returns `true` if `v` is contained in the range. #[inline(always)] pub fn contains(&self, v: u128) -> bool { if self.wraps_around() { self.start <= v || v <= self.end } else { self.start <= v && v <= self.end } } /// Returns `true` if the range wraps around. /// I.e., the range represents the union of `self.start..=MAX` and `0..=self.end`. /// Returns `false` if this is a non-wrapping range, i.e.: `self.start..=self.end`. #[inline] pub fn wraps_around(&self) -> bool { self.start > self.end } } impl Debug for WrappingRange { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { if self.start > self.end { write!(fmt, "(..={}) | ({}..)", self.end, self.start)?; } else { write!(fmt, "{}..={}", self.start, self.end)?; } Ok(()) } } /// General language calling conventions. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub enum CallConvention { C, Rust, Cold, PreserveMost, PreserveAll, // Target-specific calling conventions. ArmAapcs, CCmseNonSecureCall, CCmseNonSecureEntry, Msp430Intr, PtxKernel, X86Fastcall, X86Intr, X86Stdcall, X86ThisCall, X86VectorCall, X86_64SysV, X86_64Win64, AvrInterrupt, AvrNonBlockingInterrupt, RiscvInterrupt, }