Skip to content

Commit bf4e5e1

Browse files
committed
feat(allocator): add HashMap (#8553)
Add `HashMap` type to `oxc_allocator`. `HashMap` is a thin wrapper around `hashbrown::HashMap`, which allocates in the arena. The inner map is wrapped in `ManuallyDrop`, so `HashMap` is non-`Drop` (same as `oxc_allocator::Vec`). We use `FxHasher` for all hash maps, so I figured just make that part of the type, rather than having to specify the hasher everywhere we use `HashMap`.
1 parent fa1a6d5 commit bf4e5e1

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_allocator/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ doctest = false
2121
[dependencies]
2222
allocator-api2 = { workspace = true }
2323
bumpalo = { workspace = true, features = ["allocator-api2", "collections"] }
24+
hashbrown = { workspace = true, features = ["allocator-api2"] }
25+
rustc-hash = { workspace = true }
2426
simdutf8 = { workspace = true }
2527

2628
serde = { workspace = true, optional = true }

crates/oxc_allocator/src/hash_map.rs

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
//! A hash map without `Drop`, that uses [`FxHasher`] to hash keys, and stores data in arena allocator.
2+
//!
3+
//! See [`HashMap`] for more details.
4+
//!
5+
//! [`FxHasher`]: rustc_hash::FxHasher
6+
7+
use std::{
8+
hash::Hash,
9+
mem::ManuallyDrop,
10+
ops::{Deref, DerefMut},
11+
};
12+
13+
use bumpalo::Bump;
14+
use rustc_hash::FxBuildHasher;
15+
16+
// Re-export additional types from `hashbrown`
17+
pub use hashbrown::{
18+
hash_map::{
19+
Drain, Entry, EntryRef, ExtractIf, IntoIter, IntoKeys, IntoValues, Iter, IterMut, Keys,
20+
OccupiedError, RawEntryBuilder, RawEntryBuilderMut, Values, ValuesMut,
21+
},
22+
Equivalent, TryReserveError,
23+
};
24+
25+
use crate::Allocator;
26+
27+
type FxHashMap<'alloc, K, V> = hashbrown::HashMap<K, V, FxBuildHasher, &'alloc Bump>;
28+
29+
/// A hash map without `Drop`, that uses [`FxHasher`] to hash keys, and stores data in arena allocator.
30+
///
31+
/// Just a thin wrapper around [`hashbrown::HashMap`], which disables the `Drop` implementation.
32+
///
33+
/// All APIs are the same, except create a [`HashMap`] with
34+
/// either [`new_in`](HashMap::new_in) or [`with_capacity_in`](HashMap::with_capacity_in).
35+
///
36+
/// Must NOT be used to store types which have a [`Drop`] implementation.
37+
/// `K::drop` and `V::drop` will NOT be called on the `HashMap`'s contents when the `HashMap` is dropped.
38+
/// If `K` or `V` own memory outside of the arena, this will be a memory leak.
39+
///
40+
/// Note: This is not a soundness issue, as Rust does not support relying on `drop`
41+
/// being called to guarantee soundness.
42+
///
43+
/// [`FxHasher`]: rustc_hash::FxHasher
44+
pub struct HashMap<'alloc, K, V>(ManuallyDrop<FxHashMap<'alloc, K, V>>);
45+
46+
// Note: All methods marked `#[inline]` as they just delegate to `hashbrown`'s methods.
47+
48+
// TODO: `IntoIter`, `Drain`, and other consuming iterators provided by `hashbrown` are `Drop`.
49+
// Wrap them in `ManuallyDrop` to prevent that.
50+
51+
impl<'alloc, K, V> HashMap<'alloc, K, V> {
52+
/// Creates an empty [`HashMap`]. It will be allocated with the given allocator.
53+
///
54+
/// The hash map is initially created with a capacity of 0, so it will not allocate
55+
/// until it is first inserted into.
56+
#[inline]
57+
pub fn new_in(allocator: &'alloc Allocator) -> Self {
58+
let inner = FxHashMap::with_hasher_in(FxBuildHasher, allocator);
59+
Self(ManuallyDrop::new(inner))
60+
}
61+
62+
/// Creates an empty [`HashMap`] with the specified capacity. It will be allocated with the given allocator.
63+
///
64+
/// The hash map will be able to hold at least capacity elements without reallocating.
65+
/// If capacity is 0, the hash map will not allocate.
66+
#[inline]
67+
pub fn with_capacity_in(capacity: usize, allocator: &'alloc Allocator) -> Self {
68+
let inner = FxHashMap::with_capacity_and_hasher_in(capacity, FxBuildHasher, allocator);
69+
Self(ManuallyDrop::new(inner))
70+
}
71+
72+
/// Creates a consuming iterator visiting all the keys in arbitrary order.
73+
///
74+
/// The map cannot be used after calling this. The iterator element type is `K`.
75+
#[inline]
76+
pub fn into_keys(self) -> IntoKeys<K, V, &'alloc Bump> {
77+
let inner = ManuallyDrop::into_inner(self.0);
78+
inner.into_keys()
79+
}
80+
81+
/// Creates a consuming iterator visiting all the values in arbitrary order.
82+
///
83+
/// The map cannot be used after calling this. The iterator element type is `V`.
84+
#[inline]
85+
pub fn into_values(self) -> IntoValues<K, V, &'alloc Bump> {
86+
let inner = ManuallyDrop::into_inner(self.0);
87+
inner.into_values()
88+
}
89+
}
90+
91+
// Provide access to all `hashbrown::HashMap`'s methods via deref
92+
impl<'alloc, K, V> Deref for HashMap<'alloc, K, V> {
93+
type Target = FxHashMap<'alloc, K, V>;
94+
95+
#[inline]
96+
fn deref(&self) -> &Self::Target {
97+
&self.0
98+
}
99+
}
100+
101+
impl<'alloc, K, V> DerefMut for HashMap<'alloc, K, V> {
102+
#[inline]
103+
fn deref_mut(&mut self) -> &mut FxHashMap<'alloc, K, V> {
104+
&mut self.0
105+
}
106+
}
107+
108+
impl<'alloc, K, V> IntoIterator for HashMap<'alloc, K, V> {
109+
type IntoIter = IntoIter<K, V, &'alloc Bump>;
110+
type Item = (K, V);
111+
112+
/// Creates a consuming iterator, that is, one that moves each key-value pair out of the map
113+
/// in arbitrary order.
114+
///
115+
/// The map cannot be used after calling this.
116+
#[inline]
117+
fn into_iter(self) -> Self::IntoIter {
118+
let inner = ManuallyDrop::into_inner(self.0);
119+
// TODO: `hashbrown::hash_map::IntoIter` is `Drop`.
120+
// Wrap it in `ManuallyDrop` to prevent that.
121+
inner.into_iter()
122+
}
123+
}
124+
125+
impl<'alloc, 'i, K, V> IntoIterator for &'i HashMap<'alloc, K, V> {
126+
type IntoIter = <&'i FxHashMap<'alloc, K, V> as IntoIterator>::IntoIter;
127+
type Item = (&'i K, &'i V);
128+
129+
/// Creates an iterator over the entries of a `HashMap` in arbitrary order.
130+
///
131+
/// The iterator element type is `(&'a K, &'a V)`.
132+
///
133+
/// Return the same [`Iter`] struct as by the `iter` method on [`HashMap`].
134+
#[inline]
135+
fn into_iter(self) -> Self::IntoIter {
136+
self.0.iter()
137+
}
138+
}
139+
140+
impl<'alloc, 'i, K, V> IntoIterator for &'i mut HashMap<'alloc, K, V> {
141+
type IntoIter = <&'i mut FxHashMap<'alloc, K, V> as IntoIterator>::IntoIter;
142+
type Item = (&'i K, &'i mut V);
143+
144+
/// Creates an iterator over the entries of a `HashMap` in arbitrary order
145+
/// with mutable references to the values.
146+
///
147+
/// The iterator element type is `(&'a K, &'a mut V)`.
148+
///
149+
/// Return the same [`IterMut`] struct as by the `iter_mut` method on [`HashMap`].
150+
#[inline]
151+
fn into_iter(self) -> Self::IntoIter {
152+
self.0.iter_mut()
153+
}
154+
}
155+
156+
impl<K, V> PartialEq for HashMap<'_, K, V>
157+
where
158+
K: Eq + Hash,
159+
V: PartialEq,
160+
{
161+
#[inline]
162+
fn eq(&self, other: &Self) -> bool {
163+
self.0.eq(&other.0)
164+
}
165+
}
166+
167+
impl<K, V> Eq for HashMap<'_, K, V>
168+
where
169+
K: Eq + Hash,
170+
V: Eq,
171+
{
172+
}
173+
174+
// Note: `Index` and `Extend` are implemented via `Deref`
175+
176+
/*
177+
// Uncomment once we also provide `oxc_allocator::HashSet`
178+
impl<'alloc, T> From<HashMap<'alloc, T, ()>> for HashSet<'alloc, T> {
179+
fn from(map: HashMap<'alloc, T, ()>) -> Self {
180+
let inner_map = ManuallyDrop::into_inner(map.0);
181+
let inner_set = FxHashSet::from(inner_map);
182+
Self(ManuallyDrop::new(inner_set))
183+
}
184+
}
185+
*/

crates/oxc_allocator/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,14 @@ mod allocator_api2;
5252
mod boxed;
5353
mod clone_in;
5454
mod convert;
55+
pub mod hash_map;
5556
mod vec;
5657

5758
pub use address::{Address, GetAddress};
5859
pub use boxed::Box;
5960
pub use clone_in::CloneIn;
6061
pub use convert::{FromIn, IntoIn};
62+
pub use hash_map::HashMap;
6163
pub use vec::Vec;
6264

6365
/// A bump-allocated memory arena based on [bumpalo].

0 commit comments

Comments
 (0)