Skip to content

Commit

Permalink
feat(es/minifier): Merge functions using sequential inliner (#6148)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdy1 committed Oct 20, 2022
1 parent 0c23592 commit 12443db
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 78 deletions.
Expand Up @@ -5,9 +5,8 @@ export class B {
}
}
//// [2.ts]
async function foo() {
!async function() {
class C extends (await import("./0")).B {
}
new C().print();
}
foo();
}();
Expand Up @@ -23,14 +23,13 @@ define([
"@swc/helpers/src/_interop_require_wildcard.mjs"
], function(require, exports, _interopRequireWildcard) {
"use strict";
async function foo() {
Object.defineProperty(exports, "__esModule", {
value: !0
}), _interopRequireWildcard = _interopRequireWildcard.default, async function() {
class C extends (await new Promise((resolve, reject)=>require([
"./0"
], (m)=>resolve(_interopRequireWildcard(m)), reject))).B {
}
new C().print();
}
Object.defineProperty(exports, "__esModule", {
value: !0
}), _interopRequireWildcard = _interopRequireWildcard.default, foo();
}();
});
Expand Up @@ -26,8 +26,7 @@ Object.defineProperty(exports, "__esModule", {
value: !0
});
const _interopRequireWildcard = require("@swc/helpers/lib/_interop_require_wildcard.js").default;
async function compute(promise) {
!async function(promise) {
let j = await promise;
return j ? j.foo() : (j = await Promise.resolve().then(()=>_interopRequireWildcard(require("./1")))).backup();
}
compute(Promise.resolve().then(()=>_interopRequireWildcard(require("./0"))));
j ? j.foo() : (j = await Promise.resolve().then(()=>_interopRequireWildcard(require("./1")))).backup();
}(Promise.resolve().then(()=>_interopRequireWildcard(require("./0"))));
Expand Up @@ -17,9 +17,8 @@ Object.defineProperty(exports, "__esModule", {
value: !0
});
const _interopRequireWildcard = require("@swc/helpers/lib/_interop_require_wildcard.js").default;
async function foo() {
!async function() {
class C extends (await Promise.resolve().then(()=>_interopRequireWildcard(require("./0")))).B {
}
new C().print();
}
foo();
}();
Expand Up @@ -25,12 +25,11 @@
], factory) : (global = "undefined" != typeof globalThis ? globalThis : global || self) && factory(global.2Ts = {}, global.interopRequireWildcardMjs);
}(this, function(exports, _interopRequireWildcard) {
"use strict";
async function foo() {
Object.defineProperty(exports, "__esModule", {
value: !0
}), _interopRequireWildcard = _interopRequireWildcard.default, async function() {
class C extends (await import("./0")).B {
}
new C().print();
}
Object.defineProperty(exports, "__esModule", {
value: !0
}), _interopRequireWildcard = _interopRequireWildcard.default, foo();
}();
});
141 changes: 109 additions & 32 deletions crates/swc_ecma_minifier/src/compress/optimize/sequences.rs
Expand Up @@ -594,21 +594,7 @@ where
Stmt::Decl(Decl::Fn(f)) => {
// Check for side effects

if !f.function.decorators.is_empty() {
return None;
}
for p in &f.function.params {
if !p.decorators.is_empty() {
return None;
}

if !self.is_pat_skippable_for_seq(None, &p.pat) {
return None;
}
}

// Side-effect free function can be skipped.
vec![]
vec![Mergable::FnDecl(f)]
}

_ => return None,
Expand Down Expand Up @@ -705,6 +691,7 @@ where

!v.decls.is_empty()
}
Some(Stmt::Decl(Decl::Fn(f))) => !f.ident.is_dummy(),
Some(Stmt::Expr(s)) if s.expr.is_invalid() => false,

_ => true,
Expand Down Expand Up @@ -884,6 +871,7 @@ where
None => continue,
},
Mergable::Expr(e) => e,
Mergable::FnDecl(..) => continue,
},
)? {
did_work = true;
Expand Down Expand Up @@ -927,6 +915,7 @@ where
break;
}
}

_ => {}
}

Expand Down Expand Up @@ -956,6 +945,21 @@ where
}
}
}

// Function declaration is side-effect free.
//
// TODO(kdy1): Paramters with default value can have side effect. But this
// is very unrealistic in real-world code, so I'm
// postponing handling for it.
Mergable::FnDecl(f) => {
if f.function
.params
.iter()
.any(|p| !self.is_pat_skippable_for_seq(Some(a), &p.pat))
{
break;
}
}
}
}
}
Expand Down Expand Up @@ -1051,6 +1055,15 @@ where
return false;
}
}

