Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Robert Forsman 2022-05-28 18:38:23 -04:00
commit 3b9b9f7548
14 changed files with 748 additions and 173 deletions

View File

@ -53,6 +53,7 @@ jobs:
- nightly
features:
- ""
- "cas"
- "serde"
steps:
- name: Checkout
@ -92,7 +93,7 @@ jobs:
with:
use-cross: false
command: check
args: --target=${{ matrix.target }} --features=${{ matrix.features }}
args: --target=${{ matrix.target }} --no-default-features --features=${{ matrix.features }}
# Run cpass tests
testcpass:

View File

@ -7,17 +7,49 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Fixed
* Fixed `pool` example in docstring.
### Added
- Added support for AVR architecture.
- Added support for AVR architecture
### Changed
### Fixed
## [v0.7.13] - 2022-05-16
### Added
- Added `into_vec` to `BinaryHeap`
## [v0.7.12] - 2022-05-12
### Added
* Added support for AVR architecture.
* Add `entry` API to `IndexMap`
* Implement `IntoIterator` trait for `Indexmap`
* Implement `FromIterator` for `String`
* Add `first` and `last` methods to `IndexMap` and `IndexSet`
* Add `pop_{front_back}_unchecked` methods to `Deque`
### Changed
* Optimize the codegen of `Vec::clone`
* `riscv32i` and `riscv32imc` targets unconditionally (e.g. `build --no-default-features`) depends on `atomic-polyfill`
### Fixed
* Inserting an item that replaces an already present item will no longer
fail with an error
## [v0.7.11] - 2022-05-09
### Fixed
* Fixed `pool` example in docstring.
* Fixed undefined behavior in `Vec::truncate()`, `Vec::swap_remove_unchecked()`,
and `Hole::move_to()` (internal to the binary heap implementation).
* Fixed `BinaryHeap` elements are being dropped twice
## [v0.7.10] - 2022-01-21
### Fixed
@ -455,7 +487,10 @@ architecture.
- Initial release
[Unreleased]: https://github.com/japaric/heapless/compare/v0.7.10...HEAD
[Unreleased]: https://github.com/japaric/heapless/compare/v0.7.13...HEAD
[v0.7.13]: https://github.com/japaric/heapless/compare/v0.7.12...v0.7.13
[v0.7.12]: https://github.com/japaric/heapless/compare/v0.7.11...v0.7.12
[v0.7.11]: https://github.com/japaric/heapless/compare/v0.7.10...v0.7.11
[v0.7.10]: https://github.com/japaric/heapless/compare/v0.7.9...v0.7.10
[v0.7.9]: https://github.com/japaric/heapless/compare/v0.7.8...v0.7.9
[v0.7.8]: https://github.com/japaric/heapless/compare/v0.7.7...v0.7.8

View File

@ -12,7 +12,7 @@ keywords = ["static", "no-heap"]
license = "MIT OR Apache-2.0"
name = "heapless"
repository = "https://github.com/japaric/heapless"
version = "0.7.10"
version = "0.7.13"
[features]
default = ["cas"]
@ -27,17 +27,14 @@ mpmc_large = []
# This flag has no version guarantee, the `defmt` dependency can be updated in a patch release
defmt-impl = ["defmt"]
[target.'cfg(not(target_os = "none"))'.dev-dependencies]
scoped_threadpool = "0.1.8"
[target.thumbv6m-none-eabi.dependencies]
atomic-polyfill = { version = "0.1.2", optional = true }
[target.riscv32i-unknown-none-elf.dependencies]
atomic-polyfill = { version = "0.1.4", optional = true }
atomic-polyfill = { version = "0.1.4" }
[target.riscv32imc-unknown-none-elf.dependencies]
atomic-polyfill = { version = "0.1.4", optional = true }
atomic-polyfill = { version = "0.1.4" }
[target.avr-atmega328p.dependencies]
atomic-polyfill = { version = "0.1.8", optional = true }
@ -72,5 +69,8 @@ version = "0.1"
version = ">=0.2.0,<0.4"
optional = true
[build-dependencies]
rustc_version = "0.4.0"
[package.metadata.docs.rs]
all-features = true

View File

@ -2,6 +2,8 @@
use std::{env, error::Error};
use rustc_version::Channel;
fn main() -> Result<(), Box<dyn Error>> {
let target = env::var("TARGET")?;
@ -71,5 +73,12 @@ fn main() -> Result<(), Box<dyn Error>> {
_ => {}
}
if !matches!(
rustc_version::version_meta().unwrap().channel,
Channel::Stable | Channel::Beta
) {
println!("cargo:rustc-cfg=unstable_channel");
}
Ok(())
}

View File

