Skip to content

Commit 9a4ff20

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 d0141af + 6671c90 commit 9a4ff20

File tree

21 files changed

+339
-75
lines changed

21 files changed

+339
-75
lines changed

compiler/rustc_hir/src/hir.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -4076,11 +4076,17 @@ mod size_asserts {
40764076
use rustc_data_structures::static_assert_size;
40774077

40784078
use super::*;
4079-
// tidy-alphabetical-start
40804079
static_assert_size!(Block<'_>, 48);
40814080
static_assert_size!(Body<'_>, 24);
4081+
#[cfg(bootstrap)]
40824082
static_assert_size!(Expr<'_>, 64);
4083+
#[cfg(not(bootstrap))]
4084+
static_assert_size!(Expr<'_>, 56);
4085+
#[cfg(bootstrap)]
40834086
static_assert_size!(ExprKind<'_>, 48);
4087+
#[cfg(not(bootstrap))]
4088+
// tidy-alphabetical-start
4089+
static_assert_size!(ExprKind<'_>, 40);
40844090
static_assert_size!(FnDecl<'_>, 40);
40854091
static_assert_size!(ForeignItem<'_>, 88);
40864092
static_assert_size!(ForeignItemKind<'_>, 56);

compiler/rustc_hir_typeck/src/intrinsicck.rs

+15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use hir::HirId;
22
use rustc_abi::Primitive::Pointer;
3+
use rustc_abi::Size;
34
use rustc_errors::codes::*;
45
use rustc_errors::struct_span_code_err;
56
use rustc_hir as hir;
@@ -88,8 +89,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
8889
}
8990
}
9091

92+
fn size_to_bits(size: Size) -> u128 {
93+
let Some(bits) = u128::from(size.bytes()).checked_mul(8) 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+
};
99+
100+
bits
101+
}
102+
91103
// Try to display a sensible error with as much information as possible.
92104
let skeleton_string = |ty: Ty<'tcx>, sk: Result<_, &_>| match sk {
105+
Ok(SizeSkeleton::Pointer { tail, known_size: Some(size), .. }) => {
106+
format!("{} bits, pointer to `{tail}`", size_to_bits(size))
107+
}
93108
Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
94109
Ok(SizeSkeleton::Known(size, _)) => {
95110
if let Some(v) = u128::from(size.bytes()).checked_mul(8) {

compiler/rustc_middle/src/ty/layout.rs

+29-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use rustc_abi::{
77
Abi, AddressSpace, Align, FieldsShape, HasDataLayout, Integer, LayoutCalculator, LayoutS,
88
PointeeInfo, PointerKind, ReprOptions, Scalar, Size, TagEncoding, TargetDataLayout, Variants,
99
};
10+
use rustc_ast::Mutability;
1011
use rustc_error_messages::DiagMessage;
1112
use rustc_errors::{
1213
Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, IntoDiagArg, Level,
@@ -326,6 +327,8 @@ pub enum SizeSkeleton<'tcx> {
326327
Pointer {
327328
/// If true, this pointer is never null.
328329
non_zero: bool,
330+
/// Available if the width of the pointer is known, i.e. whether it's 1 or 2 usizes
331+
known_size: Option<Size>,
329332
/// The type which determines the unsized metadata, if any,
330333
/// of this pointer. Either a type parameter or a projection
331334
/// depending on one, with regions erased.
@@ -384,7 +387,23 @@ impl<'tcx> SizeSkeleton<'tcx> {
384387
match tail.kind() {
385388
ty::Param(_) | ty::Alias(ty::Projection | ty::Inherent, _) => {
386389
debug_assert!(tail.has_non_region_param());
387-
Ok(SizeSkeleton::Pointer { non_zero, tail: tcx.erase_regions(tail) })
390+
Ok(SizeSkeleton::Pointer {
391+
non_zero,
392+
known_size: None,
393+
tail: tcx.erase_regions(tail),
394+
})
395+
}
396+
ty::Slice(_) => {
397+
debug_assert!(tail.has_non_region_param());
398+
// Assumption: all slice pointers have the same size. At most they differ in niches or or ptr/len ordering
399+
let simple_slice =
400+
Ty::new_ptr(tcx, Ty::new_slice(tcx, tcx.types.unit), Mutability::Not);
401+
let size = tcx.layout_of(param_env.and(simple_slice)).unwrap().size;
402+
Ok(SizeSkeleton::Pointer {
403+
non_zero,
404+
known_size: Some(size),
405+
tail: tcx.erase_regions(tail),
406+
})
388407
}
389408
ty::Error(guar) => {
390409
// Fixes ICE #124031
@@ -462,7 +481,7 @@ impl<'tcx> SizeSkeleton<'tcx> {
462481
let v0 = zero_or_ptr_variant(0)?;
463482
// Newtype.
464483
if def.variants().len() == 1 {
465-
if let Some(SizeSkeleton::Pointer { non_zero, tail }) = v0 {
484+
if let Some(SizeSkeleton::Pointer { non_zero, known_size, tail }) = v0 {
466485
return Ok(SizeSkeleton::Pointer {
467486
non_zero: non_zero
468487
|| match tcx.layout_scalar_valid_range(def.did()) {
@@ -472,6 +491,7 @@ impl<'tcx> SizeSkeleton<'tcx> {
472491
}
473492
_ => false,
474493
},
494+
known_size,
475495
tail,
476496
});
477497
} else {
@@ -482,9 +502,9 @@ impl<'tcx> SizeSkeleton<'tcx> {
482502
let v1 = zero_or_ptr_variant(1)?;
483503
// Nullable pointer enum optimization.
484504
match (v0, v1) {
485-
(Some(SizeSkeleton::Pointer { non_zero: true, tail }), None)
486-
| (None, Some(SizeSkeleton::Pointer { non_zero: true, tail })) => {
487-
Ok(SizeSkeleton::Pointer { non_zero: false, tail })
505+
(Some(SizeSkeleton::Pointer { non_zero: true, known_size, tail }), None)
506+
| (None, Some(SizeSkeleton::Pointer { non_zero: true, known_size, tail })) => {
507+
Ok(SizeSkeleton::Pointer { non_zero: false, known_size, tail })
488508
}
489509
_ => Err(err),
490510
}
@@ -505,7 +525,10 @@ impl<'tcx> SizeSkeleton<'tcx> {
505525

506526
pub fn same_size(self, other: SizeSkeleton<'tcx>) -> bool {
507527
match (self, other) {
508-
(SizeSkeleton::Known(a, _), SizeSkeleton::Known(b, _)) => a == b,
528+
(
529+
SizeSkeleton::Known(a, _) | SizeSkeleton::Pointer { known_size: Some(a), .. },
530+
SizeSkeleton::Known(b, _) | SizeSkeleton::Pointer { known_size: Some(b), .. },
531+
) => a == b,
509532
(SizeSkeleton::Pointer { tail: a, .. }, SizeSkeleton::Pointer { tail: b, .. }) => {
510533
a == b
511534
}

compiler/rustc_ty_utils/src/layout.rs

+202-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::fmt::Debug;
2-
use std::iter;
2+
use std::ops::ControlFlow;
3+
use std::{cmp, iter};
34

45
use hir::def_id::DefId;
56
use rustc_abi::Integer::{I8, I32};
@@ -18,12 +19,14 @@ use rustc_middle::ty::layout::{
1819
};
1920
use rustc_middle::ty::print::with_no_trimmed_paths;
2021
use rustc_middle::ty::{
21-
self, AdtDef, CoroutineArgsExt, EarlyBinder, GenericArgsRef, Ty, TyCtxt, TypeVisitableExt,
22+
self, AdtDef, CoroutineArgsExt, EarlyBinder, GenericArgsRef, ParamEnv, Ty, TyCtxt,
23+
TypeVisitableExt,
2224
};
2325
use rustc_session::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo};
2426
use rustc_span::sym;
2527
use rustc_span::symbol::Symbol;
2628
use rustc_target::abi::{FIRST_VARIANT, FieldIdx, Layout, VariantIdx};
29+
use rustc_type_ir::DynKind;
2730
use tracing::{debug, instrument, trace};
2831
use {rustc_abi as abi, rustc_hir as hir};
2932

@@ -161,7 +164,7 @@ fn layout_of_uncached<'tcx>(
161164
};
162165
debug_assert!(!ty.has_non_region_infer());
163166

164-
Ok(match *ty.kind() {
167+
let layout = match *ty.kind() {
165168
ty::Pat(ty, pat) => {
166169
let layout = cx.layout_of(ty)?.layout;
167170
let mut layout = LayoutS::clone(&layout.0);
@@ -198,7 +201,6 @@ fn layout_of_uncached<'tcx>(
198201
}
199202
}
200203
}
201-
202204
// Basic scalars.
203205
ty::Bool => tcx.mk_layout(LayoutS::scalar(cx, Scalar::Initialized {
204206
value: Int(I8, false),
@@ -269,10 +271,32 @@ fn layout_of_uncached<'tcx>(
269271
return Ok(tcx.mk_layout(LayoutS::scalar(cx, data_ptr)));
270272
}
271273

272-
let Abi::Scalar(metadata) = metadata_layout.abi else {
274+
let Abi::Scalar(mut metadata) = metadata_layout.abi else {
273275
return Err(error(cx, LayoutError::Unknown(pointee)));
274276
};
275277

278+
if !ty.is_unsafe_ptr() && metadata_ty == tcx.types.usize {
279+
let tail = tcx.struct_tail_for_codegen(pointee, param_env);
280+
// // eprintln!("usize-meta {:?} {}", pointee, pointee_zst);
281+
match tail.kind() {
282+
ty::Slice(element) => match ty_is_non_zst(*element, param_env, tcx) {
283+
NonZst::True => {
284+
metadata.valid_range_mut().end =
285+
dl.ptr_sized_integer().signed_max() as u128
286+
}
287+
NonZst::Unknown => return Err(error(cx, LayoutError::Unknown(ty))),
288+
_ => {}
289+
},
290+
ty::Str => {
291+
metadata.valid_range_mut().end =
292+
dl.ptr_sized_integer().signed_max() as u128;
293+
}
294+
_ => {
295+
eprint!("unexpected tail {:?}", tail);
296+
}
297+
}
298+
}
299+
276300
metadata
277301
} else {
278302
let unsized_part = tcx.struct_tail_for_codegen(pointee, param_env);
@@ -281,7 +305,28 @@ fn layout_of_uncached<'tcx>(
281305
ty::Foreign(..) => {
282306
return Ok(tcx.mk_layout(LayoutS::scalar(cx, data_ptr)));
283307
}
284-
ty::Slice(_) | ty::Str => scalar_unit(Int(dl.ptr_sized_integer(), false)),
308+
ty::Slice(element) => {
309+
let mut metadata = scalar_unit(Int(dl.ptr_sized_integer(), false));
310+
if !ty.is_unsafe_ptr() {
311+
match ty_is_non_zst(*element, param_env, tcx) {
312+
NonZst::True => {
313+
metadata.valid_range_mut().end =
314+
dl.ptr_sized_integer().signed_max() as u128
315+
}
316+
NonZst::Unknown => return Err(error(cx, LayoutError::Unknown(ty))),
317+
_ => {}
318+
}
319+
}
320+
metadata
321+
}
322+
ty::Str => {
323+
let mut metadata = scalar_unit(Int(dl.ptr_sized_integer(), false));
324+
if !ty.is_unsafe_ptr() {
325+
metadata.valid_range_mut().end =
326+
dl.ptr_sized_integer().signed_max() as u128;
327+
}
328+
metadata
329+
}
285330
ty::Dynamic(..) => {
286331
let mut vtable = scalar_unit(Pointer(AddressSpace::DATA));
287332
vtable.valid_range_mut().start = 1;
@@ -673,7 +718,157 @@ fn layout_of_uncached<'tcx>(
673718
ty::Placeholder(..) | ty::Param(_) => {
674719
return Err(error(cx, LayoutError::Unknown(ty)));
675720
}
676-
})
721+
};
722+
723+
#[cfg(debug_assertions)]
724+
if layout.is_sized() && !layout.abi.is_uninhabited() {
725+
match (ty_is_non_zst(ty, param_env, tcx), layout.is_zst()) {
726+
(NonZst::Unknown, _) => {
727+
bug!("ZSTness should not be unknown at this point {:?} {:?}", ty, layout)
728+
}
729+
(n @ (NonZst::False | NonZst::Uninhabited), false) => {
730+
bug!("{:?} is not a ZST but ty_is_non_zst() thinks it is NonZst::{:?}", ty, n)
731+
}
732+
(NonZst::True, true) => bug!("{:?} is a ZST but ty_is_non_zst() thinks it isn't", ty),
733+
_ => {}
734+
}
735+
}
736+
737+
Ok(layout)
738+
}
739+
740+
fn ty_is_non_zst<'tcx>(ty: Ty<'tcx>, param_env: ParamEnv<'tcx>, tcx: TyCtxt<'tcx>) -> NonZst {
741+
fn fold_fields<'tcx>(
742+
mut it: impl Iterator<Item = Ty<'tcx>>,
743+
param_env: ParamEnv<'tcx>,
744+
tcx: TyCtxt<'tcx>,
745+
) -> NonZst {
746+
let (ControlFlow::Break(res) | ControlFlow::Continue(res)) =
747+
it.try_fold(NonZst::False, |acc, ty| {
748+
if acc == NonZst::True {
749+
return ControlFlow::Break(acc);
750+
}
751+
752+
ControlFlow::Continue(cmp::max(acc, ty_is_non_zst(ty, param_env, tcx)))
753+
});
754+
755+
res
756+
}
757+
758+
match ty.kind() {
759+
ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
760+
| ty::Uint(_)
761+
| ty::Int(_)
762+
| ty::Bool
763+
| ty::Float(_)
764+
| ty::FnPtr(_, _)
765+
| ty::RawPtr(..)
766+
| ty::Dynamic(_, _, DynKind::DynStar)
767+
| ty::Char
768+
| ty::Ref(..) => NonZst::True,
769+
770+
ty::Pat(ty, _) => ty_is_non_zst(*ty, param_env, tcx),
771+
ty::Closure(_, args) => fold_fields(args.as_closure().upvar_tys().iter(), param_env, tcx),
772+
ty::Coroutine(_, _) => NonZst::True,
773+
ty::CoroutineClosure(_, args) => {
774+
fold_fields(args.as_coroutine_closure().upvar_tys().iter(), param_env, tcx)
775+
}
776+
ty::Array(ty, len) => {
777+
let len = if len.has_aliases() {
778+
tcx.normalize_erasing_regions(param_env, *len)
779+
} else {
780+
*len
781+
};
782+
783+
if let Some(len) = len.try_to_target_usize(tcx) {
784+
if len == 0 {
785+
return NonZst::False;
786+
}
787+
let element_zst = ty_is_non_zst(*ty, param_env, tcx);
788+
if element_zst != NonZst::Unknown {
789+
return element_zst;
790+
}
791+
}
792+
NonZst::Unknown
793+
}
794+
ty::Tuple(tys) => fold_fields(tys.iter(), param_env, tcx),
795+
ty::Adt(def, args) => {
796+
if ty.is_enum() {
797+
// repr(C) enums can never be ZSTs or uninhabited.
798+
// They must have at least one variant and even if the variant has a payload that is uninhabited,
799+
// the tag is still there.
800+
if def.repr().c() {
801+
return NonZst::True;
802+
}
803+
804+
if def.variants().len() == 0 {
805+
return NonZst::Uninhabited;
806+
}
807+
// An enum is !ZST if
808+
// * it has a repr(int) and at least one non-uninhabited variant
809+
// * it has at least one variant with a !ZST payload
810+
// * it has multiple variants that are not uninhabited
811+
812+
let min_empty_variants = if def.repr().inhibit_enum_layout_opt() { 1 } else { 2 };
813+
814+
// first check without recursing
815+
let simple_variants = def.variants().iter().filter(|v| v.fields.len() == 0).count();
816+
if simple_variants >= min_empty_variants {
817+
return NonZst::True;
818+
}
819+
820+
let mut inhabited_zst_variants = 0;
821+
let mut unknown = false;
822+
823+
for variant in def.variants().iter().filter(|v| v.fields.len() != 0) {
824+
let variant_sized =
825+
fold_fields(variant.fields.iter().map(|f| f.ty(tcx, args)), param_env, tcx);
826+
827+
match variant_sized {
828+
// enum E { A(!, u32) } counts as !ZST for our purposes
829+
NonZst::True => return NonZst::True,
830+
NonZst::False => inhabited_zst_variants += 1,
831+
NonZst::Unknown => unknown = true,
832+
NonZst::Uninhabited => {}
833+
}
834+
}
835+
836+
if simple_variants + inhabited_zst_variants >= min_empty_variants {
837+
return NonZst::True;
838+
}
839+
if unknown {
840+
return NonZst::Unknown;
841+
}
842+
if simple_variants + inhabited_zst_variants == 0 {
843+
return NonZst::Uninhabited;
844+
}
845+
846+
NonZst::False
847+
} else {
848+
fold_fields(def.all_fields().map(|f| f.ty(tcx, args)), param_env, tcx)
849+
}
850+
}
851+
ty::FnDef(..) => NonZst::False,
852+
ty::Never => NonZst::Uninhabited,
853+
ty::Param(..) => NonZst::Unknown,
854+
ty::Str => NonZst::True,
855+
// treat unsized types as potentially-ZST
856+
ty::Dynamic(..) | ty::Slice(..) => NonZst::False,
857+
ty::Alias(..) => match tcx.try_normalize_erasing_regions(param_env, ty) {
858+
Ok(ty) if !matches!(ty.kind(), ty::Alias(..)) => ty_is_non_zst(ty, param_env, tcx),
859+
_ => NonZst::Unknown,
860+
},
861+
ty::Error(_) => NonZst::Unknown,
862+
_ => bug!("is_non_zst not implemented for this kind {:?}", ty),
863+
}
864+
}
865+
866+
#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
867+
enum NonZst {
868+
False,
869+
Uninhabited,
870+
Unknown,
871+
True,
677872
}
678873

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

0 commit comments

Comments
 (0)