Skip to content

Commit

Permalink
Rollup merge of rust-lang#121885 - reitermarkus:generic-nonzero-inner…
Browse files Browse the repository at this point in the history
…, r=oli-obk

Move generic `NonZero` `rustc_layout_scalar_valid_range_start` attribute to inner type.

Tracking issue: rust-lang#120257

r? ``@dtolnay``
  • Loading branch information
matthiaskrgr authored Mar 6, 2024
2 parents 744384c + a58eb80 commit 6fd5793
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 99 deletions.
44 changes: 27 additions & 17 deletions compiler/rustc_lint/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,14 @@ pub fn transparent_newtype_field<'a, 'tcx>(
}

/// Is type known to be non-null?
fn ty_is_known_nonnull<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -> bool {
fn ty_is_known_nonnull<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
ty: Ty<'tcx>,
mode: CItemKind,
) -> bool {
let ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty);

match ty.kind() {
ty::FnPtr(_) => true,
ty::Ref(..) => true,
Expand All @@ -1004,15 +1011,21 @@ fn ty_is_known_nonnull<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, mode: CItemKind) -
def.variants()
.iter()
.filter_map(|variant| transparent_newtype_field(tcx, variant))
.any(|field| ty_is_known_nonnull(tcx, field.ty(tcx, args), mode))
.any(|field| ty_is_known_nonnull(tcx, param_env, field.ty(tcx, args), mode))
}
_ => false,
}
}

/// Given a non-null scalar (or transparent) type `ty`, return the nullable version of that type.
/// If the type passed in was not scalar, returns None.
fn get_nullable_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
fn get_nullable_type<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
ty: Ty<'tcx>,
) -> Option<Ty<'tcx>> {
let ty = tcx.try_normalize_erasing_regions(param_env, ty).unwrap_or(ty);

