Skip to content

Commit 1de7815

Browse files
authored
Rollup merge of #91617 - nnethercote:improve-List-readability, r=lcnr
Improve the readability of `List<T>`. This commit does the following. - Expands on some of the things already mentioned in comments. - Describes the uniqueness assumption, which is critical but wasn't mentioned at all. - Rewrites `empty()` into a clearer form, as provided by Daniel Henry-Mantilla on Zulip. - Reorders things slightly so that more important things are higher up, and incidental things are lower down, which makes reading the code easier. r? ````@lcnr````
2 parents b9a37ad + 769a707 commit 1de7815

File tree

1 file changed

+89
-51
lines changed
  • compiler/rustc_middle/src/ty

1 file changed

+89
-51
lines changed

compiler/rustc_middle/src/ty/list.rs

+89-51
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use crate::arena::Arena;
2-
32
use rustc_serialize::{Encodable, Encoder};
4-
53
use std::alloc::Layout;
64
use std::cmp::Ordering;
75
use std::fmt;
@@ -12,49 +10,69 @@ use std::ops::Deref;
1210
use std::ptr;
1311
use std::slice;
1412

15-
extern "C" {
16-
/// A dummy type used to force `List` to be unsized while not requiring references to it be wide
17-
/// pointers.
18-
type OpaqueListContents;
19-
}
20-
21-
/// A wrapper for slices with the additional invariant
22-
/// that the slice is interned and no other slice with
23-
/// the same contents can exist in the same context.
24-
/// This means we can use pointer for both
25-
/// equality comparisons and hashing.
26-
///
27-
/// Unlike slices, the types contained in `List` are expected to be `Copy`
28-
/// and iterating over a `List` returns `T` instead of a reference.
29-
///
30-
/// Note: `Slice` was already taken by the `Ty`.
13+
/// `List<T>` is a bit like `&[T]`, but with some critical differences.
14+
/// - IMPORTANT: Every `List<T>` is *required* to have unique contents. The
15+
/// type's correctness relies on this, *but it does not enforce it*.
16+
/// Therefore, any code that creates a `List<T>` must ensure uniqueness
17+
/// itself. In practice this is achieved by interning.
18+
/// - The length is stored within the `List<T>`, so `&List<Ty>` is a thin
19+
/// pointer.
20+
/// - Because of this, you cannot get a `List<T>` that is a sub-list of another
21+
/// `List<T>`. You can get a sub-slice `&[T]`, however.
22+
/// - `List<T>` can be used with `CopyTaggedPtr`, which is useful within
23+
/// structs whose size must be minimized.
24+
/// - Because of the uniqueness assumption, we can use the address of a
25+
/// `List<T>` for faster equality comparisons and hashing.
26+
/// - `T` must be `Copy`. This lets `List<T>` be stored in a dropless arena and
27+
/// iterators return a `T` rather than a `&T`.
28+
/// - `T` must not be zero-sized.
3129
#[repr(C)]
3230
pub struct List<T> {
3331
len: usize,
32+
33+
/// Although this claims to be a zero-length array, in practice `len`
34+
/// elements are actually present.
3435
data: [T; 0],
36+
3537
opaque: OpaqueListContents,
3638
}
3739

38-
unsafe impl<'a, T: 'a> rustc_data_structures::tagged_ptr::Pointer for &'a List<T> {
39-
const BITS: usize = std::mem::align_of::<usize>().trailing_zeros() as usize;
40-
#[inline]
41-
fn into_usize(self) -> usize {
42-
self as *const List<T> as usize
43-
}
44-
#[inline]
45-
unsafe fn from_usize(ptr: usize) -> Self {
46-
&*(ptr as *const List<T>)
47-
}
48-
unsafe fn with_ref<R, F: FnOnce(&Self) -> R>(ptr: usize, f: F) -> R {
49-
// Self: Copy so this is fine
50-
let ptr = Self::from_usize(ptr);
51-
f(&ptr)
52-
}
40+
extern "C" {
41+
/// A dummy type used to force `List` to be unsized while not requiring
42+
/// references to it be wide pointers.
43+
type OpaqueListContents;
5344
}
5445

55-
unsafe impl<T: Sync> Sync for List<T> {}
46+
impl<T> List<T> {
47+
/// Returns a reference to the (unique, static) empty list.
48+
#[inline(always)]
49+
pub fn empty<'a>() -> &'a List<T> {
50+
#[repr(align(64))]
51+
struct MaxAlign;
52+
53+
assert!(mem::align_of::<T>() <= mem::align_of::<MaxAlign>());
54+
55+
#[repr(C)]
56+
struct InOrder<T, U>(T, U);
57+
58+
// The empty slice is static and contains a single `0` usize (for the
59+
// length) that is 64-byte aligned, thus featuring the necessary
60+
// trailing padding for elements with up to 64-byte alignment.
61+
static EMPTY_SLICE: InOrder<usize, MaxAlign> = InOrder(0, MaxAlign);
62+
unsafe { &*(&EMPTY_SLICE as *const _ as *const List<T>) }
63+
}
64+
}
5665

