Remove TinyList.

It is optimized for lists with a single element, avoiding the need for
an allocation in that case. But `SmallVec<[T; 1]>` also avoids the
allocation, and is better in general: more standard, log2 number of
allocations if the list exceeds one item, and a much more capable API.

This commit removes `TinyList` and converts the two uses to
`SmallVec<[T; 1]>`. It also reorders the `use` items in the relevant
file so they are in just two sections (`pub` and non-`pub`), ordered
alphabetically, instead of many sections. (This is a relevant part of
the change because I had to decide where to add a `use` item for
`SmallVec`.)
This commit is contained in:
Nicholas Nethercote 2024-05-07 08:05:02 +10:00
parent d7814e72eb
commit f5d7d346a4
4 changed files with 8 additions and 245 deletions

View File

@ -82,7 +82,6 @@
pub mod sync;
pub mod tagged_ptr;
pub mod temp_dir;
pub mod tiny_list;
pub mod transitive_relation;
pub mod unhash;
pub mod unord;

View File

@ -1,80 +0,0 @@
//! A singly-linked list.
//!
//! Using this data structure only makes sense under very specific
//! circumstances:
//!
//! - If you have a list that rarely stores more than one element, then this
//! data-structure can store the element without allocating and only uses as
//! much space as an `Option<(T, usize)>`. If T can double as the `Option`
//! discriminant, it will even only be as large as `T, usize`.
//!
//! If you expect to store more than 1 element in the common case, steer clear
//! and use a `Vec<T>`, `Box<[T]>`, or a `SmallVec<T>`.
#[cfg(test)]
mod tests;
#[derive(Clone)]
pub struct TinyList<T> {
head: Option<Element<T>>,
}
impl<T: PartialEq> TinyList<T> {
#[inline]
pub fn new() -> TinyList<T> {
TinyList { head: None }
}
#[inline]
pub fn new_single(data: T) -> TinyList<T> {
TinyList { head: Some(Element { data, next: None }) }
}
#[inline]
pub fn insert(&mut self, data: T) {
self.head = Some(Element { data, next: self.head.take().map(Box::new) });
}
#[inline]
pub fn remove(&mut self, data: &T) -> bool {
self.head = match &mut self.head {
Some(head) if head.data == *data => head.next.take().map(|x| *x),
Some(head) => return head.remove_next(data),
None => return false,
};
true
}
#[inline]
pub fn contains(&self, data: &T) -> bool {
let mut elem = self.head.as_ref();
while let Some(e) = elem {
if &e.data == data {
return true;
}
elem = e.next.as_deref();
}
false
}
}
#[derive(Clone)]
struct Element<T> {
data: T,
next: Option<Box<Element<T>>>,
}
impl<T: PartialEq> Element<T> {
fn remove_next(mut self: &mut Self, data: &T) -> bool {
loop {
match self.next {
Some(ref mut next) if next.data == *data => {
self.next = next.next.take();
return true;
}
Some(ref mut next) => self = next,
None => return false,
}
}
}
}

View File

