Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: do not clone swc Program when transpiling #23365

Merged
merged 1 commit into from
Apr 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions cli/cache/parsed_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -54,30 +56,38 @@ impl ParsedSourceCache {
&self,
module: &deno_graph::JsModule,
) -> Result<ParsedSource, deno_ast::ParseDiagnostic> {
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<str>,
media_type: MediaType,
) -> deno_core::anyhow::Result<ParsedSource, deno_ast::ParseDiagnostic> {
let parser = self.as_capturing_parser();
// this will conditionally parse because it's using a CapturingModuleParser
parser.parse_module(ParseOptions {
) -> Result<ParsedSource, deno_ast::ParseDiagnostic> {
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.
Expand All @@ -100,22 +110,29 @@ impl ParsedSourceCache {
impl deno_graph::ParsedSourceStore for ParsedSourceCache {
fn set_parsed_source(
&self,
specifier: deno_graph::ModuleSpecifier,
specifier: ModuleSpecifier,
parsed_source: ParsedSource,
) -> Option<ParsedSource> {
self.sources.lock().insert(specifier, parsed_source)
}

fn get_parsed_source(
&self,
specifier: &deno_graph::ModuleSpecifier,
specifier: &ModuleSpecifier,
) -> Option<ParsedSource> {
self.sources.lock().get(specifier).cloned()
}

fn remove_parsed_source(
&self,
specifier: &ModuleSpecifier,
) -> Option<ParsedSource> {
self.sources.lock().remove(specifier)
}

fn get_scope_analysis_parsed_source(
&self,
specifier: &deno_graph::ModuleSpecifier,
specifier: &ModuleSpecifier,
) -> Option<ParsedSource> {
let mut sources = self.sources.lock();
let parsed_source = sources.get(specifier)?;
Expand Down
24 changes: 17 additions & 7 deletions cli/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?
}
};
Copy link
Member Author

@dsherret dsherret Apr 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API will change to a single transpile method in the future: denoland/deno_ast#239 (this was just done in a non-breaking way so we could land it in the CLI without needing to do a breaking deno_ast upgrade)

debug_assert!(transpiled_source.source_map.is_none());
self.emit_cache.set_emit_code(
specifier,
Expand All @@ -124,11 +134,11 @@ impl Emitter {
let source_arc: Arc<str> = 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)
}

Expand Down
28 changes: 15 additions & 13 deletions runtime/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SourceMapData> = transpiled_source
.source_map
Expand Down