Skip to content

Commit 9e90840

Browse files
committed
Simplify NodeHeader by avoiding slices in BTreeMaps with shared roots
1 parent f795e8a commit 9e90840

File tree

3 files changed

+19
-60
lines changed

3 files changed

+19
-60
lines changed

src/liballoc/collections/btree/map.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1968,7 +1968,7 @@ where
19681968
(i, false) => i,
19691969
},
19701970
(_, Unbounded) => 0,
1971-
(true, Included(_)) => min_node.keys().len(),
1971+
(true, Included(_)) => min_node.len(),
19721972
(true, Excluded(_)) => 0,
19731973
};
19741974

@@ -1987,9 +1987,9 @@ where
19871987
}
19881988
(i, false) => i,
19891989
},
1990-
(_, Unbounded) => max_node.keys().len(),
1990+
(_, Unbounded) => max_node.len(),
19911991
(true, Included(_)) => 0,
1992-
(true, Excluded(_)) => max_node.keys().len(),
1992+
(true, Excluded(_)) => max_node.len(),
19931993
};
19941994

19951995
if !diverged {

src/liballoc/collections/btree/node.rs

+6-49
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,8 @@ pub const CAPACITY: usize = 2 * B - 1;
5454
/// `NodeHeader` because we do not want unnecessary padding between `len` and the keys.
5555
/// Crucially, `NodeHeader` can be safely transmuted to different K and V. (This is exploited
5656
/// by `as_header`.)
57-
/// See `into_key_slice` for an explanation of K2. K2 cannot be safely transmuted around
58-
/// because the size of `NodeHeader` depends on its alignment!
5957
#[repr(C)]
60-
struct NodeHeader<K, V, K2 = ()> {
58+
struct NodeHeader<K, V> {
6159
/// We use `*const` as opposed to `*mut` so as to be covariant in `K` and `V`.
6260
/// This either points to an actual node or is null.
6361
parent: *const InternalNode<K, V>,
@@ -72,9 +70,6 @@ struct NodeHeader<K, V, K2 = ()> {
7270
/// This next to `parent_idx` to encourage the compiler to join `len` and
7371
/// `parent_idx` into the same 32-bit word, reducing space overhead.
7472
len: u16,
75-
76-
/// See `into_key_slice`.
77-
keys_start: [K2; 0],
7873
}
7974
#[repr(C)]
8075
struct LeafNode<K, V> {
@@ -128,7 +123,7 @@ unsafe impl Sync for NodeHeader<(), ()> {}
128123
// We use just a header in order to save space, since no operation on an empty tree will
129124
// ever take a pointer past the first key.
130125
static EMPTY_ROOT_NODE: NodeHeader<(), ()> =
131-
NodeHeader { parent: ptr::null(), parent_idx: MaybeUninit::uninit(), len: 0, keys_start: [] };
126+
NodeHeader { parent: ptr::null(), parent_idx: MaybeUninit::uninit(), len: 0 };
132127

133128
/// The underlying representation of internal nodes. As with `LeafNode`s, these should be hidden
134129
/// behind `BoxedNode`s to prevent dropping uninitialized keys and values. Any pointer to an
@@ -390,7 +385,7 @@ impl<BorrowType, K, V, Type> NodeRef<BorrowType, K, V, Type> {
390385
}
391386

392387
/// Borrows a view into the keys stored in the node.
393-
/// Works on all possible nodes, including the shared root.
388+
/// The caller must ensure that the node is not the shared root.
394389
pub fn keys(&self) -> &[K] {
395390
self.reborrow().into_key_slice()
396391
}
@@ -528,47 +523,9 @@ impl<'a, K, V, Type> NodeRef<marker::Mut<'a>, K, V, Type> {
528523

529524
impl<'a, K: 'a, V: 'a, Type> NodeRef<marker::Immut<'a>, K, V, Type> {
530525
fn into_key_slice(self) -> &'a [K] {
531-
// We have to be careful here because we might be pointing to the shared root.
532-
// In that case, we must not create an `&LeafNode`. We could just return
533-
// an empty slice whenever the length is 0 (this includes the shared root),
534-
// but we want to avoid that run-time check.
535-
// Instead, we create a slice pointing into the node whenever possible.
536-
// We can sometimes do this even for the shared root, as the slice will be
537-
// empty and `NodeHeader` contains an empty `keys_start` array.
538-
// We cannot *always* do this because:
539-
// - `keys_start` is not correctly typed because we want `NodeHeader`'s size to
540-
// not depend on the alignment of `K` (needed because `as_header` should be safe).
541-
// For this reason, `NodeHeader` has this `K2` parameter (that's usually `()`
542-
// and hence just adds a size-0-align-1 field, not affecting layout).
543-
// If the correctly typed header is more highly aligned than the allocated header,
544-
// we cannot transmute safely.
545-
// - Even if we can transmute, the offset of a correctly typed `keys_start` might
546-
// be different and outside the bounds of the allocated header!
547-
// So we do an alignment check and a size check first, that will be evaluated
548-
// at compile-time, and only do any run-time check in the rare case that
549-
// the compile-time checks signal danger.
550-
if (mem::align_of::<NodeHeader<K, V, K>>() > mem::align_of::<NodeHeader<K, V>>()
551-
|| mem::size_of::<NodeHeader<K, V, K>>() != mem::size_of::<NodeHeader<K, V>>())
552-
&& self.is_shared_root()
553-
{
554-
&[]
555-
} else {
556-
// If we are a `LeafNode<K, V>`, we can always transmute to
557-
// `NodeHeader<K, V, K>` and `keys_start` always has the same offset
558-
// as the actual `keys`.
559-
// Thanks to the checks above, we know that we can transmute to
560-
// `NodeHeader<K, V, K>` and that `keys_start` will be
561-
// in-bounds of some allocation even if this is the shared root!
562-
// (We might be one-past-the-end, but that is allowed by LLVM.)
563-
// Thus we can use `NodeHeader<K, V, K>`
564-
// to compute the pointer where the keys start.
565-
// This entire hack will become unnecessary once
566-
// <https://github.com/rust-lang/rfcs/pull/2582> lands, then we can just take a raw
567-
// pointer to the `keys` field of `*const InternalNode<K, V>`.
568-
let header = self.as_header() as *const _ as *const NodeHeader<K, V, K>;
569-
let keys = unsafe { &(*header).keys_start as *const _ as *const K };
570-
unsafe { slice::from_raw_parts(keys, self.len()) }
571-
}
526+
debug_assert!(!self.is_shared_root());
527+
// We cannot be the shared root, so `as_leaf` is okay.
528+
unsafe { slice::from_raw_parts(MaybeUninit::first_ptr(&self.as_leaf().keys), self.len()) }
572529
}
573530

574531
/// The caller must ensure that the node is not the shared root.

src/liballoc/collections/btree/search.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,18 @@ where
6161
{
6262
// This function is defined over all borrow types (immutable, mutable, owned),
6363
// and may be called on the shared root in each case.
64-
// Crucially, we use `keys()` here, i.e., we work with immutable data.
65-
// `keys_mut()` does not support the shared root, so we cannot use it.
6664
// Using `keys()` is fine here even if BorrowType is mutable, as all we return
6765
// is an index -- not a reference.
68-
for (i, k) in node.keys().iter().enumerate() {
69-
match key.cmp(k.borrow()) {
70-
Ordering::Greater => {}
71-
Ordering::Equal => return (i, true),
72-
Ordering::Less => return (i, false),
66+
let len = node.len();
67+
// Skip search for empty nodes because `keys()` does not work on shared roots.
68+
if len > 0 {
69+
for (i, k) in node.keys().iter().enumerate() {
70+
match key.cmp(k.borrow()) {
71+
Ordering::Greater => {}
72+
Ordering::Equal => return (i, true),
73+
Ordering::Less => return (i, false),
74+
}
7375
}
7476
}
75-
(node.keys().len(), false)
77+
(len, false)
7678
}

0 commit comments

Comments
 (0)