Skip to content

Commit

Permalink
perf: Merge multiple EsmBinding (vercel/turborepo#8699)
Browse files Browse the repository at this point in the history
### Description

Merge multiple `EsmBinding` into single `CodeGeneratable`


### Testing Instructions

This is a pure optimization, so if the behavior is same it's all good.



Closes PACK-3147
  • Loading branch information
kdy1 authored Jul 11, 2024
1 parent 819d11f commit ccea243
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 95 deletions.
201 changes: 110 additions & 91 deletions crates/turbopack-ecmascript/src/references/esm/binding.rs
Original file line number Diff line number Diff line change
@@ -1,100 +1,70 @@
use anyhow::Result;
use serde::{Deserialize, Serialize};
use swc_core::{
common::{Span, SyntaxContext},
ecma::{
ast::{
ComputedPropName, Expr, Ident, KeyValueProp, Lit, MemberExpr, MemberProp, Number, Prop,
PropName, SeqExpr, SimpleAssignTarget, Str,
},
visit::fields::{CalleeField, PropField},
visit::{
fields::{CalleeField, PropField},
AstParentKind,
},
},
};
use turbo_tasks::{RcStr, Vc};
use turbo_tasks::{trace::TraceRawVcs, RcStr, TaskInput, Vc};
use turbopack_core::chunk::ChunkingContext;

use super::EsmAssetReference;
use crate::{
code_gen::{CodeGenerateable, CodeGeneration},
code_gen::{CodeGenerateable, CodeGeneration, VisitorFactory},
create_visitor,
references::AstPath,
};

#[turbo_tasks::value(shared)]
#[derive(Hash, Debug)]
pub struct EsmBindings {
pub bindings: Vec<EsmBinding>,
}

#[turbo_tasks::value_impl]
impl EsmBindings {
#[turbo_tasks::function]
pub fn new(bindings: Vec<EsmBinding>) -> Vc<Self> {
EsmBindings { bindings }.cell()
}
}

#[derive(Hash, Clone, Debug, TaskInput, Serialize, Deserialize, PartialEq, Eq, TraceRawVcs)]
pub struct EsmBinding {
pub reference: Vc<EsmAssetReference>,
pub export: Option<RcStr>,
pub ast_path: Vc<AstPath>,
}

#[turbo_tasks::value_impl]
impl EsmBinding {
#[turbo_tasks::function]
pub fn new(
reference: Vc<EsmAssetReference>,
export: Option<RcStr>,
ast_path: Vc<AstPath>,
) -> Vc<Self> {
) -> Self {
EsmBinding {
reference,
export,
ast_path,
}
.cell()
}
}

#[turbo_tasks::value_impl]
impl CodeGenerateable for EsmBinding {
#[turbo_tasks::function]
async fn code_generation(
self: Vc<Self>,
_context: Vc<Box<dyn ChunkingContext>>,
) -> Result<Vc<CodeGeneration>> {
let this = self.await?;
let mut visitors = Vec::new();
let imported_module = this.reference.get_referenced_asset();

fn make_expr(
imported_module: &str,
export: Option<&str>,
span: Span,
in_call: bool,
) -> Expr {
let span = span.with_ctxt(SyntaxContext::empty());
if let Some(export) = export {
let mut expr = Expr::Member(MemberExpr {
span,
obj: Box::new(Expr::Ident(Ident::new(imported_module.into(), span))),
prop: MemberProp::Computed(ComputedPropName {
span,
expr: Box::new(Expr::Lit(Lit::Str(Str {
span,
value: export.into(),
raw: None,
}))),
}),
});
if in_call {
expr = Expr::Seq(SeqExpr {
exprs: vec![
Box::new(Expr::Lit(Lit::Num(Number {
span,
value: 0.0,
raw: None,
}))),
Box::new(expr),
],
span,
});
}
expr
} else {
Expr::Ident(Ident::new(imported_module.into(), span))
}
}
async fn to_visitors(
&self,
visitors: &mut Vec<(Vec<AstParentKind>, Box<dyn VisitorFactory>)>,
) -> Result<()> {
let item = self.clone();
let imported_module = self.reference.get_referenced_asset();

let mut ast_path = this.ast_path.await?.clone_value();
let mut ast_path = item.ast_path.await?.clone_value();
let imported_module = imported_module.await?.get_ident().await?;

loop {
Expand All @@ -104,18 +74,17 @@ impl CodeGenerateable for EsmBinding {
Some(swc_core::ecma::visit::AstParentKind::Prop(PropField::Shorthand)) => {
ast_path.pop();
visitors.push(
create_visitor!(exact ast_path, visit_mut_prop(prop: &mut Prop) {
if let Prop::Shorthand(ident) = prop {
// TODO: Merge with the above condition when https://rust-lang.github.io/rfcs/2497-if-let-chains.html lands.
if let Some(imported_ident) = imported_module.as_deref() {
*prop = Prop::KeyValue(KeyValueProp {
key: PropName::Ident(ident.clone()),
value: Box::new(make_expr(imported_ident, this.export.as_deref(), ident.span, false))
});
}
create_visitor!(exact ast_path, visit_mut_prop(prop: &mut Prop) {
if let Prop::Shorthand(ident) = prop {
// TODO: Merge with the above condition when https://rust-lang.github.io/rfcs/2497-if-let-chains.html lands.
if let Some(imported_ident) = imported_module.as_deref() {
*prop = Prop::KeyValue(KeyValueProp {
key: PropName::Ident(ident.clone()),
value: Box::new(make_expr(imported_ident, item.export.as_deref(), ident.span, false))
});
}
}),
);
}
}));
break;
}
// Any other expression can be replaced with the import accessor.
Expand All @@ -129,16 +98,15 @@ impl CodeGenerateable for EsmBinding {
);

visitors.push(
create_visitor!(exact ast_path, visit_mut_expr(expr: &mut Expr) {
if let Some(ident) = imported_module.as_deref() {
use swc_core::common::Spanned;
*expr = make_expr(ident, this.export.as_deref(), expr.span(), in_call);
}
// If there's no identifier for the imported module,
// resolution failed and will insert code that throws
// before this expression is reached. Leave behind the original identifier.
}),
);
create_visitor!(exact ast_path, visit_mut_expr(expr: &mut Expr) {
if let Some(ident) = imported_module.as_deref() {
use swc_core::common::Spanned;
*expr = make_expr(ident, item.export.as_deref(), expr.span(), in_call);
}
// If there's no identifier for the imported module,
// resolution failed and will insert code that throws
// before this expression is reached. Leave behind the original identifier.
}));
break;
}
Some(swc_core::ecma::visit::AstParentKind::BindingIdent(
Expand All @@ -155,18 +123,16 @@ impl CodeGenerateable for EsmBinding {
ast_path.pop();

visitors.push(
create_visitor!(exact ast_path, visit_mut_simple_assign_target(l: &mut SimpleAssignTarget) {
if let Some(ident) = imported_module.as_deref() {
use swc_core::common::Spanned;
*l = match make_expr(ident, this.export.as_deref(), l.span(), false) {
Expr::Ident(ident) => SimpleAssignTarget::Ident(ident.into()),
Expr::Member(member) => SimpleAssignTarget::Member(member),
_ => unreachable!(),
};
}
}),
);

create_visitor!(exact ast_path, visit_mut_simple_assign_target(l: &mut SimpleAssignTarget) {
if let Some(ident) = imported_module.as_deref() {
use swc_core::common::Spanned;
*l = match make_expr(ident, item.export.as_deref(), l.span(), false) {
Expr::Ident(ident) => SimpleAssignTarget::Ident(ident.into()),
Expr::Member(member) => SimpleAssignTarget::Member(member),
_ => unreachable!(),
};
}
}));
break;
}
}
Expand All @@ -177,6 +143,59 @@ impl CodeGenerateable for EsmBinding {
}
}

Ok(())
}
}

#[turbo_tasks::value_impl]
impl CodeGenerateable for EsmBindings {
#[turbo_tasks::function]
async fn code_generation(
self: Vc<Self>,
_context: Vc<Box<dyn ChunkingContext>>,
) -> Result<Vc<CodeGeneration>> {
let this = self.await?;
let mut visitors = Vec::new();
let bindings = this.bindings.clone();

for item in bindings.into_iter() {
item.to_visitors(&mut visitors).await?;
}

Ok(CodeGeneration { visitors }.into())
}
}

fn make_expr(imported_module: &str, export: Option<&str>, span: Span, in_call: bool) -> Expr {
let span = span.with_ctxt(SyntaxContext::empty());
if let Some(export) = export {
let mut expr = Expr::Member(MemberExpr {
span,
obj: Box::new(Expr::Ident(Ident::new(imported_module.into(), span))),
prop: MemberProp::Computed(ComputedPropName {
span,
expr: Box::new(Expr::Lit(Lit::Str(Str {
span,
value: export.into(),
raw: None,
}))),
}),
});
if in_call {
expr = Expr::Seq(SeqExpr {
exprs: vec![
Box::new(Expr::Lit(Lit::Num(Number {
span,
value: 0.0,
raw: None,
}))),
Box::new(expr),
],
span,
});
}
expr
} else {
Expr::Ident(Ident::new(imported_module.into(), span))
}
}
19 changes: 15 additions & 4 deletions crates/turbopack-ecmascript/src/references/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ use self::{
},
cjs::CjsAssetReference,
esm::{
export::EsmExport, EsmAssetReference, EsmAsyncAssetReference, EsmExports, EsmModuleItem,
ImportMetaBinding, ImportMetaRef, UrlAssetReference,
binding::EsmBindings, export::EsmExport, EsmAssetReference, EsmAsyncAssetReference,
EsmExports, EsmModuleItem, ImportMetaBinding, ImportMetaRef, UrlAssetReference,
},
node::DirAssetReference,
raw::FileSourceReference,
Expand Down Expand Up @@ -164,6 +164,7 @@ pub struct AnalyzeEcmascriptModuleResultBuilder {
async_module: Vc<OptionAsyncModule>,
successful: bool,
source_map: Option<Vc<OptionSourceMap>>,
bindings: Vec<EsmBinding>,
}

impl AnalyzeEcmascriptModuleResultBuilder {
Expand All @@ -178,6 +179,7 @@ impl AnalyzeEcmascriptModuleResultBuilder {
async_module: Vc::cell(None),
successful: false,
source_map: None,
bindings: Vec::new(),
}
}

Expand Down Expand Up @@ -238,6 +240,10 @@ impl AnalyzeEcmascriptModuleResultBuilder {
)));
}