@ -336,6 +336,11 @@ where
self.sift_up(0, old_len);
}
/// Returns the underlying ```Vec<T,N>```. Order is arbitrary and time is O(1).
pub fn into_vec(self) -> Vec<T, N> {
self.data
}
/* Private API */
fn sift_down_to_bottom(&mut self, mut pos: usize) {
let end = self.len();
@ -428,8 +433,9 @@ impl<'a, T> Hole<'a, T> {
unsafe fn move_to(&mut self, index: usize) {
debug_assert!(index != self.pos);
debug_assert!(index < self.data.len());
let index_ptr: *const _ = self.data.get_unchecked(index);
let hole_ptr = self.data.get_unchecked_mut(self.pos);
let ptr = self.data.as_mut_ptr();
let index_ptr: *const _ = ptr.add(index);
let hole_ptr = ptr.add(self.pos);
ptr::copy_nonoverlapping(index_ptr, hole_ptr, 1);
self.pos = index;
}
@ -536,12 +542,6 @@ where
}
}
impl<T, K, const N: usize> Drop for BinaryHeap<T, K, N> {
fn drop(&mut self) {
unsafe { ptr::drop_in_place(self.data.as_mut_slice()) }
}
}
impl<T, K, const N: usize> fmt::Debug for BinaryHeap<T, K, N>
where
K: Kind,
@ -576,6 +576,65 @@ mod tests {
static mut _B: BinaryHeap<i32, Min, 16> = BinaryHeap::new();
}
#[test]
fn drop() {
droppable!();
{
let mut v: BinaryHeap<Droppable, Max, 2> = BinaryHeap::new();
v.push(Droppable::new()).ok().unwrap();
v.push(Droppable::new()).ok().unwrap();
v.pop().unwrap();
}
assert_eq!(Droppable::count(), 0);
{
let mut v: BinaryHeap<Droppable, Max, 2> = BinaryHeap::new();
v.push(Droppable::new()).ok().unwrap();
v.push(Droppable::new()).ok().unwrap();
}
assert_eq!(Droppable::count(), 0);
{
let mut v: BinaryHeap<Droppable, Min, 2> = BinaryHeap::new();
v.push(Droppable::new()).ok().unwrap();
v.push(Droppable::new()).ok().unwrap();
v.pop().unwrap();
}
assert_eq!(Droppable::count(), 0);
{
let mut v: BinaryHeap<Droppable, Min, 2> = BinaryHeap::new();
v.push(Droppable::new()).ok().unwrap();
v.push(Droppable::new()).ok().unwrap();
}
assert_eq!(Droppable::count(), 0);
}
#[test]
fn into_vec() {
droppable!();
let mut h: BinaryHeap<Droppable, Max, 2> = BinaryHeap::new();
h.push(Droppable::new()).ok().unwrap();
h.push(Droppable::new()).ok().unwrap();
h.pop().unwrap();
assert_eq!(Droppable::count(), 1);
let v = h.into_vec();
assert_eq!(Droppable::count(), 1);
core::mem::drop(v);
assert_eq!(Droppable::count(), 0);
}
#[test]
fn min() {
let mut heap = BinaryHeap::<_, Min, 16>::new();

View File

@ -271,12 +271,13 @@ impl<T, const N: usize> Deque<T, N> {
}
}
/// Removes an item from the front of the deque and returns it
/// Removes an item from the front of the deque and returns it, without checking that the deque
/// is not empty
///
/// # Safety
///
/// This assumes the deque is not empty.
pub(crate) unsafe fn pop_front_unchecked(&mut self) -> T {
/// It's undefined behavior to call this on an empty deque
pub unsafe fn pop_front_unchecked(&mut self) -> T {
debug_assert!(!self.is_empty());
let index = self.front;
@ -285,12 +286,13 @@ impl<T, const N: usize> Deque<T, N> {
(self.buffer.get_unchecked_mut(index).as_ptr() as *const T).read()
}
/// Removes an item from the back of the deque and returns it
/// Removes an item from the back of the deque and returns it, without checking that the deque
/// is not empty
///
/// # Safety
///
/// This assumes the deque is not empty.
pub(crate) unsafe fn pop_back_unchecked(&mut self) -> T {
/// It's undefined behavior to call this on an empty deque
pub unsafe fn pop_back_unchecked(&mut self) -> T {
debug_assert!(!self.is_empty());
self.full = false;
@ -565,29 +567,6 @@ mod tests {
let mut _v: Deque<i32, 4> = Deque::new();
}
macro_rules! droppable {
() => {
struct Droppable;
impl Droppable {
fn new() -> Self {
unsafe {
COUNT += 1;
}
Droppable
}
}
impl Drop for Droppable {
fn drop(&mut self) {
unsafe {
COUNT -= 1;
}
}
}
static mut COUNT: i32 = 0;
};
}
#[test]
fn drop() {
droppable!();
@ -599,7 +578,7 @@ mod tests {
v.pop_front().unwrap();
}
assert_eq!(unsafe { COUNT }, 0);
assert_eq!(Droppable::count(), 0);
{
let mut v: Deque<Droppable, 2> = Deque::new();
@ -607,14 +586,14 @@ mod tests {
v.push_back(Droppable::new()).ok().unwrap();
}
assert_eq!(unsafe { COUNT }, 0);
assert_eq!(Droppable::count(), 0);
{
let mut v: Deque<Droppable, 2> = Deque::new();
v.push_front(Droppable::new()).ok().unwrap();
v.push_front(Droppable::new()).ok().unwrap();
}
assert_eq!(unsafe { COUNT }, 0);
assert_eq!(Droppable::count(), 0);
}
#[test]
@ -754,7 +733,7 @@ mod tests {
let _ = items.next();
}
assert_eq!(unsafe { COUNT }, 0);
assert_eq!(Droppable::count(), 0);
{
let mut deque: Deque<Droppable, 2> = Deque::new();
@ -764,7 +743,7 @@ mod tests {
// Move none
}
assert_eq!(unsafe { COUNT }, 0);
assert_eq!(Droppable::count(), 0);
{
let mut deque: Deque<Droppable, 2> = Deque::new();
@ -774,7 +753,7 @@ mod tests {
let _ = items.next(); // Move partly
}
assert_eq!(unsafe { COUNT }, 0);
assert_eq!(Droppable::count(), 0);
}
#[test]

View File

@ -98,10 +98,13 @@ impl Pos {
}
}
pub enum Inserted<V> {
Done,
Swapped { prev_value: V },
RobinHood { probe: usize, old_pos: Pos },
enum Insert<K, V> {
Success(Inserted<V>),
Full((K, V)),
}
struct Inserted<V> {
index: usize,
old_value: Option<V>,
}
macro_rules! probe_loop {
@ -176,16 +179,10 @@ where
});
}
// First phase: Look for the preferred location for key.
//
// We will know if `key` is already in the map, before we need to insert it.
// When we insert they key, it might be that we need to continue displacing
// entries (robin hood hashing), in which case Inserted::RobinHood is returned
fn insert_phase_1(&mut self, hash: HashValue, key: K, value: V) -> Inserted<V> {
fn insert(&mut self, hash: HashValue, key: K, value: V) -> Insert<K, V> {
let mut probe = hash.desired_pos(Self::mask());
let mut dist = 0;
let inserted;
probe_loop!(probe < self.indices.len(), {
let pos = &mut self.indices[probe];
@ -198,39 +195,45 @@ where
let their_dist = entry_hash.probe_distance(Self::mask(), probe);
if their_dist < dist {
if self.entries.is_full() {
return Insert::Full((key, value));
}
// robin hood: steal the spot if it's better for us
let index = self.entries.len();
inserted = Inserted::RobinHood {
probe: probe,
old_pos: Pos::new(index, hash),
};
break;
unsafe { self.entries.push_unchecked(Bucket { hash, key, value }) };
return Insert::Success(Inserted {
index: self.insert_phase_2(probe, Pos::new(index, hash)),
old_value: None,
});
} else if entry_hash == hash && unsafe { self.entries.get_unchecked(i).key == key }
{
return Inserted::Swapped {
prev_value: mem::replace(
return Insert::Success(Inserted {
index: i,
old_value: Some(mem::replace(
unsafe { &mut self.entries.get_unchecked_mut(i).value },
value,
),
};
)),
});
}
} else {
if self.entries.is_full() {
return Insert::Full((key, value));
}
// empty bucket, insert here
let index = self.entries.len();
*pos = Some(Pos::new(index, hash));
inserted = Inserted::Done;
break;
unsafe { self.entries.push_unchecked(Bucket { hash, key, value }) };
return Insert::Success(Inserted {
index,
old_value: None,
});
}
dist += 1;
});
// NOTE(unsafe) we already checked (in `insert`) that we aren't exceeding the capacity
unsafe { self.entries.push_unchecked(Bucket { hash, key, value }) }
inserted
}
// phase 2 is post-insert where we forward-shift `Pos` in the indices.
fn insert_phase_2(&mut self, mut probe: usize, mut old_pos: Pos) {
fn insert_phase_2(&mut self, mut probe: usize, mut old_pos: Pos) -> usize {
probe_loop!(probe < self.indices.len(), {
let pos = unsafe { self.indices.get_unchecked_mut(probe) };
@ -242,7 +245,7 @@ where
if is_none {
*pos = Some(old_pos);
break;
return probe;
}
});
}
@ -313,6 +316,115 @@ where
}
}
/// A view into an entry in the map
pub enum Entry<'a, K, V, const N: usize> {
/// The entry corresponding to the key `K` exists in the map
Occupied(OccupiedEntry<'a, K, V, N>),
/// The entry corresponding to the key `K` does not exist in the map
Vacant(VacantEntry<'a, K, V, N>),
}
/// An occupied entry which can be manipulated
pub struct OccupiedEntry<'a, K, V, const N: usize> {
key: K,
probe: usize,
pos: usize,
core: &'a mut CoreMap<K, V, N>,
}
impl<'a, K, V, const N: usize> OccupiedEntry<'a, K, V, N>
where
K: Eq + Hash,
{
/// Gets a reference to the key that this entity corresponds to
pub fn key(&self) -> &K {
&self.key
}
/// Removes this entry from the map and yields its corresponding key and value
pub fn remove_entry(self) -> (K, V) {
self.core.remove_found(self.probe, self.pos)
}
/// Gets a reference to the value associated with this entry
pub fn get(&self) -> &V {
// SAFETY: Already checked existence at instantiation and the only mutable reference
// to the map is internally held.
unsafe { &self.core.entries.get_unchecked(self.pos).value }
}
/// Gets a mutable reference to the value associated with this entry
pub fn get_mut(&mut self) -> &mut V {
// SAFETY: Already checked existence at instantiation and the only mutable reference
// to the map is internally held.
unsafe { &mut self.core.entries.get_unchecked_mut(self.pos).value }
}
/// Consumes this entry and yields a reference to the underlying value
pub fn into_mut(self) -> &'a mut V {
// SAFETY: Already checked existence at instantiation and the only mutable reference
// to the map is internally held.
unsafe { &mut self.core.entries.get_unchecked_mut(self.pos).value }
}
/// Overwrites the underlying map's value with this entry's value
pub fn insert(self, value: V) -> V {
// SAFETY: Already checked existence at instantiation and the only mutable reference
// to the map is internally held.
unsafe {
mem::replace(
&mut self.core.entries.get_unchecked_mut(self.pos).value,
value,
)
}
}
/// Removes this entry from the map and yields its value
pub fn remove(self) -> V {
self.remove_entry().1
}
}
/// A view into an empty slot in the underlying map
pub struct VacantEntry<'a, K, V, const N: usize> {
key: K,
hash_val: HashValue,
core: &'a mut CoreMap<K, V, N>,
}
impl<'a, K, V, const N: usize> VacantEntry<'a, K, V, N>
where
K: Eq + Hash,
{
/// Get the key associated with this entry
pub fn key(&self) -> &K {
&self.key
}
/// Consumes this entry to yield to key associated with it
pub fn into_key(self) -> K {
self.key
}
/// Inserts this entry into to underlying map, yields a mutable reference to the inserted value.
/// If the map is at capacity the value is returned instead.
pub fn insert(self, value: V) -> Result<&'a mut V, V> {
if self.core.entries.is_full() {
Err(value)
} else {
match self.core.insert(self.hash_val, self.key, value) {
Insert::Success(inserted) => {
unsafe {
// SAFETY: Already checked existence at instantiation and the only mutable reference
// to the map is internally held.
Ok(&mut self.core.entries.get_unchecked_mut(inserted.index).value)
}
}
Insert::Full((_, v)) => Err(v),
}
}
}
}
/// Fixed capacity [`IndexMap`](https://docs.rs/indexmap/1/indexmap/map/struct.IndexMap.html)
///
/// Note that you cannot use `IndexMap` directly, since it is generic around the hashing algorithm
@ -492,8 +604,78 @@ where
}
}
// TODO
// pub fn entry(&mut self, key: K) -> Entry<K, V> { .. }
/// Get the first key-value pair
///
/// Computes in **O(1)** time
pub fn first(&self) -> Option<(&K, &V)> {
self.core
.entries
.first()
.map(|bucket| (&bucket.key, &bucket.value))
}
/// Get the first key-value pair, with mutable access to the value
///
/// Computes in **O(1)** time
pub fn first_mut(&mut self) -> Option<(&K, &mut V)> {
self.core
.entries
.first_mut()
.map(|bucket| (&bucket.key, &mut bucket.value))
}
/// Get the last key-value pair
///
/// Computes in **O(1)** time
pub fn last(&self) -> Option<(&K, &V)> {
self.core
.entries
.last()
.map(|bucket| (&bucket.key, &bucket.value))
}
/// Get the last key-value pair, with mutable access to the value
///
/// Computes in **O(1)** time
pub fn last_mut(&mut self) -> Option<(&K, &mut V)> {
self.core
.entries
.last_mut()
.map(|bucket| (&bucket.key, &mut bucket.value))
}
/// Returns an entry for the corresponding key
/// ```
/// use heapless::FnvIndexMap;
/// use heapless::Entry;
/// let mut map = FnvIndexMap::<_, _, 16>::new();
/// if let Entry::Vacant(v) = map.entry("a") {
/// v.insert(1).unwrap();
/// }
/// if let Entry::Occupied(mut o) = map.entry("a") {
/// println!("found {}", *o.get()); // Prints 1
/// o.insert(2);
/// }
/// // Prints 2
/// println!("val: {}", *map.get("a").unwrap());
/// ```
pub fn entry(&mut self, key: K) -> Entry<'_, K, V, N> {
let hash_val = hash_with(&key, &self.build_hasher);
if let Some((probe, pos)) = self.core.find(hash_val, &key) {
Entry::Occupied(OccupiedEntry {
key,
probe,
pos,
core: &mut self.core,
})
} else {
Entry::Vacant(VacantEntry {
key,
hash_val,
core: &mut self.core,
})
}
}
/// Return the number of key-value pairs in the map.
///
@ -654,17 +836,10 @@ where
/// assert_eq!(map[&37], "c");
/// ```
pub fn insert(&mut self, key: K, value: V) -> Result<Option<V>, (K, V)> {
if self.core.entries.is_full() {
Err((key, value))
} else {
Ok(match self.insert_phase_1(key, value) {
Inserted::Swapped { prev_value } => Some(prev_value),
Inserted::Done => None,
Inserted::RobinHood { probe, old_pos } => {
self.core.insert_phase_2(probe, old_pos);
None
}
})
let hash = hash_with(&key, &self.build_hasher);
match self.core.insert(hash, key, value) {
Insert::Success(inserted) => Ok(inserted.old_value),
Insert::Full((k, v)) => Err((k, v)),
}
}
@ -720,11 +895,6 @@ where
let h = hash_with(key, &self.build_hasher);
self.core.find(h, key)
}
fn insert_phase_1(&mut self, key: K, value: V) -> Inserted<V> {
let hash = hash_with(&key, &self.build_hasher);
self.core.insert_phase_1(hash, key, value)
}
}
impl<'a, K, Q, V, S, const N: usize> ops::Index<&'a Q> for IndexMap<K, V, S, N>
@ -857,6 +1027,34 @@ where
}
}
#[derive(Clone)]
pub struct IntoIter<K, V, const N: usize> {
entries: Vec<Bucket<K, V>, N>,
}
impl<K, V, const N: usize> Iterator for IntoIter<K, V, N> {
type Item = (K, V);
fn next(&mut self) -> Option<Self::Item> {
self.entries.pop().map(|bucket| (bucket.key, bucket.value))
}
}
impl<K, V, S, const N: usize> IntoIterator for IndexMap<K, V, S, N>
where
K: Eq + Hash,
S: BuildHasher,
{
type Item = (K, V);
type IntoIter = IntoIter<K, V, N>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
entries: self.core.entries,
}
}
}
impl<'a, K, V, S, const N: usize> IntoIterator for &'a IndexMap<K, V, S, N>
where
K: Eq + Hash,
@ -929,7 +1127,8 @@ where
#[cfg(test)]
mod tests {
use crate::FnvIndexMap;
use crate::{indexmap::Entry, FnvIndexMap};
use core::mem;
#[test]
@ -974,4 +1173,205 @@ mod tests {
assert!(a == b);
}
}
#[test]
fn into_iter() {
let mut src: FnvIndexMap<_, _, 4> = FnvIndexMap::new();
src.insert("k1", "v1").unwrap();
src.insert("k2", "v2").unwrap();
src.insert("k3", "v3").unwrap();
src.insert("k4", "v4").unwrap();
let clone = src.clone();
for (k, v) in clone.into_iter() {
assert_eq!(v, *src.get(k).unwrap());
}
}
#[test]
fn insert_replaces_on_full_map() {
let mut a: FnvIndexMap<_, _, 2> = FnvIndexMap::new();
a.insert("k1", "v1").unwrap();
a.insert("k2", "v2").unwrap();
a.insert("k1", "v2").unwrap();
assert_eq!(a.get("k1"), a.get("k2"));
}
const MAP_SLOTS: usize = 4096;
fn almost_filled_map() -> FnvIndexMap<usize, usize, MAP_SLOTS> {
let mut almost_filled = FnvIndexMap::new();
for i in 1..MAP_SLOTS {
almost_filled.insert(i, i).unwrap();
}
almost_filled
}
#[test]
fn entry_find() {
let key = 0;
let value = 0;
let mut src = almost_filled_map();
let entry = src.entry(key);
match entry {
Entry::Occupied(_) => {
panic!("Found entry without inserting");
}
Entry::Vacant(v) => {
assert_eq!(&key, v.key());
assert_eq!(key, v.into_key());
}
}
src.insert(key, value).unwrap();
let entry = src.entry(key);
match entry {
Entry::Occupied(mut o) => {
assert_eq!(&key, o.key());
assert_eq!(&value, o.get());
assert_eq!(&value, o.get_mut());
assert_eq!(&value, o.into_mut());
}
Entry::Vacant(_) => {
panic!("Entry not found");
}
}
}
#[test]
fn entry_vacant_insert() {
let key = 0;
let value = 0;
let mut src = almost_filled_map();
assert_eq!(MAP_SLOTS - 1, src.len());
let entry = src.entry(key);
match entry {
Entry::Occupied(_) => {
panic!("Entry found when empty");
}
Entry::Vacant(v) => {
v.insert(value).unwrap();
}
};
assert_eq!(value, *src.get(&key).unwrap())
}
#[test]
fn entry_occupied_insert() {
let key = 0;
let value = 0;
let value2 = 5;
let mut src = almost_filled_map();
assert_eq!(MAP_SLOTS - 1, src.len());
src.insert(key, value).unwrap();
let entry = src.entry(key);
match entry {
Entry::Occupied(o) => {
assert_eq!(value, o.insert(value2));
}
Entry::Vacant(_) => {
panic!("Entry not found");
}
};
assert_eq!(value2, *src.get(&key).unwrap())
}
#[test]
fn entry_remove_entry() {
let key = 0;
let value = 0;
let mut src = almost_filled_map();
src.insert(key, value).unwrap();
assert_eq!(MAP_SLOTS, src.len());
let entry = src.entry(key);
match entry {
Entry::Occupied(o) => {
assert_eq!((key, value), o.remove_entry());
}
Entry::Vacant(_) => {
panic!("Entry not found")
}
};
assert_eq!(MAP_SLOTS - 1, src.len());
}
#[test]
fn entry_remove() {
let key = 0;
let value = 0;
let mut src = almost_filled_map();
src.insert(key, value).unwrap();
assert_eq!(MAP_SLOTS, src.len());
let entry = src.entry(key);
match entry {
Entry::Occupied(o) => {
assert_eq!(value, o.remove());
}
Entry::Vacant(_) => {
panic!("Entry not found");
}
};
assert_eq!(MAP_SLOTS - 1, src.len());
}
#[test]
fn entry_roll_through_all() {
let mut src: FnvIndexMap<usize, usize, MAP_SLOTS> = FnvIndexMap::new();
for i in 0..MAP_SLOTS {
match src.entry(i) {
Entry::Occupied(_) => {
panic!("Entry found before insert");
}
Entry::Vacant(v) => {
v.insert(i).unwrap();
}
}
}
let add_mod = 99;
for i in 0..MAP_SLOTS {
match src.entry(i) {
Entry::Occupied(o) => {
assert_eq!(i, o.insert(i + add_mod));
}
Entry::Vacant(_) => {
panic!("Entry not found after insert");
}
}
}
for i in 0..MAP_SLOTS {
match src.entry(i) {
Entry::Occupied(o) => {
assert_eq!((i, i + add_mod), o.remove_entry());
}
Entry::Vacant(_) => {
panic!("Entry not found after insert");
}
}
}
for i in 0..MAP_SLOTS {
assert!(matches!(src.entry(i), Entry::Vacant(_)));
}
assert!(src.is_empty());
}
#[test]
fn first_last() {
let mut map = FnvIndexMap::<_, _, 4>::new();
assert_eq!(None, map.first());
assert_eq!(None, map.last());
map.insert(0, 0).unwrap();
map.insert(2, 2).unwrap();
assert_eq!(Some((&0, &0)), map.first());
assert_eq!(Some((&2, &2)), map.last());
map.insert(1, 1).unwrap();
assert_eq!(Some((&1, &1)), map.last());
*map.first_mut().unwrap().1 += 1;
*map.last_mut().unwrap().1 += 1;
assert_eq!(Some((&0, &1)), map.first());
assert_eq!(Some((&1, &2)), map.last());
}
}

