From 7f6ca1231d13ed55b59b0d906fc9d4c95f19d2b2 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 14 Apr 2024 17:15:17 -0400 Subject: [PATCH] perf: do not clone swc `Program` when transpiling (#23365) --- cli/cache/parsed_source.rs | 51 +++++++++++++++++++++++++------------- cli/emit.rs | 24 ++++++++++++------ runtime/shared.rs | 28 +++++++++++---------- 3 files changed, 66 insertions(+), 37 deletions(-) diff --git a/cli/cache/parsed_source.rs b/cli/cache/parsed_source.rs index 8d98587e2f0c50..94accb616b002b 100644 --- a/cli/cache/parsed_source.rs +++ b/cli/cache/parsed_source.rs @@ -8,8 +8,10 @@ use deno_ast::ModuleSpecifier; use deno_ast::ParsedSource; use deno_core::parking_lot::Mutex; use deno_graph::CapturingModuleParser; +use deno_graph::DefaultModuleParser; use deno_graph::ModuleParser; use deno_graph::ParseOptions; +use deno_graph::ParsedSourceStore; /// Lazily parses JS/TS sources from a `deno_graph::ModuleGraph` given /// a `ParsedSourceCache`. Note that deno_graph doesn't necessarily cause @@ -54,30 +56,38 @@ impl ParsedSourceCache { &self, module: &deno_graph::JsModule, ) -> Result { - self.get_or_parse_module( - &module.specifier, - module.source.clone(), - module.media_type, - ) + let parser = self.as_capturing_parser(); + // this will conditionally parse because it's using a CapturingModuleParser + parser.parse_module(ParseOptions { + specifier: &module.specifier, + source: module.source.clone(), + media_type: module.media_type, + // don't bother enabling because this method is currently only used for vendoring + scope_analysis: false, + }) } - /// Gets the matching `ParsedSource` from the cache - /// or parses a new one and stores that in the cache. - pub fn get_or_parse_module( + pub fn remove_or_parse_module( &self, - specifier: &deno_graph::ModuleSpecifier, + specifier: &ModuleSpecifier, source: Arc, media_type: MediaType, - ) -> deno_core::anyhow::Result { - let parser = self.as_capturing_parser(); - // this will conditionally parse because it's using a CapturingModuleParser - parser.parse_module(ParseOptions { + ) -> Result { + if let Some(parsed_source) = self.remove_parsed_source(specifier) { + if parsed_source.media_type() == media_type + && parsed_source.text_info().text_str() == source.as_ref() + { + return Ok(parsed_source); + } + } + let options = ParseOptions { specifier, source, media_type, // don't bother enabling because this method is currently only used for emitting scope_analysis: false, - }) + }; + DefaultModuleParser.parse_module(options) } /// Frees the parsed source from memory. @@ -100,7 +110,7 @@ impl ParsedSourceCache { impl deno_graph::ParsedSourceStore for ParsedSourceCache { fn set_parsed_source( &self, - specifier: deno_graph::ModuleSpecifier, + specifier: ModuleSpecifier, parsed_source: ParsedSource, ) -> Option { self.sources.lock().insert(specifier, parsed_source) @@ -108,14 +118,21 @@ impl deno_graph::ParsedSourceStore for ParsedSourceCache { fn get_parsed_source( &self, - specifier: &deno_graph::ModuleSpecifier, + specifier: &ModuleSpecifier, ) -> Option { self.sources.lock().get(specifier).cloned() } + fn remove_parsed_source( + &self, + specifier: &ModuleSpecifier, + ) -> Option { + self.sources.lock().remove(specifier) + } + fn get_scope_analysis_parsed_source( &self, - specifier: &deno_graph::ModuleSpecifier, + specifier: &ModuleSpecifier, ) -> Option { let mut sources = self.sources.lock(); let parsed_source = sources.get(specifier)?; diff --git a/cli/emit.rs b/cli/emit.rs index 0c79b11eedb2f5..07343f39d05af4 100644 --- a/cli/emit.rs +++ b/cli/emit.rs @@ -93,14 +93,24 @@ impl Emitter { { Ok(emit_code.into()) } else { - // this will use a cached version if it exists - let parsed_source = self.parsed_source_cache.get_or_parse_module( + // nothing else needs the parsed source at this point, so remove from + // the cache in order to not transpile owned + let parsed_source = self.parsed_source_cache.remove_or_parse_module( specifier, source.clone(), media_type, )?; - let transpiled_source = - parsed_source.transpile(&self.transpile_options, &self.emit_options)?; + let transpiled_source = match parsed_source + .transpile_owned(&self.transpile_options, &self.emit_options) + { + Ok(result) => result?, + Err(parsed_source) => { + // transpile_owned is more efficient and should be preferred + debug_assert!(false, "Transpile owned failed."); + parsed_source + .transpile(&self.transpile_options, &self.emit_options)? + } + }; debug_assert!(transpiled_source.source_map.is_none()); self.emit_cache.set_emit_code( specifier, @@ -124,11 +134,11 @@ impl Emitter { let source_arc: Arc = source_code.into(); let parsed_source = self .parsed_source_cache - .get_or_parse_module(specifier, source_arc, media_type)?; + .remove_or_parse_module(specifier, source_arc, media_type)?; let mut options = self.emit_options; options.source_map = SourceMapOption::None; - let transpiled_source = - parsed_source.transpile(&self.transpile_options, &options)?; + let transpiled_source = parsed_source + .transpile_owned_with_fallback(&self.transpile_options, &options)?; Ok(transpiled_source.text) } diff --git a/runtime/shared.rs b/runtime/shared.rs index c5ea2fedd49500..e18b0b93c377f8 100644 --- a/runtime/shared.rs +++ b/runtime/shared.rs @@ -95,20 +95,22 @@ pub fn maybe_transpile_source( scope_analysis: false, maybe_syntax: None, })?; - let transpiled_source = parsed.transpile( - &deno_ast::TranspileOptions { - imports_not_used_as_values: deno_ast::ImportsNotUsedAsValues::Remove, - ..Default::default() - }, - &deno_ast::EmitOptions { - source_map: if cfg!(debug_assertions) { - SourceMapOption::Separate - } else { - SourceMapOption::None + let transpiled_source = parsed + .transpile_owned( + &deno_ast::TranspileOptions { + imports_not_used_as_values: deno_ast::ImportsNotUsedAsValues::Remove, + ..Default::default() }, - ..Default::default() - }, - )?; + &deno_ast::EmitOptions { + source_map: if cfg!(debug_assertions) { + SourceMapOption::Separate + } else { + SourceMapOption::None + }, + ..Default::default() + }, + ) + .unwrap()?; let maybe_source_map: Option = transpiled_source .source_map