Mergable::FnDecl(a) => {
// TODO(kdy1): I'm not sure if we can remove this check. I added this
// just to be safe, and we may remove this check in future.
if is_ident_used_by(e.to_id(), &**a) {
log_abort!("ident used by a (fn)");
return false;
}
}
}

// We can't proceed if the rhs (a.id = b.right) is
Expand Down Expand Up @@ -1106,6 +1119,15 @@ where

_ => None,
},

Mergable::FnDecl(a) => Some(collect_infects_from(
&a.function,
AliasConfig {
marks: Some(self.marks),
ignore_nested: true,
need_all: true,
},
)),
};

if let Some(ids_used_by_a_init) = ids_used_by_a_init {
Expand Down Expand Up @@ -1194,6 +1216,13 @@ where
return false;
}
}
Mergable::FnDecl(a) => {
// TODO(kdy1): I'm not sure if this check is required.
if is_ident_used_by(left_id.to_id(), &**a) {
log_abort!("e.left is used by a ()");
return false;
}
}
}
}

Expand Down Expand Up @@ -1402,6 +1431,7 @@ where
let a = match a {
Mergable::Expr(e) => dump(*e, false),
Mergable::Var(e) => dump(*e, false),
Mergable::FnDecl(e) => dump(*e, false),
};

Some(
Expand All @@ -1420,7 +1450,7 @@ where
}

match a {
Mergable::Var(..) => {}
Mergable::Var(..) | Mergable::FnDecl(..) => {}
Mergable::Expr(a) => {
if let Expr::Seq(a) = a {
for a in a.exprs.iter_mut().rev() {
Expand Down Expand Up @@ -1472,6 +1502,12 @@ where
return Ok(false);
}
},

Mergable::FnDecl(..) => {
// A function declaration is always inlinable as it can be
// viewed as a variable with an identifier name and a
// function expression as a initialized.
}
}
}

Expand Down Expand Up @@ -1806,6 +1842,14 @@ where
crate::debug::dump(&*b, false)
);
}

Mergable::FnDecl(a) => {
trace_op!(
"sequences: Trying to merge `{}` => `{}`",
crate::debug::dump(&**a, false),
crate::debug::dump(&*b, false)
);
}
}

if self.replace_seq_update(a, b)? {
Expand Down Expand Up @@ -2033,7 +2077,7 @@ where
}
}

(left_id.clone(), right)
(left_id.clone(), Some(right))
}
_ => return Ok(false),
}
Expand Down Expand Up @@ -2066,35 +2110,57 @@ where
}

match &mut a.init {
Some(v) => (left, v),
Some(v) => (left, Some(v)),
None => {
if usage.declared_count > 1 {
return Ok(false);
}

right_val = undefined(DUMMY_SP);
(left, &mut right_val)
(left, Some(&mut right_val))
}
}
} else {
return Ok(false);
}
}

Mergable::FnDecl(a) => {
if let Some(usage) = self.data.vars.get(&a.ident.to_id()) {
if usage.ref_count != 1 || usage.reassigned() || !usage.is_fn_local {
return Ok(false);
}

if usage.inline_prevented {
return Ok(false);
}

if contains_arguments(&a.function) {
return Ok(false);
}

(a.ident.clone(), None)
} else {
return Ok(false);
}
}
};

if a_right.is_this()
|| matches!(
&**a_right,
Expr::Ident(Ident {
sym: js_word!("arguments"),
..
})
)
{
return Ok(false);
}
if contains_arguments(&**a_right) {
return Ok(false);
if let Some(a_right) = a_right {
if a_right.is_this()
|| matches!(
&**a_right,
Expr::Ident(Ident {
sym: js_word!("arguments"),
..
})
)
{
return Ok(false);
}
if contains_arguments(&**a_right) {
return Ok(false);
}
}

macro_rules! take_a {
Expand Down Expand Up @@ -2134,6 +2200,15 @@ where

Box::new(a.take())
}

Mergable::FnDecl(a) => {
// We can inline a function declaration as a function expression.

Box::new(Expr::Fn(FnExpr {
ident: Some(a.ident.take()),
function: a.function.take(),
}))
}
}
};
}
Expand Down Expand Up @@ -2307,6 +2382,7 @@ impl Visit for UsageCounter<'_> {
enum Mergable<'a> {
Var(&'a mut VarDeclarator),
Expr(&'a mut Expr),
FnDecl(&'a mut FnDecl),
}

impl Mergable<'_> {
Expand All @@ -2320,6 +2396,7 @@ impl Mergable<'_> {
Expr::Assign(s) => s.left.as_ident().map(|v| v.to_id()),
_ => None,
},
Mergable::FnDecl(f) => Some(f.ident.to_id()),
}
}
}
Expand Down