View File

@ -128,6 +128,20 @@ where
}
}
/// Get the first value
///
/// Computes in **O(1)** time
pub fn first(&self) -> Option<&T> {
self.map.first().map(|(k, _v)| k)
}
/// Get the last value
///
/// Computes in **O(1)** time
pub fn last(&self) -> Option<&T> {
self.map.last().map(|(k, _v)| k)
}
/// Visits the values representing the difference, i.e. the values that are in `self` but not in
/// `other`.
///

View File

@ -77,7 +77,7 @@
pub use binary_heap::BinaryHeap;
pub use deque::Deque;
pub use histbuf::{HistoryBuffer, OldestOrdered};
pub use indexmap::{Bucket, FnvIndexMap, IndexMap, Pos};
pub use indexmap::{Bucket, Entry, FnvIndexMap, IndexMap, OccupiedEntry, Pos, VacantEntry};
pub use indexset::{FnvIndexSet, IndexSet};
pub use linear_map::LinearMap;
#[cfg(all(has_cas, feature = "cas"))]
@ -85,6 +85,10 @@ pub use pool::singleton::arc::Arc;
pub use string::String;
pub use vec::Vec;
#[macro_use]
#[cfg(test)]
mod test_helpers;
mod deque;
mod histbuf;
mod indexmap;

