Skip to content

Commit 557359f

Browse files
committed
Auto merge of #113166 - moulins:ref-niches-initial, r=oli-obk
Prototype: Add unstable `-Z reference-niches` option MCP: rust-lang/compiler-team#641 Relevant RFC: rust-lang/rfcs#3204 This prototype adds a new `-Z reference-niches` option, controlling the range of valid bit-patterns for reference types (`&T` and `&mut T`), thereby enabling new enum niching opportunities. Like `-Z randomize-layout`, this setting is crate-local; as such, references to built-in types (primitives, tuples, ...) are not affected. The possible settings are (here, `MAX` denotes the all-1 bit-pattern): | `-Z reference-niches=` | Valid range | |:---:|:---:| | `null` (the default) | `1..=MAX` | | `size` | `1..=(MAX- size)` | | `align` | `align..=MAX.align_down_to(align)` | | `size,align` | `align..=(MAX-size).align_down_to(align)` | ------ This is very WIP, and I'm not sure the approach I've taken here is the best one, but stage 1 tests pass locally; I believe this is in a good enough state to unleash this upon unsuspecting 3rd-party code, and see what breaks.
2 parents 1e6c09a + 7f10908 commit 557359f

File tree

48 files changed

+1065
-294
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1065
-294
lines changed

compiler/rustc_abi/src/lib.rs

+98-10
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ bitflags! {
4949
}
5050
}
5151

52+
/// Which niches (beyond the `null` niche) are available on references.
53+
#[derive(Default, Copy, Clone, Hash, Debug, Eq, PartialEq)]
54+
#[cfg_attr(feature = "nightly", derive(Encodable, Decodable, HashStable_Generic))]
55+
pub struct ReferenceNichePolicy {
56+
pub size: bool,
57+
pub align: bool,
58+
}
59+
5260
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
5361
#[cfg_attr(feature = "nightly", derive(Encodable, Decodable, HashStable_Generic))]
5462
pub enum IntegerType {
@@ -346,6 +354,33 @@ impl TargetDataLayout {
346354
}
347355
}
348356

