Skip to content

Commit cbf560f

Browse files
committed
Auto merge of rust-lang#116542 - the8472:slice-ref-len-validity, r=<try>
Add range metadata to slice lengths This adds range information to the slice-len in fat pointers if we can conservatively determine that the pointee is not a ZST without having to normalize the pointee type. I only intended to pass the `!range` to llvm but apparently this also lets the length in fat pointers be used for its niches 😅. Ideally this would use the naive-layout computation from rust-lang#113166 to calculate a better approximation of the pointee size, but that PR got reverted.
2 parents bf71dae + c054bbc commit cbf560f

File tree

16 files changed

+301
-57
lines changed

16 files changed

+301
-57
lines changed

Diff for: compiler/rustc_hir/src/hir.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -3765,14 +3765,26 @@ impl<'hir> Node<'hir> {
37653765
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
37663766
mod size_asserts {
37673767
use super::*;
3768-
// tidy-alphabetical-start
37693768
static_assert_size!(Block<'_>, 48);
37703769
static_assert_size!(Body<'_>, 24);
3770+
#[cfg(bootstrap)]
37713771
static_assert_size!(Expr<'_>, 64);
3772+
#[cfg(not(bootstrap))]
3773+
static_assert_size!(Expr<'_>, 56);
3774+
#[cfg(bootstrap)]
37723775
static_assert_size!(ExprKind<'_>, 48);
3776+
#[cfg(not(bootstrap))]
3777+
static_assert_size!(ExprKind<'_>, 40);
37733778
static_assert_size!(FnDecl<'_>, 40);
3779+
#[cfg(bootstrap)]
37743780
static_assert_size!(ForeignItem<'_>, 72);
3781+
#[cfg(not(bootstrap))]
3782+
static_assert_size!(ForeignItem<'_>, 64);
3783+
#[cfg(bootstrap)]
37753784
static_assert_size!(ForeignItemKind<'_>, 40);
3785+
#[cfg(not(bootstrap))]
3786+
static_assert_size!(ForeignItemKind<'_>, 32);
3787+
// tidy-alphabetical-start
37763788
static_assert_size!(GenericArg<'_>, 32);
37773789
static_assert_size!(GenericBound<'_>, 48);
37783790
static_assert_size!(Generics<'_>, 56);

Diff for: compiler/rustc_hir_typeck/src/intrinsicck.rs

+16-11
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use rustc_hir as hir;
44
use rustc_index::Idx;
55
use rustc_middle::ty::layout::{LayoutError, SizeSkeleton};
66
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt};
7-
use rustc_target::abi::{Pointer, VariantIdx};
7+
use rustc_target::abi::{Pointer, Size, VariantIdx};
88

99
use super::FnCtxt;
1010

@@ -84,19 +84,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8484
}
8585
}
8686

87+
fn size_to_bits(size: Size) -> u128 {
88+
let Some(bits) = u128::from(size.bytes()).checked_mul(8) else {
89+
// `u128` should definitely be able to hold the size of different architectures
90+
// larger sizes should be reported as error `are too big for the current architecture`
91+
// otherwise we have a bug somewhere
92+
bug!("{:?} overflow for u128", size)
93+
};
94+
95+
bits
96+
}
97+
8798
// Try to display a sensible error with as much information as possible.
8899
let skeleton_string = |ty: Ty<'tcx>, sk: Result<_, &_>| match sk {
89-
Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
90-
Ok(SizeSkeleton::Known(size)) => {
91-
if let Some(v) = u128::from(size.bytes()).checked_mul(8) {
92-
format!("{v} bits")
93-
} else {
94-
// `u128` should definitely be able to hold the size of different architectures
95-
// larger sizes should be reported as error `are too big for the current architecture`
96-
// otherwise we have a bug somewhere
97-
bug!("{:?} overflow for u128", size)
98-
}
100+
Ok(SizeSkeleton::Pointer { tail, known_size: Some(size), .. }) => {
101+
format!("{} bits, pointer to `{tail}`", size_to_bits(size))
99102
}
103+
Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
104+
Ok(SizeSkeleton::Known(size)) => format!("{} bits", size_to_bits(size)),
100105
Ok(SizeSkeleton::Generic(size)) => {
101106
if let Some(size) = size.try_eval_target_usize(tcx, self.param_env) {
102107
format!("{size} bytes")

Diff for: compiler/rustc_middle/src/ty/layout.rs

+29-6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::middle::codegen_fn_attrs::CodegenFnAttrFlags;
33
use crate::query::TyCtxtAt;
44
use crate::ty::normalize_erasing_regions::NormalizationError;
55
use crate::ty::{self, Ty, TyCtxt, TypeVisitableExt};
6+
use hir::Mutability;
67
use rustc_error_messages::DiagMessage;
78
use rustc_errors::{
89
Diag, DiagArgValue, DiagCtxt, Diagnostic, EmissionGuarantee, IntoDiagArg, Level,
@@ -297,6 +298,8 @@ pub enum SizeSkeleton<'tcx> {
297298
Pointer {
298299
/// If true, this pointer is never null.
299300
non_zero: bool,
301+
/// Available if the width of the pointer is known, i.e. whether it's 1 or 2 usizes
302+
known_size: Option<Size>,
300303
/// The type which determines the unsized metadata, if any,
301304
/// of this pointer. Either a type parameter or a projection
302305
/// depending on one, with regions erased.
@@ -334,7 +337,23 @@ impl<'tcx> SizeSkeleton<'tcx> {
334337
match tail.kind() {
335338
ty::Param(_) | ty::Alias(ty::Projection | ty::Inherent, _) => {
336339
debug_assert!(tail.has_non_region_param());
337-
Ok(SizeSkeleton::Pointer { non_zero, tail: tcx.erase_regions(tail) })
340+
Ok(SizeSkeleton::Pointer {
341+
non_zero,
342+
known_size: None,
343+
tail: tcx.erase_regions(tail),
344+
})
345+
}
346+
ty::Slice(_) => {
347+
debug_assert!(tail.has_non_region_param());
348+
// Assumption: all slice pointers have the same size. At most they differ in niches or or ptr/len ordering
349+
let simple_slice =
350+
Ty::new_ptr(tcx, Ty::new_slice(tcx, tcx.types.unit), Mutability::Not);
351+
let size = tcx.layout_of(param_env.and(simple_slice)).unwrap().size;
352+
Ok(SizeSkeleton::Pointer {
353+
non_zero,
354+
known_size: Some(size),
355+
tail: tcx.erase_regions(tail),
356+
})
338357
}
339358
_ => bug!(
340359
"SizeSkeleton::compute({ty}): layout errored ({err:?}), yet \
@@ -407,7 +426,7 @@ impl<'tcx> SizeSkeleton<'tcx> {
407426
let v0 = zero_or_ptr_variant(0)?;
408427
// Newtype.
409428
if def.variants().len() == 1 {
410-
if let Some(SizeSkeleton::Pointer { non_zero, tail }) = v0 {
429+
if let Some(SizeSkeleton::Pointer { non_zero, known_size, tail }) = v0 {
411430
return Ok(SizeSkeleton::Pointer {
412431
non_zero: non_zero
413432
|| match tcx.layout_scalar_valid_range(def.did()) {
@@ -417,6 +436,7 @@ impl<'tcx> SizeSkeleton<'tcx> {
417436
}
418437
_ => false,
419438
},
439+
known_size,
420440
tail,
421441
});
422442
} else {
@@ -427,9 +447,9 @@ impl<'tcx> SizeSkeleton<'tcx> {
427447
let v1 = zero_or_ptr_variant(1)?;
428448
// Nullable pointer enum optimization.
429449
match (v0, v1) {
430-
(Some(SizeSkeleton::Pointer { non_zero: true, tail }), None)
431-
| (None, Some(SizeSkeleton::Pointer { non_zero: true, tail })) => {
432-
Ok(SizeSkeleton::Pointer { non_zero: false, tail })
450+
(Some(SizeSkeleton::Pointer { non_zero: true, known_size, tail }), None)
451+
| (None, Some(SizeSkeleton::Pointer { non_zero: true, known_size, tail })) => {
452+
Ok(SizeSkeleton::Pointer { non_zero: false, known_size, tail })
433453
}
434454
_ => Err(err),
435455
}
@@ -450,7 +470,10 @@ impl<'tcx> SizeSkeleton<'tcx> {
450470

451471
pub fn same_size(self, other: SizeSkeleton<'tcx>) -> bool {
452472
match (self, other) {
453-
(SizeSkeleton::Known(a), SizeSkeleton::Known(b)) => a == b,
473+
(
474+
SizeSkeleton::Known(a) | SizeSkeleton::Pointer { known_size: Some(a), .. },
475+
SizeSkeleton::Known(b) | SizeSkeleton::Pointer { known_size: Some(b), .. },
476+
) => a == b,
454477
(SizeSkeleton::Pointer { tail: a, .. }, SizeSkeleton::Pointer { tail: b, .. }) => {
455478
a == b
456479
}

Diff for: compiler/rustc_ty_utils/src/layout.rs

+192-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::layout::ty::ParamEnv;
12
use hir::def_id::DefId;
23
use rustc_hir as hir;
34
use rustc_index::bit_set::BitSet;
@@ -13,9 +14,12 @@ use rustc_session::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo};
1314
use rustc_span::sym;
1415
use rustc_span::symbol::Symbol;
1516
use rustc_target::abi::*;
17+
use rustc_type_ir::DynKind;
1618

19+
use std::cmp;
1720
use std::fmt::Debug;
1821
use std::iter;
22+
use std::ops::ControlFlow;
1923

2024
use crate::errors::{
2125
MultipleArrayFieldsSimdType, NonPrimitiveSimdType, OversizedSimdType, ZeroLengthSimdType,
@@ -123,7 +127,7 @@ fn layout_of_uncached<'tcx>(
123127
};
124128
debug_assert!(!ty.has_non_region_infer());
125129

126-
Ok(match *ty.kind() {
130+
let layout = match *ty.kind() {
127131
// Basic scalars.
128132
ty::Bool => tcx.mk_layout(LayoutS::scalar(
129133
cx,
@@ -201,10 +205,32 @@ fn layout_of_uncached<'tcx>(
201205
return Ok(tcx.mk_layout(LayoutS::scalar(cx, data_ptr)));
202206
}
203207

204-
let Abi::Scalar(metadata) = metadata_layout.abi else {
208+
let Abi::Scalar(mut metadata) = metadata_layout.abi else {
205209
return Err(error(cx, LayoutError::Unknown(pointee)));
206210
};
207211

212+
if !ty.is_unsafe_ptr() && metadata_ty == tcx.types.usize {
213+
let tail = tcx.struct_tail_erasing_lifetimes(pointee, param_env);
214+
// // eprintln!("usize-meta {:?} {}", pointee, pointee_zst);
215+
match tail.kind() {
216+
ty::Slice(element) => match ty_is_non_zst(*element, param_env, tcx) {
217+
NonZst::True => {
218+
metadata.valid_range_mut().end =
219+
dl.ptr_sized_integer().signed_max() as u128
220+
}
221+
NonZst::Unknown => return Err(error(cx, LayoutError::Unknown(ty))),
222+
_ => {}
223+
},
224+
ty::Str => {
225+
metadata.valid_range_mut().end =
226+
dl.ptr_sized_integer().signed_max() as u128;
227+
}
228+
_ => {
229+
eprint!("unexpected tail {:?}", tail);
230+
}
231+
}
232+
}
233+
208234
metadata
209235
} else {
210236
let unsized_part = tcx.struct_tail_erasing_lifetimes(pointee, param_env);
@@ -213,7 +239,28 @@ fn layout_of_uncached<'tcx>(
213239
ty::Foreign(..) => {
214240
return Ok(tcx.mk_layout(LayoutS::scalar(cx, data_ptr)));
215241
}
216-
ty::Slice(_) | ty::Str => scalar_unit(Int(dl.ptr_sized_integer(), false)),
242+
ty::Slice(element) => {
243+
let mut metadata = scalar_unit(Int(dl.ptr_sized_integer(), false));
244+
if !ty.is_unsafe_ptr() {
245+
match ty_is_non_zst(*element, param_env, tcx) {
246+
NonZst::True => {
247+
metadata.valid_range_mut().end =
248+
dl.ptr_sized_integer().signed_max() as u128
249+
}
250+
NonZst::Unknown => return Err(error(cx, LayoutError::Unknown(ty))),
251+
_ => {}
252+
}
253+
}
254+
metadata
255+
}
256+
ty::Str => {
257+
let mut metadata = scalar_unit(Int(dl.ptr_sized_integer(), false));
258+
if !ty.is_unsafe_ptr() {
259+
metadata.valid_range_mut().end =
260+
dl.ptr_sized_integer().signed_max() as u128;
261+
}
262+
metadata
263+
}
217264
ty::Dynamic(..) => {
218265
let mut vtable = scalar_unit(Pointer(AddressSpace::DATA));
219266
vtable.valid_range_mut().start = 1;
@@ -606,7 +653,148 @@ fn layout_of_uncached<'tcx>(
606653
ty::Placeholder(..) | ty::Param(_) => {
607654
return Err(error(cx, LayoutError::Unknown(ty)));
608655
}
609-
})
656+
};
657+
658+
#[cfg(debug_assertions)]
659+
if layout.is_sized() && !layout.abi.is_uninhabited() {
660+
match (ty_is_non_zst(ty, param_env, tcx), layout.is_zst()) {
661+
(NonZst::Unknown, _) => {
662+
bug!("ZSTness should not be unknown at this point {:?} {:?}", ty, layout)
663+
}
664+
(NonZst::False | NonZst::Uninhabited, false) => {
665+
bug!("{:?} is not a ZST but ty_is_non_zst() thinks it is", ty)
666+
}
667+
(NonZst::True, true) => bug!("{:?} is a ZST but ty_is_non_zst() thinks it isn't", ty),
668+
_ => {}
669+
}
670+
}
671+
672+
Ok(layout)
673+
}
674+
675+
fn ty_is_non_zst<'tcx>(ty: Ty<'tcx>, param_env: ParamEnv<'tcx>, tcx: TyCtxt<'tcx>) -> NonZst {
676+
fn fold_fields<'tcx>(
677+
mut it: impl Iterator<Item = Ty<'tcx>>,
678+
param_env: ParamEnv<'tcx>,
679+
tcx: TyCtxt<'tcx>,
680+
) -> NonZst {
681+
let (ControlFlow::Break(res) | ControlFlow::Continue(res)) =
682+
it.try_fold(NonZst::False, |acc, ty| {
683+
if acc == NonZst::True {
684+
return ControlFlow::Break(acc);
685+
}
686+
687+
ControlFlow::Continue(cmp::max(acc, ty_is_non_zst(ty, param_env, tcx)))
688+
});
689+
690+
res
691+
}
692+
693+
match ty.kind() {
694+
ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
695+
| ty::Uint(_)
696+
| ty::Int(_)
697+
| ty::Bool
698+
| ty::Float(_)
699+
| ty::FnPtr(_)
700+
| ty::RawPtr(..)
701+
| ty::Dynamic(_, _, DynKind::DynStar)
702+
| ty::Char
703+
| ty::Ref(..) => NonZst::True,
704+
705+
ty::Closure(_, args) => fold_fields(args.as_closure().upvar_tys().iter(), param_env, tcx),
706+
ty::Coroutine(_, _) => NonZst::True,
707+
ty::CoroutineClosure(_, args) => {
708+
fold_fields(args.as_coroutine_closure().upvar_tys().iter(), param_env, tcx)
709+
}
710+
ty::Array(ty, len) => {
711+
let len = if len.has_projections() {
712+
tcx.normalize_erasing_regions(param_env, *len)
713+
} else {
714+
*len
715+
};
716+
717+
if let Some(len) = len.try_to_target_usize(tcx) {
718+
if len == 0 {
719+
return NonZst::False;
720+
}
721+
let element_zst = ty_is_non_zst(*ty, param_env, tcx);
722+
if element_zst != NonZst::Unknown {
723+
return element_zst;
724+
}
725+
}
726+
NonZst::Unknown
727+
}
728+
ty::Tuple(tys) => fold_fields(tys.iter(), param_env, tcx),
729+
ty::Adt(def, args) => {
730+
if ty.is_enum() {
731+
if def.variants().len() == 0 {
732+
return NonZst::Uninhabited;
733+
}
734+
// An enum is !ZST if
735+
// * it has a repr and at least one non-uninhabited variant
736+
// * it has at least one variant with a !ZST payload
737+
// * it has multiple variants that are not uninhabited
738+
739+
let min_empty_variants = if def.repr().inhibit_enum_layout_opt() { 1 } else { 2 };
740+
741+
// first check without recursing
742+
let simple_variants = def.variants().iter().filter(|v| v.fields.len() == 0).count();
743+
if simple_variants >= min_empty_variants {
744+
return NonZst::True;
745+
}
746+
747+
let mut inhabited_zst_variants = 0;
748+
let mut unknown = false;
749+
750+
for variant in def.variants().iter().filter(|v| v.fields.len() != 0) {
751+
let variant_sized =
752+
fold_fields(variant.fields.iter().map(|f| f.ty(tcx, args)), param_env, tcx);
753+
754+
match variant_sized {
755+
// enum E { A(!, u32) } counts as !ZST for our purposes
756+
NonZst::True => return NonZst::True,
757+
NonZst::False => inhabited_zst_variants += 1,
758+
NonZst::Unknown => unknown = true,
759+
NonZst::Uninhabited => {}
760+
}
761+
}
762+
763+
if simple_variants + inhabited_zst_variants >= min_empty_variants {
764+
return NonZst::True;
765+
}
766+
if unknown {
767+
return NonZst::Unknown;
768+
}
769+
if simple_variants + inhabited_zst_variants == 0 {
770+
return NonZst::Uninhabited;
771+
}
772+
773+
NonZst::False
774+
} else {
775+
fold_fields(def.all_fields().map(|f| f.ty(tcx, args)), param_env, tcx)
776+
}
777+
}
778+
ty::FnDef(..) => NonZst::False,
779+
ty::Never => NonZst::Uninhabited,
780+
ty::Param(..) => NonZst::Unknown,
781+
ty::Str => NonZst::True,
782+
// treat unsized types as potentially-ZST
783+
ty::Dynamic(..) | ty::Slice(..) => NonZst::False,
784+
ty::Alias(..) => match tcx.try_normalize_erasing_regions(param_env, ty) {
785+
Ok(ty) if !matches!(ty.kind(), ty::Alias(..)) => ty_is_non_zst(ty, param_env, tcx),
786+
_ => NonZst::Unknown,
787+
},
788+
_ => bug!("is_non_zst not implemented for this kind {:?}", ty),
789+
}
790+
}
791+
792+
#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
793+
pub enum NonZst {
794+
False,
795+
Uninhabited,
796+
Unknown,
797+
True,
610798
}
611799

612800
/// Overlap eligibility and variant assignment for each CoroutineSavedLocal.

0 commit comments

Comments
 (0)