View File

@ -1,4 +1,4 @@
use core::{cmp::Ordering, fmt, fmt::Write, hash, ops, str};
use core::{cmp::Ordering, fmt, fmt::Write, hash, iter, ops, str};
use hash32;
@ -307,6 +307,36 @@ impl<const N: usize> str::FromStr for String<N> {
}
}
impl<const N: usize> iter::FromIterator<char> for String<N> {
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
let mut new = String::new();
for c in iter {
new.push(c).unwrap();
}
new
}
}
impl<'a, const N: usize> iter::FromIterator<&'a char> for String<N> {
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
let mut new = String::new();
for c in iter {
new.push(*c).unwrap();
}
new
}
}
impl<'a, const N: usize> iter::FromIterator<&'a str> for String<N> {
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
let mut new = String::new();
for c in iter {
new.push_str(c).unwrap();
}
new
}
}
impl<const N: usize> Clone for String<N> {
fn clone(&self) -> Self {
Self {
@ -558,6 +588,20 @@ mod tests {
assert_eq!(e, ());
}
#[test]
fn from_iter() {
let mut v: Vec<char, 5> = Vec::new();
v.push('h').unwrap();
v.push('e').unwrap();
v.push('l').unwrap();
v.push('l').unwrap();
v.push('o').unwrap();
let string1: String<5> = v.iter().collect(); //&char
let string2: String<5> = "hello".chars().collect(); //char
assert_eq!(string1, "hello");
assert_eq!(string2, "hello");
}
#[test]
#[should_panic]
fn from_panic() {

23
src/test_helpers.rs Normal file
View File

@ -0,0 +1,23 @@
macro_rules! droppable {
() => {
static COUNT: core::sync::atomic::AtomicI32 = core::sync::atomic::AtomicI32::new(0);
#[derive(Eq, Ord, PartialEq, PartialOrd)]
struct Droppable(i32);
impl Droppable {
fn new() -> Self {
COUNT.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
Droppable(Self::count())
}
fn count() -> i32 {
COUNT.load(core::sync::atomic::Ordering::Relaxed)
}
}
impl Drop for Droppable {
fn drop(&mut self) {
COUNT.fetch_sub(1, core::sync::atomic::Ordering::Relaxed);
}
}
};
}

View File

@ -34,12 +34,18 @@ use hash32;
/// assert_eq!(*vec, [7, 1, 2, 3]);
/// ```
pub struct Vec<T, const N: usize> {
buffer: [MaybeUninit<T>; N],
// NOTE order is important for optimizations. the `len` first layout lets the compiler optimize
// `new` to: reserve stack space and zero the first word. With the fields in the reverse order
// the compiler optimizes `new` to `memclr`-ing the *entire* stack space, including the `buffer`
// field which should be left uninitialized. Optimizations were last checked with Rust 1.60
len: usize,
buffer: [MaybeUninit<T>; N],
}
impl<T, const N: usize> Vec<T, N> {
const INIT: MaybeUninit<T> = MaybeUninit::uninit();
const ELEM: MaybeUninit<T> = MaybeUninit::uninit();
const INIT: [MaybeUninit<T>; N] = [Self::ELEM; N]; // important for optimization of `new`
/// Constructs a new, empty vector with a fixed capacity of `N`
///
@ -60,8 +66,8 @@ impl<T, const N: usize> Vec<T, N> {
crate::sealed::greater_than_eq_0::<N>();
Self {
buffer: [Self::INIT; N],
len: 0,
buffer: Self::INIT,
}
}
@ -92,7 +98,12 @@ impl<T, const N: usize> Vec<T, N> {
T: Clone,
{
let mut new = Self::new();
new.extend_from_slice(self.as_slice()).unwrap();
// avoid `extend_from_slice` as that introduces a runtime check / panicking branch
for elem in self {
unsafe {
new.push_unchecked(elem.clone());
}
}
new
}
@ -263,13 +274,24 @@ impl<T, const N: usize> Vec<T, N> {
/// Shortens the vector, keeping the first `len` elements and dropping the rest.
pub fn truncate(&mut self, len: usize) {
// drop any extra elements
while len < self.len {
// decrement len before the drop_in_place(), so a panic on Drop
// doesn't re-drop the just-failed value.
self.len -= 1;
let len = self.len;
unsafe { ptr::drop_in_place(self.as_mut_slice().get_unchecked_mut(len)) };
// This is safe because:
//
// * the slice passed to `drop_in_place` is valid; the `len > self.len`
// case avoids creating an invalid slice, and
// * the `len` of the vector is shrunk before calling `drop_in_place`,
// such that no value will be dropped twice in case `drop_in_place`
// were to panic once (if it panics twice, the program aborts).
unsafe {
// Note: It's intentional that this is `>` and not `>=`.
// Changing it to `>=` has negative performance
// implications in some cases. See rust-lang/rust#78884 for more.
if len > self.len {
return;
}
let remaining_len = self.len - len;
let s = ptr::slice_from_raw_parts_mut(self.as_mut_ptr().add(len), remaining_len);
self.len = len;
ptr::drop_in_place(s);
}
}
@ -471,11 +493,11 @@ impl<T, const N: usize> Vec<T, N> {
pub unsafe fn swap_remove_unchecked(&mut self, index: usize) -> T {
let length = self.len();
debug_assert!(index < length);
ptr::swap(
self.as_mut_slice().get_unchecked_mut(index),
self.as_mut_slice().get_unchecked_mut(length - 1),
);
self.pop_unchecked()
let value = ptr::read(self.as_ptr().add(index));
let base_ptr = self.as_mut_ptr();
ptr::copy(base_ptr.add(length - 1), base_ptr.add(index), 1);
self.len -= 1;
value
}
/// Returns true if the vec is full
@ -890,29 +912,6 @@ mod tests {
assert!(v.is_full());
}
macro_rules! droppable {
() => {
struct Droppable;
impl Droppable {
fn new() -> Self {
unsafe {
COUNT += 1;
}
Droppable
}
}
impl Drop for Droppable {
fn drop(&mut self) {
unsafe {
COUNT -= 1;
}
}
}
static mut COUNT: i32 = 0;
};
}
#[test]
fn drop() {
droppable!();
@ -924,7 +923,7 @@ mod tests {
v.pop().unwrap();
}
assert_eq!(unsafe { COUNT }, 0);
assert_eq!(Droppable::count(), 0);
{
let mut v: Vec<Droppable, 2> = Vec::new();
@ -932,7 +931,7 @@ mod tests {
v.push(Droppable::new()).ok().unwrap();
}
assert_eq!(unsafe { COUNT }, 0);
assert_eq!(Droppable::count(), 0);
}
#[test]
@ -1067,7 +1066,7 @@ mod tests {
let _ = items.next();
}
assert_eq!(unsafe { COUNT }, 0);
assert_eq!(Droppable::count(), 0);
{
let mut vec: Vec<Droppable, 2> = Vec::new();
@ -1077,7 +1076,7 @@ mod tests {
// Move none
}
assert_eq!(unsafe { COUNT }, 0);
assert_eq!(Droppable::count(), 0);
{
let mut vec: Vec<Droppable, 2> = Vec::new();
@ -1087,7 +1086,7 @@ mod tests {
let _ = items.next(); // Move partly
}
assert_eq!(unsafe { COUNT }, 0);
assert_eq!(Droppable::count(), 0);
}
#[test]

View File

@ -1,7 +1,7 @@
# false positives in thread::spawn (?)
race:*dealloc
race:*drop_slow*
race:__call_tls_dtors
race:std::panic::catch_unwind
race:std::thread::scope
# false positives in scoped_threadpool (?)
race:*drop*
# std::thread::spawn false positive; seen on Ubuntu 20.04 but not on Arch Linux (2022-04-29)
race:drop_in_place*JoinHandle
race:alloc::sync::Arc<*>::drop_slow
race:__call_tls_dtors

View File

@ -1,11 +1,11 @@
#![cfg_attr(unstable_channel, feature(scoped_threads))]
#![deny(rust_2018_compatibility)]
#![deny(rust_2018_idioms)]
#![deny(warnings)]
use std::{sync::mpsc, thread};
use std::thread;
use heapless::{mpmc::Q64, spsc};
use scoped_threadpool::Pool;
use heapless::spsc;
#[test]
fn once() {
@ -51,6 +51,7 @@ fn twice() {
}
#[test]
#[cfg(unstable_channel)]
fn scoped() {
let mut rb: spsc::Queue<i32, 5> = spsc::Queue::new();
@ -59,12 +60,12 @@ fn scoped() {
{
let (mut p, mut c) = rb.split();
Pool::new(2).scoped(move |scope| {
scope.execute(move || {
thread::scope(move |scope| {
scope.spawn(move || {
p.enqueue(1).unwrap();
});
scope.execute(move || {
scope.spawn(move || {
c.dequeue().unwrap();
});
});
@ -75,6 +76,7 @@ fn scoped() {
#[test]
#[cfg_attr(miri, ignore)] // too slow
#[cfg(unstable_channel)]
fn contention() {
const N: usize = 1024;
@ -83,8 +85,8 @@ fn contention() {
{
let (mut p, mut c) = rb.split();
Pool::new(2).scoped(move |scope| {
scope.execute(move || {
thread::scope(move |scope| {
scope.spawn(move || {
let mut sum: u32 = 0;
for i in 0..(2 * N) {
@ -95,7 +97,7 @@ fn contention() {
println!("producer: {}", sum);
});
scope.execute(move || {
scope.spawn(move || {
let mut sum: u32 = 0;
for _ in 0..(2 * N) {
@ -120,15 +122,20 @@ fn contention() {
#[test]
#[cfg_attr(miri, ignore)] // too slow
#[cfg(unstable_channel)]
fn mpmc_contention() {
use std::sync::mpsc;
use heapless::mpmc::Q64;
const N: u32 = 64;
static Q: Q64<u32> = Q64::new();
let (s, r) = mpsc::channel();
Pool::new(2).scoped(|scope| {
thread::scope(|scope| {
let s1 = s.clone();
scope.execute(move || {
scope.spawn(move || {
let mut sum: u32 = 0;
for i in 0..(16 * N) {
@ -141,7 +148,7 @@ fn mpmc_contention() {
});
let s2 = s.clone();
scope.execute(move || {
scope.spawn(move || {
let mut sum: u32 = 0;
for _ in 0..(16 * N) {
@ -166,6 +173,7 @@ fn mpmc_contention() {
#[test]
#[cfg_attr(miri, ignore)] // too slow
#[cfg(unstable_channel)]
fn unchecked() {
const N: usize = 1024;
@ -178,14 +186,14 @@ fn unchecked() {
{
let (mut p, mut c) = rb.split();
Pool::new(2).scoped(move |scope| {
scope.execute(move || {
thread::scope(move |scope| {
scope.spawn(move || {
for _ in 0..N / 2 - 1 {
p.enqueue(2).unwrap();
}
});
scope.execute(move || {
scope.spawn(move || {
let mut sum: usize = 0;
for _ in 0..N / 2 - 1 {
@ -246,8 +254,8 @@ fn pool() {
A::grow(unsafe { &mut M });
Pool::new(2).scoped(move |scope| {
scope.execute(move || {
thread::scope(move |scope| {
scope.spawn(move || {
for _ in 0..N / 4 {
let a = A::alloc().unwrap();
let b = A::alloc().unwrap();
@ -257,7 +265,7 @@ fn pool() {
}
});
scope.execute(move || {
scope.spawn(move || {
for _ in 0..N / 2 {
let a = A::alloc().unwrap();
let a = a.init([2; 8]);