Some(match *ty.kind() {
ty::Adt(field_def, field_args) => {
let inner_field_ty = {
Expand All @@ -1028,22 +1041,19 @@ fn get_nullable_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>>
.expect("No non-zst fields in transparent type.")
.ty(tcx, field_args)
};
return get_nullable_type(tcx, inner_field_ty);
return get_nullable_type(tcx, param_env, inner_field_ty);
}
ty::Int(ty) => Ty::new_int(tcx, ty),
ty::Uint(ty) => Ty::new_uint(tcx, ty),
ty::RawPtr(ty_mut) => Ty::new_ptr(tcx, ty_mut),
// As these types are always non-null, the nullable equivalent of
// Option<T> of these types are their raw pointer counterparts.
// `Option<T>` of these types are their raw pointer counterparts.
ty::Ref(_region, ty, mutbl) => Ty::new_ptr(tcx, ty::TypeAndMut { ty, mutbl }),
ty::FnPtr(..) => {
// There is no nullable equivalent for Rust's function pointers -- you
// must use an Option<fn(..) -> _> to represent it.
ty
}

// We should only ever reach this case if ty_is_known_nonnull is extended
// to other types.
// There is no nullable equivalent for Rust's function pointers,
// you must use an `Option<fn(..) -> _>` to represent it.
ty::FnPtr(..) => ty,
// We should only ever reach this case if `ty_is_known_nonnull` is
// extended to other types.
ref unhandled => {
debug!(
"get_nullable_type: Unhandled scalar kind: {:?} while checking {:?}",
Expand All @@ -1056,7 +1066,7 @@ fn get_nullable_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>>

/// Check if this enum can be safely exported based on the "nullable pointer optimization". If it
/// can, return the type that `ty` can be safely converted to, otherwise return `None`.
/// Currently restricted to function pointers, boxes, references, `core::num::NonZero*`,
/// Currently restricted to function pointers, boxes, references, `core::num::NonZero`,
/// `core::ptr::NonNull`, and `#[repr(transparent)]` newtypes.
/// FIXME: This duplicates code in codegen.
pub(crate) fn repr_nullable_ptr<'tcx>(
Expand All @@ -1075,7 +1085,7 @@ pub(crate) fn repr_nullable_ptr<'tcx>(
_ => return None,
};

if !ty_is_known_nonnull(tcx, field_ty, ckind) {
if !ty_is_known_nonnull(tcx, param_env, field_ty, ckind) {
return None;
}

Expand All @@ -1099,10 +1109,10 @@ pub(crate) fn repr_nullable_ptr<'tcx>(
WrappingRange { start: 0, end }
if end == field_ty_scalar.size(&tcx).unsigned_int_max() - 1 =>
{
return Some(get_nullable_type(tcx, field_ty).unwrap());
return Some(get_nullable_type(tcx, param_env, field_ty).unwrap());
}
WrappingRange { start: 1, .. } => {
return Some(get_nullable_type(tcx, field_ty).unwrap());
return Some(get_nullable_type(tcx, param_env, field_ty).unwrap());
}
WrappingRange { start, end } => {
unreachable!("Unhandled start and end range: ({}, {})", start, end)
Expand Down
146 changes: 96 additions & 50 deletions library/core/src/num/nonzero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,13 @@ use crate::hash::{Hash, Hasher};
use crate::intrinsics;
use crate::marker::StructuralPartialEq;
use crate::ops::{BitOr, BitOrAssign, Div, Neg, Rem};
use crate::panic::{RefUnwindSafe, UnwindSafe};
use crate::ptr;
use crate::str::FromStr;

use super::from_str_radix;
use super::{IntErrorKind, ParseIntError};

mod private {
#[unstable(
feature = "nonzero_internals",
reason = "implementation detail which may disappear or be replaced at any time",
issue = "none"
)]
#[const_trait]
pub trait Sealed {}
}

/// A marker trait for primitive types which can be zero.
///
/// This is an implementation detail for <code>[NonZero]\<T></code> which may disappear or be replaced at any time.
Expand All @@ -34,38 +26,70 @@ mod private {
issue = "none"
)]
#[const_trait]
pub unsafe trait ZeroablePrimitive: Sized + Copy + private::Sealed {}
pub unsafe trait ZeroablePrimitive: Sized + Copy + private::Sealed {
#[doc(hidden)]
type NonZeroInner: Sized + Copy;
}

macro_rules! impl_zeroable_primitive {
($primitive:ty) => {
#[unstable(
feature = "nonzero_internals",
reason = "implementation detail which may disappear or be replaced at any time",
issue = "none"
)]
impl const private::Sealed for $primitive {}

#[unstable(
feature = "nonzero_internals",
reason = "implementation detail which may disappear or be replaced at any time",
issue = "none"
)]
unsafe impl const ZeroablePrimitive for $primitive {}
($($NonZeroInner:ident ( $primitive:ty )),+ $(,)?) => {
mod private {
#[unstable(
feature = "nonzero_internals",
reason = "implementation detail which may disappear or be replaced at any time",
issue = "none"
)]
#[const_trait]
pub trait Sealed {}

$(
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(transparent)]
#[rustc_layout_scalar_valid_range_start(1)]
#[rustc_nonnull_optimization_guaranteed]
#[unstable(
feature = "nonzero_internals",
reason = "implementation detail which may disappear or be replaced at any time",
issue = "none"
)]
pub struct $NonZeroInner($primitive);
)+
}

$(
#[unstable(
feature = "nonzero_internals",
reason = "implementation detail which may disappear or be replaced at any time",
issue = "none"
)]
impl const private::Sealed for $primitive {}

#[unstable(
feature = "nonzero_internals",
reason = "implementation detail which may disappear or be replaced at any time",
issue = "none"
)]
unsafe impl const ZeroablePrimitive for $primitive {
type NonZeroInner = private::$NonZeroInner;
}
)+
};
}

