From f16a9ea9ad362b71d37774870080f5b6545e4f2f Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 30 Nov 2023 18:31:56 +0100 Subject: [PATCH] Drop dependency on `DashMap` DashMap doesn't work in multi-threaded WebAssembly in Safari: https://bugs.webkit.org/show_bug.cgi?id=265581 --- Cargo.lock | 22 ------------ Cargo.toml | 2 -- crates/typst-cli/src/world.rs | 2 +- crates/typst/Cargo.toml | 2 -- .../typst/src/introspection/introspector.rs | 34 +++++++++++++++--- crates/typst/src/introspection/locator.rs | 4 +-- crates/typst/src/util/pico.rs | 36 +++++++++++++------ 7 files changed, 58 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2dcef7719..caa55d214 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -781,15 +781,6 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.14.3" @@ -1231,17 +1222,6 @@ dependencies = [ "arrayvec", ] -[[package]] -name = "lasso" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4644821e1c3d7a560fe13d842d13f587c07348a1a05d3a797152d41c90c56df2" -dependencies = [ - "ahash", - "dashmap", - "hashbrown 0.13.2", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -2644,7 +2624,6 @@ dependencies = [ "ciborium", "comemo", "csv", - "dashmap", "ecow", "fontdb", "hayagriva", @@ -2657,7 +2636,6 @@ dependencies = [ "image", "indexmap 2.1.0", "kurbo", - "lasso", "lipsum", "log", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index 2aca6f188..913d875b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,6 @@ clap_mangen = "0.2.10" codespan-reporting = "0.11" comemo = "0.3.1" csv = "1" -dashmap = "5.5" dirs = "5" ecow = { version = "0.2", features = ["serde"] } env_proxy = "0.4" @@ -59,7 +58,6 @@ include_dir = "0.7" indexmap = { version = "2", features = ["serde"] } inferno = "0.11.15" kurbo = "0.9" -lasso = { version = "0.7.2", features = ["ahasher", "multi-threaded"] } lipsum = "0.9" log = "0.4" miniz_oxide = "0.7" diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs index f375c6488..994566793 100644 --- a/crates/typst-cli/src/world.rs +++ b/crates/typst-cli/src/world.rs @@ -111,7 +111,7 @@ impl SystemWorld { /// Reset the compilation state in preparation of a new compilation. pub fn reset(&mut self) { - for slot in self.slots.borrow_mut().values_mut() { + for slot in self.slots.get_mut().values_mut() { slot.reset(); } self.now.take(); diff --git a/crates/typst/Cargo.toml b/crates/typst/Cargo.toml index 93cdfd8ad..af90a1d8b 100644 --- a/crates/typst/Cargo.toml +++ b/crates/typst/Cargo.toml @@ -24,7 +24,6 @@ chinese-number = { workspace = true } ciborium = { workspace = true } comemo = { workspace = true } csv = { workspace = true } -dashmap = { workspace = true } ecow = { workspace = true} fontdb = { workspace = true } hayagriva = { workspace = true } @@ -37,7 +36,6 @@ icu_segmenter = { workspace = true } image = { workspace = true } indexmap = { workspace = true } kurbo = { workspace = true } -lasso = { workspace = true } lipsum = { workspace = true } log = { workspace = true } once_cell = { workspace = true } diff --git a/crates/typst/src/introspection/introspector.rs b/crates/typst/src/introspection/introspector.rs index ff9d6c649..bbf43472f 100644 --- a/crates/typst/src/introspection/introspector.rs +++ b/crates/typst/src/introspection/introspector.rs @@ -2,9 +2,9 @@ use std::collections::{BTreeSet, HashMap}; use std::fmt::{self, Debug, Formatter}; use std::hash::Hash; use std::num::NonZeroUsize; +use std::sync::RwLock; use comemo::Prehashed; -use dashmap::DashMap; use ecow::{eco_format, EcoVec}; use indexmap::IndexMap; use smallvec::SmallVec; @@ -32,7 +32,7 @@ pub struct Introspector { /// even if all top-level queries are distinct, they often have shared /// subqueries. Example: Individual counter queries with `before` that /// all depend on a global counter query. - queries: DashMap>>, + queries: QueryCache, } impl Introspector { @@ -118,8 +118,8 @@ impl Introspector { /// Query for all matching elements. pub fn query(&self, selector: &Selector) -> EcoVec> { let hash = crate::util::hash128(selector); - if let Some(output) = self.queries.get(&hash) { - return output.clone(); + if let Some(output) = self.queries.get(hash) { + return output; } let output = match selector { @@ -256,7 +256,7 @@ impl Default for Introspector { elems: IndexMap::new(), labels: HashMap::new(), page_numberings: vec![], - queries: DashMap::new(), + queries: QueryCache::default(), } } } @@ -266,3 +266,27 @@ impl Debug for Introspector { f.pad("Introspector(..)") } } + +/// Caches queries. +#[derive(Default)] +struct QueryCache(RwLock>>>); + +impl QueryCache { + fn get(&self, hash: u128) -> Option>> { + self.0.read().unwrap().get(&hash).cloned() + } + + fn insert(&self, hash: u128, output: EcoVec>) { + self.0.write().unwrap().insert(hash, output); + } + + fn clear(&mut self) { + self.0.get_mut().unwrap().clear(); + } +} + +impl Clone for QueryCache { + fn clone(&self) -> Self { + Self(RwLock::new(self.0.read().unwrap().clone())) + } +} diff --git a/crates/typst/src/introspection/locator.rs b/crates/typst/src/introspection/locator.rs index c9c34b9c1..586f841d0 100644 --- a/crates/typst/src/introspection/locator.rs +++ b/crates/typst/src/introspection/locator.rs @@ -66,7 +66,7 @@ impl<'a> Locator<'a> { let disambiguator = self.disambiguator_impl(hash); // Bump the next disambiguator up by one. - self.hashes.borrow_mut().insert(hash, disambiguator + 1); + self.hashes.get_mut().insert(hash, disambiguator + 1); // Create the location in its default variant. Location { hash, disambiguator, variant: 0 } @@ -78,7 +78,7 @@ impl<'a> Locator<'a> { match item { FrameItem::Group(group) => self.visit_frame(&group.frame), FrameItem::Meta(Meta::Elem(elem), _) => { - let mut hashes = self.hashes.borrow_mut(); + let hashes = self.hashes.get_mut(); let loc = elem.location().unwrap(); let entry = hashes.entry(loc.hash).or_default(); diff --git a/crates/typst/src/util/pico.rs b/crates/typst/src/util/pico.rs index 9b0e46f16..827d6b5bb 100644 --- a/crates/typst/src/util/pico.rs +++ b/crates/typst/src/util/pico.rs @@ -1,13 +1,21 @@ +use std::collections::HashMap; use std::fmt::{self, Debug, Formatter}; +use std::sync::RwLock; use ecow::EcoString; -use lasso::{Spur, ThreadedRodeo}; use once_cell::sync::Lazy; use crate::foundations::cast; /// The global string interner. -static INTERNER: Lazy = Lazy::new(ThreadedRodeo::new); +static INTERNER: Lazy> = + Lazy::new(|| RwLock::new(Interner { to_id: HashMap::new(), from_id: Vec::new() })); + +/// A string interner. +struct Interner { + to_id: HashMap<&'static str, PicoStr>, + from_id: Vec<&'static str>, +} /// An interned string. /// @@ -16,22 +24,30 @@ static INTERNER: Lazy = Lazy::new(ThreadedRodeo::new); /// unnecessarily. For this reason, the user should use the [`PicoStr::resolve`] /// method to get the underlying string, such that the lookup is done only once. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] -pub struct PicoStr(Spur); +pub struct PicoStr(u32); impl PicoStr { /// Creates a new interned string. - pub fn new(s: impl AsRef) -> Self { - Self(INTERNER.get_or_intern(s.as_ref())) - } + pub fn new(string: &str) -> Self { + if let Some(&id) = INTERNER.read().unwrap().to_id.get(string) { + return id; + } - /// Creates a new interned string from a static string. - pub fn static_(s: &'static str) -> Self { - Self(INTERNER.get_or_intern_static(s)) + let mut interner = INTERNER.write().unwrap(); + let num = interner.from_id.len().try_into().expect("out of string ids"); + + // Create a new entry forever by leaking the string. PicoStr is only + // used for strings that aren't created en masse, so it is okay. + let id = Self(num); + let string = Box::leak(string.to_string().into_boxed_str()); + interner.to_id.insert(string, id); + interner.from_id.push(string); + id } /// Resolves the interned string. pub fn resolve(&self) -> &'static str { - INTERNER.resolve(&self.0) + INTERNER.read().unwrap().from_id[self.0 as usize] } }