Skip to content

Commit 9be6f5e

Browse files
committed
perf: source map from json
1 parent fb30758 commit 9be6f5e

File tree

8 files changed

+749
-302
lines changed

8 files changed

+749
-302
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ itertools = "0.13"
4242
codspeed-criterion-compat = { version = "2.7.2", default-features = false, optional = true }
4343
static_assertions = "1.1.0"
4444
simd-json = "0.14.3"
45+
self_cell = "1.2.0"
4546

4647
[dev-dependencies]
4748
twox-hash = "2.1.0"

src/cached_source.rs

Lines changed: 113 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use crate::{
1313
stream_chunks_of_source_map, StreamChunks,
1414
},
1515
rope::Rope,
16-
MapOptions, Source, SourceMap,
16+
BoxSource, MapOptions, Source, SourceExt, SourceMap,
1717
};
1818

1919
/// It tries to reused cached results from other methods to avoid calculations,
@@ -49,36 +49,61 @@ use crate::{
4949
/// "Hello World\nconsole.log('test');\nconsole.log('test2');\nHello2\n"
5050
/// );
5151
/// ```
52-
pub struct CachedSource<T> {
53-
inner: Arc<T>,
54-
cached_hash: Arc<OnceLock<u64>>,
55-
cached_maps:
56-
Arc<DashMap<MapOptions, Option<SourceMap>, BuildHasherDefault<FxHasher>>>,
52+
53+
#[derive(Debug)]
54+
struct CachedSourceOwner {
55+
inner: BoxSource,
56+
cached_hash: OnceLock<u64>,
57+
}
58+
59+
#[derive(Debug)]
60+
struct CachedSourceDependent<'a> {
61+
cached_colomns_map: OnceLock<Option<SourceMap<'static>>>,
62+
cached_line_only_map: OnceLock<Option<SourceMap<'static>>>,
63+
phantom: std::marker::PhantomData<&'a ()>,
5764
}
5865

59-
impl<T> CachedSource<T> {
66+
self_cell::self_cell!(
67+
struct CachedSourceCell {
68+
owner: CachedSourceOwner,
69+
70+
#[covariant]
71+
dependent: CachedSourceDependent,
72+
}
73+
74+
impl { Debug }
75+
);
76+
77+
/// A wrapper around any [`Source`] that caches expensive computations to improve performance.
78+
pub struct CachedSource(CachedSourceCell);
79+
80+
impl CachedSource {
6081
/// Create a [CachedSource] with the original [Source].
61-
pub fn new(inner: T) -> Self {
62-
Self {
63-
inner: Arc::new(inner),
64-
cached_hash: Default::default(),
65-
cached_maps: Default::default(),
66-
}
82+
pub fn new<T: SourceExt>(inner: T) -> Self {
83+
let owner = CachedSourceOwner {
84+
inner: inner.boxed(),
85+
cached_hash: OnceLock::new(),
86+
};
87+
Self(CachedSourceCell::new(owner, |_| CachedSourceDependent {
88+
cached_colomns_map: Default::default(),
89+
cached_line_only_map: Default::default(),
90+
phantom: std::marker::PhantomData,
91+
}))
6792
}
6893

6994
/// Get the original [Source].
70-
pub fn original(&self) -> &T {
71-
&self.inner
95+
pub fn original(&self) -> &BoxSource {
96+
&self.0.borrow_owner().inner
7297
}
7398
}
7499

75-
impl<T: Source + Hash + PartialEq + Eq + 'static> Source for CachedSource<T> {
100+
impl Source for CachedSource {
76101
fn source(&self) -> Cow<str> {
77-
self.inner.source()
102+
self.0.borrow_owner().inner.source()
78103
}
79104

80105
fn rope(&self) -> Rope<'_> {
81-
self.inner.rope()
106+
self.0.borrow_owner().inner.rope()
82107
}
83108

84109
fn buffer(&self) -> Cow<[u8]> {
@@ -88,101 +113,118 @@ impl<T: Source + Hash + PartialEq + Eq + 'static> Source for CachedSource<T> {
88113
}
89114

90115
fn size(&self) -> usize {
91-
self.inner.size()
116+
self.0.borrow_owner().inner.size()
92117
}
93118

94119
fn map(&self, options: &MapOptions) -> Option<SourceMap> {
95-
if let Some(map) = self.cached_maps.get(options) {
96-
map.clone()
120+
if options.columns {
121+
self.0.with_dependent(|owner, dependent| {
122+
dependent.cached_colomns_map.get_or_init(|| {
123+
let map = owner.inner.map(options);
124+
unsafe { std::mem::transmute::<Option<SourceMap>, Option<SourceMap<'static>>>(map) }
125+
})
126+
.as_ref()
127+
.map(|m| m.as_borrowed())
128+
})
97129
} else {
98-
let map = self.inner.map(options);
99-
self.cached_maps.insert(options.clone(), map.clone());
100-
map
130+
self.0.with_dependent(|owner, dependent| {
131+
dependent.cached_line_only_map.get_or_init(|| {
132+
let map = owner.inner.map(options);
133+
unsafe { std::mem::transmute::<Option<SourceMap>, Option<SourceMap<'static>>>(map) }
134+
})
135+
.as_ref()
136+
.map(|m| m.as_borrowed())
137+
})
101138
}
102139
}
103140

104141
fn to_writer(&self, writer: &mut dyn std::io::Write) -> std::io::Result<()> {
105-
self.inner.to_writer(writer)
142+
self.0.borrow_owner().inner.to_writer(writer)
106143
}
107144
}
108145

109-
impl<T: Source + Hash + PartialEq + Eq + 'static> StreamChunks
110-
for CachedSource<T>
111-
{
146+
impl StreamChunks for CachedSource {
112147
fn stream_chunks<'a>(
113148
&'a self,
114149
options: &MapOptions,
115150
on_chunk: crate::helpers::OnChunk<'_, 'a>,
116151
on_source: crate::helpers::OnSource<'_, 'a>,
117152
on_name: crate::helpers::OnName<'_, 'a>,
118153
) -> crate::helpers::GeneratedInfo {
119-
let cached_map = self.cached_maps.entry(options.clone());
120-
match cached_map {
121-
Entry::Occupied(entry) => {
122-
let source = self.rope();
123-
if let Some(map) = entry.get() {
124-
#[allow(unsafe_code)]
125-
// SAFETY: We guarantee that once a `SourceMap` is stored in the cache, it will never be removed.
126-
// Therefore, even if we force its lifetime to be longer, the reference remains valid.
127-
// This is based on the following assumptions:
128-
// 1. `SourceMap` will be valid for the entire duration of the application.
129-
// 2. The cached `SourceMap` will not be manually removed or replaced, ensuring the reference's safety.
130-
let map =
131-
unsafe { std::mem::transmute::<&SourceMap, &'a SourceMap>(map) };
132-
stream_chunks_of_source_map(
133-
source, map, on_chunk, on_source, on_name, options,
134-
)
135-
} else {
136-
stream_chunks_of_raw_source(
137-
source, options, on_chunk, on_source, on_name,
138-
)
139-
}
154+
let cached = if options.columns {
155+
self.0.borrow_dependent().cached_colomns_map.get()
156+
} else {
157+
self.0.borrow_dependent().cached_line_only_map.get()
158+
};
159+
match cached {
160+
Some(Some(map)) => {
161+
let source = self.0.borrow_owner().inner.rope();
162+
stream_chunks_of_source_map(
163+
source, map, on_chunk, on_source, on_name, options,
164+
)
165+
}
166+
Some(None) => {
167+
let source = self.0.borrow_owner().inner.rope();
168+
stream_chunks_of_raw_source(
169+
source, options, on_chunk, on_source, on_name,
170+
)
140171
}
141-
Entry::Vacant(entry) => {
142-
let (generated_info, map) = stream_and_get_source_and_map(
143-
&self.inner as &T,
172+
None => {
173+
if options.columns {
174+
self.0.with_dependent(|owner, dependent| {
175+
let (generated_info, map) = stream_and_get_source_and_map(
176+
&owner.inner,
144177
options,
145178
on_chunk,
146179
on_source,
147180
on_name,
148181
);
149-
entry.insert(map);
182+
dependent.cached_colomns_map.get_or_init(|| {
183+
unsafe { std::mem::transmute::<Option<SourceMap>, Option<SourceMap<'static>>>(map) }
184+
});
185+
generated_info
186+
})
187+
} else {
188+
self.0.with_dependent(|owner, dependent| {
189+
let (generated_info, map) = stream_and_get_source_and_map(
190+
&owner.inner,
191+
options,
192+
on_chunk,
193+
on_source,
194+
on_name,
195+
);
196+
dependent.cached_line_only_map.get_or_init(|| {
197+
unsafe { std::mem::transmute::<Option<SourceMap>, Option<SourceMap<'static>>>(map) }
198+
});
150199
generated_info
200+
})
201+
}
151202
}
152203
}
153204
}
154205
}
155206

156-
impl<T> Clone for CachedSource<T> {
157-
fn clone(&self) -> Self {
158-
Self {
159-
inner: self.inner.clone(),
160-
cached_hash: self.cached_hash.clone(),
161-
cached_maps: self.cached_maps.clone(),
162-
}
163-
}
164-
}
165-
166-
impl<T: Source + Hash + PartialEq + Eq + 'static> Hash for CachedSource<T> {
207+
impl Hash for CachedSource {
167208
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
168-
(self.cached_hash.get_or_init(|| {
209+
let owner = self.0.borrow_owner();
210+
(owner.cached_hash.get_or_init(|| {
169211
let mut hasher = FxHasher::default();
170-
self.inner.hash(&mut hasher);
212+
owner.inner.hash(&mut hasher);
171213
hasher.finish()
172214
}))
173215
.hash(state);
174216
}
175217
}
176218

177-
impl<T: PartialEq> PartialEq for CachedSource<T> {
219+
impl PartialEq for CachedSource {
178220
fn eq(&self, other: &Self) -> bool {
179-
self.inner == other.inner
221+
&self.0.borrow_owner().inner == &other.0.borrow_owner().inner
180222
}
181223
}
182224

183-
impl<T: Eq> Eq for CachedSource<T> {}
225+
impl Eq for CachedSource {}
184226

185-
impl<T: std::fmt::Debug> std::fmt::Debug for CachedSource<T> {
227+
impl std::fmt::Debug for CachedSource {
186228
fn fmt(
187229
&self,
188230
f: &mut std::fmt::Formatter<'_>,
@@ -194,7 +236,7 @@ impl<T: std::fmt::Debug> std::fmt::Debug for CachedSource<T> {
194236
writeln!(
195237
f,
196238
"{indent_str}{:indent$?}",
197-
self.inner,
239+
self.0.borrow_owner().inner,
198240
indent = indent + 2
199241
)?;
200242
write!(f, "{indent_str}).boxed()")
@@ -230,25 +272,6 @@ mod tests {
230272
assert_eq!(map.mappings(), ";;AACA");
231273
}
232274

233-
#[test]
234-
fn should_allow_to_store_and_share_cached_data() {
235-
let original = OriginalSource::new("Hello World", "test.txt");
236-
let source = CachedSource::new(original);
237-
let clone = source.clone();
238-
239-
// fill up cache
240-
let map_options = MapOptions::default();
241-
source.source();
242-
source.buffer();
243-
source.size();
244-
source.map(&map_options);
245-
246-
assert_eq!(
247-
*clone.cached_maps.get(&map_options).unwrap().value(),
248-
source.map(&map_options)
249-
);
250-
}
251-
252275
#[test]
253276
fn should_return_the_correct_size_for_binary_files() {
254277
let source = OriginalSource::new(

0 commit comments

Comments
 (0)