From 1a83c5b55bdad84baca69d121d9c3e6f357e8570 Mon Sep 17 00:00:00 2001 From: "Celina G. Val" Date: Mon, 18 Dec 2023 19:52:25 +0000 Subject: [PATCH] Add function ABI and type layout to StableMIR This change introduces a new module to StableMIR named `abi` with information from `rustc_target::abi` and `rustc_abi`, that allow users to retrieve more low level information required to perform bit-precise analysis. The layout of a type can be retrieved via `Ty::layout`, and the instance ABI can be retrieved via `Instance::fn_abi()`. To properly handle errors while retrieve layout information, we had to implement a few layout related traits. --- .../rustc_smir/src/rustc_internal/internal.rs | 9 + compiler/rustc_smir/src/rustc_internal/mod.rs | 6 + compiler/rustc_smir/src/rustc_smir/context.rs | 75 ++++- .../rustc_smir/src/rustc_smir/convert/abi.rs | 229 ++++++++++++++ .../rustc_smir/src/rustc_smir/convert/mod.rs | 20 +- compiler/rustc_smir/src/rustc_smir/mod.rs | 13 + compiler/stable_mir/src/abi.rs | 286 ++++++++++++++++++ compiler/stable_mir/src/compiler_interface.rs | 10 + compiler/stable_mir/src/lib.rs | 1 + compiler/stable_mir/src/mir/mono.rs | 6 + compiler/stable_mir/src/ty.rs | 6 + .../stable-mir/check_allocation.rs | 1 - tests/ui-fulldeps/stable-mir/check_defs.rs | 6 +- tests/ui-fulldeps/stable-mir/check_layout.rs | 117 +++++++ 14 files changed, 760 insertions(+), 25 deletions(-) create mode 100644 compiler/rustc_smir/src/rustc_smir/convert/abi.rs create mode 100644 compiler/stable_mir/src/abi.rs create mode 100644 tests/ui-fulldeps/stable-mir/check_layout.rs diff --git a/compiler/rustc_smir/src/rustc_internal/internal.rs b/compiler/rustc_smir/src/rustc_internal/internal.rs index 63207200353..bbc98af45c0 100644 --- a/compiler/rustc_smir/src/rustc_internal/internal.rs +++ b/compiler/rustc_smir/src/rustc_internal/internal.rs @@ -7,6 +7,7 @@ use crate::rustc_smir::Tables; use rustc_middle::ty::{self as rustc_ty, Ty as InternalTy}; use rustc_span::Symbol; +use stable_mir::abi::Layout; use stable_mir::mir::alloc::AllocId; use stable_mir::mir::mono::{Instance, MonoItem, StaticDef}; use stable_mir::mir::{Mutability, Safety}; @@ -460,6 +461,14 @@ impl<'tcx> RustcInternal<'tcx> for Span { } } +impl<'tcx> RustcInternal<'tcx> for Layout { + type T = rustc_target::abi::Layout<'tcx>; + + fn internal(&self, tables: &mut Tables<'tcx>) -> Self::T { + tables.layouts[*self] + } +} + impl<'tcx, T> RustcInternal<'tcx> for &T where T: RustcInternal<'tcx>, diff --git a/compiler/rustc_smir/src/rustc_internal/mod.rs b/compiler/rustc_smir/src/rustc_internal/mod.rs index c3db9b358e8..4bac98909ad 100644 --- a/compiler/rustc_smir/src/rustc_internal/mod.rs +++ b/compiler/rustc_smir/src/rustc_internal/mod.rs @@ -12,6 +12,7 @@ use rustc_middle::ty::TyCtxt; use rustc_span::def_id::{CrateNum, DefId}; use rustc_span::Span; use scoped_tls::scoped_thread_local; +use stable_mir::abi::Layout; use stable_mir::ty::IndexedVal; use stable_mir::Error; use std::cell::Cell; @@ -136,6 +137,10 @@ impl<'tcx> Tables<'tcx> { pub(crate) fn static_def(&mut self, did: DefId) -> stable_mir::mir::mono::StaticDef { stable_mir::mir::mono::StaticDef(self.create_def_id(did)) } + + pub(crate) fn layout_id(&mut self, layout: rustc_target::abi::Layout<'tcx>) -> Layout { + self.layouts.create_or_fetch(layout) + } } pub fn crate_num(item: &stable_mir::Crate) -> CrateNum { @@ -180,6 +185,7 @@ where types: IndexMap::default(), instances: IndexMap::default(), constants: IndexMap::default(), + layouts: IndexMap::default(), })); stable_mir::compiler_interface::run(&tables, || init(&tables, f)) } diff --git a/compiler/rustc_smir/src/rustc_smir/context.rs b/compiler/rustc_smir/src/rustc_smir/context.rs index 2361a04a6d7..621766c695e 100644 --- a/compiler/rustc_smir/src/rustc_smir/context.rs +++ b/compiler/rustc_smir/src/rustc_smir/context.rs @@ -3,12 +3,19 @@ //! This trait is currently the main interface between the Rust compiler, //! and the `stable_mir` crate. +#![allow(rustc::usage_of_qualified_ty)] + +use rustc_abi::HasDataLayout; use rustc_middle::ty; +use rustc_middle::ty::layout::{ + FnAbiOf, FnAbiOfHelpers, HasParamEnv, HasTyCtxt, LayoutOf, LayoutOfHelpers, +}; use rustc_middle::ty::print::{with_forced_trimmed_paths, with_no_trimmed_paths}; use rustc_middle::ty::{ - GenericPredicates, Instance, ParamEnv, ScalarInt, TypeVisitableExt, ValTree, + GenericPredicates, Instance, List, ParamEnv, ScalarInt, TyCtxt, TypeVisitableExt, ValTree, }; use rustc_span::def_id::LOCAL_CRATE; +use stable_mir::abi::{FnAbi, Layout, LayoutShape}; use stable_mir::compiler_interface::Context; use stable_mir::mir::alloc::GlobalAlloc; use stable_mir::mir::mono::{InstanceDef, StaticDef}; @@ -280,7 +287,6 @@ impl<'tcx> Context for TablesWrapper<'tcx> { tables.tcx.mk_ty_from_kind(internal_kind).stable(&mut *tables) } - #[allow(rustc::usage_of_qualified_ty)] fn new_box_ty(&self, ty: stable_mir::ty::Ty) -> stable_mir::ty::Ty { let mut tables = self.0.borrow_mut(); let inner = ty.internal(&mut *tables); @@ -335,6 +341,12 @@ impl<'tcx> Context for TablesWrapper<'tcx> { instance.ty(tables.tcx, ParamEnv::reveal_all()).stable(&mut *tables) } + fn instance_abi(&self, def: InstanceDef) -> Result { + let mut tables = self.0.borrow_mut(); + let instance = tables.instances[def]; + Ok(tables.fn_abi_of_instance(instance, List::empty())?.stable(&mut *tables)) + } + fn instance_def_id(&self, def: InstanceDef) -> stable_mir::DefId { let mut tables = self.0.borrow_mut(); let def_id = tables.instances[def].def_id(); @@ -473,6 +485,65 @@ impl<'tcx> Context for TablesWrapper<'tcx> { ) } } + + fn ty_layout(&self, ty: Ty) -> Result { + let mut tables = self.0.borrow_mut(); + let ty = ty.internal(&mut *tables); + let layout = tables.layout_of(ty)?.layout; + Ok(layout.stable(&mut *tables)) + } + + fn layout_shape(&self, id: Layout) -> LayoutShape { + let mut tables = self.0.borrow_mut(); + id.internal(&mut *tables).0.stable(&mut *tables) + } } pub struct TablesWrapper<'tcx>(pub RefCell>); + +/// Implement error handling for extracting function ABI information. +impl<'tcx> FnAbiOfHelpers<'tcx> for Tables<'tcx> { + type FnAbiOfResult = Result<&'tcx rustc_target::abi::call::FnAbi<'tcx, ty::Ty<'tcx>>, Error>; + + #[inline] + fn handle_fn_abi_err( + &self, + err: ty::layout::FnAbiError<'tcx>, + _span: rustc_span::Span, + fn_abi_request: ty::layout::FnAbiRequest<'tcx>, + ) -> Error { + Error::new(format!("Failed to get ABI for `{fn_abi_request:?}`: {err:?}")) + } +} + +impl<'tcx> LayoutOfHelpers<'tcx> for Tables<'tcx> { + type LayoutOfResult = Result, Error>; + + #[inline] + fn handle_layout_err( + &self, + err: ty::layout::LayoutError<'tcx>, + _span: rustc_span::Span, + ty: ty::Ty<'tcx>, + ) -> Error { + Error::new(format!("Failed to get layout for `{ty}`: {err}")) + } +} + +impl<'tcx> HasParamEnv<'tcx> for Tables<'tcx> { + fn param_env(&self) -> ty::ParamEnv<'tcx> { + ty::ParamEnv::reveal_all() + } +} + +impl<'tcx> HasTyCtxt<'tcx> for Tables<'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.tcx + } +} + +impl<'tcx> HasDataLayout for Tables<'tcx> { + fn data_layout(&self) -> &rustc_abi::TargetDataLayout { + self.tcx.data_layout() + } +} diff --git a/compiler/rustc_smir/src/rustc_smir/convert/abi.rs b/compiler/rustc_smir/src/rustc_smir/convert/abi.rs new file mode 100644 index 00000000000..46ae0929bcd --- /dev/null +++ b/compiler/rustc_smir/src/rustc_smir/convert/abi.rs @@ -0,0 +1,229 @@ +//! Conversion of internal Rust compiler `rustc_target::abi` and `rustc_abi` items to stable ones. + +#![allow(rustc::usage_of_qualified_ty)] + +use crate::rustc_smir::{Stable, Tables}; +use rustc_middle::ty; +use rustc_target::abi::call::Conv; +use stable_mir::abi::{ + ArgAbi, CallConvention, FieldsShape, FnAbi, Layout, LayoutShape, PassMode, TagEncoding, + TyAndLayout, ValueAbi, VariantsShape, +}; +use stable_mir::ty::{Align, IndexedVal, Size, VariantIdx}; +use stable_mir::{opaque, Opaque}; + +impl<'tcx> Stable<'tcx> for rustc_target::abi::VariantIdx { + type T = VariantIdx; + fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { + VariantIdx::to_val(self.as_usize()) + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Endian { + type T = stable_mir::target::Endian; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + match self { + rustc_abi::Endian::Little => stable_mir::target::Endian::Little, + rustc_abi::Endian::Big => stable_mir::target::Endian::Big, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::TyAndLayout<'tcx, ty::Ty<'tcx>> { + type T = TyAndLayout; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + TyAndLayout { ty: self.ty.stable(tables), layout: self.layout.stable(tables) } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::Layout<'tcx> { + type T = Layout; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + tables.layout_id(*self) + } +} + +impl<'tcx> Stable<'tcx> + for rustc_abi::LayoutS +{ + type T = LayoutShape; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + LayoutShape { + fields: self.fields.stable(tables), + variants: self.variants.stable(tables), + abi: self.abi.stable(tables), + abi_align: self.align.abi.stable(tables), + size: self.size.stable(tables), + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::call::FnAbi<'tcx, ty::Ty<'tcx>> { + type T = FnAbi; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + FnAbi { + args: self.args.as_ref().stable(tables), + ret: self.ret.stable(tables), + fixed_count: self.fixed_count, + conv: self.conv.stable(tables), + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::call::ArgAbi<'tcx, ty::Ty<'tcx>> { + type T = ArgAbi; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + ArgAbi { + ty: self.layout.ty.stable(tables), + layout: self.layout.layout.stable(tables), + mode: self.mode.stable(tables), + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::call::Conv { + type T = CallConvention; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + match self { + Conv::C => CallConvention::C, + Conv::Rust => CallConvention::Rust, + Conv::Cold => CallConvention::Cold, + Conv::PreserveMost => CallConvention::PreserveMost, + Conv::PreserveAll => CallConvention::PreserveAll, + Conv::ArmAapcs => CallConvention::ArmAapcs, + Conv::CCmseNonSecureCall => CallConvention::CCmseNonSecureCall, + Conv::Msp430Intr => CallConvention::Msp430Intr, + Conv::PtxKernel => CallConvention::PtxKernel, + Conv::X86Fastcall => CallConvention::X86Fastcall, + Conv::X86Intr => CallConvention::X86Intr, + Conv::X86Stdcall => CallConvention::X86Stdcall, + Conv::X86ThisCall => CallConvention::X86ThisCall, + Conv::X86VectorCall => CallConvention::X86VectorCall, + Conv::X86_64SysV => CallConvention::X86_64SysV, + Conv::X86_64Win64 => CallConvention::X86_64Win64, + Conv::AmdGpuKernel => CallConvention::AmdGpuKernel, + Conv::AvrInterrupt => CallConvention::AvrInterrupt, + Conv::AvrNonBlockingInterrupt => CallConvention::AvrNonBlockingInterrupt, + Conv::RiscvInterrupt { .. } => CallConvention::RiscvInterrupt, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_target::abi::call::PassMode { + type T = PassMode; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + match self { + rustc_target::abi::call::PassMode::Ignore => PassMode::Ignore, + rustc_target::abi::call::PassMode::Direct(_) => PassMode::Direct, + rustc_target::abi::call::PassMode::Pair(_, _) => PassMode::Pair, + rustc_target::abi::call::PassMode::Cast { .. } => PassMode::Cast, + rustc_target::abi::call::PassMode::Indirect { .. } => PassMode::Indirect, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::FieldsShape { + type T = FieldsShape; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + match self { + rustc_abi::FieldsShape::Primitive => FieldsShape::Primitive, + rustc_abi::FieldsShape::Union(count) => FieldsShape::Union(*count), + rustc_abi::FieldsShape::Array { stride, count } => { + FieldsShape::Array { stride: stride.stable(tables), count: *count } + } + rustc_abi::FieldsShape::Arbitrary { offsets, .. } => { + FieldsShape::Arbitrary { offsets: offsets.iter().as_slice().stable(tables) } + } + } + } +} + +impl<'tcx> Stable<'tcx> + for rustc_abi::Variants +{ + type T = VariantsShape; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + match self { + rustc_abi::Variants::Single { index } => { + VariantsShape::Single { index: index.stable(tables) } + } + rustc_abi::Variants::Multiple { tag, tag_encoding, tag_field, variants } => { + VariantsShape::Multiple { + tag: tag.stable(tables), + tag_encoding: tag_encoding.stable(tables), + tag_field: *tag_field, + variants: variants.iter().as_slice().stable(tables), + } + } + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::TagEncoding { + type T = TagEncoding; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + match self { + rustc_abi::TagEncoding::Direct => TagEncoding::Direct, + rustc_abi::TagEncoding::Niche { untagged_variant, niche_variants, niche_start } => { + TagEncoding::Niche { + untagged_variant: untagged_variant.stable(tables), + niche_variants: niche_variants.stable(tables), + niche_start: *niche_start, + } + } + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Abi { + type T = ValueAbi; + + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + match *self { + rustc_abi::Abi::Uninhabited => ValueAbi::Uninhabited, + rustc_abi::Abi::Scalar(scalar) => ValueAbi::Scalar(scalar.stable(tables)), + rustc_abi::Abi::ScalarPair(first, second) => { + ValueAbi::ScalarPair(first.stable(tables), second.stable(tables)) + } + rustc_abi::Abi::Vector { element, count } => { + ValueAbi::Vector { element: element.stable(tables), count } + } + rustc_abi::Abi::Aggregate { sized } => ValueAbi::Aggregate { sized }, + } + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Size { + type T = Size; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + self.bytes_usize() + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Align { + type T = Align; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + self.bytes() + } +} + +impl<'tcx> Stable<'tcx> for rustc_abi::Scalar { + type T = Opaque; + + fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { + opaque(self) + } +} diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mod.rs b/compiler/rustc_smir/src/rustc_smir/convert/mod.rs index 7021bdda735..8b7b26f969c 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mod.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mod.rs @@ -1,10 +1,10 @@ //! Conversion of internal Rust compiler items to stable ones. use rustc_target::abi::FieldIdx; -use stable_mir::ty::{IndexedVal, VariantIdx}; use crate::rustc_smir::{Stable, Tables}; +mod abi; mod error; mod mir; mod ty; @@ -26,13 +26,6 @@ impl<'tcx> Stable<'tcx> for FieldIdx { } } -impl<'tcx> Stable<'tcx> for rustc_target::abi::VariantIdx { - type T = VariantIdx; - fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { - VariantIdx::to_val(self.as_usize()) - } -} - impl<'tcx> Stable<'tcx> for rustc_hir::CoroutineSource { type T = stable_mir::mir::CoroutineSource; fn stable(&self, _: &mut Tables<'tcx>) -> Self::T { @@ -79,14 +72,3 @@ impl<'tcx> Stable<'tcx> for rustc_span::Span { tables.create_span(*self) } } - -impl<'tcx> Stable<'tcx> for rustc_abi::Endian { - type T = stable_mir::target::Endian; - - fn stable(&self, _tables: &mut Tables<'tcx>) -> Self::T { - match self { - rustc_abi::Endian::Little => stable_mir::target::Endian::Little, - rustc_abi::Endian::Big => stable_mir::target::Endian::Big, - } - } -} diff --git a/compiler/rustc_smir/src/rustc_smir/mod.rs b/compiler/rustc_smir/src/rustc_smir/mod.rs index ae6cf3fe3e8..cf6ac5285c2 100644 --- a/compiler/rustc_smir/src/rustc_smir/mod.rs +++ b/compiler/rustc_smir/src/rustc_smir/mod.rs @@ -12,9 +12,11 @@ use rustc_middle::mir; use rustc_middle::mir::interpret::AllocId; use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; use rustc_span::def_id::{CrateNum, DefId, LOCAL_CRATE}; +use stable_mir::abi::Layout; use stable_mir::mir::mono::InstanceDef; use stable_mir::ty::{ConstId, Span}; use stable_mir::ItemKind; +use std::ops::RangeInclusive; use tracing::debug; use crate::rustc_internal::IndexMap; @@ -32,6 +34,7 @@ pub struct Tables<'tcx> { pub(crate) types: IndexMap, stable_mir::ty::Ty>, pub(crate) instances: IndexMap, InstanceDef>, pub(crate) constants: IndexMap, ConstId>, + pub(crate) layouts: IndexMap, Layout>, } impl<'tcx> Tables<'tcx> { @@ -162,3 +165,13 @@ where (self.0.stable(tables), self.1.stable(tables)) } } + +impl<'tcx, T> Stable<'tcx> for RangeInclusive +where + T: Stable<'tcx>, +{ + type T = RangeInclusive; + fn stable(&self, tables: &mut Tables<'tcx>) -> Self::T { + RangeInclusive::new(self.start().stable(tables), self.end().stable(tables)) + } +} diff --git a/compiler/stable_mir/src/abi.rs b/compiler/stable_mir/src/abi.rs new file mode 100644 index 00000000000..d4e8e874c1f --- /dev/null +++ b/compiler/stable_mir/src/abi.rs @@ -0,0 +1,286 @@ +use crate::compiler_interface::with; +use crate::mir::FieldIdx; +use crate::ty::{Align, IndexedVal, Size, Ty, VariantIdx}; +use crate::Opaque; +use std::num::NonZeroUsize; +use std::ops::RangeInclusive; + +/// A function ABI definition. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +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, +} + +impl FnAbi { + pub fn is_c_variadic(&self) -> bool { + self.args.len() > self.fixed_count as usize + } +} + +/// Information about the ABI of a function's argument, or return value. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +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)] +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, + /// Pass a pair's elements directly in two arguments. + /// + /// The argument has a layout abi of `ScalarPair`. + Pair, + /// Pass the argument after casting it. + Cast, + /// Pass the argument indirectly via a hidden pointer. + Indirect, +} + +/// The layout of a type, alongside the type itself. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct TyAndLayout { + pub ty: Ty, + pub layout: Layout, +} + +/// The layout of a type in memory. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct LayoutShape { + /// The fields location withing 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 == 0 && self.abi_align == 1 + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +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)] +pub enum FieldsShape { + /// Scalar primitives and `!`, which never have fields. + Primitive, + + /// All fields start at no offset. The `usize` is the field count. + Union(NonZeroUsize), + + /// 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)] +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)] +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)] +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, + } + } +} + +/// We currently do not support `Scalar`, and use opaque instead. +type Scalar = Opaque; + +/// General language calling conventions. +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum CallConvention { + C, + Rust, + + Cold, + PreserveMost, + PreserveAll, + + // Target-specific calling conventions. + ArmAapcs, + CCmseNonSecureCall, + + Msp430Intr, + + PtxKernel, + + X86Fastcall, + X86Intr, + X86Stdcall, + X86ThisCall, + X86VectorCall, + + X86_64SysV, + X86_64Win64, + + AmdGpuKernel, + AvrInterrupt, + AvrNonBlockingInterrupt, + + RiscvInterrupt, +} diff --git a/compiler/stable_mir/src/compiler_interface.rs b/compiler/stable_mir/src/compiler_interface.rs index 57c60b70d8a..98b1d484c03 100644 --- a/compiler/stable_mir/src/compiler_interface.rs +++ b/compiler/stable_mir/src/compiler_interface.rs @@ -5,6 +5,7 @@ use std::cell::Cell; +use crate::abi::{FnAbi, Layout, LayoutShape}; use crate::mir::alloc::{AllocId, GlobalAlloc}; use crate::mir::mono::{Instance, InstanceDef, StaticDef}; use crate::mir::Body; @@ -173,6 +174,15 @@ pub trait Context { /// Return information about the target machine. fn target_info(&self) -> MachineInfo; + + /// Get an instance ABI. + fn instance_abi(&self, def: InstanceDef) -> Result; + + /// Get the layout of a type. + fn ty_layout(&self, ty: Ty) -> Result; + + /// Get the layout shape. + fn layout_shape(&self, id: Layout) -> LayoutShape; } // A thread local variable that stores a pointer to the tables mapping between TyCtxt diff --git a/compiler/stable_mir/src/lib.rs b/compiler/stable_mir/src/lib.rs index 8c66bfb2e98..4941e54fe4b 100644 --- a/compiler/stable_mir/src/lib.rs +++ b/compiler/stable_mir/src/lib.rs @@ -33,6 +33,7 @@ use crate::mir::Body; use crate::mir::Mutability; use crate::ty::{ImplDef, ImplTrait, IndexedVal, Span, TraitDecl, TraitDef, Ty}; +pub mod abi; #[macro_use] pub mod crate_def; pub mod compiler_interface; diff --git a/compiler/stable_mir/src/mir/mono.rs b/compiler/stable_mir/src/mir/mono.rs index c126de23c4b..70d44ef8c22 100644 --- a/compiler/stable_mir/src/mir/mono.rs +++ b/compiler/stable_mir/src/mir/mono.rs @@ -1,3 +1,4 @@ +use crate::abi::FnAbi; use crate::crate_def::CrateDef; use crate::mir::Body; use crate::ty::{Allocation, ClosureDef, ClosureKind, FnDef, GenericArgs, IndexedVal, Ty}; @@ -56,6 +57,11 @@ impl Instance { with(|context| context.instance_ty(self.def)) } + /// Retrieve information about this instance binary interface. + pub fn fn_abi(&self) -> Result { + with(|cx| cx.instance_abi(self.def)) + } + /// Retrieve the instance's mangled name used for calling the given instance. /// /// This will also look up the correct name of instances from upstream crates. diff --git a/compiler/stable_mir/src/ty.rs b/compiler/stable_mir/src/ty.rs index 4807a9028eb..1d4d7b6d352 100644 --- a/compiler/stable_mir/src/ty.rs +++ b/compiler/stable_mir/src/ty.rs @@ -3,6 +3,7 @@ use super::{ mir::{Body, Mutability}, with, DefId, Error, Symbol, }; +use crate::abi::Layout; use crate::crate_def::CrateDef; use crate::mir::alloc::{read_target_int, read_target_uint, AllocId}; use crate::target::MachineInfo; @@ -85,6 +86,11 @@ impl Ty { pub fn unsigned_ty(inner: UintTy) -> Ty { Ty::from_rigid_kind(RigidTy::Uint(inner)) } + + /// Get a type layout. + pub fn layout(self) -> Result { + with(|cx| cx.ty_layout(self)) + } } impl Ty { diff --git a/tests/ui-fulldeps/stable-mir/check_allocation.rs b/tests/ui-fulldeps/stable-mir/check_allocation.rs index 8554630e9c9..7ce3597206b 100644 --- a/tests/ui-fulldeps/stable-mir/check_allocation.rs +++ b/tests/ui-fulldeps/stable-mir/check_allocation.rs @@ -209,7 +209,6 @@ fn check_len(item: CrateItem) { assert_eq!(alloc.read_uint(), Ok(2)); } -// Use internal API to find a function in a crate. fn get_item<'a>( items: &'a CrateItems, item: (ItemKind, &str), diff --git a/tests/ui-fulldeps/stable-mir/check_defs.rs b/tests/ui-fulldeps/stable-mir/check_defs.rs index ad667511332..e9a2599d873 100644 --- a/tests/ui-fulldeps/stable-mir/check_defs.rs +++ b/tests/ui-fulldeps/stable-mir/check_defs.rs @@ -69,9 +69,9 @@ fn extract_elem_ty(ty: Ty) -> Ty { /// Check signature and type of `Vec::::new` and its generic version. fn test_vec_new(instance: mir::mono::Instance) { - let sig = instance.ty().kind().fn_sig().unwrap().skip_binder(); - assert_matches!(sig.inputs(), &[]); - let elem_ty = extract_elem_ty(sig.output()); + let sig = instance.fn_abi().unwrap(); + assert_eq!(&sig.args, &[]); + let elem_ty = extract_elem_ty(sig.ret.ty); assert_matches!(elem_ty.kind(), TyKind::RigidTy(RigidTy::Uint(UintTy::U8))); // Get the signature for Vec::::new. diff --git a/tests/ui-fulldeps/stable-mir/check_layout.rs b/tests/ui-fulldeps/stable-mir/check_layout.rs new file mode 100644 index 00000000000..1c33103b0fe --- /dev/null +++ b/tests/ui-fulldeps/stable-mir/check_layout.rs @@ -0,0 +1,117 @@ +// run-pass +//! Test information regarding type layout. + +// ignore-stage1 +// ignore-cross-compile +// ignore-remote +// ignore-windows-gnu mingw has troubles with linking https://github.com/rust-lang/rust/pull/116837 + +#![feature(rustc_private)] +#![feature(assert_matches)] +#![feature(control_flow_enum)] +#![feature(ascii_char, ascii_char_variants)] + +extern crate rustc_hir; +extern crate rustc_middle; +#[macro_use] +extern crate rustc_smir; +extern crate rustc_driver; +extern crate rustc_interface; +extern crate stable_mir; + +use rustc_middle::ty::TyCtxt; +use rustc_smir::rustc_internal; +use stable_mir::abi::{ArgAbi, CallConvention, FieldsShape, PassMode, VariantsShape}; +use stable_mir::mir::mono::Instance; +use stable_mir::{CrateDef, CrateItems, ItemKind}; +use std::assert_matches::assert_matches; +use std::convert::TryFrom; +use std::io::Write; +use std::ops::ControlFlow; + +const CRATE_NAME: &str = "input"; + +/// This function uses the Stable MIR APIs to get information about the test crate. +fn test_stable_mir(_tcx: TyCtxt<'_>) -> ControlFlow<()> { + // Find items in the local crate. + let items = stable_mir::all_local_items(); + let target_fn = *get_item(&items, (ItemKind::Fn, "fn_abi")).unwrap(); + let instance = Instance::try_from(target_fn).unwrap(); + let fn_abi = instance.fn_abi().unwrap(); + assert_eq!(fn_abi.conv, CallConvention::Rust); + assert_eq!(fn_abi.args.len(), 2); + + check_ignore(&fn_abi.args[0]); + check_primitive(&fn_abi.args[1]); + check_result(fn_abi.ret); + + ControlFlow::Continue(()) +} + +/// Check the argument to be ignored: `ignore: [u8; 0]`. +fn check_ignore(abi: &ArgAbi) { + assert!(abi.ty.kind().is_array()); + assert_eq!(abi.mode, PassMode::Ignore); + let layout = abi.layout.shape(); + assert!(layout.is_sized()); + assert!(layout.is_1zst()); +} + +/// Check the primitive argument: `primitive: char`. +fn check_primitive(abi: &ArgAbi) { + assert!(abi.ty.kind().is_char()); + assert_eq!(abi.mode, PassMode::Direct); + let layout = abi.layout.shape(); + assert!(layout.is_sized()); + assert!(!layout.is_1zst()); + assert_matches!(layout.fields, FieldsShape::Primitive); +} + +/// Check the return value: `Result`. +fn check_result(abi: ArgAbi) { + assert!(abi.ty.kind().is_enum()); + assert_eq!(abi.mode, PassMode::Indirect); + let layout = abi.layout.shape(); + assert!(layout.is_sized()); + assert_matches!(layout.fields, FieldsShape::Arbitrary { .. }); + assert_matches!(layout.variants, VariantsShape::Multiple { .. }) +} + +fn get_item<'a>( + items: &'a CrateItems, + item: (ItemKind, &str), +) -> Option<&'a stable_mir::CrateItem> { + items.iter().find(|crate_item| (item.0 == crate_item.kind()) && crate_item.name() == item.1) +} + +/// This test will generate and analyze a dummy crate using the stable mir. +/// For that, it will first write the dummy crate into a file. +/// Then it will create a `StableMir` using custom arguments and then +/// it will run the compiler. +fn main() { + let path = "alloc_input.rs"; + generate_input(&path).unwrap(); + let args = vec![ + "rustc".to_string(), + "--crate-type=lib".to_string(), + "--crate-name".to_string(), + CRATE_NAME.to_string(), + path.to_string(), + ]; + run!(args, tcx, test_stable_mir(tcx)).unwrap(); +} + +fn generate_input(path: &str) -> std::io::Result<()> { + let mut file = std::fs::File::create(path)?; + write!( + file, + r#" + #[allow(unused_variables)] + pub fn fn_abi(ignore: [u8; 0], primitive: char) -> Result {{ + // We only care about the signature. + todo!() + }} + "# + )?; + Ok(()) +}