@ -1,155 +0,0 @@
use super::*;
extern crate test;
use test::{black_box, Bencher};
impl<T> TinyList<T> {
fn len(&self) -> usize {
let (mut elem, mut count) = (self.head.as_ref(), 0);
while let Some(e) = elem {
count += 1;
elem = e.next.as_deref();
}
count
}
}
#[test]
fn test_contains_and_insert() {
fn do_insert(i: u32) -> bool {
i % 2 == 0
}
let mut list = TinyList::new();
for i in 0..10 {
for j in 0..i {
if do_insert(j) {
assert!(list.contains(&j));
} else {
assert!(!list.contains(&j));
}
}
assert!(!list.contains(&i));
if do_insert(i) {
list.insert(i);
assert!(list.contains(&i));
}
}
}
#[test]
fn test_remove_first() {
let mut list = TinyList::new();
list.insert(1);
list.insert(2);
list.insert(3);
list.insert(4);
assert_eq!(list.len(), 4);
assert!(list.remove(&4));
assert!(!list.contains(&4));
assert_eq!(list.len(), 3);
assert!(list.contains(&1));
assert!(list.contains(&2));
assert!(list.contains(&3));
}
#[test]
fn test_remove_last() {
let mut list = TinyList::new();
list.insert(1);
list.insert(2);
list.insert(3);
list.insert(4);
assert_eq!(list.len(), 4);
assert!(list.remove(&1));
assert!(!list.contains(&1));
assert_eq!(list.len(), 3);
assert!(list.contains(&2));
assert!(list.contains(&3));
assert!(list.contains(&4));
}
#[test]
fn test_remove_middle() {
let mut list = TinyList::new();
list.insert(1);
list.insert(2);
list.insert(3);
list.insert(4);
assert_eq!(list.len(), 4);
assert!(list.remove(&2));
assert!(!list.contains(&2));
assert_eq!(list.len(), 3);
assert!(list.contains(&1));
assert!(list.contains(&3));
assert!(list.contains(&4));
}
#[test]
fn test_remove_single() {
let mut list = TinyList::new();
list.insert(1);
assert_eq!(list.len(), 1);
assert!(list.remove(&1));
assert!(!list.contains(&1));
assert_eq!(list.len(), 0);
}
#[bench]
fn bench_insert_empty(b: &mut Bencher) {
b.iter(|| {
let mut list = black_box(TinyList::new());
list.insert(1);
list
})
}
#[bench]
fn bench_insert_one(b: &mut Bencher) {
b.iter(|| {
let mut list = black_box(TinyList::new_single(0));
list.insert(1);
list
})
}
#[bench]
fn bench_contains_empty(b: &mut Bencher) {
b.iter(|| black_box(TinyList::new()).contains(&1));
}
#[bench]
fn bench_contains_unknown(b: &mut Bencher) {
b.iter(|| black_box(TinyList::new_single(0)).contains(&1));
}
#[bench]
fn bench_contains_one(b: &mut Bencher) {
b.iter(|| black_box(TinyList::new_single(1)).contains(&1));
}
#[bench]
fn bench_remove_empty(b: &mut Bencher) {
b.iter(|| black_box(TinyList::new()).remove(&1));
}
#[bench]
fn bench_remove_unknown(b: &mut Bencher) {
b.iter(|| black_box(TinyList::new_single(0)).remove(&1));
}
#[bench]
fn bench_remove_one(b: &mut Bencher) {
b.iter(|| black_box(TinyList::new_single(1)).remove(&1));
}

View File

@ -125,10 +125,11 @@ macro_rules! throw_ub_custom {
use std::num::NonZero;
use std::sync::atomic::{AtomicU32, Ordering};
use smallvec::{smallvec, SmallVec};
use rustc_ast::LitKind;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::sync::{HashMapExt, Lock};
use rustc_data_structures::tiny_list::TinyList;
use rustc_errors::ErrorGuaranteed;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_macros::{HashStable, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable};
@ -266,8 +267,8 @@ pub fn specialized_encode_alloc_id<'tcx, E: TyEncoder<I = TyCtxt<'tcx>>>(
#[derive(Clone)]
enum State {
Empty,
InProgressNonAlloc(TinyList<DecodingSessionId>),
InProgress(TinyList<DecodingSessionId>, AllocId),
InProgressNonAlloc(SmallVec<[DecodingSessionId; 1]>),
InProgress(SmallVec<[DecodingSessionId; 1]>, AllocId),
Done(AllocId),
}
@ -337,8 +338,7 @@ pub fn decode_alloc_id<'tcx, D>(&self, decoder: &mut D) -> AllocId
// If this is an allocation, we need to reserve an
// `AllocId` so we can decode cyclic graphs.
let alloc_id = decoder.interner().reserve_alloc_id();
*entry =
State::InProgress(TinyList::new_single(self.session_id), alloc_id);
*entry = State::InProgress(smallvec![self.session_id], alloc_id);
Some(alloc_id)
}
AllocDiscriminant::Fn
@ -346,8 +346,7 @@ pub fn decode_alloc_id<'tcx, D>(&self, decoder: &mut D) -> AllocId
| AllocDiscriminant::VTable => {
// Fns and statics cannot be cyclic, and their `AllocId`
// is determined later by interning.
*entry =
State::InProgressNonAlloc(TinyList::new_single(self.session_id));
*entry = State::InProgressNonAlloc(smallvec![self.session_id]);
None
}
}
@ -357,7 +356,7 @@ pub fn decode_alloc_id<'tcx, D>(&self, decoder: &mut D) -> AllocId
bug!("this should be unreachable");
} else {
// Start decoding concurrently.
sessions.insert(self.session_id);
sessions.push(self.session_id);
None
}
}
@ -367,7 +366,7 @@ pub fn decode_alloc_id<'tcx, D>(&self, decoder: &mut D) -> AllocId
return alloc_id;
} else {
// Start decoding concurrently.
sessions.insert(self.session_id);
sessions.push(self.session_id);
Some(alloc_id)
}
}