1 comment on commit 12443db

@github-actions
Copy link

Choose a reason for hiding this comment

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

Benchmark

Benchmark suite Current: 12443db Previous: e9ac7a7 Ratio
es/full/bugs-1 355384 ns/iter (± 25402) 362314 ns/iter (± 40966) 0.98
es/full/minify/libraries/antd 1893440426 ns/iter (± 36346312) 1842875614 ns/iter (± 35650332) 1.03
es/full/minify/libraries/d3 424251777 ns/iter (± 13081447) 419155702 ns/iter (± 143449773) 1.01
es/full/minify/libraries/echarts 1631846441 ns/iter (± 87168922) 1563170424 ns/iter (± 77400698) 1.04
es/full/minify/libraries/jquery 107126511 ns/iter (± 5265593) 111756793 ns/iter (± 5822318) 0.96
es/full/minify/libraries/lodash 123148536 ns/iter (± 5657883) 123245082 ns/iter (± 10164644) 1.00
es/full/minify/libraries/moment 62661002 ns/iter (± 1737627) 62305994 ns/iter (± 5772039) 1.01
es/full/minify/libraries/react 20524444 ns/iter (± 464131) 20103522 ns/iter (± 1130795) 1.02
es/full/minify/libraries/terser 311144744 ns/iter (± 8781594) 373490359 ns/iter (± 76326283) 0.83
es/full/minify/libraries/three 553039943 ns/iter (± 14378683) 640502983 ns/iter (± 32927963) 0.86
es/full/minify/libraries/typescript 3569411436 ns/iter (± 142012726) 4031541830 ns/iter (± 199008095) 0.89
es/full/minify/libraries/victory 888749870 ns/iter (± 33952003) 884406782 ns/iter (± 55973040) 1.00
es/full/minify/libraries/vue 176328632 ns/iter (± 15096903) 182894704 ns/iter (± 10531966) 0.96
es/full/codegen/es3 34044 ns/iter (± 1628) 34722 ns/iter (± 1498) 0.98
es/full/codegen/es5 34620 ns/iter (± 1866) 34974 ns/iter (± 3010) 0.99
es/full/codegen/es2015 33693 ns/iter (± 707) 37173 ns/iter (± 5741) 0.91
es/full/codegen/es2016 34182 ns/iter (± 1248) 35788 ns/iter (± 4688) 0.96
es/full/codegen/es2017 33745 ns/iter (± 1438) 35633 ns/iter (± 9940) 0.95
es/full/codegen/es2018 33755 ns/iter (± 1334) 34743 ns/iter (± 1698) 0.97
es/full/codegen/es2019 33908 ns/iter (± 1368) 36485 ns/iter (± 4372) 0.93
es/full/codegen/es2020 33914 ns/iter (± 1640) 34606 ns/iter (± 2067) 0.98
es/full/all/es3 196617376 ns/iter (± 9613968) 239118550 ns/iter (± 30653078) 0.82
es/full/all/es5 200703823 ns/iter (± 20020507) 231900460 ns/iter (± 33230099) 0.87
es/full/all/es2015 152431941 ns/iter (± 9692612) 178608203 ns/iter (± 26294152) 0.85
es/full/all/es2016 151984236 ns/iter (± 8255381) 177873049 ns/iter (± 23927581) 0.85
es/full/all/es2017 150998140 ns/iter (± 60644625) 182416660 ns/iter (± 21452222) 0.83
es/full/all/es2018 146400812 ns/iter (± 10823080) 187208008 ns/iter (± 21256263) 0.78
es/full/all/es2019 149141397 ns/iter (± 12865756) 180039422 ns/iter (± 22820278) 0.83
es/full/all/es2020 140575279 ns/iter (± 9102937) 174181144 ns/iter (± 18042766) 0.81
es/full/parser 742267 ns/iter (± 74160) 781471 ns/iter (± 43979) 0.95
es/full/base/fixer 26628 ns/iter (± 1092) 27362 ns/iter (± 1319) 0.97
es/full/base/resolver_and_hygiene 93869 ns/iter (± 5654) 97993 ns/iter (± 5422) 0.96
serialization of ast node 219 ns/iter (± 6) 216 ns/iter (± 7) 1.01
serialization of serde 222 ns/iter (± 3) 222 ns/iter (± 13) 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.