impl_zeroable_primitive!(u8);
impl_zeroable_primitive!(u16);
impl_zeroable_primitive!(u32);
impl_zeroable_primitive!(u64);
impl_zeroable_primitive!(u128);
impl_zeroable_primitive!(usize);
impl_zeroable_primitive!(i8);
impl_zeroable_primitive!(i16);
impl_zeroable_primitive!(i32);
impl_zeroable_primitive!(i64);
impl_zeroable_primitive!(i128);
impl_zeroable_primitive!(isize);
impl_zeroable_primitive!(
NonZeroU8Inner(u8),
NonZeroU16Inner(u16),
NonZeroU32Inner(u32),
NonZeroU64Inner(u64),
NonZeroU128Inner(u128),
NonZeroUsizeInner(usize),
NonZeroI8Inner(i8),
NonZeroI16Inner(i16),
NonZeroI32Inner(i32),
NonZeroI64Inner(i64),
NonZeroI128Inner(i128),
NonZeroIsizeInner(isize),
);

/// A value that is known not to equal zero.
///
Expand All @@ -80,10 +104,9 @@ impl_zeroable_primitive!(isize);
/// ```
#[unstable(feature = "generic_nonzero", issue = "120257")]
#[repr(transparent)]
#[rustc_layout_scalar_valid_range_start(1)]
#[rustc_nonnull_optimization_guaranteed]
#[rustc_diagnostic_item = "NonZero"]
pub struct NonZero<T: ZeroablePrimitive>(T);
pub struct NonZero<T: ZeroablePrimitive>(T::NonZeroInner);

macro_rules! impl_nonzero_fmt {
($Trait:ident) => {
Expand All @@ -107,15 +130,33 @@ impl_nonzero_fmt!(Octal);
impl_nonzero_fmt!(LowerHex);
impl_nonzero_fmt!(UpperHex);

macro_rules! impl_nonzero_auto_trait {
(unsafe $Trait:ident) => {
#[stable(feature = "nonzero", since = "1.28.0")]
unsafe impl<T> $Trait for NonZero<T> where T: ZeroablePrimitive + $Trait {}
};
($Trait:ident) => {
#[stable(feature = "nonzero", since = "1.28.0")]
impl<T> $Trait for NonZero<T> where T: ZeroablePrimitive + $Trait {}
};
}

// Implement auto-traits manually based on `T` to avoid docs exposing
// the `ZeroablePrimitive::NonZeroInner` implementation detail.
impl_nonzero_auto_trait!(RefUnwindSafe);
impl_nonzero_auto_trait!(unsafe Send);
impl_nonzero_auto_trait!(unsafe Sync);
impl_nonzero_auto_trait!(Unpin);
impl_nonzero_auto_trait!(UnwindSafe);

#[stable(feature = "nonzero", since = "1.28.0")]
impl<T> Clone for NonZero<T>
where
T: ZeroablePrimitive,
{
#[inline]
fn clone(&self) -> Self {
// SAFETY: The contained value is non-zero.
unsafe { Self(self.0) }
Self(self.0)
}
}

Expand Down Expand Up @@ -188,19 +229,19 @@ where
#[inline]
fn max(self, other: Self) -> Self {
// SAFETY: The maximum of two non-zero values is still non-zero.
unsafe { Self(self.get().max(other.get())) }
unsafe { Self::new_unchecked(self.get().max(other.get())) }
}

#[inline]
fn min(self, other: Self) -> Self {
// SAFETY: The minimum of two non-zero values is still non-zero.
unsafe { Self(self.get().min(other.get())) }
unsafe { Self::new_unchecked(self.get().min(other.get())) }
}

#[inline]
fn clamp(self, min: Self, max: Self) -> Self {
// SAFETY: A non-zero value clamped between two non-zero values is still non-zero.
unsafe { Self(self.get().clamp(min.get(), max.get())) }
unsafe { Self::new_unchecked(self.get().clamp(min.get(), max.get())) }
}
}

Expand Down Expand Up @@ -240,7 +281,7 @@ where
#[inline]
fn bitor(self, rhs: Self) -> Self::Output {
// SAFETY: Bitwise OR of two non-zero values is still non-zero.
unsafe { Self(self.get() | rhs.get()) }
unsafe { Self::new_unchecked(self.get() | rhs.get()) }
}
}