357+
#[inline]
358+
pub fn target_usize_max(&self) -> u64 {
359+
self.pointer_size.unsigned_int_max().try_into().unwrap()
360+
}
361+
362+
#[inline]
363+
pub fn target_isize_min(&self) -> i64 {
364+
self.pointer_size.signed_int_min().try_into().unwrap()
365+
}
366+
367+
#[inline]
368+
pub fn target_isize_max(&self) -> i64 {
369+
self.pointer_size.signed_int_max().try_into().unwrap()
370+
}
371+
372+
/// Returns the (inclusive) range of possible addresses for an allocation with
373+
/// the given size and alignment.
374+
///
375+
/// Note that this doesn't take into account target-specific limitations.
376+
#[inline]
377+
pub fn address_range_for(&self, size: Size, align: Align) -> (u64, u64) {
378+
let end = Size::from_bytes(self.target_usize_max());
379+
let min = align.bytes();
380+
let max = (end - size).align_down_to(align).bytes();
381+
(min, max)
382+
}
383+
349384
#[inline]
350385
pub fn vector_align(&self, vec_size: Size) -> AbiAndPrefAlign {
351386
for &(size, align) in &self.vector_align {
@@ -473,6 +508,12 @@ impl Size {
473508
Size::from_bytes((self.bytes() + mask) & !mask)
474509
}
475510

511+
#[inline]
512+
pub fn align_down_to(self, align: Align) -> Size {
513+
let mask = align.bytes() - 1;
514+
Size::from_bytes(self.bytes() & !mask)
515+
}
516+
476517
#[inline]
477518
pub fn is_aligned(self, align: Align) -> bool {
478519
let mask = align.bytes() - 1;
@@ -967,6 +1008,43 @@ impl WrappingRange {
9671008
}
9681009
}
9691010

1011+
/// Returns `true` if `range` is contained in `self`.
1012+
#[inline(always)]
1013+
pub fn contains_range<I: Into<u128> + Ord>(&self, range: RangeInclusive<I>) -> bool {
1014+
if range.is_empty() {
1015+
return true;
1016+
}
1017+
1018+
let (vmin, vmax) = range.into_inner();
1019+
let (vmin, vmax) = (vmin.into(), vmax.into());
1020+
1021+
if self.start <= self.end {
1022+
self.start <= vmin && vmax <= self.end
1023+
} else {
1024+
// The last check is needed to cover the following case:
1025+
// `vmin ... start, end ... vmax`. In this special case there is no gap
1026+
// between `start` and `end` so we must return true.
1027+
self.start <= vmin || vmax <= self.end || self.start == self.end + 1
1028+
}
1029+
}
1030+
1031+
/// Returns `true` if `range` has an overlap with `self`.
1032+
#[inline(always)]
1033+
pub fn overlaps_range<I: Into<u128> + Ord>(&self, range: RangeInclusive<I>) -> bool {
1034+
if range.is_empty() {
1035+
return false;
1036+
}
1037+
1038+
let (vmin, vmax) = range.into_inner();
1039+
let (vmin, vmax) = (vmin.into(), vmax.into());
1040+
1041+
if self.start <= self.end {
1042+
self.start <= vmax && vmin <= self.end
1043+
} else {
1044+
self.start <= vmax || vmin <= self.end
1045+
}
1046+
}
1047+
9701048
/// Returns `self` with replaced `start`
9711049
#[inline(always)]
9721050
pub fn with_start(mut self, start: u128) -> Self {
@@ -984,9 +1062,15 @@ impl WrappingRange {
9841062
/// Returns `true` if `size` completely fills the range.
9851063
#[inline]
9861064
pub fn is_full_for(&self, size: Size) -> bool {
1065+
debug_assert!(self.is_in_range_for(size));
1066+
self.start == (self.end.wrapping_add(1) & size.unsigned_int_max())
1067+
}
1068+
1069+
/// Returns `true` if the range is valid for `size`.
1070+
#[inline(always)]
1071+
pub fn is_in_range_for(&self, size: Size) -> bool {
9871072
let max_value = size.unsigned_int_max();
988-
debug_assert!(self.start <= max_value && self.end <= max_value);
989-
self.start == (self.end.wrapping_add(1) & max_value)
1073+
self.start <= max_value && self.end <= max_value
9901074
}
9911075
}
9921076

@@ -1427,16 +1511,21 @@ impl Niche {
14271511

14281512
pub fn reserve<C: HasDataLayout>(&self, cx: &C, count: u128) -> Option<(u128, Scalar)> {
14291513
assert!(count > 0);
1514+
if count > self.available(cx) {
1515+
return None;
1516+
}
14301517

14311518
let Self { value, valid_range: v, .. } = *self;
1432-
let size = value.size(cx);
1433-
assert!(size.bits() <= 128);
1434-
let max_value = size.unsigned_int_max();
1519+
let max_value = value.size(cx).unsigned_int_max();
1520+
let distance_end_zero = max_value - v.end;
14351521

1436-
let niche = v.end.wrapping_add(1)..v.start;
1437-
let available = niche.end.wrapping_sub(niche.start) & max_value;
1438-
if count > available {
1439-
return None;
1522+
// Null-pointer optimization. This is guaranteed by Rust (at least for `Option<_>`),
1523+
// and offers better codegen opportunities.
1524+
if count == 1 && matches!(value, Pointer(_)) && !v.contains(0) {
1525+
// Select which bound to move to minimize the number of lost niches.
1526+
let valid_range =
1527+
if v.start - 1 > distance_end_zero { v.with_end(0) } else { v.with_start(0) };
1528+
return Some((0, Scalar::Initialized { value, valid_range }));
14401529
}
14411530

14421531
// Extend the range of valid values being reserved by moving either `v.start` or `v.end` bound.
@@ -1459,7 +1548,6 @@ impl Niche {
14591548
let end = v.end.wrapping_add(count) & max_value;
14601549
Some((start, Scalar::Initialized { value, valid_range: v.with_end(end) }))
14611550
};
1462-
let distance_end_zero = max_value - v.end;
14631551
if v.start > v.end {
14641552
// zero is unavailable because wrapping occurs
14651553
move_end(v)

compiler/rustc_codegen_gcc/src/type_of.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,8 @@ impl<'tcx> LayoutGccExt<'tcx> for TyAndLayout<'tcx> {
339339
return pointee;
340340
}
341341

342-
let result = Ty::ty_and_layout_pointee_info_at(*self, cx, offset);
342+
let assume_valid_ptr = true;
343+
let result = Ty::ty_and_layout_pointee_info_at(*self, cx, offset, assume_valid_ptr);
343344

344345
cx.pointee_infos.borrow_mut().insert((self.ty, offset), result);
345346
result

compiler/rustc_codegen_llvm/src/type_of.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,8 @@ impl<'tcx> LayoutLlvmExt<'tcx> for TyAndLayout<'tcx> {
411411
if let Some(&pointee) = cx.pointee_infos.borrow().get(&(self.ty, offset)) {
412412
return pointee;
413413
}
414-
415-
let result = Ty::ty_and_layout_pointee_info_at(*self, cx, offset);
414+
let assume_valid_ptr = true;
415+
let result = Ty::ty_and_layout_pointee_info_at(*self, cx, offset, assume_valid_ptr);
416416

417417
cx.pointee_infos.borrow_mut().insert((self.ty, offset), result);
418418
result

compiler/rustc_const_eval/messages.ftl

-1
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,6 @@ const_eval_not_enough_caller_args =
244244
const_eval_null_box = {$front_matter}: encountered a null box
245245
const_eval_null_fn_ptr = {$front_matter}: encountered a null function pointer
246246
const_eval_null_ref = {$front_matter}: encountered a null reference
247-
const_eval_nullable_ptr_out_of_range = {$front_matter}: encountered a potentially null pointer, but expected something that cannot possibly fail to be {$in_range}
248247
const_eval_nullary_intrinsic_fail =
249248
could not evaluate nullary intrinsic
250249

compiler/rustc_const_eval/src/const_eval/machine.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use rustc_hir::def::DefKind;
22
use rustc_hir::{LangItem, CRATE_HIR_ID};
33
use rustc_middle::mir;
4-
use rustc_middle::mir::interpret::PointerArithmetic;
54
use rustc_middle::ty::layout::{FnAbiOf, TyAndLayout};
65
use rustc_middle::ty::{self, Ty, TyCtxt};
76
use rustc_session::lint::builtin::INVALID_ALIGNMENT;
@@ -17,7 +16,7 @@ use rustc_ast::Mutability;
1716
use rustc_hir::def_id::DefId;
1817
use rustc_middle::mir::AssertMessage;
1918
use rustc_span::symbol::{sym, Symbol};
20-
use rustc_target::abi::{Align, Size};
19+
use rustc_target::abi::{Align, HasDataLayout as _, Size};
2120
use rustc_target::spec::abi::Abi as CallAbi;
2221

2322
use crate::errors::{LongRunning, LongRunningWarn};
@@ -304,8 +303,8 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
304303
Ok(ControlFlow::Break(()))
305304
} else {
306305
// Not alignable in const, return `usize::MAX`.
307-
let usize_max = Scalar::from_target_usize(self.target_usize_max(), self);
308-
self.write_scalar(usize_max, dest)?;
306+
let usize_max = self.data_layout().target_usize_max();
307+
self.write_scalar(Scalar::from_target_usize(usize_max, self), dest)?;
309308
self.return_to_block(ret)?;
310309
Ok(ControlFlow::Break(()))
311310
}
@@ -333,7 +332,7 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
333332
// Inequality with integers other than null can never be known for sure.
334333
(Scalar::Int(int), ptr @ Scalar::Ptr(..))
335334
| (ptr @ Scalar::Ptr(..), Scalar::Int(int))
336-
if int.is_null() && !self.scalar_may_be_null(ptr)? =>
335+
if int.is_null() && !self.ptr_scalar_range(ptr)?.contains(&0) =>
337336
{
338337
0
339338
}

compiler/rustc_const_eval/src/errors.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,6 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
617617
MutableRefInConst => const_eval_mutable_ref_in_const,
618618
NullFnPtr => const_eval_null_fn_ptr,
619619
NeverVal => const_eval_never_val,
620-
NullablePtrOutOfRange { .. } => const_eval_nullable_ptr_out_of_range,
621620
PtrOutOfRange { .. } => const_eval_ptr_out_of_range,
622621
OutOfRange { .. } => const_eval_out_of_range,
623622
UnsafeCell => const_eval_unsafe_cell,
@@ -732,9 +731,7 @@ impl<'tcx> ReportErrorExt for ValidationErrorInfo<'tcx> {
732731
| InvalidFnPtr { value } => {
733732
err.set_arg("value", value);
734733
}
735-
NullablePtrOutOfRange { range, max_value } | PtrOutOfRange { range, max_value } => {
736-
add_range_arg(range, max_value, handler, err)
737-
}
734+
PtrOutOfRange { range, max_value } => add_range_arg(range, max_value, handler, err),
738735
OutOfRange { range, max_value, value } => {
739736
err.set_arg("value", value);
740737
add_range_arg(range, max_value, handler, err);

compiler/rustc_const_eval/src/interpret/discriminant.rs

+14-10
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
33
use rustc_middle::ty::layout::{LayoutOf, PrimitiveExt};
44
use rustc_middle::{mir, ty};
5-
use rustc_target::abi::{self, TagEncoding};
6-
use rustc_target::abi::{VariantIdx, Variants};
5+
use rustc_target::abi::{self, TagEncoding, VariantIdx, Variants, WrappingRange};
76

87
use super::{ImmTy, InterpCx, InterpResult, Machine, OpTy, PlaceTy, Scalar};
98

@@ -180,19 +179,24 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
180179
// discriminant (encoded in niche/tag) and variant index are the same.
181180
let variants_start = niche_variants.start().as_u32();
182181
let variants_end = niche_variants.end().as_u32();
182+
let variants_len = u128::from(variants_end - variants_start);
183183
let variant = match tag_val.try_to_int() {
184184
Err(dbg_val) => {
185185
// So this is a pointer then, and casting to an int failed.
186186
// Can only happen during CTFE.
187-
// The niche must be just 0, and the ptr not null, then we know this is
188-
// okay. Everything else, we conservatively reject.
189-
let ptr_valid = niche_start == 0
190-
&& variants_start == variants_end
191-
&& !self.scalar_may_be_null(tag_val)?;
192-
if !ptr_valid {
187+
// The pointer and niches ranges must be disjoint, then we know
188+
// this is the untagged variant (as the value is not in the niche).
189+
// Everything else, we conservatively reject.
190+
let range = self.ptr_scalar_range(tag_val)?;
191+
let niches = WrappingRange {
192+
start: niche_start,
193+
end: niche_start.wrapping_add(variants_len),
194+
};
195+
if niches.overlaps_range(range) {
193196
throw_ub!(InvalidTag(dbg_val))
197+
} else {
198+
untagged_variant
194199
}
195-
untagged_variant
196200
}
197201
Ok(tag_bits) => {
198202
let tag_bits = tag_bits.assert_bits(tag_layout.size);
@@ -205,7 +209,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
205209
let variant_index_relative =
206210
variant_index_relative_val.to_scalar().assert_bits(tag_val.layout.size);
207211
// Check if this is in the range that indicates an actual discriminant.
208-
if variant_index_relative <= u128::from(variants_end - variants_start) {
212+
if variant_index_relative <= variants_len {
209213
let variant_index_relative = u32::try_from(variant_index_relative)
210214
.expect("we checked that this fits into a u32");
211215
// Then computing the absolute variant idx should not overflow any more.

compiler/rustc_const_eval/src/interpret/intrinsics.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@
55
use rustc_hir::def_id::DefId;
66
use rustc_middle::mir::{
77
self,
8-
interpret::{
9-
Allocation, ConstAllocation, ConstValue, GlobalId, InterpResult, PointerArithmetic, Scalar,
10-
},
8+
interpret::{Allocation, ConstAllocation, ConstValue, GlobalId, InterpResult, Scalar},
119
BinOp, NonDivergingIntrinsic,
1210
};
1311
use rustc_middle::ty;
1412
use rustc_middle::ty::layout::{LayoutOf as _, ValidityRequirement};
1513
use rustc_middle::ty::GenericArgsRef;
1614
use rustc_middle::ty::{Ty, TyCtxt};
1715
use rustc_span::symbol::{sym, Symbol};
18-
use rustc_target::abi::{Abi, Align, Primitive, Size};
16+
use rustc_target::abi::{Abi, Align, HasDataLayout as _, Primitive, Size};
1917

2018
use super::{
2119
util::ensure_monomorphic_enough, CheckInAllocMsg, ImmTy, InterpCx, Machine, OpTy, PlaceTy,
@@ -361,11 +359,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
361359
)?;
362360

363361
// Perform division by size to compute return value.
362+
let dl = self.data_layout();
364363
let ret_layout = if intrinsic_name == sym::ptr_offset_from_unsigned {
365-
assert!(0 <= dist && dist <= self.target_isize_max());
364+
assert!(0 <= dist && dist <= dl.target_isize_max());
366365
usize_layout
367366
} else {
368-
assert!(self.target_isize_min() <= dist && dist <= self.target_isize_max());
367+
assert!(dl.target_isize_min() <= dist && dist <= dl.target_isize_max());
369368
isize_layout
370369
};
371370
let pointee_layout = self.layout_of(instance_args.type_at(0))?;

compiler/rustc_const_eval/src/interpret/memory.rs

+26-15
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::assert_matches::assert_matches;
1010
use std::borrow::Cow;
1111
use std::collections::VecDeque;
1212
use std::fmt;
13+
use std::ops::RangeInclusive;
1314
use std::ptr;
1415

1516
use rustc_ast::Mutability;
@@ -1222,24 +1223,34 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
12221223

12231224
/// Machine pointer introspection.
12241225
impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
1225-
/// Test if this value might be null.
1226+
/// Turn a pointer-sized scalar into a (non-empty) range of possible values.
12261227
/// If the machine does not support ptr-to-int casts, this is conservative.
1227-
pub fn scalar_may_be_null(&self, scalar: Scalar<M::Provenance>) -> InterpResult<'tcx, bool> {
1228-
Ok(match scalar.try_to_int() {
1229-
Ok(int) => int.is_null(),
1230-
Err(_) => {
1231-
// Can only happen during CTFE.
1232-
let ptr = scalar.to_pointer(self)?;
1233-
match self.ptr_try_get_alloc_id(ptr) {
1234-
Ok((alloc_id, offset, _)) => {
1235-
let (size, _align, _kind) = self.get_alloc_info(alloc_id);
1236-
// If the pointer is out-of-bounds, it may be null.
1237-
// Note that one-past-the-end (offset == size) is still inbounds, and never null.
1238-
offset > size
1239-
}
1240-
Err(_offset) => bug!("a non-int scalar is always a pointer"),
1228+
pub fn ptr_scalar_range(
1229+
&self,
1230+
scalar: Scalar<M::Provenance>,
1231+
) -> InterpResult<'tcx, RangeInclusive<u64>> {
1232+
if let Ok(int) = scalar.to_target_usize(self) {
1233+
return Ok(int..=int);
1234+
}
1235+
1236+
let ptr = scalar.to_pointer(self)?;
1237+
1238+
// Can only happen during CTFE.
1239+
Ok(match self.ptr_try_get_alloc_id(ptr) {
1240+
Ok((alloc_id, offset, _)) => {
1241+
let offset = offset.bytes();
1242+
let (size, align, _) = self.get_alloc_info(alloc_id);
1243+
let dl = self.data_layout();
1244+
if offset > size.bytes() {
1245+
// If the pointer is out-of-bounds, we do not have a
1246+
// meaningful range to return.
1247+
0..=dl.target_usize_max()
1248+
} else {
1249+
let (min, max) = dl.address_range_for(size, align);
1250+
(min + offset)..=(max + offset)
12411251
}
12421252
}
1253+
Err(_offset) => bug!("a non-int scalar is always a pointer"),
12431254
})
12441255
}
12451256

0 commit comments

Comments
 (0)