diff --git a/compiler/rustc_middle/src/ty/layout.rs b/compiler/rustc_middle/src/ty/layout.rs index 5cf91916129..f9e55704caf 100644 --- a/compiler/rustc_middle/src/ty/layout.rs +++ b/compiler/rustc_middle/src/ty/layout.rs @@ -15,7 +15,7 @@ use rustc_target::abi::call::FnAbi; use rustc_target::abi::*; use rustc_target::spec::{abi::Abi as SpecAbi, HasTargetSpec, PanicStrategy, Target}; -use std::cmp::{self, Ordering}; +use std::cmp; use std::fmt; use std::num::NonZeroUsize; use std::ops::Bound; @@ -316,8 +316,8 @@ impl<'tcx> SizeSkeleton<'tcx> { // First, try computing an exact naive layout (this covers simple types with generic // references, where a full static layout would fail). if let Ok(layout) = tcx.naive_layout_of(param_env.and(ty)) { - if layout.is_exact { - return Ok(SizeSkeleton::Known(layout.min_size)); + if layout.exact { + return Ok(SizeSkeleton::Known(layout.size)); } } @@ -650,51 +650,146 @@ impl std::ops::DerefMut for TyAndNaiveLayout<'_> { } } -/// A naive underestimation of the layout of a type. +/// Extremely simplified representation of a type's layout. +/// +/// #[derive(Copy, Clone, Debug, HashStable)] pub struct NaiveLayout { - pub min_size: Size, - pub min_align: Align, - // If `true`, `min_size` and `min_align` are guaranteed to be exact. - pub is_exact: bool, + pub abi: NaiveAbi, + pub size: Size, + pub align: Align, + /// If `true`, `size` and `align` are exact. + pub exact: bool, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, HashStable)] +pub enum NaiveAbi { + /// A scalar layout, always implies `exact`. + Scalar(Primitive), + /// An uninhabited layout. (needed to properly track `Scalar`) + Uninhabited, + /// An unsized aggregate. (needed to properly track `Scalar`) + Unsized, + Any, +} + +impl NaiveAbi { + #[inline] + pub fn as_aggregate(self) -> Self { + match self { + NaiveAbi::Scalar(_) => NaiveAbi::Any, + _ => self, + } + } } impl NaiveLayout { - pub const UNKNOWN: Self = Self { min_size: Size::ZERO, min_align: Align::ONE, is_exact: false }; - pub const EMPTY: Self = Self { min_size: Size::ZERO, min_align: Align::ONE, is_exact: true }; + pub const EMPTY: Self = + Self { size: Size::ZERO, align: Align::ONE, exact: true, abi: NaiveAbi::Any }; - pub fn is_compatible_with(&self, layout: Layout<'_>) -> bool { - let cmp = |cmp: Ordering| match (cmp, self.is_exact) { - (Ordering::Less | Ordering::Equal, false) => true, - (Ordering::Equal, true) => true, - (_, _) => false, - }; + pub fn is_refined_by(&self, layout: Layout<'_>) -> bool { + if self.size > layout.size() || self.align > layout.align().abi { + return false; + } - cmp(self.min_size.cmp(&layout.size())) && cmp(self.min_align.cmp(&layout.align().abi)) + if let NaiveAbi::Scalar(prim) = self.abi { + assert!(self.exact); + if !matches!(layout.abi(), Abi::Scalar(s) if s.primitive() == prim) { + return false; + } + } + + !self.exact || (self.size, self.align) == (layout.size(), layout.align().abi) + } + + /// Returns if this layout is known to be pointer-like (`None` if uncertain) + /// + /// See the corresponding `Layout::is_pointer_like` method. + pub fn is_pointer_like(&self, dl: &TargetDataLayout) -> Option { + match self.abi { + NaiveAbi::Scalar(_) => { + assert!(self.exact); + Some(self.size == dl.pointer_size && self.align == dl.pointer_align.abi) + } + NaiveAbi::Uninhabited | NaiveAbi::Unsized => Some(false), + NaiveAbi::Any if self.exact => Some(false), + NaiveAbi::Any => None, + } } #[must_use] - pub fn pad_to_align(mut self) -> Self { - self.min_size = self.min_size.align_to(self.min_align); + #[inline] + pub fn packed(mut self, align: Align) -> Self { + if self.align > align { + self.align = align; + self.abi = self.abi.as_aggregate(); + } self } #[must_use] - pub fn concat(&self, other: &Self, cx: &C) -> Option { - Some(Self { - min_size: self.min_size.checked_add(other.min_size, cx)?, - min_align: std::cmp::max(self.min_align, other.min_align), - is_exact: self.is_exact && other.is_exact, - }) + #[inline] + pub fn align_to(mut self, align: Align) -> Self { + if align > self.align { + self.align = align; + self.abi = self.abi.as_aggregate(); + } + self } #[must_use] - pub fn union(&self, other: &Self) -> Self { - Self { - min_size: std::cmp::max(self.min_size, other.min_size), - min_align: std::cmp::max(self.min_align, other.min_align), - is_exact: self.is_exact && other.is_exact, + #[inline] + pub fn pad_to_align(mut self, align: Align) -> Self { + let new_size = self.size.align_to(align); + if new_size > self.size { + self.abi = self.abi.as_aggregate(); + self.size = new_size; } + self + } + + #[must_use] + #[inline] + pub fn concat(&self, other: &Self, dl: &TargetDataLayout) -> Option { + use NaiveAbi::*; + + let size = self.size.checked_add(other.size, dl)?; + let align = cmp::max(self.align, other.align); + let exact = self.exact && other.exact; + let abi = match (self.abi, other.abi) { + // The uninhabited and unsized ABIs override everything. + (Uninhabited, _) | (_, Uninhabited) => Uninhabited, + (Unsized, _) | (_, Unsized) => Unsized, + // A scalar struct must have a single non ZST-field. + (_, s @ Scalar(_)) if exact && self.size == Size::ZERO => s, + (s @ Scalar(_), _) if exact && other.size == Size::ZERO => s, + // Default case. + (_, _) => Any, + }; + Some(Self { abi, size, align, exact }) + } + + #[must_use] + #[inline] + pub fn union(&self, other: &Self) -> Self { + use NaiveAbi::*; + + let size = cmp::max(self.size, other.size); + let align = cmp::max(self.align, other.align); + let exact = self.exact && other.exact; + let abi = match (self.abi, other.abi) { + // The unsized ABI overrides everything. + (Unsized, _) | (_, Unsized) => Unsized, + // A scalar union must have a single non ZST-field. + (_, s @ Scalar(_)) if exact && self.size == Size::ZERO => s, + (s @ Scalar(_), _) if exact && other.size == Size::ZERO => s, + // ...or identical scalar fields. + (Scalar(s1), Scalar(s2)) if s1 == s2 => Scalar(s1), + // Default cases. + (Uninhabited, Uninhabited) => Uninhabited, + (_, _) => Any, + }; + Self { abi, size, align, exact } } } diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs index 930e62d6388..761f5327f6d 100644 --- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -223,9 +223,20 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS); } - if let Ok(layout) = tcx.layout_of(key) - && layout.layout.is_pointer_like(&tcx.data_layout) - { + // First, try computing an exact naive layout in case the type is generic. + let is_pointer_like = if let Ok(layout) = tcx.naive_layout_of(key) { + layout.is_pointer_like(&tcx.data_layout).unwrap_or_else(|| { + // Second, we fall back to full layout computation. + tcx.layout_of(key) + .ok() + .filter(|l| l.layout.is_pointer_like(&tcx.data_layout)) + .is_some() + }) + } else { + false + }; + + if is_pointer_like { // FIXME: We could make this faster by making a no-constraints response ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) } else { diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs index aa195d70a9f..f1d870269a6 100644 --- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs +++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs @@ -979,9 +979,20 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { return; } - if let Ok(layout) = tcx.layout_of(key) - && layout.layout.is_pointer_like(&tcx.data_layout) - { + // First, try computing an exact naive layout in case the type is generic. + let is_pointer_like = if let Ok(layout) = tcx.naive_layout_of(key) { + layout.is_pointer_like(&tcx.data_layout).unwrap_or_else(|| { + // Second, we fall back to full layout computation. + tcx.layout_of(key) + .ok() + .filter(|l| l.layout.is_pointer_like(&tcx.data_layout)) + .is_some() + }) + } else { + false + }; + + if is_pointer_like { candidates.vec.push(BuiltinCandidate { has_nested: false }); } } diff --git a/compiler/rustc_ty_utils/src/layout.rs b/compiler/rustc_ty_utils/src/layout.rs index d484448a0fe..26960a71c70 100644 --- a/compiler/rustc_ty_utils/src/layout.rs +++ b/compiler/rustc_ty_utils/src/layout.rs @@ -5,8 +5,8 @@ use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::mir::{GeneratorLayout, GeneratorSavedLocal}; use rustc_middle::query::{LocalCrate, Providers}; use rustc_middle::ty::layout::{ - IntegerExt, LayoutCx, LayoutError, LayoutOf, NaiveLayout, TyAndLayout, TyAndNaiveLayout, - MAX_SIMD_LANES, + IntegerExt, LayoutCx, LayoutError, LayoutOf, NaiveAbi, NaiveLayout, TyAndLayout, + TyAndNaiveLayout, MAX_SIMD_LANES, }; use rustc_middle::ty::{ self, AdtDef, EarlyBinder, GenericArgsRef, ReprOptions, Ty, TyCtxt, TypeVisitableExt, @@ -90,12 +90,8 @@ fn layout_of<'tcx>( let cx = LayoutCx { tcx, param_env }; let layout = layout_of_uncached(&cx, ty)?; - if !naive.is_compatible_with(layout) { - bug!( - "the naive layout isn't compatible with the actual layout:\n{:#?}\n{:#?}", - naive, - layout, - ); + if !naive.is_refined_by(layout) { + bug!("the naive layout isn't refined by the actual layout:\n{:#?}\n{:#?}", naive, layout,); } let layout = TyAndLayout { ty, layout }; @@ -120,9 +116,10 @@ fn naive_layout_of_uncached<'tcx>( let dl = cx.data_layout(); let scalar = |value: Primitive| NaiveLayout { - min_size: value.size(dl), - min_align: value.align(dl).abi, - is_exact: true, + abi: NaiveAbi::Scalar(value), + size: value.size(dl), + align: value.align(dl).abi, + exact: true, }; let univariant = |fields: &mut dyn Iterator>, @@ -133,24 +130,29 @@ fn naive_layout_of_uncached<'tcx>( return Err(error(cx, LayoutError::Unknown(ty))); } - // For simplicity, ignore inter-field padding; this may underestimate the size. - // FIXME(reference_niches): Be smarter and implement something closer to the real layout logic. - let mut layout = NaiveLayout::UNKNOWN; + let linear = repr.inhibit_struct_field_reordering_opt(); + let pack = repr.pack.unwrap_or(Align::MAX); + let mut layout = NaiveLayout::EMPTY; + for field in fields { - let field = cx.naive_layout_of(field)?; + let field = cx.naive_layout_of(field)?.packed(pack); + if linear { + layout = layout.pad_to_align(field.align); + } layout = layout - .concat(&field, cx) + .concat(&field, dl) .ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?; } if let Some(align) = repr.align { - layout.min_align = std::cmp::max(layout.min_align, align); - } - if let Some(pack) = repr.pack { - layout.min_align = std::cmp::min(layout.min_align, pack); + layout = layout.align_to(align); } - Ok(layout.pad_to_align()) + if linear { + layout.abi = layout.abi.as_aggregate(); + } + + Ok(layout.pad_to_align(layout.align)) }; debug_assert!(!ty.has_non_region_infer()); @@ -168,17 +170,17 @@ fn naive_layout_of_uncached<'tcx>( ty::FnPtr(_) => scalar(Pointer(dl.instruction_address_space)), // The never type. - ty::Never => NaiveLayout::EMPTY, + ty::Never => NaiveLayout { abi: NaiveAbi::Uninhabited, ..NaiveLayout::EMPTY }, // Potentially-wide pointers. ty::Ref(_, pointee, _) | ty::RawPtr(ty::TypeAndMut { ty: pointee, .. }) => { let data_ptr = scalar(Pointer(AddressSpace::DATA)); if let Some(metadata) = ptr_metadata_scalar(cx, pointee)? { // Effectively a (ptr, meta) tuple. - data_ptr - .concat(&scalar(metadata.primitive()), cx) - .ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))? - .pad_to_align() + let l = data_ptr + .concat(&scalar(metadata.primitive()), dl) + .ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?; + l.pad_to_align(l.align) } else { // No metadata, this is a thin pointer. data_ptr @@ -187,7 +189,7 @@ fn naive_layout_of_uncached<'tcx>( ty::Dynamic(_, _, ty::DynStar) => { let ptr = scalar(Pointer(AddressSpace::DATA)); - ptr.concat(&ptr, cx).ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))? + ptr.concat(&ptr, dl).ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))? } // Arrays and slices. @@ -196,26 +198,29 @@ fn naive_layout_of_uncached<'tcx>( .ok_or_else(|| error(cx, LayoutError::Unknown(ty)))?; let element = cx.naive_layout_of(element)?; NaiveLayout { - min_size: element - .min_size + abi: element.abi.as_aggregate(), + size: element + .size .checked_mul(count, cx) .ok_or_else(|| error(cx, LayoutError::SizeOverflow(ty)))?, ..*element } } - ty::Slice(element) => NaiveLayout { - min_size: Size::ZERO, - // NOTE: this could be unconditionally exact if `NaiveLayout` guaranteed exact align. - ..*cx.naive_layout_of(element)? - }, - ty::Str => NaiveLayout::EMPTY, + ty::Slice(element) => { + let element = cx.naive_layout_of(element)?; + NaiveLayout { abi: NaiveAbi::Unsized, size: Size::ZERO, ..*element } + } - // Odd unit types. - ty::FnDef(..) | ty::Dynamic(_, _, ty::Dyn) | ty::Foreign(..) => NaiveLayout::EMPTY, + ty::FnDef(..) => NaiveLayout::EMPTY, + + // Unsized types. + ty::Str | ty::Dynamic(_, _, ty::Dyn) | ty::Foreign(..) => { + NaiveLayout { abi: NaiveAbi::Unsized, ..NaiveLayout::EMPTY } + } // FIXME(reference_niches): try to actually compute a reasonable layout estimate, // without duplicating too much code from `generator_layout`. - ty::Generator(..) => NaiveLayout::UNKNOWN, + ty::Generator(..) => NaiveLayout { exact: false, ..NaiveLayout::EMPTY }, ty::Closure(_, ref substs) => { univariant(&mut substs.as_closure().upvar_tys(), &ReprOptions::default())? @@ -225,33 +230,50 @@ fn naive_layout_of_uncached<'tcx>( ty::Adt(def, substs) if def.is_union() => { let repr = def.repr(); - let only_variant = &def.variants()[FIRST_VARIANT]; - only_variant.fields.iter().try_fold(NaiveLayout::EMPTY, |layout, f| { - let mut fields = std::iter::once(f.ty(tcx, substs)); - univariant(&mut fields, &repr).map(|l| layout.union(&l)) - })? + let pack = repr.pack.unwrap_or(Align::MAX); + if repr.pack.is_some() && repr.align.is_some() { + cx.tcx.sess.delay_span_bug(DUMMY_SP, "union cannot be packed and aligned"); + return Err(error(cx, LayoutError::Unknown(ty))); + } + + let mut layout = NaiveLayout::EMPTY; + for f in &def.variants()[FIRST_VARIANT].fields { + let field = cx.naive_layout_of(f.ty(tcx, substs))?; + layout = layout.union(&field.packed(pack)); + } + + // Unions are always inhabited, and never scalar if `repr(C)`. + if !matches!(layout.abi, NaiveAbi::Scalar(_)) || repr.inhibit_enum_layout_opt() { + layout.abi = NaiveAbi::Any; + } + + if let Some(align) = repr.align { + layout = layout.align_to(align); + } + layout.pad_to_align(layout.align) } ty::Adt(def, substs) => { let repr = def.repr(); - let base = if def.is_struct() && !repr.simd() { - // FIXME(reference_niches): compute proper alignment for SIMD types. - NaiveLayout::EMPTY - } else { - // For simplicity, assume that any discriminant field (if it exists) - // gets niched inside one of the variants; this will underestimate the size - // (and sometimes alignment) of enums. - // FIXME(reference_niches): Be smarter and actually take into accoount the discriminant. + let base = NaiveLayout { + // For simplicity, assume that any enum has its discriminant field (if it exists) + // niched inside one of the variants; this will underestimate the size (and sometimes + // alignment) of enums. We also doesn't compute exact alignment for SIMD structs. + // FIXME(reference_niches): Be smarter here. // Also consider adding a special case for null-optimized enums, so that we can have // `Option<&T>: PointerLike` in generic contexts. - NaiveLayout::UNKNOWN + exact: !def.is_enum() && !repr.simd(), + // An ADT with no inhabited variants should have an uninhabited ABI. + abi: NaiveAbi::Uninhabited, + ..NaiveLayout::EMPTY }; - def.variants().iter().try_fold(base, |layout, v| { + let layout = def.variants().iter().try_fold(base, |layout, v| { let mut fields = v.fields.iter().map(|f| f.ty(tcx, substs)); let vlayout = univariant(&mut fields, &repr)?; Ok(layout.union(&vlayout)) - })? + })?; + layout.pad_to_align(layout.align) } // Types with no meaningful known layout. @@ -354,8 +376,8 @@ fn layout_of_uncached<'tcx>( }; let (min_addr, max_addr) = dl.address_range_for( - if niches.size { naive.min_size } else { Size::ZERO }, - if niches.align { naive.min_align } else { Align::ONE }, + if niches.size { naive.size } else { Size::ZERO }, + if niches.align { naive.align } else { Align::ONE }, ); *data_ptr.valid_range_mut() = diff --git a/tests/ui/dyn-star/param-env-region-infer.next.stderr b/tests/ui/dyn-star/param-env-region-infer.next.stderr index d86405462f4..51df71a373e 100644 --- a/tests/ui/dyn-star/param-env-region-infer.next.stderr +++ b/tests/ui/dyn-star/param-env-region-infer.next.stderr @@ -9,7 +9,6 @@ note: ...which requires type-checking `make_dyn_star`... | LL | fn make_dyn_star<'a, T: PointerLike + Debug + 'a>(t: T) -> impl PointerLike + Debug + 'a { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: ...which requires computing layout of `make_dyn_star::{opaque#0}`... = note: ...which requires computing layout (naive) of `make_dyn_star::{opaque#0}`... = note: ...which requires normalizing `make_dyn_star::{opaque#0}`... = note: ...which again requires computing type of `make_dyn_star::{opaque#0}`, completing the cycle