summaryrefslogtreecommitdiffstats
path: root/third_party/rust/thunderdome/src
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 14:29:10 +0000
commit2aa4a82499d4becd2284cdb482213d541b8804dd (patch)
treeb80bf8bf13c3766139fbacc530efd0dd9d54394c /third_party/rust/thunderdome/src
parentInitial commit. (diff)
downloadfirefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz
firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/thunderdome/src')
-rw-r--r--third_party/rust/thunderdome/src/arena.rs478
-rw-r--r--third_party/rust/thunderdome/src/drain.rs96
-rw-r--r--third_party/rust/thunderdome/src/free_pointer.rs46
-rw-r--r--third_party/rust/thunderdome/src/generation.rs61
-rw-r--r--third_party/rust/thunderdome/src/into_iter.rs79
-rw-r--r--third_party/rust/thunderdome/src/iter.rs82
-rw-r--r--third_party/rust/thunderdome/src/iter_mut.rs82
-rw-r--r--third_party/rust/thunderdome/src/lib.rs90
8 files changed, 1014 insertions, 0 deletions
diff --git a/third_party/rust/thunderdome/src/arena.rs b/third_party/rust/thunderdome/src/arena.rs
new file mode 100644
index 0000000000..7125266071
--- /dev/null
+++ b/third_party/rust/thunderdome/src/arena.rs
@@ -0,0 +1,478 @@
+use std::convert::TryInto;
+use std::mem::replace;
+use std::ops;
+
+use crate::drain::Drain;
+use crate::free_pointer::FreePointer;
+use crate::generation::Generation;
+use crate::into_iter::IntoIter;
+use crate::iter::Iter;
+use crate::iter_mut::IterMut;
+
+/// Container that can have elements inserted into it and removed from it.
+///
+/// Indices use the [`Index`][Index] type, created by inserting values with
+/// [`Arena::insert`][Arena::insert].
+#[derive(Debug, Clone)]
+pub struct Arena<T> {
+ storage: Vec<Entry<T>>,
+ len: u32,
+ first_free: Option<FreePointer>,
+}
+
+/// Index type for [`Arena`][Arena] that has a generation attached to it.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct Index {
+ pub(crate) slot: u32,
+ pub(crate) generation: Generation,
+}
+
+impl Index {
+ /// Convert this `Index` to an equivalent `u64` representation. Mostly
+ /// useful for passing to code outside of Rust.
+ #[allow(clippy::integer_arithmetic)]
+ pub fn to_bits(self) -> u64 {
+ // This is safe because a `u32` bit-shifted by 32 will still fit in a `u64`.
+ ((self.generation.to_u32() as u64) << 32) | (self.slot as u64)
+ }
+
+ /// Convert back from a value generated with `Index::to_bits`. Don't call
+ /// this with arbitrary inputs; you'll almost certainly just get invalid
+ /// and/or malformed indices.
+ ///
+ /// If fed an index which was not generated by thunderdome or even just run
+ /// `Index::from_bits(0)`, this function may panic!
+ #[allow(clippy::integer_arithmetic)]
+ pub fn from_bits(bits: u64) -> Self {
+ // By bit-shifting right by 32, we're undoing the left-shift in `to_bits`
+ // thus this is okay by the same rationale.
+ let generation = Generation::from_u32((bits >> 32) as u32);
+ let slot = bits as u32;
+
+ Self { generation, slot }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub(crate) enum Entry<T> {
+ Occupied(OccupiedEntry<T>),
+ Empty(EmptyEntry),
+}
+
+impl<T> Entry<T> {
+ /// Consume the entry, and if it's occupied, return the value.
+ fn into_value(self) -> Option<T> {
+ match self {
+ Entry::Occupied(occupied) => Some(occupied.value),
+ Entry::Empty(_) => None,
+ }
+ }
+
+ /// If the entry is empty, return a copy of the emptiness data.
+ fn get_empty(&self) -> Option<EmptyEntry> {
+ match self {
+ Entry::Empty(empty) => Some(*empty),
+ Entry::Occupied(_) => None,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+pub(crate) struct OccupiedEntry<T> {
+ pub(crate) generation: Generation,
+ pub(crate) value: T,
+}
+
+#[derive(Debug, Clone, Copy)]
+pub(crate) struct EmptyEntry {
+ pub(crate) generation: Generation,
+ pub(crate) next_free: Option<FreePointer>,
+}
+
+impl<T> Arena<T> {
+ /// Construct an empty arena.
+ pub fn new() -> Self {
+ Self {
+ storage: Vec::new(),
+ len: 0,
+ first_free: None,
+ }
+ }
+
+ /// Construct an empty arena with space to hold exactly `capacity` elements
+ /// without reallocating.
+ pub fn with_capacity(capacity: usize) -> Self {
+ Self {
+ storage: Vec::with_capacity(capacity),
+ len: 0,
+ first_free: None,
+ }
+ }
+
+ /// Return the number of elements contained in the arena.
+ pub fn len(&self) -> usize {
+ self.len as usize
+ }
+
+ /// Return the number of elements the arena can hold without allocating,
+ /// including the elements currently in the arena.
+ pub fn capacity(&self) -> usize {
+ self.storage.capacity()
+ }
+
+ /// Returns whether the arena is empty.
+ pub fn is_empty(&self) -> bool {
+ self.len == 0
+ }
+
+ /// Insert a new value into the arena, returning an index that can be used
+ /// to later retrieve the value.
+ pub fn insert(&mut self, value: T) -> Index {
+ // This value will definitely be inserted, so we can update length now.
+ self.len = self
+ .len
+ .checked_add(1)
+ .unwrap_or_else(|| panic!("Cannot insert more than u32::MAX elements into Arena"));
+
+ // If there was a previously free entry, we can re-use its slot as long
+ // as we increment its generation.
+ if let Some(free_pointer) = self.first_free {
+ let slot = free_pointer.slot();
+ let entry = self.storage.get_mut(slot as usize).unwrap_or_else(|| {
+ unreachable!("first_free pointed past the end of the arena's storage")
+ });
+
+ let empty = entry
+ .get_empty()
+ .unwrap_or_else(|| unreachable!("first_free pointed to an occupied entry"));
+
+ // If there is another empty entry after this one, we'll update the
+ // arena to point to it to use it on the next insertion.
+ self.first_free = empty.next_free;
+
+ // Overwrite the entry directly using our mutable reference instead
+ // of indexing into our storage again. This should avoid an
+ // additional bounds check.
+ let generation = empty.generation.next();
+ *entry = Entry::Occupied(OccupiedEntry { generation, value });
+
+ Index { slot, generation }
+ } else {
+ // There were no more empty entries left in our free list, so we'll
+ // create a new first-generation entry and push it into storage.
+
+ let generation = Generation::first();
+ let slot: u32 = self.storage.len().try_into().unwrap_or_else(|_| {
+ unreachable!("Arena storage exceeded what can be represented by a u32")
+ });
+
+ self.storage
+ .push(Entry::Occupied(OccupiedEntry { generation, value }));
+
+ Index { slot, generation }
+ }
+ }
+
+ /// Get an immutable reference to a value inside the arena by
+ /// [`Index`][Index], returning `None` if the index is not contained in the
+ /// arena.
+ pub fn get(&self, index: Index) -> Option<&T> {
+ match self.storage.get(index.slot as usize) {
+ Some(Entry::Occupied(occupied)) if occupied.generation == index.generation => {
+ Some(&occupied.value)
+ }
+ _ => None,
+ }
+ }
+
+ /// Get a mutable reference to a value inside the arena by [`Index`][Index],
+ /// returning `None` if the index is not contained in the arena.
+ pub fn get_mut(&mut self, index: Index) -> Option<&mut T> {
+ match self.storage.get_mut(index.slot as usize) {
+ Some(Entry::Occupied(occupied)) if occupied.generation == index.generation => {
+ Some(&mut occupied.value)
+ }
+ _ => None,
+ }
+ }
+
+ /// Remove the value contained at the given index from the arena, returning
+ /// it if it was present.
+ pub fn remove(&mut self, index: Index) -> Option<T> {
+ let entry = self.storage.get_mut(index.slot as usize)?;
+
+ match entry {
+ Entry::Occupied(occupied) if occupied.generation == index.generation => {
+ // We can replace an occupied entry with an empty entry with the
+ // same generation. On next insertion, this generation will
+ // increment.
+ let new_entry = Entry::Empty(EmptyEntry {
+ generation: occupied.generation,
+ next_free: self.first_free,
+ });
+
+ // Swap our new entry into our storage and take ownership of the
+ // old entry. We'll consume it for its value so we can give that
+ // back to our caller.
+ let old_entry = replace(entry, new_entry);
+ let value = old_entry.into_value().unwrap_or_else(|| unreachable!());
+
+ // The next time we insert, we can re-use the empty entry we
+ // just created. If another removal happens before then, that
+ // entry will be used before this one (FILO).
+ self.first_free = Some(FreePointer::from_slot(index.slot));
+
+ self.len = self.len.checked_sub(1).unwrap_or_else(|| unreachable!());
+
+ Some(value)
+ }
+ _ => None,
+ }
+ }
+
+ /// Invalidate the given index and return a new index to the same value. This
+ /// is roughly equivalent to `remove` followed by `insert`, but much faster.
+ /// If the old index is already invalid, this method returns `None`.
+ pub fn invalidate(&mut self, index: Index) -> Option<Index> {
+ let entry = self.storage.get_mut(index.slot as usize)?;
+
+ match entry {
+ Entry::Occupied(occupied) if occupied.generation == index.generation => {
+ occupied.generation = occupied.generation.next();
+
+ Some(Index {
+ generation: occupied.generation,
+ ..index
+ })
+ }
+ _ => None,
+ }
+ }
+
+ /// Clear the arena and drop all elements.
+ pub fn clear(&mut self) {
+ self.drain().for_each(drop);
+ }
+
+ /// Iterate over all of the indexes and values contained in the arena.
+ ///
+ /// Iteration order is not defined.
+ pub fn iter(&self) -> Iter<'_, T> {
+ Iter {
+ inner: self.storage.iter().enumerate(),
+ len: self.len,
+ }
+ }
+
+ /// Iterate over all of the indexes and values contained in the arena, with
+ /// mutable access to each value.
+ ///
+ /// Iteration order is not defined.
+ pub fn iter_mut(&mut self) -> IterMut<'_, T> {
+ IterMut {
+ inner: self.storage.iter_mut().enumerate(),
+ len: self.len,
+ }
+ }
+
+ /// Returns an iterator that removes each element from the arena.
+ ///
+ /// Iteration order is not defined.
+ ///
+ /// If the iterator is dropped before it is fully consumed, any uniterated
+ /// items will be dropped from the arena, and the arena will be empty.
+ /// The arena's capacity will not be changed.
+ pub fn drain(&mut self) -> Drain<'_, T> {
+ Drain {
+ arena: self,
+ slot: 0,
+ }
+ }
+}
+
+/// Methods exposed only within the crate.
+impl<T> Arena<T> {
+ /// This method is a lot like `remove`, but takes no generation. It's used
+ /// as part of `drain` and can likely be exposed as a public API eventually.
+ pub(crate) fn remove_entry_by_slot(&mut self, slot: u32) -> Option<(Index, T)> {
+ let entry = self.storage.get_mut(slot as usize)?;
+
+ match entry {
+ Entry::Occupied(occupied) => {
+ // Construct the index that would be used to access this entry.
+ let index = Index {
+ generation: occupied.generation,
+ slot,
+ };
+
+ // This occupied entry will be replaced with an empty one of the
+ // same generation. Generation will be incremented on the next
+ // insert.
+ let next_entry = Entry::Empty(EmptyEntry {
+ generation: occupied.generation,
+ next_free: self.first_free,
+ });
+
+ // Swap new entry into place and consume the old one.
+ let old_entry = replace(entry, next_entry);
+ let value = old_entry.into_value().unwrap_or_else(|| unreachable!());
+
+ // Set this entry as the next one that should be inserted into,
+ // should an insertion happen.
+ self.first_free = Some(FreePointer::from_slot(slot));
+
+ self.len = self.len.checked_sub(1).unwrap_or_else(|| unreachable!());
+
+ Some((index, value))
+ }
+ _ => None,
+ }
+ }
+}
+
+impl<T> Default for Arena<T> {
+ fn default() -> Self {
+ Arena::new()
+ }
+}
+
+impl<T> IntoIterator for Arena<T> {
+ type Item = (Index, T);
+ type IntoIter = IntoIter<T>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ IntoIter {
+ arena: self,
+ slot: 0,
+ }
+ }
+}
+
+impl<T> ops::Index<Index> for Arena<T> {
+ type Output = T;
+
+ fn index(&self, index: Index) -> &Self::Output {
+ self.get(index)
+ .unwrap_or_else(|| panic!("No entry at index {:?}", index))
+ }
+}
+
+impl<T> ops::IndexMut<Index> for Arena<T> {
+ fn index_mut(&mut self, index: Index) -> &mut Self::Output {
+ self.get_mut(index)
+ .unwrap_or_else(|| panic!("No entry at index {:?}", index))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::{Arena, Index};
+
+ use std::mem::size_of;
+
+ #[test]
+ fn size_of_index() {
+ assert_eq!(size_of::<Index>(), 8);
+ assert_eq!(size_of::<Option<Index>>(), 8);
+ }
+
+ #[test]
+ fn new() {
+ let arena: Arena<u32> = Arena::new();
+ assert_eq!(arena.len(), 0);
+ assert_eq!(arena.capacity(), 0);
+ }
+
+ #[test]
+ fn with_capacity() {
+ let arena: Arena<u32> = Arena::with_capacity(8);
+ assert_eq!(arena.len(), 0);
+ assert_eq!(arena.capacity(), 8);
+ }
+
+ #[test]
+ fn insert_and_get() {
+ let mut arena = Arena::new();
+
+ let one = arena.insert(1);
+ assert_eq!(arena.len(), 1);
+ assert_eq!(arena.get(one), Some(&1));
+
+ let two = arena.insert(2);
+ assert_eq!(arena.len(), 2);
+ assert_eq!(arena.get(one), Some(&1));
+ assert_eq!(arena.get(two), Some(&2));
+ }
+
+ #[test]
+ fn insert_remove_get() {
+ let mut arena = Arena::new();
+ let one = arena.insert(1);
+
+ let two = arena.insert(2);
+ assert_eq!(arena.len(), 2);
+ assert_eq!(arena.remove(two), Some(2));
+
+ let three = arena.insert(3);
+ assert_eq!(arena.len(), 2);
+ assert_eq!(arena.get(one), Some(&1));
+ assert_eq!(arena.get(three), Some(&3));
+ assert_eq!(arena.get(two), None);
+ }
+
+ #[test]
+ fn get_mut() {
+ let mut arena = Arena::new();
+ let foo = arena.insert(5);
+
+ let handle = arena.get_mut(foo).unwrap();
+ *handle = 6;
+
+ assert_eq!(arena.get(foo), Some(&6));
+ }
+
+ #[test]
+ fn insert_remove_insert_capacity() {
+ let mut arena = Arena::with_capacity(2);
+ assert_eq!(arena.capacity(), 2);
+
+ let a = arena.insert("a");
+ let b = arena.insert("b");
+ assert_eq!(arena.len(), 2);
+ assert_eq!(arena.capacity(), 2);
+
+ arena.remove(a);
+ arena.remove(b);
+ assert_eq!(arena.len(), 0);
+ assert_eq!(arena.capacity(), 2);
+
+ let _a2 = arena.insert("a2");
+ let _b2 = arena.insert("b2");
+ assert_eq!(arena.len(), 2);
+ assert_eq!(arena.capacity(), 2);
+ }
+
+ #[test]
+ fn invalidate() {
+ let mut arena = Arena::new();
+
+ let a = arena.insert("a");
+ assert_eq!(arena.get(a), Some(&"a"));
+
+ let new_a = arena.invalidate(a).unwrap();
+ assert_eq!(arena.get(a), None);
+ assert_eq!(arena.get(new_a), Some(&"a"));
+ }
+
+ #[test]
+ fn index_bits_roundtrip() {
+ let index = Index::from_bits(0x1BADCAFE_DEADBEEF);
+ assert_eq!(index.to_bits(), 0x1BADCAFE_DEADBEEF);
+ }
+
+ #[test]
+ #[should_panic]
+ fn index_bits_panic_on_zero_generation() {
+ Index::from_bits(0x00000000_DEADBEEF);
+ }
+}
diff --git a/third_party/rust/thunderdome/src/drain.rs b/third_party/rust/thunderdome/src/drain.rs
new file mode 100644
index 0000000000..930c0962fa
--- /dev/null
+++ b/third_party/rust/thunderdome/src/drain.rs
@@ -0,0 +1,96 @@
+use std::iter::{ExactSizeIterator, FusedIterator};
+
+use crate::arena::{Arena, Index};
+
+/// See [`Arena::drain`][Arena::drain].
+pub struct Drain<'a, T> {
+ pub(crate) arena: &'a mut Arena<T>,
+ pub(crate) slot: u32,
+}
+
+impl<'a, T> Iterator for Drain<'a, T> {
+ type Item = (Index, T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ // If there are no entries remaining in the arena, we should always
+ // return None. Using this check instead of comparing with the
+ // arena's size allows us to skip any trailing empty entries.
+ if self.arena.is_empty() {
+ return None;
+ }
+
+ // slot may overflow if the arena's underlying storage contains more
+ // than 2^32 elements, but its internal length value was not
+ // changed, as it overflowing would panic before reaching this code.
+ let slot = self.slot;
+ self.slot = self
+ .slot
+ .checked_add(1)
+ .unwrap_or_else(|| panic!("Overflowed u32 trying to drain Arena"));
+
+ // If this entry is occupied, this method will mark it as an empty.
+ // Otherwise, we'll continue looping until we've drained all
+ // occupied entries from the arena.
+ if let Some((index, value)) = self.arena.remove_entry_by_slot(slot) {
+ return Some((index, value));
+ }
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.arena.len(), Some(self.arena.len()))
+ }
+}
+
+impl<'a, T> FusedIterator for Drain<'a, T> {}
+impl<'a, T> ExactSizeIterator for Drain<'a, T> {}
+
+impl<'a, T> Drop for Drain<'a, T> {
+ // Continue iterating/dropping if there are any elements left.
+ fn drop(&mut self) {
+ self.for_each(drop);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::Arena;
+
+ use std::collections::HashSet;
+
+ #[test]
+ fn drain() {
+ let mut arena = Arena::with_capacity(2);
+ let one = arena.insert(1);
+ let two = arena.insert(2);
+
+ let mut drained_pairs = HashSet::new();
+ {
+ let mut drain = arena.drain();
+ assert_eq!(drain.size_hint(), (2, Some(2)));
+
+ drained_pairs.insert(drain.next().unwrap());
+ assert_eq!(drain.size_hint(), (1, Some(1)));
+
+ // Do not fully drain so we can ensure everything is dropped when the
+ // `Drain` is dropped.
+ assert_eq!(drain.size_hint(), (1, Some(1)));
+ }
+
+ assert_eq!(arena.len(), 0);
+ assert_eq!(arena.capacity(), 2);
+ assert_eq!(drained_pairs.len(), 1);
+
+ // We should still be able to use the arena after this.
+ let one_prime = arena.insert(1);
+ let two_prime = arena.insert(2);
+
+ assert_eq!(arena.len(), 2);
+ assert_eq!(arena.capacity(), 2);
+ assert_eq!(arena.get(one_prime), Some(&1));
+ assert_eq!(arena.get(two_prime), Some(&2));
+ assert_eq!(arena.get(one), None);
+ assert_eq!(arena.get(two), None);
+ }
+}
diff --git a/third_party/rust/thunderdome/src/free_pointer.rs b/third_party/rust/thunderdome/src/free_pointer.rs
new file mode 100644
index 0000000000..e0d5f4ae8a
--- /dev/null
+++ b/third_party/rust/thunderdome/src/free_pointer.rs
@@ -0,0 +1,46 @@
+use std::num::NonZeroU32;
+
+/// Contains a reference to a free slot in an arena, encapsulating NonZeroU32
+/// to prevent off-by-one errors and leaking unsafety.
+///
+/// Uses NonZeroU32 to stay small when put inside an `Option`.
+#[derive(Debug, Clone, Copy)]
+#[repr(transparent)]
+pub(crate) struct FreePointer(NonZeroU32);
+
+impl FreePointer {
+ #[must_use]
+ pub(crate) fn from_slot(slot: u32) -> Self {
+ let value = slot
+ .checked_add(1)
+ .expect("u32 overflowed calculating free pointer from u32");
+
+ // This is safe because any u32 + 1 that didn't overflow must not be
+ // zero.
+ FreePointer(unsafe { NonZeroU32::new_unchecked(value) })
+ }
+
+ #[must_use]
+ #[allow(clippy::integer_arithmetic)]
+ pub(crate) fn slot(self) -> u32 {
+ // This will never underflow due to the field being guaranteed non-zero.
+ self.0.get() - 1
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::FreePointer;
+
+ #[test]
+ fn from_slot() {
+ let ptr = FreePointer::from_slot(0);
+ assert_eq!(ptr.slot(), 0);
+ }
+
+ #[test]
+ #[should_panic(expected = "u32 overflowed calculating free pointer from u32")]
+ fn panic_on_overflow() {
+ let _ = FreePointer::from_slot(std::u32::MAX);
+ }
+}
diff --git a/third_party/rust/thunderdome/src/generation.rs b/third_party/rust/thunderdome/src/generation.rs
new file mode 100644
index 0000000000..e6982c383c
--- /dev/null
+++ b/third_party/rust/thunderdome/src/generation.rs
@@ -0,0 +1,61 @@
+use std::num::NonZeroU32;
+
+/// Tracks the generation of an entry in an arena. Encapsulates NonZeroU32 to
+/// reduce the number of redundant checks needed, as well as enforcing checked
+/// arithmetic on advancing a generation.
+///
+/// Uses NonZeroU32 to help `Index` stay the same size when put inside an
+/// `Option`.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[repr(transparent)]
+pub(crate) struct Generation(NonZeroU32);
+
+impl Generation {
+ #[must_use]
+ pub(crate) fn first() -> Self {
+ // This is safe because 1 is not zero.
+ Generation(unsafe { NonZeroU32::new_unchecked(1) })
+ }
+
+ #[must_use]
+ pub(crate) fn next(self) -> Self {
+ let last_generation = self.0.get();
+ let next_generation = last_generation.checked_add(1).unwrap_or(1);
+
+ // This is safe because value that would overflow is instead made 1.
+ Generation(unsafe { NonZeroU32::new_unchecked(next_generation) })
+ }
+
+ pub(crate) fn to_u32(self) -> u32 {
+ self.0.get()
+ }
+
+ pub(crate) fn from_u32(gen: u32) -> Self {
+ Generation(NonZeroU32::new(gen).expect("generation IDs must be nonzero!"))
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::Generation;
+
+ use std::num::NonZeroU32;
+
+ #[test]
+ fn first_and_next() {
+ let first = Generation::first();
+ assert_eq!(first.0.get(), 1);
+
+ let second = first.next();
+ assert_eq!(second.0.get(), 2);
+ }
+
+ #[test]
+ fn wrap_on_overflow() {
+ let max = Generation(NonZeroU32::new(std::u32::MAX).unwrap());
+ assert_eq!(max.0.get(), std::u32::MAX);
+
+ let next = max.next();
+ assert_eq!(next.0.get(), 1);
+ }
+}
diff --git a/third_party/rust/thunderdome/src/into_iter.rs b/third_party/rust/thunderdome/src/into_iter.rs
new file mode 100644
index 0000000000..e7dd0d9403
--- /dev/null
+++ b/third_party/rust/thunderdome/src/into_iter.rs
@@ -0,0 +1,79 @@
+use std::iter::{ExactSizeIterator, FusedIterator};
+
+use crate::arena::{Arena, Index};
+
+/// Iterator typed used when an Arena is turned [`IntoIterator`][IntoIterator].
+pub struct IntoIter<T> {
+ pub(crate) arena: Arena<T>,
+ pub(crate) slot: u32,
+}
+
+impl<T> Iterator for IntoIter<T> {
+ type Item = (Index, T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ // If there are no entries remaining in the arena, we should always
+ // return None. Using this check instead of comparing with the
+ // arena's size allows us to skip any trailing empty entries.
+ if self.arena.is_empty() {
+ return None;
+ }
+
+ // slot may overflow if the arena's underlying storage contains more
+ // than 2^32 elements, but its internal length value was not
+ // changed, as it overflowing would panic before reaching this code.
+ let slot = self.slot;
+ self.slot = self
+ .slot
+ .checked_add(1)
+ .unwrap_or_else(|| panic!("Overflowed u32 trying to into_iter Arena"));
+
+ // If this entry is occupied, this method will mark it as an empty.
+ // Otherwise, we'll continue looping until we've removed all
+ // occupied entries from the arena.
+ if let Some((index, value)) = self.arena.remove_entry_by_slot(slot) {
+ return Some((index, value));
+ }
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.arena.len(), Some(self.arena.len()))
+ }
+}
+
+impl<T> FusedIterator for IntoIter<T> {}
+impl<T> ExactSizeIterator for IntoIter<T> {}
+
+#[cfg(test)]
+mod test {
+ use crate::Arena;
+
+ use std::collections::HashSet;
+
+ #[test]
+ fn into_iter() {
+ let mut arena = Arena::with_capacity(2);
+ let one = arena.insert(1);
+ let two = arena.insert(2);
+
+ let mut pairs = HashSet::new();
+ let mut into_iter = arena.into_iter();
+ assert_eq!(into_iter.size_hint(), (2, Some(2)));
+
+ pairs.insert(into_iter.next().unwrap());
+ assert_eq!(into_iter.size_hint(), (1, Some(1)));
+
+ pairs.insert(into_iter.next().unwrap());
+ assert_eq!(into_iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(into_iter.next(), None);
+ assert_eq!(into_iter.next(), None);
+ assert_eq!(into_iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(pairs.len(), 2);
+ assert!(pairs.contains(&(one, 1)));
+ assert!(pairs.contains(&(two, 2)));
+ }
+}
diff --git a/third_party/rust/thunderdome/src/iter.rs b/third_party/rust/thunderdome/src/iter.rs
new file mode 100644
index 0000000000..0a2404cae7
--- /dev/null
+++ b/third_party/rust/thunderdome/src/iter.rs
@@ -0,0 +1,82 @@
+use std::convert::TryInto;
+use std::iter::{Enumerate, ExactSizeIterator, FusedIterator};
+use std::slice;
+
+use crate::arena::{Entry, Index};
+
+/// See [`Arena::iter`][Arena::iter].
+pub struct Iter<'a, T> {
+ pub(crate) len: u32,
+ pub(crate) inner: Enumerate<slice::Iter<'a, Entry<T>>>,
+}
+
+impl<'a, T> Iterator for Iter<'a, T> {
+ type Item = (Index, &'a T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ if self.len == 0 {
+ return None;
+ }
+
+ match self.inner.next()? {
+ (_, Entry::Empty(_)) => continue,
+ (slot, Entry::Occupied(occupied)) => {
+ self.len = self
+ .len
+ .checked_sub(1)
+ .unwrap_or_else(|| unreachable!("Underflowed u32 trying to iterate Arena"));
+
+ let slot: u32 = slot
+ .try_into()
+ .unwrap_or_else(|_| unreachable!("Overflowed u32 trying to iterate Arena"));
+
+ let index = Index {
+ slot,
+ generation: occupied.generation,
+ };
+
+ return Some((index, &occupied.value));
+ }
+ }
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.len as usize, Some(self.len as usize))
+ }
+}
+
+impl<'a, T> FusedIterator for Iter<'a, T> {}
+impl<'a, T> ExactSizeIterator for Iter<'a, T> {}
+
+#[cfg(test)]
+mod test {
+ use crate::Arena;
+
+ use std::collections::HashSet;
+
+ #[test]
+ fn iter() {
+ let mut arena = Arena::with_capacity(2);
+ let one = arena.insert(1);
+ let two = arena.insert(2);
+
+ let mut pairs = HashSet::new();
+ let mut iter = arena.iter();
+ assert_eq!(iter.size_hint(), (2, Some(2)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (1, Some(1)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert!(pairs.contains(&(one, &1)));
+ assert!(pairs.contains(&(two, &2)));
+ }
+}
diff --git a/third_party/rust/thunderdome/src/iter_mut.rs b/third_party/rust/thunderdome/src/iter_mut.rs
new file mode 100644
index 0000000000..7ff71c12e3
--- /dev/null
+++ b/third_party/rust/thunderdome/src/iter_mut.rs
@@ -0,0 +1,82 @@
+use std::convert::TryInto;
+use std::iter::{Enumerate, ExactSizeIterator, FusedIterator};
+use std::slice;
+
+use crate::arena::{Entry, Index};
+
+/// See [`Arena::iter_mut`][Arena::iter_mut].
+pub struct IterMut<'a, T> {
+ pub(crate) len: u32,
+ pub(crate) inner: Enumerate<slice::IterMut<'a, Entry<T>>>,
+}
+
+impl<'a, T> Iterator for IterMut<'a, T> {
+ type Item = (Index, &'a T);
+
+ fn next(&mut self) -> Option<Self::Item> {
+ loop {
+ if self.len == 0 {
+ return None;
+ }
+
+ match self.inner.next()? {
+ (_, Entry::Empty(_)) => continue,
+ (slot, Entry::Occupied(occupied)) => {
+ self.len = self
+ .len
+ .checked_sub(1)
+ .unwrap_or_else(|| unreachable!("Underflowed u32 trying to iterate Arena"));
+
+ let slot: u32 = slot
+ .try_into()
+ .unwrap_or_else(|_| unreachable!("Overflowed u32 trying to iterate Arena"));
+
+ let index = Index {
+ slot,
+ generation: occupied.generation,
+ };
+
+ return Some((index, &mut occupied.value));
+ }
+ }
+ }
+ }
+
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ (self.len as usize, Some(self.len as usize))
+ }
+}
+
+impl<'a, T> FusedIterator for IterMut<'a, T> {}
+impl<'a, T> ExactSizeIterator for IterMut<'a, T> {}
+
+#[cfg(test)]
+mod test {
+ use crate::Arena;
+
+ use std::collections::HashSet;
+
+ #[test]
+ fn iter_mut() {
+ let mut arena = Arena::with_capacity(2);
+ let one = arena.insert(1);
+ let two = arena.insert(2);
+
+ let mut pairs = HashSet::new();
+ let mut iter = arena.iter_mut();
+ assert_eq!(iter.size_hint(), (2, Some(2)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (1, Some(1)));
+
+ pairs.insert(iter.next().unwrap());
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.next(), None);
+ assert_eq!(iter.size_hint(), (0, Some(0)));
+
+ assert!(pairs.contains(&(one, &1)));
+ assert!(pairs.contains(&(two, &2)));
+ }
+}
diff --git a/third_party/rust/thunderdome/src/lib.rs b/third_party/rust/thunderdome/src/lib.rs
new file mode 100644
index 0000000000..08bd29e07e
--- /dev/null
+++ b/third_party/rust/thunderdome/src/lib.rs
@@ -0,0 +1,90 @@
+/*!
+[![GitHub CI Status](https://github.com/LPGhatguy/thunderdome/workflows/CI/badge.svg)](https://github.com/LPGhatguy/thunderdome/actions)
+[![thunderdome on crates.io](https://img.shields.io/crates/v/thunderdome.svg)](https://crates.io/crates/thunderdome)
+[![thunderdome docs](https://img.shields.io/badge/docs-docs.rs-orange.svg)](https://docs.rs/thunderdome)
+
+Thunderdome is a ~gladitorial~ generational arena inspired by
+[generational-arena](https://crates.io/crates/generational-arena),
+[slotmap](https://crates.io/crates/slotmap), and
+[slab](https://crates.io/crates/slab). It provides constant time insertion,
+lookup, and removal via small (8 byte) keys returned from `Arena`.
+
+Thunderdome's key type, `Index`, is still 8 bytes when put inside of an
+`Option<T>` thanks to Rust's `NonZero*` types.
+
+## Basic Examples
+
+```rust
+# use thunderdome::{Arena, Index};
+let mut arena = Arena::new();
+
+let foo = arena.insert("Foo");
+let bar = arena.insert("Bar");
+
+assert_eq!(arena[foo], "Foo");
+assert_eq!(arena[bar], "Bar");
+
+arena[bar] = "Replaced";
+assert_eq!(arena[bar], "Replaced");
+
+let foo_value = arena.remove(foo);
+assert_eq!(foo_value, Some("Foo"));
+
+// The slot previously used by foo will be reused for baz
+let baz = arena.insert("Baz");
+assert_eq!(arena[baz], "Baz");
+
+// foo is no longer a valid key
+assert_eq!(arena.get(foo), None);
+```
+
+## Comparison With Similar Crates
+
+| Feature | Thunderdome | generational-arena | slotmap | slab |
+|------------------------------|-------------|--------------------|---------|------|
+| Generational Indices¹ | Yes | Yes | Yes | No |
+| `size_of::<Index>()` | 8 | 16 | 8 | 8 |
+| `size_of::<Option<Index>>()` | 8 | 24 | 8 | 16 |
+| Max Elements | 2³² | 2⁶⁴ | 2³² | 2⁶⁴ |
+| Non-`Copy` Values | Yes | Yes | Sorta² | Yes |
+| `no_std` Support | No | Yes | No | No |
+| Serde Support | No | Yes | Yes | No |
+
+* Sizes calculated on rustc `1.44.0-x86_64-pc-windows-msvc`
+* See [the Thunderdome comparison
+ Cargo.toml](https://github.com/LPGhatguy/thunderdome/blob/main/comparison/Cargo.toml)
+ for versions of each library tested.
+
+1. Generational indices help solve the [ABA
+ Problem](https://en.wikipedia.org/wiki/ABA_problem), which can cause dangling
+ keys to mistakenly access newly-inserted data.
+2. slotmap's `SlotMap` and `HopSlotMap` require values to be `Copy` on stable
+ Rust versions. slotmap's `DenseSlotMap` type supports non-`Copy` types on
+ stable, but has different performance trade-offs.
+
+## Minimum Supported Rust Version (MSRV)
+
+Thunderdome supports Rust 1.34.1 and newer. Until Thunderdome reaches 1.0,
+changes to the MSRV will require major version bumps. After 1.0, MSRV changes
+will only require minor version bumps, but will need significant justification.
+*/
+
+#![forbid(missing_docs)]
+// This crate is sensitive to integer overflow and wrapping behavior. As such,
+// we should usually use methods like `checked_add` and `checked_sub` instead
+// of the `Add` or `Sub` operators.
+#![deny(clippy::integer_arithmetic)]
+
+mod arena;
+mod drain;
+mod free_pointer;
+mod generation;
+mod into_iter;
+mod iter;
+mod iter_mut;
+
+pub use crate::arena::{Arena, Index};
+pub use crate::drain::Drain;
+pub use crate::into_iter::IntoIter;
+pub use crate::iter::Iter;
+pub use crate::iter_mut::IterMut;