Expand All @@ -254,7 +295,7 @@ where
#[inline]
fn bitor(self, rhs: T) -> Self::Output {
// SAFETY: Bitwise OR of a non-zero value with anything is still non-zero.
unsafe { Self(self.get() | rhs) }
unsafe { Self::new_unchecked(self.get() | rhs) }
}
}

Expand All @@ -268,7 +309,7 @@ where
#[inline]
fn bitor(self, rhs: NonZero<T>) -> Self::Output {
// SAFETY: Bitwise OR of anything with a non-zero value is still non-zero.
unsafe { NonZero(self | rhs.get()) }
unsafe { NonZero::new_unchecked(self | rhs.get()) }
}
}

Expand Down Expand Up @@ -345,7 +386,7 @@ where
pub fn from_mut(n: &mut T) -> Option<&mut Self> {
// SAFETY: Memory layout optimization guarantees that `Option<NonZero<T>>` has
// the same layout and size as `T`, with `0` representing `None`.
let opt_n = unsafe { &mut *(n as *mut T as *mut Option<Self>) };
let opt_n = unsafe { &mut *(ptr::from_mut(n).cast::<Option<Self>>()) };

opt_n.as_mut()
}
Expand Down Expand Up @@ -388,12 +429,17 @@ where
// memory somewhere. If the value of `self` was from by-value argument
// of some not-inlined function, LLVM don't have range metadata
// to understand that the value cannot be zero.
match Self::new(self.0) {
Some(Self(n)) => n,
//
// SAFETY: `Self` is guaranteed to have the same layout as `Option<Self>`.
match unsafe { intrinsics::transmute_unchecked(self) } {
None => {
// SAFETY: `NonZero` is guaranteed to only contain non-zero values, so this is unreachable.
unsafe { intrinsics::unreachable() }
}
Some(Self(inner)) => {
// SAFETY: `T::NonZeroInner` is guaranteed to have the same layout as `T`.
unsafe { intrinsics::transmute_unchecked(inner) }
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: constructing invalid value: encountered 0, but expected something greater or equal to 1
error: Undefined Behavior: constructing invalid value at .0: encountered 0, but expected something greater or equal to 1
--> $DIR/cast_fn_ptr_invalid_callee_ret.rs:LL:CC
|
LL | f();
| ^^^ constructing invalid value: encountered 0, but expected something greater or equal to 1
| ^^^ constructing invalid value at .0: encountered 0, but expected something greater or equal to 1
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: constructing invalid value: encountered 0, but expected something greater or equal to 1
error: Undefined Behavior: constructing invalid value at .0: encountered 0, but expected something greater or equal to 1
--> $DIR/cast_fn_ptr_invalid_caller_arg.rs:LL:CC
|
LL | Call(_res = f(*ptr), ReturnTo(retblock), UnwindContinue())
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0, but expected something greater or equal to 1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .0: encountered 0, but expected something greater or equal to 1
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/consts/const-eval/raw-bytes.32bit.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ error[E0080]: it is undefined behavior to use this value
--> $DIR/raw-bytes.rs:59:1
|
LL | const NULL_U8: NonZero<u8> = unsafe { mem::transmute(0u8) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0, but expected something greater or equal to 1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .0: encountered 0, but expected something greater or equal to 1
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
= note: the raw bytes of the constant (size: 1, align: 1) {
Expand All @@ -79,7 +79,7 @@ error[E0080]: it is undefined behavior to use this value
--> $DIR/raw-bytes.rs:61:1
|
LL | const NULL_USIZE: NonZero<usize> = unsafe { mem::transmute(0usize) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value: encountered 0, but expected something greater or equal to 1
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ constructing invalid value at .0: encountered 0, but expected something greater or equal to 1
|
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rustc repository if you believe it should not be considered undefined behavior.
= note: the raw bytes of the constant (size: 4, align: 4) {
Expand Down
Loading

0 comments on commit 6fd5793

Please sign in to comment.