5766
impl<T: Copy> List<T> {
67+
/// Allocates a list from `arena` and copies the contents of `slice` into it.
68+
///
69+
/// WARNING: the contents *must be unique*, such that no list with these
70+
/// contents has been previously created. If not, operations such as `eq`
71+
/// and `hash` might give incorrect results.
72+
///
73+
/// Panics if `T` is `Drop`, or `T` is zero-sized, or the slice is empty
74+
/// (because the empty list exists statically, and is available via
75+
/// `empty()`).
5876
#[inline]
5977
pub(super) fn from_arena<'tcx>(arena: &'tcx Arena<'tcx>, slice: &[T]) -> &'tcx List<T> {
6078
assert!(!mem::needs_drop::<T>());
@@ -73,7 +91,7 @@ impl<T: Copy> List<T> {
7391
.cast::<T>()
7492
.copy_from_nonoverlapping(slice.as_ptr(), slice.len());
7593

76-
&mut *mem
94+
&*mem
7795
}
7896
}
7997

@@ -107,11 +125,24 @@ impl<S: Encoder, T: Encodable<S>> Encodable<S> for &List<T> {
107125
}
108126
}
109127

128+
impl<T: PartialEq> PartialEq for List<T> {
129+
#[inline]
130+
fn eq(&self, other: &List<T>) -> bool {
131+
// Pointer equality implies list equality (due to the unique contents
132+
// assumption).
133+
ptr::eq(self, other)
134+
}
135+
}
136+
137+
impl<T: Eq> Eq for List<T> {}
138+
110139
impl<T> Ord for List<T>
111140
where
112141
T: Ord,
113142
{
114143
fn cmp(&self, other: &List<T>) -> Ordering {
144+
// Pointer equality implies list equality (due to the unique contents
145+
// assumption), but the contents must be compared otherwise.
115146
if self == other { Ordering::Equal } else { <[T] as Ord>::cmp(&**self, &**other) }
116147
}
117148
}
@@ -121,6 +152,8 @@ where
121152
T: PartialOrd,
122153
{
123154
fn partial_cmp(&self, other: &List<T>) -> Option<Ordering> {
155+
// Pointer equality implies list equality (due to the unique contents
156+
// assumption), but the contents must be compared otherwise.
124157
if self == other {
125158
Some(Ordering::Equal)
126159
} else {
@@ -129,17 +162,11 @@ where
129162
}
130163
}
131164

132-
impl<T: PartialEq> PartialEq for List<T> {
133-
#[inline]
134-
fn eq(&self, other: &List<T>) -> bool {
135-
ptr::eq(self, other)
136-
}
137-
}
138-
impl<T: Eq> Eq for List<T> {}
139-
140165
impl<T> Hash for List<T> {
141166
#[inline]
142167
fn hash<H: Hasher>(&self, s: &mut H) {
168+
// Pointer hashing is sufficient (due to the unique contents
169+
// assumption).
143170
(self as *const List<T>).hash(s)
144171
}
145172
}
@@ -168,13 +195,24 @@ impl<'a, T: Copy> IntoIterator for &'a List<T> {
168195
}
169196
}
170197

171-
impl<T> List<T> {
172-
#[inline(always)]
173-
pub fn empty<'a>() -> &'a List<T> {
174-
#[repr(align(64), C)]
175-
struct EmptySlice([u8; 64]);
176-
static EMPTY_SLICE: EmptySlice = EmptySlice([0; 64]);
177-
assert!(mem::align_of::<T>() <= 64);
178-
unsafe { &*(&EMPTY_SLICE as *const _ as *const List<T>) }
198+
unsafe impl<T: Sync> Sync for List<T> {}
199+
200+
unsafe impl<'a, T: 'a> rustc_data_structures::tagged_ptr::Pointer for &'a List<T> {
201+
const BITS: usize = std::mem::align_of::<usize>().trailing_zeros() as usize;
202+
203+
#[inline]
204+
fn into_usize(self) -> usize {
205+
self as *const List<T> as usize
206+
}
207+
208+
#[inline]
209+
unsafe fn from_usize(ptr: usize) -> &'a List<T> {
210+
&*(ptr as *const List<T>)
211+
}
212+
213+
unsafe fn with_ref<R, F: FnOnce(&Self) -> R>(ptr: usize, f: F) -> R {
214+
// `Self` is `&'a List<T>` which impls `Copy`, so this is fine.
215+
let ptr = Self::from_usize(ptr);
216+
f(&ptr)
179217
}
180218
}

0 commit comments

Comments
 (0)