Skip to content

Commit

Permalink
Add shim
Browse files Browse the repository at this point in the history
  • Loading branch information
arendjr committed Nov 27, 2024
1 parent 873086c commit 3e350f9
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 4 deletions.
10 changes: 9 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ biome_migrate = { path = "./crates/biome_migrate" }
biome_service = { path = "./crates/biome_service" }
biome_test_utils = { path = "./crates/biome_test_utils" }
biome_ungrammar = { path = "./crates/biome_ungrammar" }
papaya_facade = { path = "./crates/papaya_facade" }
tests_macros = { path = "./crates/tests_macros" }

# Crates needed in the workspace
Expand All @@ -188,7 +189,6 @@ indexmap = { version = "2.6.0" }
insta = "1.41.1"
natord = "1.0.9"
oxc_resolver = "1.12.0"
papaya = "0.1.4"
proc-macro2 = "1.0.86"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ getrandom = { workspace = true, features = ["js"] }
ignore = { workspace = true }
indexmap = { workspace = true, features = ["serde"] }
oxc_resolver = { workspace = true }
papaya = { workspace = true }
papaya_facade = { workspace = true }
regex = { workspace = true }
rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_service/src/workspace/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use biome_parser::AnyParse;
use biome_project::{NodeJsProject, PackageJson, PackageType, Project};
use biome_rowan::NodeCache;
use indexmap::IndexSet;
use papaya::HashMap;
use papaya_facade::HashMap;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
Expand Down
21 changes: 21 additions & 0 deletions crates/papaya_facade/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

[package]
authors.workspace = true
categories.workspace = true
description = "Facade for papaya with a WASM-compatible shim"
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
name = "papaya_facade"
repository.workspace = true
version = "0.0.0"

[lints]
workspace = true

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
papaya = "0.1.4"

[target.'cfg(target_arch = "wasm32")'.dependencies]
rustc-hash = { workspace = true }
9 changes: 9 additions & 0 deletions crates/papaya_facade/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#[cfg(not(target_arch = "wasm32"))]
mod papaya;
#[cfg(target_arch = "wasm32")]
mod shim;

#[cfg(not(target_arch = "wasm32"))]
pub use crate::papaya::HashMap;
#[cfg(target_arch = "wasm32")]
pub use shim::HashMap;
1 change: 1 addition & 0 deletions crates/papaya_facade/src/papaya.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub use ::papaya::HashMap;
305 changes: 305 additions & 0 deletions crates/papaya_facade/src/shim.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
use std::{borrow::Borrow, cell::UnsafeCell, collections::hash_map, fmt, hash::Hash};

use rustc_hash::{FxBuildHasher, FxHashMap};

/// Provides a shim with the same API as `papaya`, but which is fundamentally
/// single-threaded and works in a WASM environment.
///
/// SAFETY: This shim is *only* safe in a single-threaded WASM environment.
/// Concurrent access to this hash map may lead to undefined behavior.
#[derive(Debug)]
pub struct HashMap<K, V> {
inner: UnsafeCell<FxHashMap<K, V>>,
}

// SAFETY: This is only intended for single-threaded WASM environments.
unsafe impl<K, V> Sync for HashMap<K, V> {}

impl<K, V> Default for HashMap<K, V> {
fn default() -> Self {
Self {
inner: UnsafeCell::default(),
}
}
}

impl<K, V> HashMap<K, V> {
pub fn new() -> HashMap<K, V> {
HashMap::with_capacity(0)
}

pub fn with_capacity(capacity: usize) -> Self {
Self {
inner: UnsafeCell::new(FxHashMap::with_capacity_and_hasher(capacity, FxBuildHasher)),
}
}

#[inline]
pub fn pin(&self) -> HashMapRef<'_, K, V> {
HashMapRef {
guard: self.guard(),
map: self,
}
}

#[inline]
pub fn pin_owned(&self) -> HashMapRef<'_, K, V> {
self.pin()
}

#[inline]
pub fn guard(&self) -> Guard<V> {
Guard {
removed_values: Default::default(),
}
}

#[inline]
pub fn owned_guard(&self) -> Guard<V> {
self.guard()
}

pub fn len(&self) -> usize {
self.raw().len()
}

#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}

/// SAFETY: No mutable references to the hash map may exist at the same time.
fn raw(&self) -> &FxHashMap<K, V> {
unsafe { &*self.inner.get() }
}

/// SAFETY: No other references to the hash map may exist at the same time.
#[allow(clippy::mut_from_ref)]
fn raw_mut(&self) -> &mut FxHashMap<K, V> {
unsafe { &mut *self.inner.get() }
}
}

pub struct Guard<V> {
removed_values: UnsafeCell<Vec<V>>,
}

