From 187f431de916238fddccdf93cb5b904b298e4cdc Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 15 Mar 2024 15:41:33 +0800 Subject: [PATCH 1/3] perf: using Arc instead of String --- src/builder.rs | 17 ++++++------- src/decoder.rs | 18 ++++++++++---- src/ram_bundle.rs | 10 ++++---- src/sourceview.rs | 55 ++++++++++++++++++------------------------- src/types.rs | 39 +++++++++++++++--------------- tests/test_namemap.rs | 4 ++-- 6 files changed, 72 insertions(+), 71 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index bf0e005..62cfe7d 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -5,6 +5,7 @@ use std::env; use std::fs; use std::io::Read; use std::path::{Path, PathBuf}; +use std::sync::Arc; use debugid::DebugId; use rustc_hash::FxHashMap; @@ -20,13 +21,13 @@ use crate::types::{RawToken, SourceMap, Token}; /// type can help. pub struct SourceMapBuilder { file: Option, - name_map: FxHashMap, - names: Vec, + name_map: FxHashMap, u32>, + names: Vec>, tokens: Vec, - source_map: FxHashMap, + source_map: FxHashMap, u32>, source_root: Option, - sources: Vec, - source_contents: Vec>, + sources: Vec>, + source_contents: Vec>>, sources_mapping: Vec, debug_id: Option, } @@ -108,7 +109,7 @@ impl SourceMapBuilder { /// Changes the source name for an already set source. pub fn set_source(&mut self, src_id: u32, src: &str) { assert!(src_id != !0, "Cannot set sources for tombstone source id"); - self.sources[src_id as usize] = src.to_string(); + self.sources[src_id as usize] = src.into(); } /// Looks up a source name for an ID. @@ -122,7 +123,7 @@ impl SourceMapBuilder { if self.sources.len() > self.source_contents.len() { self.source_contents.resize(self.sources.len(), None); } - self.source_contents[src_id as usize] = contents.map(str::to_owned); + self.source_contents[src_id as usize] = contents.map(Into::into); } /// Returns the current source contents for a source. @@ -272,7 +273,7 @@ impl SourceMapBuilder { prefix.push('/'); } if source.starts_with(&prefix) { - *source = source[prefix.len()..].to_string(); + *source = source[prefix.len()..].into(); break; } } diff --git a/src/decoder.rs b/src/decoder.rs index e701c47..685a1f6 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -187,18 +187,22 @@ pub fn decode_regular(rsm: RawSourceMap) -> Result { } } - let sources = sources.into_iter().map(Option::unwrap_or_default).collect(); + let sources = sources + .into_iter() + .map(Option::unwrap_or_default) + .map(Into::into) + .collect(); // apparently we can encounter some non string types in real world // sourcemaps :( let names = names .into_iter() .map(|val| match val { - Value::String(s) => s, - Value::Number(num) => num.to_string(), + Value::String(s) => s.into(), + Value::Number(num) => num.to_string().into(), _ => "".into(), }) - .collect::>(); + .collect::>(); // file sometimes is not a string for unexplicable reasons let file = rsm.file.map(|val| match val { @@ -206,7 +210,11 @@ pub fn decode_regular(rsm: RawSourceMap) -> Result { _ => "".into(), }); - let mut sm = SourceMap::new(file, tokens, names, sources, rsm.sources_content); + let source_content = rsm + .sources_content + .map(|x| x.into_iter().map(|v| v.map(Into::into)).collect::>()); + + let mut sm = SourceMap::new(file, tokens, names, sources, source_content); sm.set_source_root(rsm.source_root); sm.set_debug_id(rsm.debug_id); diff --git a/src/ram_bundle.rs b/src/ram_bundle.rs index 23f4401..993a099 100644 --- a/src/ram_bundle.rs +++ b/src/ram_bundle.rs @@ -70,9 +70,9 @@ impl<'a> RamBundleModule<'a> { /// Returns a source view of the data. /// /// This operation fails if the source code is not valid UTF-8. - pub fn source_view(&self) -> Result> { + pub fn source_view(&self) -> Result { match std::str::from_utf8(self.data) { - Ok(s) => Ok(SourceView::new(s)), + Ok(s) => Ok(SourceView::new(s.into())), Err(e) => Err(Error::Utf8(e)), } } @@ -361,7 +361,7 @@ impl<'a> SplitRamBundleModuleIter<'a> { fn split_module( &self, module: RamBundleModule<'a>, - ) -> Result, SourceMap)>> { + ) -> Result> { let module_offset = self .offsets .get(module.id()) @@ -377,7 +377,7 @@ impl<'a> SplitRamBundleModuleIter<'a> { return Err(Error::InvalidRamBundleEntry); } - let source: SourceView<'a> = module.source_view()?; + let source: SourceView = module.source_view()?; let line_count = source.line_count() as u32; let ending_line = starting_line + line_count; let last_line_len = source @@ -416,7 +416,7 @@ impl<'a> SplitRamBundleModuleIter<'a> { } impl<'a> Iterator for SplitRamBundleModuleIter<'a> { - type Item = Result<(String, SourceView<'a>, SourceMap)>; + type Item = Result<(String, SourceView, SourceMap)>; fn next(&mut self) -> Option { while let Some(module_result) = self.ram_bundle_iter.next() { diff --git a/src/sourceview.rs b/src/sourceview.rs index b466982..61f0a15 100644 --- a/src/sourceview.rs +++ b/src/sourceview.rs @@ -1,9 +1,9 @@ -use std::borrow::Cow; use std::fmt; use std::slice; use std::str; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering; +use std::sync::Arc; use std::sync::Mutex; use if_chain::if_chain; @@ -14,19 +14,13 @@ use crate::js_identifiers::{get_javascript_token, is_valid_javascript_identifier use crate::types::{idx_from_token, sourcemap_from_token, Token}; /// An iterator that iterates over tokens in reverse. -pub struct RevTokenIter<'view, 'viewbase, 'map> -where - 'viewbase: 'view, -{ - sv: &'view SourceView<'viewbase>, +pub struct RevTokenIter<'view, 'map> { + sv: &'view SourceView, token: Option>, source_line: Option<(&'view str, usize, usize, usize)>, } -impl<'view, 'viewbase, 'map> Iterator for RevTokenIter<'view, 'viewbase, 'map> -where - 'viewbase: 'view, -{ +impl<'view, 'map> Iterator for RevTokenIter<'view, 'map> { type Item = (Token<'map>, Option<&'view str>); fn next(&mut self) -> Option<(Token<'map>, Option<&'view str>)> { @@ -118,7 +112,7 @@ where } pub struct Lines<'a> { - sv: &'a SourceView<'a>, + sv: &'a SourceView, idx: u32, } @@ -139,14 +133,14 @@ impl<'a> Iterator for Lines<'a> { /// /// This type is used to implement fairly efficient source mapping /// operations. -pub struct SourceView<'a> { - source: Cow<'a, str>, +pub struct SourceView { + source: Arc, processed_until: AtomicUsize, lines: Mutex>, } -impl<'a> Clone for SourceView<'a> { - fn clone(&self) -> SourceView<'a> { +impl Clone for SourceView { + fn clone(&self) -> SourceView { SourceView { source: self.source.clone(), processed_until: AtomicUsize::new(0), @@ -155,7 +149,7 @@ impl<'a> Clone for SourceView<'a> { } } -impl<'a> fmt::Debug for SourceView<'a> { +impl fmt::Debug for SourceView { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("SourceView") .field("source", &self.source()) @@ -163,20 +157,20 @@ impl<'a> fmt::Debug for SourceView<'a> { } } -impl<'a> SourceView<'a> { +impl SourceView { /// Creates an optimized view of a given source. - pub fn new(source: &'a str) -> SourceView<'a> { + pub fn new(source: Arc) -> SourceView { SourceView { - source: Cow::Borrowed(source), + source, processed_until: AtomicUsize::new(0), lines: Mutex::new(vec![]), } } /// Creates an optimized view from a given source string - pub fn from_string(source: String) -> SourceView<'static> { + pub fn from_string(source: String) -> SourceView { SourceView { - source: Cow::Owned(source), + source: source.into(), processed_until: AtomicUsize::new(0), lines: Mutex::new(vec![]), } @@ -264,7 +258,7 @@ impl<'a> SourceView<'a> { } /// Returns an iterator over all lines. - pub fn lines(&'a self) -> Lines<'a> { + pub fn lines(&self) -> Lines { Lines { sv: self, idx: 0 } } @@ -273,10 +267,7 @@ impl<'a> SourceView<'a> { &self.source } - fn rev_token_iter<'this, 'map>( - &'this self, - token: Token<'map>, - ) -> RevTokenIter<'this, 'a, 'map> { + fn rev_token_iter<'this, 'map>(&'this self, token: Token<'map>) -> RevTokenIter<'this, 'map> { RevTokenIter { sv: self, token: Some(token), @@ -332,7 +323,7 @@ impl<'a> SourceView<'a> { #[test] #[allow(clippy::cognitive_complexity)] fn test_minified_source_view() { - let view = SourceView::new("a\nb\nc"); + let view = SourceView::new("a\nb\nc".into()); assert_eq!(view.get_line(0), Some("a")); assert_eq!(view.get_line(0), Some("a")); assert_eq!(view.get_line(2), Some("c")); @@ -341,7 +332,7 @@ fn test_minified_source_view() { assert_eq!(view.line_count(), 3); - let view = SourceView::new("a\r\nb\r\nc"); + let view = SourceView::new("a\r\nb\r\nc".into()); assert_eq!(view.get_line(0), Some("a")); assert_eq!(view.get_line(0), Some("a")); assert_eq!(view.get_line(2), Some("c")); @@ -350,7 +341,7 @@ fn test_minified_source_view() { assert_eq!(view.line_count(), 3); - let view = SourceView::new("abc👌def\nblah"); + let view = SourceView::new("abc👌def\nblah".into()); assert_eq!(view.get_line_slice(0, 0, 3), Some("abc")); assert_eq!(view.get_line_slice(0, 3, 1), Some("👌")); assert_eq!(view.get_line_slice(0, 3, 2), Some("👌")); @@ -362,7 +353,7 @@ fn test_minified_source_view() { assert_eq!(view.get_line_slice(1, 0, 5), None); assert_eq!(view.get_line_slice(1, 0, 12), None); - let view = SourceView::new("a\nb\nc\n"); + let view = SourceView::new("a\nb\nc\n".into()); assert_eq!(view.get_line(0), Some("a")); assert_eq!(view.get_line(1), Some("b")); assert_eq!(view.get_line(2), Some("c")); @@ -371,6 +362,6 @@ fn test_minified_source_view() { fn is_send() {} fn is_sync() {} - is_send::>(); - is_sync::>(); + is_send::(); + is_sync::(); } diff --git a/src/types.rs b/src/types.rs index ce7d22f..22688a1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -3,6 +3,7 @@ use std::cmp::Ordering; use std::fmt; use std::io::{Read, Write}; use std::path::Path; +use std::sync::Arc; use crate::builder::SourceMapBuilder; use crate::decoder::{decode, decode_slice}; @@ -277,7 +278,7 @@ impl<'a> Token<'a> { } /// Returns the referenced source view. - pub fn get_source_view(&self) -> Option<&SourceView<'_>> { + pub fn get_source_view(&self) -> Option<&SourceView> { self.i.get_source_view(self.get_src_id()) } } @@ -460,11 +461,11 @@ pub struct SourceMap { pub(crate) file: Option, pub(crate) tokens: Vec, pub(crate) index: Vec<(u32, u32, u32)>, - pub(crate) names: Vec, + pub(crate) names: Vec>, pub(crate) source_root: Option, - pub(crate) sources: Vec, - pub(crate) sources_prefixed: Option>, - pub(crate) sources_content: Vec>>, + pub(crate) sources: Vec>, + pub(crate) sources_prefixed: Option>>, + pub(crate) sources_content: Vec>, pub(crate) debug_id: Option, } @@ -575,9 +576,9 @@ impl SourceMap { pub fn new( file: Option, tokens: Vec, - names: Vec, - sources: Vec, - sources_content: Option>>, + names: Vec>, + sources: Vec>, + sources_content: Option>>>, ) -> SourceMap { let mut index: Vec<_> = tokens .iter() @@ -596,7 +597,7 @@ impl SourceMap { sources_content: sources_content .unwrap_or_default() .into_iter() - .map(|opt| opt.map(SourceView::from_string)) + .map(|opt| opt.map(SourceView::new)) .collect(), debug_id: None, } @@ -627,7 +628,7 @@ impl SourceMap { self.source_root.as_deref() } - fn prefix_source(source_root: &str, source: &str) -> String { + fn prefix_source(source_root: &str, source: &str) -> Arc { let source_root = source_root.strip_suffix('/').unwrap_or(source_root); let is_valid = !source.is_empty() && (source.starts_with('/') @@ -635,9 +636,9 @@ impl SourceMap { || source.starts_with("https:")); if is_valid { - source.to_string() + source.into() } else { - format!("{source_root}/{source}") + format!("{source_root}/{source}").into() } } @@ -692,12 +693,12 @@ impl SourceMap { /// functions that do not have clear function names. (For instance it's /// recommended that dotted function names are not passed to this /// function). - pub fn get_original_function_name<'a>( + pub fn get_original_function_name( &self, line: u32, col: u32, minified_name: &str, - sv: &'a SourceView<'a>, + sv: &SourceView, ) -> Option<&str> { self.lookup_token(line, col) .and_then(|token| sv.get_original_function_name(token, minified_name)) @@ -719,7 +720,7 @@ impl SourceMap { /// /// This panics if a source is set that does not exist. pub fn set_source(&mut self, idx: u32, value: &str) { - self.sources[idx as usize] = value.to_string(); + self.sources[idx as usize] = value.into(); if let Some(sources_prefixed) = self.sources_prefixed.as_mut() { // If sources_prefixed is `Some`, we must have a nonempty `source_root`. @@ -737,7 +738,7 @@ impl SourceMap { } /// Returns the sources content as source view. - pub fn get_source_view(&self, idx: u32) -> Option<&SourceView<'_>> { + pub fn get_source_view(&self, idx: u32) -> Option<&SourceView> { self.sources_content .get(idx as usize) .and_then(Option::as_ref) @@ -869,7 +870,7 @@ impl SourceMap { } } if need_common_prefix { - if let Some(prefix) = find_common_prefix(self.sources.iter().map(String::as_str)) { + if let Some(prefix) = find_common_prefix(self.sources.iter().map(AsRef::as_ref)) { prefixes.push(prefix); } } @@ -1141,12 +1142,12 @@ impl SourceMapIndex { /// functions that do not have clear function names. (For instance it's /// recommended that dotted function names are not passed to this /// function). - pub fn get_original_function_name<'a>( + pub fn get_original_function_name( &self, line: u32, col: u32, minified_name: &str, - sv: &'a SourceView<'a>, + sv: &SourceView, ) -> Option<&str> { self.lookup_token(line, col) .and_then(|token| sv.get_original_function_name(token, minified_name)) diff --git a/tests/test_namemap.rs b/tests/test_namemap.rs index 986ee6c..19c256e 100644 --- a/tests/test_namemap.rs +++ b/tests/test_namemap.rs @@ -4,7 +4,7 @@ use sourcemap::{SourceMap, SourceView}; fn test_basic_name_mapping() { let input: &[_] = br#"{"version":3,"file":"test.min.js","sources":["test.js"],"names":["makeAFailure","testingStuff","Error","onSuccess","data","onFailure","invoke","cb","failed","test","value"],"mappings":"AAAA,GAAIA,cAAe,WACjB,QAASC,KACP,GAAIA,GAAe,EACnB,MAAM,IAAIC,OAAMD,GAGlB,QAASE,GAAUC,GACjBH,IAGF,QAASI,GAAUD,GACjB,KAAM,IAAIF,OAAM,WAGlB,QAASI,GAAOF,GACd,GAAIG,GAAK,IACT,IAAIH,EAAKI,OAAQ,CACfD,EAAKF,MACA,CACLE,EAAKJ,EAEPI,EAAGH,GAGL,QAASK,KACP,GAAIL,IAAQI,OAAQ,KAAME,MAAO,GACjCJ,GAAOF,GAGT,MAAOK","sourcesContent":["var makeAFailure = (function() {\n function testingStuff() {\n var testingStuff = 42;\n throw new Error(testingStuff);\n }\n\n function onSuccess(data) {\n testingStuff();\n }\n\n function onFailure(data) {\n throw new Error('failed!');\n }\n\n function invoke(data) {\n var cb = null;\n if (data.failed) {\n cb = onFailure;\n } else {\n cb = onSuccess;\n }\n cb(data);\n }\n\n function test() {\n var data = {failed: true, value: 42};\n invoke(data);\n }\n\n return test;\n})();\n"]}"#; let minified_file = r#"var makeAFailure=function(){function n(){var n=42;throw new Error(n)}function r(r){n()}function e(n){throw new Error("failed!")}function i(n){var i=null;if(n.failed){i=e}else{i=r}i(n)}function u(){var n={failed:true,value:42};i(n)}return u}();"#; - let sv = SourceView::new(minified_file); + let sv = SourceView::new(minified_file.into()); let sm = SourceMap::from_reader(input).unwrap(); let name = sm.get_original_function_name(0, 107, "e", &sv); @@ -27,7 +27,7 @@ fn test_basic_name_mapping() { fn test_unicode_mapping() { let input = r#"{"version":3,"file":"test.min.js","sources":["test.js"],"names":["makeAFailure","onSuccess","data","onFailure","Error","invoke","cb","failed","㮏","value"],"mappings":"AAAA,GAAIA,cAAe,WACjB,QAASC,GAAUC,IAEnB,QAASC,GAAUD,IACjB,WACE,KAAM,IAAIE,OAAM,eAIpB,QAASC,GAAOH,GACd,GAAII,GAAK,IACT,IAAIJ,EAAKK,OAAQ,CACfD,EAAKH,MACA,CACLG,EAAKL,EAEPK,EAAGJ,GAGI,QAASM,KAChB,GAAIN,IAAQK,OAAQ,KAAME,MAAO,GACjCJ,GAAOH,GAGT,MAAOM","sourcesContent":["var makeAFailure = (function() {\n function onSuccess(data) {}\n\n function onFailure(data) {\n (function() {\n throw new Error('failed!');\n })();\n }\n\n function invoke(data) {\n var cb = null;\n if (data.failed) {\n cb = onFailure;\n } else {\n cb = onSuccess;\n }\n cb(data);\n }\n\n /* 😍 */ function 㮏() {\n var data = {failed: true, value: 42};\n invoke(data);\n }\n\n return 㮏;\n})();\n"]}"#.as_bytes(); let minified_file = r#"var makeAFailure=function(){function n(n){}function e(n){(function(){throw new Error("failed!")})()}function i(i){var r=null;if(i.failed){r=e}else{r=n}r(i)}function r(){var n={failed:true,value:42};i(n)}return r}();"#; - let sv = SourceView::new(minified_file); + let sv = SourceView::new(minified_file.into()); let sm = SourceMap::from_reader(input).unwrap(); // stacktrace From bfa5d88d0f75fd75ab274081c39679f67c6285e0 Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 15 Mar 2024 15:52:58 +0800 Subject: [PATCH 2/3] perf: add more --- src/builder.rs | 10 +++++----- src/decoder.rs | 2 +- src/types.rs | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 62cfe7d..838de5e 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -20,12 +20,12 @@ use crate::types::{RawToken, SourceMap, Token}; /// objects is generally not very comfortable. As a general aid this /// type can help. pub struct SourceMapBuilder { - file: Option, + file: Option>, name_map: FxHashMap, u32>, names: Vec>, tokens: Vec, source_map: FxHashMap, u32>, - source_root: Option, + source_root: Option>, sources: Vec>, source_contents: Vec>>, sources_mapping: Vec, @@ -54,7 +54,7 @@ impl SourceMapBuilder { pub fn new(file: Option<&str>) -> SourceMapBuilder { SourceMapBuilder { file: file.map(str::to_owned), - name_map: FxHashMap::default(), + name_map: HashMap::new(), names: vec![], tokens: vec![], source_map: FxHashMap::default(), @@ -72,7 +72,7 @@ impl SourceMapBuilder { } /// Sets the file for the sourcemap (optional) - pub fn set_file>(&mut self, value: Option) { + pub fn set_file>>(&mut self, value: Option) { self.file = value.map(Into::into); } @@ -82,7 +82,7 @@ impl SourceMapBuilder { } /// Sets a new value for the source_root. - pub fn set_source_root>(&mut self, value: Option) { + pub fn set_source_root>>(&mut self, value: Option) { self.source_root = value.map(Into::into); } diff --git a/src/decoder.rs b/src/decoder.rs index 685a1f6..c74c773 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -206,7 +206,7 @@ pub fn decode_regular(rsm: RawSourceMap) -> Result { // file sometimes is not a string for unexplicable reasons let file = rsm.file.map(|val| match val { - Value::String(s) => s, + Value::String(s) => s.into(), _ => "".into(), }); diff --git a/src/types.rs b/src/types.rs index 22688a1..d0d93a1 100644 --- a/src/types.rs +++ b/src/types.rs @@ -458,11 +458,11 @@ pub struct SourceMapIndex { /// rejected with an error on reading. #[derive(Clone, Debug)] pub struct SourceMap { - pub(crate) file: Option, + pub(crate) file: Option>, pub(crate) tokens: Vec, pub(crate) index: Vec<(u32, u32, u32)>, pub(crate) names: Vec>, - pub(crate) source_root: Option, + pub(crate) source_root: Option>, pub(crate) sources: Vec>, pub(crate) sources_prefixed: Option>>, pub(crate) sources_content: Vec>, @@ -574,7 +574,7 @@ impl SourceMap { /// - `sources` a vector of source filenames /// - `sources_content` optional source contents pub fn new( - file: Option, + file: Option>, tokens: Vec, names: Vec>, sources: Vec>, @@ -619,7 +619,7 @@ impl SourceMap { } /// Sets a new value for the file. - pub fn set_file>(&mut self, value: Option) { + pub fn set_file>>(&mut self, value: Option) { self.file = value.map(Into::into); } @@ -643,7 +643,7 @@ impl SourceMap { } /// Sets a new value for the source_root. - pub fn set_source_root>(&mut self, value: Option) { + pub fn set_source_root>>(&mut self, value: Option) { self.source_root = value.map(Into::into); match self.source_root.as_deref().filter(|rs| !rs.is_empty()) { From 89244e85ddd189f85ca7eeb05a219ed87dda1b10 Mon Sep 17 00:00:00 2001 From: underfin Date: Fri, 15 Mar 2024 18:35:23 +0800 Subject: [PATCH 3/3] chore: rebase --- src/builder.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/builder.rs b/src/builder.rs index 838de5e..d8e0357 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -53,8 +53,8 @@ impl SourceMapBuilder { /// Creates a new source map builder and sets the file. pub fn new(file: Option<&str>) -> SourceMapBuilder { SourceMapBuilder { - file: file.map(str::to_owned), - name_map: HashMap::new(), + file: file.map(Into::into), + name_map: FxHashMap::default(), names: vec![], tokens: vec![], source_map: FxHashMap::default(),