fix(es/transforms/compat): Improve performance (#1673)

swc_ecma_transforms_compat:
 - `classes`: Fast-path.
 - `destructuring`: Fast-path.
 - `sticky_regex`: Fast-path.
 - `spread`: Fast-path.
 - `shorthand`: Fast-path.
 - `typeof_symbol`: Fast-path.
 - `block_scoped_functions`: Fast path.
 - `duplicate_keys`: Use fxhash.
 - `instance_of`: Fast path.
 - `reserved_words`: Make string comparison efficient.
This commit is contained in:
강동윤 2021-05-11 13:53:36 +09:00 committed by GitHub
parent 9381d0dbc2
commit 2ad0af9e91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 283 additions and 160 deletions

View File

@ -1,3 +1,13 @@
//! [JsWord] is an interened string.
//!
//! This type should be used instead of [String] for values, because lots of
//! values are duplicated. For example, if an identifer is named `myVariable`,
//! there will be lots of identifier usages with the value `myVariable`.
//!
//! This type
//! - makes equality comparison faster.
//! - reduces memory usage.
#![allow(clippy::unreadable_literal)]
include!(concat!(env!("OUT_DIR"), "/js_word.rs"));

View File

@ -63,7 +63,20 @@ impl Globals {
}
}
// scoped_thread_local!(pub static GLOBALS: Globals);
/// Storage for span hygiene data.
///
/// This variable is used to manage identifiers or to identify nodes.
/// Note that it's stored as a thread-local storage, but actually it's shared
/// between threads.
///
/// # Usages
///
/// ## Span hygiene
///
/// [Mark]s are stored in this variable.
///
/// You can see the document how swc uses the span hygiene info at
/// https://rustdoc.swc.rs/swc_ecma_transforms_base/resolver/fn.resolver_with_mark.html
pub static GLOBALS: ::scoped_tls::ScopedKey<Globals> = ::scoped_tls::ScopedKey {
inner: {
thread_local!(static FOO: ::std::cell::Cell<usize> = {

View File

@ -2,6 +2,7 @@
extern crate test;
use swc_common::comments::SingleThreadedComments;
use swc_common::FileName;
use swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax};
use test::Bencher;
@ -71,97 +72,21 @@ fn yui(b: &mut Bencher) {
bench_module(b, Default::default(), include_str!("./files/yui-3.12.0.js"))
}
#[bench]
fn colors_ts(b: &mut Bencher) {
// Copied from ratel-rust
bench_module(
b,
Syntax::Typescript(Default::default()),
include_str!("../colors.js"),
)
}
#[bench]
fn angular_ts(b: &mut Bencher) {
bench_module(
b,
Syntax::Typescript(Default::default()),
include_str!("./files/angular-1.2.5.js"),
)
}
#[bench]
fn backbone_ts(b: &mut Bencher) {
bench_module(
b,
Syntax::Typescript(Default::default()),
include_str!("./files/backbone-1.1.0.js"),
)
}
#[bench]
fn jquery_ts(b: &mut Bencher) {
bench_module(
b,
Syntax::Typescript(Default::default()),
include_str!("./files/jquery-1.9.1.js"),
)
}
#[bench]
fn jquery_mobile_ts(b: &mut Bencher) {
bench_module(
b,
Syntax::Typescript(Default::default()),
include_str!("./files/jquery.mobile-1.4.2.js"),
)
}
#[bench]
fn mootools_ts(b: &mut Bencher) {
bench_module(
b,
Syntax::Typescript(Default::default()),
include_str!("./files/mootools-1.4.5.js"),
)
}
#[bench]
fn underscore_ts(b: &mut Bencher) {
bench_module(
b,
Syntax::Typescript(Default::default()),
include_str!("./files/underscore-1.5.2.js"),
)
}
#[bench]
fn yui_ts(b: &mut Bencher) {
bench_module(
b,
Syntax::Typescript(Default::default()),
include_str!("./files/yui-3.12.0.js"),
)
}
#[bench]
fn large(b: &mut Bencher) {
bench_module(
b,
Syntax::Typescript(Default::default()),
include_str!("../../codegen/benches/large-partial.js"),
)
}
fn bench_module(b: &mut Bencher, syntax: Syntax, src: &'static str) {
b.bytes = src.len() as _;
let _ = ::testing::run_test(false, |cm, _| {
let comments = SingleThreadedComments::default();
let fm = cm.new_source_file(FileName::Anon, src.into());
b.iter(|| {
let _ = test::black_box({
let lexer = Lexer::new(syntax, Default::default(), StringInput::from(&*fm), None);
let lexer = Lexer::new(
syntax,
Default::default(),
StringInput::from(&*fm),
Some(&comments),
);
let mut parser = Parser::new_from(lexer);
parser.parse_module()
});

View File

@ -60,7 +60,12 @@ macro_rules! add_to {
}};
}
scoped_thread_local!(pub static HELPERS: Helpers);
scoped_thread_local!(
/// This variable is used to manage helper scripts like `_inherits` from babel.
///
/// The instance contains flags where each flag denotes if a helper script should be injected.
pub static HELPERS: Helpers
);
/// Tracks used helper methods. (e.g. __extends)
#[derive(Debug, Default)]

View File

@ -6,7 +6,7 @@ edition = "2018"
license = "Apache-2.0/MIT"
name = "swc_ecma_transforms_compat"
repository = "https://github.com/swc-project/swc.git"
version = "0.16.1"
version = "0.16.2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -1,6 +1,12 @@
use swc_common::{Spanned, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Check;
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::UsageFinder;
use swc_ecma_visit::noop_visit_type;
use swc_ecma_visit::Node;
use swc_ecma_visit::Visit;
use swc_ecma_visit::VisitWith;
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
pub fn block_scoped_functions() -> impl Fold {
@ -10,6 +16,7 @@ pub fn block_scoped_functions() -> impl Fold {
#[derive(Clone, Copy)]
struct BlockScopedFns;
#[fast_path(BlockScopedFnFinder)]
impl Fold for BlockScopedFns {
noop_fold_type!();
@ -61,6 +68,32 @@ impl Fold for BlockScopedFns {
}
}
#[derive(Default)]
struct BlockScopedFnFinder {
found: bool,
}
impl Visit for BlockScopedFnFinder {
noop_visit_type!();
fn visit_stmts(&mut self, stmts: &[Stmt], _: &dyn Node) {
for n in stmts {
n.visit_with(&Invalid { span: DUMMY_SP }, self);
}
self.found |= stmts.iter().any(|stmt| match stmt {
Stmt::Decl(Decl::Fn(..)) => true,
_ => false,
});
}
}
impl Check for BlockScopedFnFinder {
fn should_handle(&self) -> bool {
self.found
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -13,6 +13,8 @@ use swc_common::{Mark, Spanned, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::helper;
use swc_ecma_transforms_base::native::is_native;
use swc_ecma_transforms_base::perf::Check;
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::quote_expr;
use swc_ecma_utils::quote_str;
use swc_ecma_utils::{
@ -187,6 +189,7 @@ where
}
}
#[fast_path(ClassFinder)]
impl<C> Fold for Classes<C>
where
C: Comments,
@ -202,26 +205,6 @@ where
}
fn fold_decl(&mut self, n: Decl) -> Decl {
fn should_work(node: &Decl) -> bool {
struct Visitor {
found: bool,
}
impl Visit for Visitor {
noop_visit_type!();
fn visit_class(&mut self, _: &Class, _: &dyn Node) {
self.found = true
}
}
let mut v = Visitor { found: false };
node.visit_with(&Invalid { span: DUMMY_SP } as _, &mut v);
v.found
}
// fast path
if !should_work(&n) {
return n;
}
let n = match n {
Decl::Class(decl) => Decl::Var(self.fold_class_as_var_decl(decl.ident, decl.class)),
_ => n,
@ -968,3 +951,22 @@ fn escape_keywords(mut e: Box<Expr>) -> Box<Expr> {
e
}
#[derive(Default)]
struct ClassFinder {
found: bool,
}
impl Visit for ClassFinder {
noop_visit_type!();
fn visit_class(&mut self, _: &Class, _: &dyn Node) {
self.found = true
}
}
impl Check for ClassFinder {
fn should_handle(&self) -> bool {
self.found
}
}

View File

@ -3,6 +3,8 @@ use std::iter;
use swc_common::{Spanned, SyntaxContext, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::helper;
use swc_ecma_transforms_base::perf::Check;
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::alias_ident_for;
use swc_ecma_utils::alias_if_required;
use swc_ecma_utils::has_rest_pat;
@ -454,6 +456,7 @@ impl AssignFolder {
}
}
#[fast_path(DestructuringVisitor)]
impl Fold for Destructuring {
noop_fold_type!();
@ -527,6 +530,7 @@ struct AssignFolder {
ignore_return_value: Option<()>,
}
#[fast_path(DestructuringVisitor)]
impl Fold for AssignFolder {
noop_fold_type!();
@ -1129,6 +1133,7 @@ where
v.found
}
#[derive(Default)]
struct DestructuringVisitor {
found: bool,
}
@ -1144,3 +1149,9 @@ impl Visit for DestructuringVisitor {
}
}
}
impl Check for DestructuringVisitor {
fn should_handle(&self) -> bool {
self.found
}
}

View File

@ -1,4 +1,4 @@
use std::collections::HashSet;
use fxhash::FxHashSet;
use swc_atoms::JsWord;
use swc_common::Spanned;
use swc_ecma_ast::*;
@ -34,8 +34,8 @@ impl Fold for DuplicateKeys {
#[derive(Default)]
struct PropFolder {
getter_props: HashSet<JsWord>,
setter_props: HashSet<JsWord>,
getter_props: FxHashSet<JsWord>,
setter_props: FxHashSet<JsWord>,
}
impl Fold for PropFolder {
@ -85,7 +85,7 @@ impl Fold for PropFolder {
}
struct PropNameFolder<'a> {
props: &'a mut HashSet<JsWord>,
props: &'a mut FxHashSet<JsWord>,
}
impl<'a> Fold for PropNameFolder<'a> {
noop_fold_type!();

View File

@ -1,6 +1,7 @@
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::helper;
use swc_ecma_transforms_base::perf::Check;
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::ExprFactory;
use swc_ecma_visit::noop_visit_type;
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith, Node, Visit, VisitWith};
@ -35,32 +36,11 @@ pub fn instance_of() -> impl Fold {
}
struct InstanceOf;
#[fast_path(InstnaceOfFinder)]
impl Fold for InstanceOf {
noop_fold_type!();
fn fold_expr(&mut self, expr: Expr) -> Expr {
fn should_work(node: &Expr) -> bool {
struct Visitor {
found: bool,
}
impl Visit for Visitor {
noop_visit_type!();
fn visit_bin_expr(&mut self, e: &BinExpr, _: &dyn Node) {
if e.op == op!("instanceof") {
self.found = true
}
}
}
let mut v = Visitor { found: false };
node.visit_with(&Invalid { span: DUMMY_SP } as _, &mut v);
v.found
}
// fast path
if !should_work(&expr) {
return expr;
}
let expr = expr.fold_children_with(self);
match expr {
@ -79,3 +59,26 @@ impl Fold for InstanceOf {
}
}
}
#[derive(Default)]
struct InstnaceOfFinder {
found: bool,
}
impl Visit for InstnaceOfFinder {
noop_visit_type!();
fn visit_bin_expr(&mut self, e: &BinExpr, _: &dyn Node) {
e.visit_children_with(self);
if e.op == op!("instanceof") {
self.found = true
}
}
}
impl Check for InstnaceOfFinder {
fn should_handle(&self) -> bool {
self.found
}
}

View File

@ -1,5 +1,11 @@
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Check;
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::quote_ident;
use swc_ecma_visit::noop_visit_type;
use swc_ecma_visit::Node;
use swc_ecma_visit::Visit;
use swc_ecma_visit::VisitWith;
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
/// Compile ES2015 shorthand properties to ES5
@ -43,6 +49,7 @@ pub fn shorthand() -> impl 'static + Fold {
#[derive(Clone, Copy)]
struct Shorthand;
#[fast_path(ShorthandFinder)]
impl Fold for Shorthand {
noop_fold_type!();
@ -66,6 +73,27 @@ impl Fold for Shorthand {
}
}
#[derive(Default)]
struct ShorthandFinder {
found: bool,
}
impl Visit for ShorthandFinder {
noop_visit_type!();
fn visit_prop(&mut self, n: &Prop, _: &dyn Node) {
n.visit_children_with(self);
self.found |= n.is_shorthand() || n.is_method();
}
}
impl Check for ShorthandFinder {
fn should_handle(&self) -> bool {
self.found
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -5,6 +5,8 @@ use swc_common::{util::move_map::MoveMap, Span, Spanned, DUMMY_SP};
use swc_ecma_ast::*;
use swc_ecma_transforms_base::ext::ExprRefExt;
use swc_ecma_transforms_base::helper;
use swc_ecma_transforms_base::perf::Check;
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::alias_ident_for;
use swc_ecma_utils::is_literal;
use swc_ecma_utils::member_expr;
@ -13,6 +15,10 @@ use swc_ecma_utils::quote_ident;
use swc_ecma_utils::undefined;
use swc_ecma_utils::ExprFactory;
use swc_ecma_utils::StmtLike;
use swc_ecma_visit::noop_visit_type;
use swc_ecma_visit::Node;
use swc_ecma_visit::Visit;
use swc_ecma_visit::VisitWith;
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
pub fn spread(c: Config) -> impl Fold {
@ -37,6 +43,7 @@ struct ActualFolder {
vars: Vec<VarDeclarator>,
}
#[fast_path(SpreadFinder)]
impl Fold for Spread {
noop_fold_type!();
@ -75,6 +82,7 @@ impl Spread {
}
}
#[fast_path(SpreadFinder)]
impl Fold for ActualFolder {
noop_fold_type!();
@ -455,3 +463,24 @@ fn expand_literal_args(
expand(&mut buf, args);
buf
}
#[derive(Default)]
struct SpreadFinder {
found: bool,
}
impl Visit for SpreadFinder {
noop_visit_type!();
fn visit_expr_or_spread(&mut self, n: &ExprOrSpread, _: &dyn Node) {
n.visit_children_with(self);
self.found |= n.spread.is_some();
}
}
impl Check for SpreadFinder {
fn should_handle(&self) -> bool {
self.found
}
}

View File

@ -1,8 +1,13 @@
use swc_atoms::JsWord;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::perf::Check;
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::quote_ident;
use swc_ecma_utils::ExprFactory;
use swc_ecma_visit::noop_visit_type;
use swc_ecma_visit::Node;
use swc_ecma_visit::Visit;
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
/// Compile ES2015 sticky regex to an ES5 RegExp constructor
@ -26,6 +31,7 @@ pub fn sticky_regex() -> impl 'static + Fold {
#[derive(Clone, Copy)]
struct StickyRegex;
#[fast_path(StickyRegexVisitor)]
impl Fold for StickyRegex {
noop_fold_type!();
@ -61,6 +67,25 @@ impl Fold for StickyRegex {
}
}
#[derive(Default)]
struct StickyRegexVisitor {
found: bool,
}
impl Visit for StickyRegexVisitor {
noop_visit_type!();
fn visit_regex(&mut self, n: &Regex, _: &dyn Node) {
self.found |= n.flags.contains('y');
}
}
impl Check for StickyRegexVisitor {
fn should_handle(&self) -> bool {
self.found
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -1,7 +1,8 @@
use swc_atoms::js_word;
use swc_common::DUMMY_SP;
use swc_ecma_ast::*;
use swc_ecma_transforms_base::helper;
use swc_ecma_transforms_base::perf::Check;
use swc_ecma_transforms_macros::fast_path;
use swc_ecma_utils::ExprFactory;
use swc_ecma_visit::noop_visit_type;
use swc_ecma_visit::{noop_fold_type, Fold, FoldWith, Node, Visit, VisitWith};
@ -13,15 +14,11 @@ pub fn typeof_symbol() -> impl Fold {
#[derive(Clone)]
struct TypeOfSymbol;
#[fast_path(TypeOfFinder)]
impl Fold for TypeOfSymbol {
noop_fold_type!();
fn fold_expr(&mut self, expr: Expr) -> Expr {
// fast path
if !should_work(&expr) {
return expr;
}
let expr = expr.fold_children_with(self);
match expr {
@ -71,22 +68,27 @@ impl Fold for TypeOfSymbol {
}
}
fn should_work(node: &Expr) -> bool {
struct Visitor {
#[derive(Default)]
struct TypeOfFinder {
found: bool,
}
impl Visit for Visitor {
impl Visit for TypeOfFinder {
noop_visit_type!();
fn visit_unary_expr(&mut self, e: &UnaryExpr, _: &dyn Node) {
e.visit_children_with(self);
if e.op == op!("typeof") {
self.found = true
}
}
}
let mut v = Visitor { found: false };
node.visit_with(&Invalid { span: DUMMY_SP } as _, &mut v);
v.found
impl Check for TypeOfFinder {
fn should_handle(&self) -> bool {
self.found
}
}
fn is_non_symbol_literal(e: &Expr) -> bool {

View File

@ -55,12 +55,47 @@ impl VisitMut for EsReservedWord {
}
fn is_reserved(sym: &JsWord) -> bool {
match &**sym {
"enum" | "implements" | "package" | "protected" | "interface" | "private" | "public"
| "await" | "break" | "case" | "catch" | "class" | "const" | "continue" | "debugger"
| "default" | "delete" | "do" | "else" | "export" | "extends" | "finally" | "for"
| "function" | "if" | "in" | "instanceof" | "new" | "return" | "super" | "switch"
| "this" | "throw" | "try" | "typeof" | "var" | "void" | "while" | "with" | "yield" => true,
match *sym {
js_word!("enum")
| js_word!("implements")
| js_word!("package")
| js_word!("protected")
| js_word!("interface")
| js_word!("private")
| js_word!("public")
| js_word!("await")
| js_word!("break")
| js_word!("case")
| js_word!("catch")
| js_word!("class")
| js_word!("const")
| js_word!("continue")
| js_word!("debugger")
| js_word!("default")
| js_word!("delete")
| js_word!("do")
| js_word!("else")
| js_word!("export")
| js_word!("extends")
| js_word!("finally")
| js_word!("for")
| js_word!("function")
| js_word!("if")
| js_word!("in")
| js_word!("instanceof")
| js_word!("new")
| js_word!("return")
| js_word!("super")
| js_word!("switch")
| js_word!("this")
| js_word!("throw")
| js_word!("try")
| js_word!("typeof")
| js_word!("var")
| js_word!("void")
| js_word!("while")
| js_word!("with")
| js_word!("yield") => true,
_ => false,
}

View File

@ -1701,8 +1701,10 @@ impl<'a> UsageFinder<'a> {
}
}
// Used for error reporting in transform.
scoped_thread_local!(pub static HANDLER: Handler);
scoped_thread_local!(
/// Used for error reporting in transform.
pub static HANDLER: Handler
);
/// make a new expression which evaluates `val` preserving side effects, if any.
pub fn preserve_effects<I>(span: Span, val: Expr, exprs: I) -> Expr