impl<V> Guard<V> {
/// SAFETY: No mutable references to the vector may exist at the same time.
fn raw_removed_values(&self) -> &Vec<V> {
unsafe { &*self.removed_values.get() }
}

/// SAFETY: No other references to the vector may exist at the same time.
#[allow(clippy::mut_from_ref)]
fn raw_removed_values_mut(&self) -> &mut Vec<V> {
unsafe { &mut *self.removed_values.get() }
}
}

pub struct HashMapRef<'map, K, V> {
guard: Guard<V>,
map: &'map HashMap<K, V>,
}

impl<'map, K, V> HashMapRef<'map, K, V>
where
K: Hash + Eq,
{
#[inline]
pub fn map(&self) -> &'map HashMap<K, V> {
self.map
}

#[inline]
pub fn len(&self) -> usize {
self.map.len()
}

#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}

#[inline]
pub fn contains_key<Q>(&self, key: &Q) -> bool
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.get(key).is_some()
}

#[inline]
pub fn get<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.map.raw().get(key)
}

#[inline]
pub fn get_key_value<Q>(&self, key: &Q) -> Option<(&K, &V)>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
self.map.raw().get_key_value(key)
}

#[inline]
pub fn insert(&self, key: K, value: V) -> Option<&V> {
match self.map.raw_mut().insert(key, value) {
Some(old_value) => {
self.guard.raw_removed_values_mut().push(old_value);
self.guard.raw_removed_values().last()
}
None => None,
}
}

#[inline]
pub fn try_insert(&self, key: K, value: V) -> Result<&V, OccupiedError<'_, V>>
where
K: Clone,
{
if let Some(current) = self.map.raw().get(&key) {
return Err(OccupiedError {
current,
not_inserted: value,
});
}

self.map.raw_mut().insert(key.clone(), value);
self.map.raw().get(&key).ok_or_else(|| unreachable!())
}

#[inline]
pub fn get_or_insert(&self, key: K, value: V) -> &V
where
K: Clone,
{
// Note that we use `try_insert` instead of `compute` or `get_or_insert_with` here, as it
// allows us to avoid the closure indirection.
match self.try_insert(key, value) {
Ok(inserted) => inserted,
Err(OccupiedError { current, .. }) => current,
}
}

#[inline]
pub fn get_or_insert_with<F>(&self, key: K, f: F) -> &V
where
F: FnOnce() -> V,
K: Clone,
{
if let Some(current) = self.map.raw().get(&key) {
return current;
}

self.map.raw_mut().insert(key.clone(), f());
self.map.raw().get(&key).unwrap()
}

#[inline]
pub fn update<F>(&self, key: K, update: F) -> Option<&V>
where
F: Fn(&V) -> V,
K: Clone,
{
self.map.raw().get(&key).and_then(|current| {
self.map.raw_mut().insert(key.clone(), update(current));
self.map.raw().get(&key)
})
}

#[inline]
pub fn update_or_insert<F>(&self, key: K, update: F, value: V) -> &V
where
F: Fn(&V) -> V,
K: Clone,
{
self.update_or_insert_with(key, update, || value)
}

#[inline]
pub fn update_or_insert_with<U, F>(&self, key: K, update: U, f: F) -> &V
where
F: FnOnce() -> V,
K: Clone,
U: Fn(&V) -> V,
{
let value = match self.map.raw().get(&key) {
Some(current) => update(current),
None => f(),
};

self.map.raw_mut().insert(key.clone(), value);
self.map.raw().get(&key).unwrap()
}

#[inline]
pub fn remove<Q>(&self, key: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized,
{
match self.map.raw_mut().remove(key) {
Some(old_value) => {
self.guard.raw_removed_values_mut().push(old_value);
self.guard.raw_removed_values().last()
}
None => None,
}
}

#[inline]
pub fn clear(&self) {
self.map().raw_mut().clear()
}

#[inline]
pub fn reserve(&self, additional: usize) {
self.map().raw_mut().reserve(additional)
}

#[inline]
pub fn iter(&self) -> Iter<'_, K, V> {
Iter {
inner: self.map().raw().iter(),
}
}
}

pub struct OccupiedError<'a, V: 'a> {
pub current: &'a V,
pub not_inserted: V,
}

pub struct Iter<'g, K, V> {
inner: hash_map::Iter<'g, K, V>,
}

impl<'g, K: 'g, V: 'g> Iterator for Iter<'g, K, V> {
type Item = (&'g K, &'g V);

#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}

impl<K, V> fmt::Debug for Iter<'_, K, V>
where
K: fmt::Debug,
V: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list()
.entries(Iter {
inner: self.inner.clone(),
})
.finish()
}
}
Loading

0 comments on commit 3e350f9

Please sign in to comment.