pub fn add_binding(&mut self, binding: EsmBinding) {
self.bindings.push(binding);
}

/// Sets the analysis result ES export.
pub fn set_source_map(&mut self, source_map: Vc<OptionSourceMap>) {
self.source_map = Some(source_map);
Expand All @@ -264,6 +270,11 @@ impl AnalyzeEcmascriptModuleResultBuilder {
mut self,
track_reexport_references: bool,
) -> Result<Vc<AnalyzeEcmascriptModuleResult>> {
let bindings = EsmBindings::new(take(&mut self.bindings));
if !bindings.await?.bindings.is_empty() {
self.add_code_gen(bindings);
}

let mut references: Vec<_> = self.references.into_iter().collect();
for r in references.iter_mut() {
*r = r.resolve().await?;
Expand Down Expand Up @@ -1063,7 +1074,7 @@ pub(crate) async fn analyse_ecmascript_module_internal(
.add_reference(EsmModuleIdAssetReference::new(*r, Vc::cell(ast_path)))
} else {
analysis.add_local_reference(*r);
analysis.add_code_gen(EsmBinding::new(*r, export, Vc::cell(ast_path)));
analysis.add_binding(EsmBinding::new(*r, export, Vc::cell(ast_path)));
}
}
}
Expand Down Expand Up @@ -1966,7 +1977,7 @@ async fn handle_free_var_reference(
.resolve()
.await?;
analysis.add_reference(esm_reference);
analysis.add_code_gen(EsmBinding::new(
analysis.add_binding(EsmBinding::new(
esm_reference,
export.clone(),
Vc::cell(ast_path.to_vec()),
Expand Down

0 comments on commit ccea243

Please sign in to comment.