From cf5d4f6281c4f627b835d57dcbcd384400a84e19 Mon Sep 17 00:00:00 2001 From: Tobias Koppers Date: Fri, 7 Feb 2025 14:46:52 +0100 Subject: [PATCH] try to avoid cloning AST during code generation, pass final_read_hint --- .../crates/turbopack-ecmascript/src/lib.rs | 73 +++++++++++++++---- .../turbopack-ecmascript/src/swc_comments.rs | 49 ++++++++++--- 2 files changed, 98 insertions(+), 24 deletions(-) diff --git a/turbopack/crates/turbopack-ecmascript/src/lib.rs b/turbopack/crates/turbopack-ecmascript/src/lib.rs index 27a6232dc6666..bdbdf870719e0 100644 --- a/turbopack/crates/turbopack-ecmascript/src/lib.rs +++ b/turbopack/crates/turbopack-ecmascript/src/lib.rs @@ -34,11 +34,16 @@ pub mod utils; pub mod webpack; pub mod worker_chunk; -use std::fmt::{Display, Formatter}; +use std::{ + fmt::{Display, Formatter}, + mem::take, + sync::Arc, +}; use anyhow::Result; use chunk::EcmascriptChunkItem; use code_gen::{CodeGenerateable, CodeGeneration, CodeGenerationHoistedStmt}; +use either::Either; use parse::{parse, ParseResult}; use path_visitor::ApplyVisitors; use references::esm::UrlRewriteBehavior; @@ -46,7 +51,7 @@ pub use references::{AnalyzeEcmascriptModuleResult, TURBOPACK_HELPER}; use serde::{Deserialize, Serialize}; pub use static_code::StaticEcmascriptCode; use swc_core::{ - common::{Globals, Mark, GLOBALS}, + common::{comments::Comments, util::take::Take, Globals, Mark, GLOBALS}, ecma::{ ast::{self, ModuleItem, Program, Script}, codegen::{text_writer::JsWriter, Emitter}, @@ -835,18 +840,51 @@ async fn gen_content_with_code_gens( generate_source_map: Vc, original_source_map: ResolvedVc, ) -> Result> { - let parsed = parsed.await?; + let parsed = parsed.final_read_hint().await?; match &*parsed { - ParseResult::Ok { - program, - source_map, - globals, - eval_context, - comments, - .. - } => { - let mut program = program.clone(); + ParseResult::Ok { .. } => { + // We need a mutable version of the AST. We try to avoid cloning it by unwrapping the + // ReadRef. + let mut parsed = ReadRef::try_unwrap(parsed); + let (mut program, source_map, globals, eval_context, comments) = match &mut parsed { + Ok(ParseResult::Ok { + program, + source_map, + globals, + eval_context, + comments, + }) => ( + program.take(), + &*source_map, + &*globals, + &*eval_context, + match Arc::try_unwrap(take(comments)) { + Ok(comments) => Either::Left(comments), + Err(comments) => Either::Right(comments), + }, + ), + Err(parsed) => { + let ParseResult::Ok { + program, + source_map, + globals, + eval_context, + comments, + } = &**parsed + else { + unreachable!(); + }; + ( + program.clone(), + source_map, + globals, + eval_context, + Either::Right(comments.clone()), + ) + } + _ => unreachable!(), + }; process_content_with_code_gens( &mut program, @@ -864,7 +902,15 @@ async fn gen_content_with_code_gens( let generate_source_map = *generate_source_map.await?; { - let comments = comments.consumable(); + let comments = match comments { + Either::Left(comments) => Either::Left(comments.into_consumable()), + Either::Right(ref comments) => Either::Right(comments.consumable()), + }; + let comments: &dyn Comments = match &comments { + Either::Left(comments) => comments, + Either::Right(comments) => comments, + }; + let mut emitter = Emitter { cfg: swc_core::ecma::codegen::Config::default(), cm: source_map.clone(), @@ -876,6 +922,7 @@ async fn gen_content_with_code_gens( generate_source_map.then_some(&mut mappings), ), }; + emitter.emit_program(&program)?; } diff --git a/turbopack/crates/turbopack-ecmascript/src/swc_comments.rs b/turbopack/crates/turbopack-ecmascript/src/swc_comments.rs index c4f7a683ce1f4..4c9000aa06aed 100644 --- a/turbopack/crates/turbopack-ecmascript/src/swc_comments.rs +++ b/turbopack/crates/turbopack-ecmascript/src/swc_comments.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, mem::take}; +use std::{borrow::Cow, cell::RefCell, mem::take}; use rustc_hash::FxHashMap; use swc_core::{ @@ -12,6 +12,7 @@ use swc_core::{ /// Immutable version of [SwcComments] which doesn't allow mutation. The `take` /// variants are still implemented, but do not mutate the content. They are used /// by the SWC Emitter. +#[derive(Default)] pub struct ImmutableComments { pub leading: FxHashMap>, pub trailing: FxHashMap>, @@ -39,8 +40,12 @@ impl ImmutableComments { } } + pub fn into_consumable(self) -> CowComments<'static> { + CowComments::owned(self) + } + pub fn consumable(&self) -> CowComments<'_> { - CowComments::new(self) + CowComments::borrowed(self) } } @@ -186,25 +191,44 @@ impl Comments for ImmutableComments { } pub struct CowComments<'a> { - leading: RefCell>>, - trailing: RefCell>>, + leading: RefCell>>>, + trailing: RefCell>>>, } impl<'a> CowComments<'a> { - fn new(comments: &'a ImmutableComments) -> Self { + fn borrowed(comments: &'a ImmutableComments) -> Self { Self { leading: RefCell::new( comments .leading .iter() - .map(|(&key, value)| (key, value)) + .map(|(&key, value)| (key, Cow::Borrowed(value))) .collect(), ), trailing: RefCell::new( comments .trailing .iter() - .map(|(&key, value)| (key, value)) + .map(|(&key, value)| (key, Cow::Borrowed(value))) + .collect(), + ), + } + } + + fn owned(comments: ImmutableComments) -> Self { + Self { + leading: RefCell::new( + comments + .leading + .into_iter() + .map(|(key, value)| (key, Cow::Owned(value))) + .collect(), + ), + trailing: RefCell::new( + comments + .trailing + .into_iter() + .map(|(key, value)| (key, Cow::Owned(value))) .collect(), ), } @@ -240,14 +264,17 @@ impl Comments for CowComments<'_> { &self, pos: swc_core::common::BytePos, ) -> Option> { - self.leading.borrow_mut().remove(&pos).map(|v| v.to_owned()) + self.leading + .borrow_mut() + .remove(&pos) + .map(|v| v.into_owned()) } fn get_leading( &self, pos: swc_core::common::BytePos, ) -> Option> { - self.leading.borrow().get(&pos).map(|&v| v.to_owned()) + self.leading.borrow().get(&pos).map(|v| (**v).clone()) } fn add_trailing( @@ -281,14 +308,14 @@ impl Comments for CowComments<'_> { self.trailing .borrow_mut() .remove(&pos) - .map(|v| v.to_owned()) + .map(|v| v.into_owned()) } fn get_trailing( &self, pos: swc_core::common::BytePos, ) -> Option> { - self.trailing.borrow().get(&pos).map(|&v| v.to_owned()) + self.trailing.borrow().get(&pos).map(|v| (**v).clone()) } fn add_pure_comment(&self, _pos: swc_core::common::BytePos) {