Improve optimizer (#660)

Although it's quite naive at the moment, I added two optimization passes.

 - dead code elimination (Closes #607)
 - inlining
This commit is contained in:
강동윤 2020-02-13 11:45:14 +09:00 committed by GitHub
parent 82e73b1121
commit 348052b017
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 8192 additions and 2615 deletions

View File

@ -1,6 +1,6 @@
[package]
name = "swc_common"
version = "0.5.2"
version = "0.5.3"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"

View File

@ -1,5 +1,9 @@
use super::{Fold, FoldWith, Visit, VisitWith};
use crate::util::move_map::MoveMap;
use crate::{
pass::{CompilerPass, Repeated, RepeatedPass},
util::move_map::MoveMap,
};
use std::borrow::Cow;
#[macro_export]
macro_rules! chain {
@ -90,3 +94,35 @@ where
self.second.visit(node);
}
}
impl<A, B> CompilerPass for AndThen<A, B>
where
A: CompilerPass,
B: CompilerPass,
{
fn name() -> Cow<'static, str> {
format!("{} -> {}", A::name(), B::name()).into()
}
}
impl<A, B, At> RepeatedPass<At> for AndThen<A, B>
where
A: RepeatedPass<At>,
B: RepeatedPass<At>,
{
}
impl<A, B> Repeated for AndThen<A, B>
where
A: Repeated,
B: Repeated,
{
fn changed(&self) -> bool {
self.first.changed() || self.second.changed()
}
fn reset(&mut self) {
self.first.reset();
self.second.reset();
}
}

View File

@ -24,6 +24,7 @@ pub mod fold;
pub mod input;
pub mod iter;
pub mod macros;
pub mod pass;
mod pos;
mod rustc_data_structures;
pub mod serializer;

85
common/src/pass.rs Normal file
View File

@ -0,0 +1,85 @@
use crate::{Fold, FoldWith};
use serde::export::PhantomData;
use std::borrow::Cow;
pub trait CompilerPass {
/// Name should follow hyphen-case
///
/// TODO: timing
fn name() -> Cow<'static, str>;
}
pub trait Repeated: CompilerPass {
/// Should run again?
fn changed(&self) -> bool;
/// Reset.
fn reset(&mut self);
}
pub trait RepeatedPass<At>: Repeated + CompilerPass {}
impl<T, P> RepeatedPass<T> for P where P: CompilerPass + Repeated {}
#[derive(Debug, Copy, Clone)]
pub struct Repeat<P, At>
where
P: RepeatedPass<At>,
{
pass: P,
at: PhantomData<At>,
}
impl<P, At> Repeat<P, At>
where
P: RepeatedPass<At>,
{
pub fn new(pass: P) -> Self {
Self {
pass,
at: PhantomData,
}
}
}
impl<P, At> CompilerPass for Repeat<P, At>
where
P: RepeatedPass<At>,
{
fn name() -> Cow<'static, str> {
format!("Repeat({})", P::name()).into()
}
}
impl<P, At> Repeated for Repeat<P, At>
where
P: RepeatedPass<At>,
{
fn changed(&self) -> bool {
self.pass.changed()
}
fn reset(&mut self) {
self.pass.reset()
}
}
#[cfg(feature = "fold")]
impl<P, At> Fold<At> for Repeat<P, At>
where
At: FoldWith<Self> + FoldWith<P>,
P: RepeatedPass<At>,
{
fn fold(&mut self, mut node: At) -> At {
loop {
self.pass.reset();
node = node.fold_with(&mut self.pass);
if !self.pass.changed() {
break;
}
}
node
}
}

View File

@ -22,7 +22,7 @@ swc_ecma_ast = { version = "0.17.0", path ="../ast" }
swc_ecma_parser_macros = { version = "0.4", path ="./macros" }
enum_kind = { version = "0.2", path ="../../macros/enum_kind" }
unicode-xid = "0.2"
log = { version = "0.4", features = ["release_max_level_debug"] }
log = "0.4"
either = { version = "1.4" }
serde = { version = "1", features = ["derive"] }
smallvec = "1"

View File

@ -30,6 +30,7 @@ arrayvec = "0.5.1"
serde_json = "1"
smallvec = "1"
is-macro = "0.1"
log = "0.4.8"
[dev-dependencies]
testing = { version = "0.5", path ="../../testing" }

View File

@ -49,7 +49,7 @@ pub(super) struct CaseHandler<'a> {
listing_len: usize,
/// A sparse array whose keys correspond to locations in this.listing
/// that have been marked as branch/jump targets.
/// that have been marked as stmt/jump targets.
marked: Vec<Loc>,
leaps: LeapManager,

View File

@ -149,6 +149,24 @@ impl Fold<Stmt> for Fixer {
}
}
impl Fold<IfStmt> for Fixer {
fn fold(&mut self, node: IfStmt) -> IfStmt {
let node: IfStmt = node.fold_children(self);
match *node.cons {
Stmt::If(..) => IfStmt {
cons: box Stmt::Block(BlockStmt {
span: node.cons.span(),
stmts: vec![*node.cons],
}),
..node
},
_ => node,
}
}
}
macro_rules! context_fn_args {
($T:tt, $is_new:expr) => {
impl Fold<$T> for Fixer {
@ -510,6 +528,17 @@ impl Fold<Expr> for Fixer {
}
e @ Expr::Seq(..)
| e @ Expr::Update(..)
| e
@
Expr::Unary(UnaryExpr {
op: op!("delete"), ..
})
| e
@
Expr::Unary(UnaryExpr {
op: op!("void"), ..
})
| e @ Expr::Yield(..)
| e @ Expr::Cond(..)
| e @ Expr::Assign(..)
@ -1039,4 +1068,6 @@ var store = global[SHARED] || (global[SHARED] = {});
foo: 1
});"
);
test_fixer!(void_and_bin, "(void 0) * 2", "(void 0) * 2");
}

View File

@ -1,9 +1,5 @@
pub use self::{
inline_globals::InlineGlobals,
json_parse::JsonParse,
simplify::{expr_simplifier, simplifier},
};
pub use self::{inline_globals::InlineGlobals, json_parse::JsonParse, simplify::simplifier};
mod inline_globals;
mod json_parse;
mod simplify;
pub mod simplify;

View File

@ -1,30 +0,0 @@
//! Ported from closure compiler.
pub use self::dce::dce;
use self::expr::SimplifyExpr;
use crate::pass::Pass;
use swc_common::{Fold, FoldWith};
use swc_ecma_ast::*;
pub mod dce;
mod expr;
/// Not intended for general use. Use [simplifier] instead.
///
/// Ported from `PeepholeFoldConstants` of google closure compler.
pub fn expr_simplifier() -> impl Pass + 'static {
SimplifyExpr
}
/// Ported from `PeepholeRemoveDeadCode` and `PeepholeFoldConstants` of google
/// closure compiler.
pub fn simplifier() -> impl Pass + 'static {
Simplifier
}
struct Simplifier;
impl Fold<Program> for Simplifier {
fn fold(&mut self, p: Program) -> Program {
p.fold_with(&mut expr_simplifier()).fold_with(&mut dce())
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,11 @@
use super::dce;
use crate::optimization::expr_simplifier;
use super::{super::expr_simplifier, dead_branch_remover};
use swc_common::chain;
macro_rules! test_stmt {
($l:expr, $r:expr) => {
test_transform!(
::swc_ecma_parser::Syntax::default(),
|_| chain!(expr_simplifier(), dce()),
|_| chain!(expr_simplifier(), dead_branch_remover()),
$l,
$r
)
@ -1662,3 +1661,63 @@ fn test_function_call_references_setter_is_not_removed() {
"foo();",
));
}
#[test]
fn custom_loop_1() {
test(
"let b = 2;
let a = 1;
if (2) {
a = 2;
}
let c;
if (a) {
c = 3;
}",
"let b = 2;
let a = 1;
a = 2;
let c;
if (a) c = 3;",
);
}
#[test]
fn custom_loop_2() {
test(
"let b = 2;
let a = 1;
if (2) {
a = 2;
}
let c;
if (a) {
c = 3;
}",
"let b = 2;
let a = 1;
a = 2;
let c;
if (a) c = 3;",
);
}
#[test]
fn custom_loop_3() {
test(
"let c;
if (2) c = 3;
console.log(c);",
"let c;
c = 3;
console.log(c);",
);
}

View File

@ -0,0 +1,67 @@
use super::Dce;
use swc_common::{Fold, FoldWith, Spanned};
use swc_ecma_ast::*;
use swc_ecma_utils::{find_ids, ident::IdentLike};
impl Fold<FnDecl> for Dce<'_> {
fn fold(&mut self, mut f: FnDecl) -> FnDecl {
if self.is_marked(f.span()) {
return f;
}
if self.marking_phase || self.included.contains(&f.ident.to_id()) {
f.function.span = f.function.span.apply_mark(self.config.used_mark);
f.function.body = self.fold_in_marking_phase(f.function.body);
}
f.fold_children(self)
}
}
impl Fold<ClassDecl> for Dce<'_> {
fn fold(&mut self, mut node: ClassDecl) -> ClassDecl {
if self.is_marked(node.span()) {
return node;
}
if self.marking_phase || self.included.contains(&node.ident.to_id()) {
node.class.span = node.class.span.apply_mark(self.config.used_mark);
}
node.fold_children(self)
}
}
impl Fold<VarDecl> for Dce<'_> {
fn fold(&mut self, var: VarDecl) -> VarDecl {
if self.is_marked(var.span) {
return var;
}
let var: VarDecl = var.fold_children(self);
if self.included.is_empty() {
return var;
}
let ids: Vec<Ident> = find_ids(&var.decls);
for i in ids {
for i1 in &self.included {
if i1.0 == i.sym && i1.1 == i.span.ctxt() {
return VarDecl {
span: var.span.apply_mark(self.config.used_mark),
..var
};
}
}
}
var
}
}
preserve!(TsInterfaceDecl);
preserve!(TsTypeAliasDecl);
preserve!(TsEnumDecl);
preserve!(TsModuleDecl);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,149 @@
use super::Dce;
use swc_common::{Fold, FoldWith};
use swc_ecma_ast::*;
use swc_ecma_utils::find_ids;
impl Fold<ImportDecl> for Dce<'_> {
fn fold(&mut self, import: ImportDecl) -> ImportDecl {
if self.is_marked(import.span) {
return import;
}
let mut import: ImportDecl = import.fold_children(self);
// Side effect import
if import.specifiers.is_empty() {
import.span = import.span.apply_mark(self.config.used_mark);
return import;
}
// First run.
if self.included.is_empty() {
return import;
}
// TODO: Drop unused imports.
// e.g) import { foo, bar } from './foo';
// foo()
let ids: Vec<Ident> = find_ids(&import.specifiers);
for id in ids {
for c in &self.included {
if c.0 == id.sym && c.1 == id.span.ctxt() {
import.span = import.span.apply_mark(self.config.used_mark);
return import;
}
}
}
import
}
}
impl Fold<ExportDecl> for Dce<'_> {
fn fold(&mut self, mut node: ExportDecl) -> ExportDecl {
if self.is_marked(node.span) {
return node;
}
let i = match node.decl {
Decl::Class(ClassDecl { ref ident, .. }) | Decl::Fn(FnDecl { ref ident, .. }) => ident,
// Preserve types
Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => {
node.span = node.span.apply_mark(self.config.used_mark);
return node;
}
// Preserve only exported variables
Decl::Var(ref mut v) => {
// If config.used is None, all exports are preserved
if let Some(..) = self.config.used {
v.decls.retain(|d| self.should_include(d));
}
if !v.decls.is_empty() {
node.span = node.span.apply_mark(self.config.used_mark);
node.decl = self.fold_in_marking_phase(node.decl);
}
return node;
}
};
if self.is_exported(&i.sym) {
node.span = node.span.apply_mark(self.config.used_mark);
node.decl = self.fold_in_marking_phase(node.decl);
}
node
}
}
impl Fold<ExportDefaultExpr> for Dce<'_> {
fn fold(&mut self, mut node: ExportDefaultExpr) -> ExportDefaultExpr {
if self.is_marked(node.span) {
return node;
}
if self.is_exported(&js_word!("default")) {
node.span = node.span.apply_mark(self.config.used_mark);
node.expr = self.fold_in_marking_phase(node.expr);
}
node
}
}
impl Fold<NamedExport> for Dce<'_> {
fn fold(&mut self, mut node: NamedExport) -> NamedExport {
if self.is_marked(node.span) {
return node;
}
// Export only when it's required.
node.specifiers.retain(|s| match s {
ExportSpecifier::Namespace(s) => self.is_exported(&s.name.sym),
ExportSpecifier::Default(..) => self.is_exported(&js_word!("default")),
ExportSpecifier::Named(s) => {
self.is_exported(&s.exported.as_ref().unwrap_or_else(|| &s.orig).sym)
}
});
if !node.specifiers.is_empty() {
node.span = node.span.apply_mark(self.config.used_mark);
node.specifiers = self.fold_in_marking_phase(node.specifiers);
}
node
}
}
impl Fold<ExportDefaultDecl> for Dce<'_> {
fn fold(&mut self, mut node: ExportDefaultDecl) -> ExportDefaultDecl {
if self.is_marked(node.span) {
return node;
}
// TODO: Export only when it's required. (i.e. check self.used_exports)
node.span = node.span.apply_mark(self.config.used_mark);
node.decl = self.fold_in_marking_phase(node.decl);
node
}
}
impl Fold<ExportAll> for Dce<'_> {
fn fold(&mut self, node: ExportAll) -> ExportAll {
if self.is_marked(node.span) {
return node;
}
unimplemented!("dce: `export * from 'foo'`")
}
}
preserve!(TsImportEqualsDecl);
preserve!(TsExportAssignment);
preserve!(TsNamespaceExportDecl);

View File

@ -0,0 +1,200 @@
use super::Dce;
use fxhash::FxHashSet;
use swc_common::{Visit, VisitWith};
use swc_ecma_ast::*;
use swc_ecma_utils::{ident::IdentLike, ExprExt, Id};
impl Dce<'_> {
pub fn should_include<T>(&mut self, node: &T) -> bool
where
T: for<'any> VisitWith<SideEffectVisitor<'any>>,
{
let mut v = SideEffectVisitor {
included: &mut self.included,
exports: self.config.used.as_ref().map(|v| &**v),
found: false,
};
node.visit_with(&mut v);
v.found
}
}
pub(super) struct SideEffectVisitor<'a> {
included: &'a mut FxHashSet<Id>,
exports: Option<&'a [Id]>,
found: bool,
}
impl SideEffectVisitor<'_> {
fn include(&self, i: &Id) -> bool {
let id = i.to_id();
if self.included.contains(&id) {
return true;
}
if let Some(exports) = self.exports {
if exports.contains(&id) {
return true;
}
}
false
}
}
impl Visit<Expr> for SideEffectVisitor<'_> {
fn visit(&mut self, node: &Expr) {
log::debug!("Visit<Expr>");
if self.found || node.is_pure_callee() {
return;
}
match node {
Expr::Lit(..) | Expr::PrivateName(..) | Expr::TsConstAssertion(..) => return,
_ => {}
}
node.visit_children(self)
}
}
impl Visit<AssignExpr> for SideEffectVisitor<'_> {
fn visit(&mut self, node: &AssignExpr) {
if self.found {
return;
}
node.left.visit_with(self);
match &*node.right {
Expr::Ident(..) => {
//TODO: Check for alias
}
right => right.visit_with(self),
}
}
}
impl Visit<MemberExpr> for SideEffectVisitor<'_> {
fn visit(&mut self, _: &MemberExpr) {
self.found = true;
// if self.found {
// return;
// }
// node.obj.visit_with(self);
// if node.computed {
// node.prop.visit_with(self);
// }
}
}
impl Visit<Ident> for SideEffectVisitor<'_> {
fn visit(&mut self, node: &Ident) {
if self.found {
return;
}
self.found |= self.include(&node.to_id());
}
}
impl Visit<CallExpr> for SideEffectVisitor<'_> {
fn visit(&mut self, node: &CallExpr) {
if self.found {
return;
}
match node.callee {
ExprOrSuper::Expr(ref e) if e.is_pure_callee() => return,
_ => {}
}
self.found = true;
}
}
impl Visit<NewExpr> for SideEffectVisitor<'_> {
fn visit(&mut self, _: &NewExpr) {
if self.found {
return;
}
self.found = true;
}
}
impl Visit<ExprOrSpread> for SideEffectVisitor<'_> {
fn visit(&mut self, node: &ExprOrSpread) {
if self.found {
return;
}
if node.spread.is_some() {
self.found = true;
}
if !self.found {
node.expr.visit_with(self);
}
}
}
impl Visit<ReturnStmt> for SideEffectVisitor<'_> {
fn visit(&mut self, _: &ReturnStmt) {
self.found = true;
}
}
impl Visit<ThrowStmt> for SideEffectVisitor<'_> {
fn visit(&mut self, _: &ThrowStmt) {
self.found = true;
}
}
impl Visit<BreakStmt> for SideEffectVisitor<'_> {
fn visit(&mut self, _: &BreakStmt) {
self.found = true;
}
}
impl Visit<ContinueStmt> for SideEffectVisitor<'_> {
fn visit(&mut self, _: &ContinueStmt) {
self.found = true;
}
}
impl Visit<ForStmt> for SideEffectVisitor<'_> {
fn visit(&mut self, _: &ForStmt) {
self.found = true;
}
}
impl Visit<ForInStmt> for SideEffectVisitor<'_> {
fn visit(&mut self, _: &ForInStmt) {
self.found = true;
}
}
impl Visit<ForOfStmt> for SideEffectVisitor<'_> {
fn visit(&mut self, _: &ForOfStmt) {
self.found = true;
}
}
impl Visit<WhileStmt> for SideEffectVisitor<'_> {
fn visit(&mut self, _: &WhileStmt) {
self.found = true;
}
}
impl Visit<DoWhileStmt> for SideEffectVisitor<'_> {
fn visit(&mut self, _: &DoWhileStmt) {
self.found = true;
}
}

View File

@ -0,0 +1,293 @@
use super::Dce;
use swc_common::{fold::FoldWith, Fold, Spanned};
use swc_ecma_ast::*;
impl Fold<ExprStmt> for Dce<'_> {
fn fold(&mut self, node: ExprStmt) -> ExprStmt {
if self.is_marked(node.span) {
return node;
}
if self.should_include(&node.expr) {
let stmt = ExprStmt {
span: node.span.apply_mark(self.config.used_mark),
expr: self.fold_in_marking_phase(node.expr),
};
return stmt;
}
node.fold_children(self)
}
}
impl Fold<BlockStmt> for Dce<'_> {
fn fold(&mut self, node: BlockStmt) -> BlockStmt {
if self.is_marked(node.span) {
return node;
}
let stmts = node.stmts.fold_with(self);
let mut span = node.span;
if stmts.iter().any(|stmt| self.is_marked(stmt.span())) {
span = span.apply_mark(self.config.used_mark);
}
BlockStmt { span, stmts }
}
}
impl Fold<IfStmt> for Dce<'_> {
fn fold(&mut self, node: IfStmt) -> IfStmt {
if self.is_marked(node.span) {
return node;
}
let mut node: IfStmt = node.fold_children(self);
if self.is_marked(node.test.span())
|| self.is_marked(node.cons.span())
|| self.is_marked(node.alt.span())
{
node.span = node.span.apply_mark(self.config.used_mark);
node.test = self.fold_in_marking_phase(node.test);
node.cons = self.fold_in_marking_phase(node.cons);
node.alt = self.fold_in_marking_phase(node.alt);
}
node
}
}
impl Fold<ReturnStmt> for Dce<'_> {
fn fold(&mut self, mut node: ReturnStmt) -> ReturnStmt {
if self.is_marked(node.span) {
return node;
}
node.span = node.span.apply_mark(self.config.used_mark);
let mut node = node.fold_children(self);
if self.is_marked(node.arg.span()) {
node.arg = self.fold_in_marking_phase(node.arg)
}
node
}
}
impl Fold<ThrowStmt> for Dce<'_> {
fn fold(&mut self, mut node: ThrowStmt) -> ThrowStmt {
if self.is_marked(node.span) {
return node;
}
node.span = node.span.apply_mark(self.config.used_mark);
let mut node = node.fold_children(self);
if self.is_marked(node.arg.span()) {
node.arg = self.fold_in_marking_phase(node.arg)
}
node
}
}
impl Fold<LabeledStmt> for Dce<'_> {
fn fold(&mut self, mut node: LabeledStmt) -> LabeledStmt {
if self.is_marked(node.span) {
return node;
}
node.body = node.body.fold_with(self);
if self.is_marked(node.body.span()) {
node.span = node.span.apply_mark(self.config.used_mark);
node.body = self.fold_in_marking_phase(node.body);
}
node
}
}
impl Fold<SwitchStmt> for Dce<'_> {
fn fold(&mut self, mut node: SwitchStmt) -> SwitchStmt {
if self.is_marked(node.span) {
return node;
}
node = node.fold_children(self);
// TODO: Handle fallthrough
// Drop useless switch case.
// node.cases.retain(|case| {
// self.is_marked(case.span)
// });
if self.is_marked(node.discriminant.span())
|| node.cases.iter().any(|case| self.is_marked(case.span))
{
node.span = node.span.apply_mark(self.config.used_mark);
node.cases = self.fold_in_marking_phase(node.cases);
}
node
}
}
impl Fold<SwitchCase> for Dce<'_> {
fn fold(&mut self, mut node: SwitchCase) -> SwitchCase {
if self.is_marked(node.span) {
return node;
}
node = node.fold_children(self);
if self.is_marked(node.test.span()) || node.cons.iter().any(|v| self.is_marked(v.span())) {
node.span = node.span.apply_mark(self.config.used_mark);
node.test = self.fold_in_marking_phase(node.test);
node.cons = self.fold_in_marking_phase(node.cons);
}
node
}
}
impl Fold<TryStmt> for Dce<'_> {
fn fold(&mut self, mut node: TryStmt) -> TryStmt {
if self.is_marked(node.span) {
return node;
}
node = node.fold_children(self);
if self.is_marked(node.block.span())
|| self.is_marked(node.handler.span())
|| self.is_marked(node.finalizer.span())
{
node.span = node.span.apply_mark(self.config.used_mark);
node.block = self.fold_in_marking_phase(node.block);
node.handler = self.fold_in_marking_phase(node.handler);
node.finalizer = self.fold_in_marking_phase(node.finalizer);
}
node
}
}
impl Fold<WhileStmt> for Dce<'_> {
fn fold(&mut self, mut node: WhileStmt) -> WhileStmt {
if self.is_marked(node.span) {
return node;
}
node = node.fold_children(self);
if self.is_marked(node.test.span()) || self.is_marked(node.body.span()) {
node.span = node.span.apply_mark(self.config.used_mark);
node.test = self.fold_in_marking_phase(node.test);
node.body = self.fold_in_marking_phase(node.body);
}
node
}
}
impl Fold<DoWhileStmt> for Dce<'_> {
fn fold(&mut self, mut node: DoWhileStmt) -> DoWhileStmt {
if self.is_marked(node.span) {
return node;
}
node = node.fold_children(self);
if self.is_marked(node.test.span()) || self.is_marked(node.body.span()) {
node.span = node.span.apply_mark(self.config.used_mark);
node.test = self.fold_in_marking_phase(node.test);
node.body = self.fold_in_marking_phase(node.body);
}
node
}
}
impl Fold<ForStmt> for Dce<'_> {
fn fold(&mut self, mut node: ForStmt) -> ForStmt {
if self.is_marked(node.span) {
return node;
}
node = node.fold_children(self);
if self.is_marked(node.init.span())
|| self.is_marked(node.test.span())
|| self.is_marked(node.update.span())
|| self.is_marked(node.body.span())
{
node.span = node.span.apply_mark(self.config.used_mark);
node.test = self.fold_in_marking_phase(node.test);
node.init = self.fold_in_marking_phase(node.init);
node.update = self.fold_in_marking_phase(node.update);
node.body = self.fold_in_marking_phase(node.body);
}
node
}
}
impl Fold<ForInStmt> for Dce<'_> {
fn fold(&mut self, mut node: ForInStmt) -> ForInStmt {
if self.is_marked(node.span) {
return node;
}
node = node.fold_children(self);
if self.is_marked(node.left.span())
|| self.is_marked(node.right.span())
|| self.is_marked(node.body.span())
{
node.span = node.span.apply_mark(self.config.used_mark);
node.left = self.fold_in_marking_phase(node.left);
node.right = self.fold_in_marking_phase(node.right);
node.body = self.fold_in_marking_phase(node.body);
}
node
}
}
impl Fold<ForOfStmt> for Dce<'_> {
fn fold(&mut self, mut node: ForOfStmt) -> ForOfStmt {
if self.is_marked(node.span) {
return node;
}
node = node.fold_children(self);
if self.is_marked(node.left.span())
|| self.is_marked(node.right.span())
|| self.is_marked(node.body.span())
{
node.span = node.span.apply_mark(self.config.used_mark);
node.left = self.fold_in_marking_phase(node.left);
node.right = self.fold_in_marking_phase(node.right);
node.body = self.fold_in_marking_phase(node.body);
}
node
}
}
preserve!(DebuggerStmt);
preserve!(WithStmt);
preserve!(BreakStmt);
preserve!(ContinueStmt);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ use super::SimplifyExpr;
fn fold(src: &str, expected: &str) {
test_transform!(
::swc_ecma_parser::Syntax::default(),
|_| SimplifyExpr,
|_| SimplifyExpr { changed: false },
src,
expected,
true
@ -15,6 +15,11 @@ fn fold_same(s: &str) {
fold(s, s)
}
#[test]
fn regex() {
fold("/ab/?x=1:x=2", "x=1");
}
#[test]
fn object() {
fold("!({a:foo()});", "foo(), false;");

View File

@ -0,0 +1,850 @@
use self::scope::{Scope, ScopeKind, VarType};
use crate::{pass::RepeatedJsPass, scope::IdentType};
use std::borrow::Cow;
use swc_common::{
pass::{CompilerPass, Repeated},
Fold, FoldWith, Visit, VisitWith,
};
use swc_ecma_ast::*;
use swc_ecma_utils::{contains_this_expr, find_ids, ident::IdentLike, undefined, Id};
mod scope;
#[derive(Debug, Default)]
pub struct Config {}
/// Note: this pass assumes that resolver is invoked before the pass.
///
/// As swc focuses on reducing gzipped file size, all strings are inlined.
///
///
/// # TODOs
///
/// - Handling of `void 0`
/// - Properly handle binary expressions.
/// - Track variables access by a function
///
/// Currently all functions are treated as a black box, and all the pass gives
/// up inlining variables across a function call or a constructor call.
pub fn inlining(_: Config) -> impl RepeatedJsPass + 'static {
Inlining {
phase: Phase::Analysis,
is_first_run: true,
changed: false,
scope: Default::default(),
var_decl_kind: VarDeclKind::Var,
ident_type: IdentType::Ref,
pat_mode: PatFoldingMode::VarDecl,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Phase {
Analysis,
Inlining,
}
impl CompilerPass for Inlining<'_> {
fn name() -> Cow<'static, str> {
Cow::Borrowed("inlining")
}
}
impl Repeated for Inlining<'_> {
fn changed(&self) -> bool {
self.changed
}
fn reset(&mut self) {
self.changed = false;
self.is_first_run = false;
}
}
struct Inlining<'a> {
phase: Phase,
is_first_run: bool,
changed: bool,
scope: Scope<'a>,
var_decl_kind: VarDeclKind,
ident_type: IdentType,
pat_mode: PatFoldingMode,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PatFoldingMode {
Assign,
Param,
CatchParam,
VarDecl,
}
impl Inlining<'_> {
fn fold_with_child<T>(&mut self, kind: ScopeKind, node: T) -> T
where
T: 'static + for<'any> FoldWith<Inlining<'any>>,
{
self.with_child(kind, node, |child, node| node.fold_children(child))
}
}
impl Fold<Vec<ModuleItem>> for Inlining<'_> {
fn fold(&mut self, mut items: Vec<ModuleItem>) -> Vec<ModuleItem> {
let old_phase = self.phase;
self.phase = Phase::Analysis;
items = items.fold_children(self);
log::debug!("Switching to Inlining phase");
// Inline
self.phase = Phase::Inlining;
items = items.fold_children(self);
self.phase = old_phase;
items
}
}
impl Fold<Vec<Stmt>> for Inlining<'_> {
fn fold(&mut self, mut items: Vec<Stmt>) -> Vec<Stmt> {
let old_phase = self.phase;
match old_phase {
Phase::Analysis => {
items = items.fold_children(self);
}
Phase::Inlining => {
self.phase = Phase::Analysis;
items = items.fold_children(self);
// Inline
self.phase = Phase::Inlining;
items = items.fold_children(self);
self.phase = old_phase
}
}
items
}
}
impl Fold<VarDecl> for Inlining<'_> {
fn fold(&mut self, decl: VarDecl) -> VarDecl {
self.var_decl_kind = decl.kind;
decl.fold_children(self)
}
}
impl Fold<VarDeclarator> for Inlining<'_> {
fn fold(&mut self, mut node: VarDeclarator) -> VarDeclarator {
let kind = VarType::Var(self.var_decl_kind);
node.init = node.init.fold_with(self);
self.pat_mode = PatFoldingMode::VarDecl;
match self.phase {
Phase::Analysis => match node.name {
Pat::Ident(ref name) => {
//
match &node.init {
None => {
if self.var_decl_kind != VarDeclKind::Const {
self.declare(name.to_id(), None, true, kind);
}
}
// Constants
Some(box e @ Expr::Lit(..)) | Some(box e @ Expr::Ident(..))
if self.var_decl_kind == VarDeclKind::Const =>
{
if self.is_first_run {
self.scope.constants.insert(name.to_id(), Some(e.clone()));
}
}
Some(..) if self.var_decl_kind == VarDeclKind::Const => {
if self.is_first_run {
self.scope.constants.insert(name.to_id(), None);
}
}
// Bindings
Some(box e @ Expr::Lit(..)) | Some(box e @ Expr::Ident(..)) => {
self.declare(name.to_id(), Some(Cow::Borrowed(&e)), false, kind);
if self.scope.is_inline_prevented(&e) {
self.scope.prevent_inline(&name.to_id());
}
}
Some(ref e) => {
if self.var_decl_kind != VarDeclKind::Const {
self.declare(name.to_id(), Some(Cow::Borrowed(&e)), false, kind);
if contains_this_expr(&node.init) {
self.scope.prevent_inline(&name.to_id());
return node;
}
}
}
}
}
_ => {}
},
Phase::Inlining => {
match node.name {
Pat::Ident(ref name) => {
if self.var_decl_kind != VarDeclKind::Const {
let id = name.to_id();
log::trace!("Trying to optimize variable declaration: {:?}", id);
if self.scope.is_inline_prevented(&Expr::Ident(name.clone()))
|| !self
.scope
.has_same_this(&id, node.init.as_ref().map(|v| &**v))
{
log::trace!("Inline is prevented for {:?}", id);
return node;
}
let init = node.init.take().fold_with(self);
log::trace!("\tInit: {:?}", init);
match init {
Some(box Expr::Ident(ref ri)) => {
self.declare(
name.to_id(),
Some(Cow::Owned(Expr::Ident(ri.clone()))),
false,
kind,
);
}
_ => {}
}
match init {
Some(ref e) => {
if self.scope.is_inline_prevented(&e) {
log::trace!(
"Inlining is not possible as inline of the \
initialization was prevented"
);
node.init = init;
self.scope.prevent_inline(&name.to_id());
return node;
}
}
_ => {}
}
let e = match init {
None => None,
Some(box e @ Expr::Lit(..)) | Some(box e @ Expr::Ident(..)) => {
Some(e)
}
Some(box e) => {
if self.scope.is_inline_prevented(&Expr::Ident(name.clone())) {
node.init = Some(box e);
return node;
}
if let Some(cnt) = self.scope.read_cnt(&name.to_id()) {
if cnt == 1 {
Some(e)
} else {
node.init = Some(box e);
return node;
}
} else {
node.init = Some(box e);
return node;
}
}
};
// log::trace!("({}): Inserting {:?}", self.scope.depth(),
// name.to_id());
self.declare(name.to_id(), e.map(Cow::Owned), false, kind);
return node;
}
}
_ => {}
}
}
}
node.name = node.name.fold_with(self);
node
}
}
impl Fold<BlockStmt> for Inlining<'_> {
fn fold(&mut self, node: BlockStmt) -> BlockStmt {
self.fold_with_child(ScopeKind::Block, node)
}
}
impl Fold<ArrowExpr> for Inlining<'_> {
fn fold(&mut self, node: ArrowExpr) -> ArrowExpr {
self.fold_with_child(ScopeKind::Fn { named: false }, node)
}
}
impl Fold<Function> for Inlining<'_> {
fn fold(&mut self, node: Function) -> Function {
self.with_child(
ScopeKind::Fn { named: false },
node,
move |child, mut node| {
child.pat_mode = PatFoldingMode::Param;
node.params = node.params.fold_with(child);
node.body = match node.body {
None => None,
Some(v) => Some(v.fold_children(child)),
};
node
},
)
}
}
impl Fold<FnDecl> for Inlining<'_> {
fn fold(&mut self, node: FnDecl) -> FnDecl {
if self.phase == Phase::Analysis {
self.declare(
node.ident.to_id(),
None,
true,
VarType::Var(VarDeclKind::Var),
);
}
let function = node.function;
let function = self.with_child(
ScopeKind::Fn { named: true },
function,
|child, mut node| {
child.pat_mode = PatFoldingMode::Param;
node.params = node.params.fold_with(child);
node.body = match node.body {
None => None,
Some(v) => Some(v.fold_children(child)),
};
node
},
);
FnDecl { function, ..node }
}
}
impl Fold<FnExpr> for Inlining<'_> {
fn fold(&mut self, node: FnExpr) -> FnExpr {
if let Some(ref ident) = node.ident {
self.scope.add_write(&ident.to_id(), true);
}
FnExpr {
function: node.function.fold_with(self),
..node
}
}
}
impl Fold<IfStmt> for Inlining<'_> {
fn fold(&mut self, mut node: IfStmt) -> IfStmt {
node.test = node.test.fold_with(self);
node.cons = self.fold_with_child(ScopeKind::Cond, node.cons);
node.alt = self.fold_with_child(ScopeKind::Cond, node.alt);
node
}
}
impl Fold<SwitchCase> for Inlining<'_> {
fn fold(&mut self, node: SwitchCase) -> SwitchCase {
self.fold_with_child(ScopeKind::Block, node)
}
}
impl Fold<CatchClause> for Inlining<'_> {
fn fold(&mut self, node: CatchClause) -> CatchClause {
self.with_child(ScopeKind::Block, node, move |child, mut node| {
child.pat_mode = PatFoldingMode::CatchParam;
node.param = node.param.fold_with(child);
match child.phase {
Phase::Analysis => {
let ids: Vec<Id> = find_ids(&node.param);
for id in ids {
child.scope.prevent_inline(&id);
}
}
Phase::Inlining => {}
}
node.body = node.body.fold_with(child);
node
})
}
}
impl Fold<CallExpr> for Inlining<'_> {
fn fold(&mut self, mut node: CallExpr) -> CallExpr {
node.callee = node.callee.fold_with(self);
if self.phase == Phase::Analysis {
match node.callee {
ExprOrSuper::Expr(ref callee) => {
self.scope.mark_this_sensitive(&callee);
}
_ => {}
}
}
node.args = node.args.fold_with(self);
self.scope.store_inline_barrier(self.phase);
node
}
}
impl Fold<NewExpr> for Inlining<'_> {
fn fold(&mut self, mut node: NewExpr) -> NewExpr {
node.callee = node.callee.fold_with(self);
if self.phase == Phase::Analysis {
self.scope.mark_this_sensitive(&node.callee);
}
node.args = node.args.fold_with(self);
self.scope.store_inline_barrier(self.phase);
node
}
}
impl Fold<AssignExpr> for Inlining<'_> {
fn fold(&mut self, e: AssignExpr) -> AssignExpr {
log::trace!("{:?}; Fold<AssignExpr>", self.phase);
self.pat_mode = PatFoldingMode::Assign;
let e = AssignExpr {
left: match e.left {
PatOrExpr::Expr(left) | PatOrExpr::Pat(box Pat::Expr(left)) => {
//
match *left {
Expr::Member(ref left) => {
log::trace!("Assign to member expression!");
let mut v = IdentListVisitor {
scope: &mut self.scope,
};
left.visit_with(&mut v);
e.right.visit_with(&mut v);
}
_ => {}
}
PatOrExpr::Expr(left)
}
PatOrExpr::Pat(p) => PatOrExpr::Pat(p.fold_with(self)),
},
right: e.right.fold_with(self),
..e
};
match e.op {
op!("=") => {}
_ => {
let mut v = IdentListVisitor {
scope: &mut self.scope,
};
e.left.visit_with(&mut v);
e.right.visit_with(&mut v)
}
}
if self.scope.is_inline_prevented(&e.right) {
// Prevent inline for lhd
let ids: Vec<Id> = find_ids(&e.left);
for id in ids {
self.scope.prevent_inline(&id);
}
return e;
}
match *e.right {
Expr::Lit(..) | Expr::Ident(..) => {
//
match e.left {
PatOrExpr::Pat(box Pat::Ident(ref i))
| PatOrExpr::Expr(box Expr::Ident(ref i)) => {
let id = i.to_id();
self.scope.add_write(&id, false);
if let Some(var) = self.scope.find_binding(&id) {
if !var.is_inline_prevented() {
*var.value.borrow_mut() = Some(*e.right.clone());
}
}
}
_ => {}
}
}
_ => {}
}
e
}
}
impl Fold<MemberExpr> for Inlining<'_> {
fn fold(&mut self, mut e: MemberExpr) -> MemberExpr {
e.obj = e.obj.fold_with(self);
if e.computed {
e.prop = e.prop.fold_with(self);
}
e
}
}
impl Fold<Expr> for Inlining<'_> {
fn fold(&mut self, node: Expr) -> Expr {
let node: Expr = node.fold_children(self);
// Codes like
//
// var y;
// y = x;
// use(y)
//
// should be transformed to
//
// var y;
// x;
// use(x)
//
// We cannot know if this is possible while analysis phase
if self.phase == Phase::Inlining {
match node {
Expr::Assign(e @ AssignExpr { op: op!("="), .. }) => {
match e.left {
PatOrExpr::Pat(box Pat::Ident(ref i))
| PatOrExpr::Expr(box Expr::Ident(ref i)) => {
if let Some(var) = self.scope.find_binding_from_current(&i.to_id()) {
if var.is_undefined.get() && !var.is_inline_prevented() {
if !self.scope.is_inline_prevented(&e.right) {
*var.value.borrow_mut() = Some(*e.right.clone());
var.is_undefined.set(false);
return *e.right;
}
}
}
}
_ => {}
}
return Expr::Assign(e);
}
_ => {}
}
}
match node {
Expr::Ident(ref i) => {
let id = i.to_id();
if self.is_first_run {
if let Some(expr) = self.scope.find_constant(&id) {
self.changed = true;
return expr.clone().fold_with(self);
}
}
match self.phase {
Phase::Analysis => {
self.scope.add_read(&id);
}
Phase::Inlining => {
log::trace!("Trying to inline: {:?}", id);
let expr = if let Some(var) = self.scope.find_binding(&id) {
log::trace!("VarInfo: {:?}", var);
if !var.is_inline_prevented() {
let expr = var.value.borrow();
if let Some(expr) = &*expr {
if node != *expr {
self.changed = true;
}
Some(expr.clone())
} else {
if var.is_undefined.get() {
return *undefined(i.span);
} else {
log::trace!("Not a cheap expression");
None
}
}
} else {
log::trace!("Inlining is prevented");
None
}
} else {
None
};
if let Some(expr) = expr {
return expr;
}
}
}
}
_ => {}
}
node
}
}
impl Fold<UpdateExpr> for Inlining<'_> {
fn fold(&mut self, node: UpdateExpr) -> UpdateExpr {
let mut v = IdentListVisitor {
scope: &mut self.scope,
};
node.arg.visit_with(&mut v);
node
}
}
impl Fold<UnaryExpr> for Inlining<'_> {
fn fold(&mut self, node: UnaryExpr) -> UnaryExpr {
match node.op {
op!("delete") => {
let mut v = IdentListVisitor {
scope: &mut self.scope,
};
node.arg.visit_with(&mut v);
return node;
}
_ => {}
}
node.fold_children(self)
}
}
impl Fold<Pat> for Inlining<'_> {
fn fold(&mut self, node: Pat) -> Pat {
let node: Pat = node.fold_children(self);
match node {
Pat::Ident(ref i) => match self.pat_mode {
PatFoldingMode::Param => {
self.declare(
i.to_id(),
Some(Cow::Owned(Expr::Ident(i.clone()))),
false,
VarType::Param,
);
}
PatFoldingMode::CatchParam => {
self.declare(
i.to_id(),
Some(Cow::Owned(Expr::Ident(i.clone()))),
false,
VarType::Var(VarDeclKind::Var),
);
}
PatFoldingMode::VarDecl => {}
PatFoldingMode::Assign => {
if let Some(..) = self.scope.find_binding_from_current(&i.to_id()) {
} else {
self.scope.add_write(&i.to_id(), false);
}
}
},
_ => {}
}
node
}
}
impl Fold<ForInStmt> for Inlining<'_> {
fn fold(&mut self, mut node: ForInStmt) -> ForInStmt {
self.pat_mode = PatFoldingMode::Param;
node.left = node.left.fold_with(self);
{
node.left.visit_with(&mut IdentListVisitor {
scope: &mut self.scope,
});
}
{
node.right.visit_with(&mut IdentListVisitor {
scope: &mut self.scope,
});
}
node.right = node.right.fold_with(self);
node.body = self.fold_with_child(ScopeKind::Loop, node.body);
node
}
}
impl Fold<ForOfStmt> for Inlining<'_> {
fn fold(&mut self, mut node: ForOfStmt) -> ForOfStmt {
self.pat_mode = PatFoldingMode::Param;
node.left = node.left.fold_with(self);
{
node.left.visit_with(&mut IdentListVisitor {
scope: &mut self.scope,
});
}
{
node.right.visit_with(&mut IdentListVisitor {
scope: &mut self.scope,
});
}
node.right = node.right.fold_with(self);
node.body = self.fold_with_child(ScopeKind::Loop, node.body);
node
}
}
impl Fold<ForStmt> for Inlining<'_> {
fn fold(&mut self, mut node: ForStmt) -> ForStmt {
node.init = node.init.fold_with(self);
{
node.init.visit_with(&mut IdentListVisitor {
scope: &mut self.scope,
});
}
{
node.test.visit_with(&mut IdentListVisitor {
scope: &mut self.scope,
});
}
{
node.update.visit_with(&mut IdentListVisitor {
scope: &mut self.scope,
});
}
node.test = node.test.fold_with(self);
node.update = node.update.fold_with(self);
node.body = self.fold_with_child(ScopeKind::Loop, node.body);
if node.init.is_none() && node.test.is_none() && node.update.is_none() {
self.scope.store_inline_barrier(self.phase);
}
node
}
}
impl Fold<WhileStmt> for Inlining<'_> {
fn fold(&mut self, mut node: WhileStmt) -> WhileStmt {
{
node.test.visit_with(&mut IdentListVisitor {
scope: &mut self.scope,
});
}
node.test = node.test.fold_with(self);
node.body = self.fold_with_child(ScopeKind::Loop, node.body);
node
}
}
impl Fold<DoWhileStmt> for Inlining<'_> {
fn fold(&mut self, mut node: DoWhileStmt) -> DoWhileStmt {
{
node.test.visit_with(&mut IdentListVisitor {
scope: &mut self.scope,
});
}
node.test = node.test.fold_with(self);
node.body = self.fold_with_child(ScopeKind::Loop, node.body);
node
}
}
impl Fold<BinExpr> for Inlining<'_> {
fn fold(&mut self, node: BinExpr) -> BinExpr {
match node.op {
op!("&&") | op!("||") => BinExpr {
left: node.left.fold_with(self),
..node
},
_ => node.fold_children(self),
}
}
}
impl Fold<TryStmt> for Inlining<'_> {
fn fold(&mut self, node: TryStmt) -> TryStmt {
node.block.visit_with(&mut IdentListVisitor {
scope: &mut self.scope,
});
TryStmt {
// TODO:
// block: node.block.fold_with(self),
handler: node.handler.fold_with(self),
..node
}
}
}
#[derive(Debug)]
struct IdentListVisitor<'a, 'b> {
scope: &'a mut Scope<'b>,
}
impl Visit<MemberExpr> for IdentListVisitor<'_, '_> {
fn visit(&mut self, node: &MemberExpr) {
node.obj.visit_with(self);
if node.computed {
node.prop.visit_with(self);
}
}
}
impl Visit<Ident> for IdentListVisitor<'_, '_> {
fn visit(&mut self, node: &Ident) {
self.scope.add_write(&node.to_id(), true);
}
}

View File

@ -0,0 +1,675 @@
use super::{Inlining, Phase};
use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
use indexmap::map::{Entry, IndexMap};
use std::{
borrow::Cow,
cell::{Cell, RefCell},
collections::VecDeque,
};
use swc_atoms::js_word;
use swc_ecma_ast::*;
use swc_ecma_utils::{ident::IdentLike, Id};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScopeKind {
/// If / Switch
Cond,
Loop,
Block,
Fn {
named: bool,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum VarType {
Param,
Var(VarDeclKind),
}
impl Default for ScopeKind {
fn default() -> Self {
Self::Fn { named: false }
}
}
impl Inlining<'_> {
pub(super) fn with_child<F, T>(&mut self, kind: ScopeKind, node: T, op: F) -> T
where
F: for<'any> FnOnce(&mut Inlining<'any>, T) -> T,
{
let (node, unresolved_usages, bindings) = {
let mut child = Inlining {
phase: self.phase,
is_first_run: self.is_first_run,
changed: false,
scope: Scope::new(Some(&self.scope), kind),
var_decl_kind: VarDeclKind::Var,
ident_type: self.ident_type,
pat_mode: self.pat_mode,
};
let node = op(&mut child, node);
self.changed |= child.changed;
(node, child.scope.unresolved_usages, child.scope.bindings)
};
log::trace!("propagating variables");
self.scope.unresolved_usages.extend(unresolved_usages);
if match kind {
ScopeKind::Fn { .. } => false,
_ => true,
} {
let v = bindings;
for (id, v) in v.into_iter().filter_map(|(id, v)| {
if v.kind == VarType::Var(VarDeclKind::Var) {
Some((id, v))
} else {
None
}
}) {
let v: VarInfo = v;
log::debug!("Hoisting a variable {:?}", id);
if self.scope.unresolved_usages.contains(&id) {
v.inline_prevented.set(true)
}
v.hoisted.set(true);
*v.value.borrow_mut() = None;
v.is_undefined.set(false);
self.scope.bindings.insert(id, v);
}
}
node
}
/// Note: this method stores the value only if init is [Cow::Owned] or it's
/// [Expr::Ident] or [Expr::Lit].
pub(super) fn declare(
&mut self,
id: Id,
init: Option<Cow<Expr>>,
is_change: bool,
kind: VarType,
) {
log::trace!(
"({}, {:?}) declare({})",
self.scope.depth(),
self.phase,
id.0
);
let init = init.map(|cow| match cow {
Cow::Owned(v) => Cow::Owned(v),
Cow::Borrowed(b) => match b {
Expr::Ident(..) | Expr::Lit(..) => Cow::Owned(b.clone()),
_ => Cow::Borrowed(b),
},
});
let is_undefined = self.var_decl_kind == VarDeclKind::Var
&& !is_change
&& init.is_none()
&& self.phase == Phase::Inlining;
let mut alias_of = None;
let value_idx = match init.as_ref().map(|v| &**v) {
Some(&Expr::Ident(ref vi)) => {
if let Some((value_idx, value_var)) = self.scope.idx_val(&vi.to_id()) {
alias_of = Some(value_var.kind);
Some((value_idx, vi.to_id()))
} else {
None
}
}
_ => None,
};
let is_inline_prevented = self.scope.should_prevent_inline_because_of_scope(&id)
|| match init {
Some(ref e) => self.scope.is_inline_prevented(&e),
_ => false,
};
if is_inline_prevented {
log::trace!("\tdeclare: Inline prevented: {:?}", id)
}
if is_undefined {
log::trace!("\tdeclare: {:?} is undefined", id);
}
let idx = match self.scope.bindings.entry(id.clone()) {
Entry::Occupied(mut e) => {
e.get().is_undefined.set(is_undefined);
e.get().read_cnt.set(0);
e.get().read_from_nested_scope.set(false);
match init {
Some(Cow::Owned(v)) => e.get_mut().value = RefCell::new(Some(v)),
None => e.get_mut().value = RefCell::new(None),
_ => {}
}
e.get().inline_prevented.set(is_inline_prevented);
e.index()
}
Entry::Vacant(e) => {
let idx = e.index();
e.insert(VarInfo {
kind,
alias_of,
read_from_nested_scope: Cell::new(false),
read_cnt: Cell::new(0),
inline_prevented: Cell::new(is_inline_prevented),
is_undefined: Cell::new(is_undefined),
value: RefCell::new(match init {
Some(Cow::Owned(v)) => Some(v),
_ => None,
}),
this_sensitive: Cell::new(false),
hoisted: Cell::new(false),
});
idx
}
};
{
let mut cur = Some(&self.scope);
while let Some(scope) = cur {
if let ScopeKind::Fn { .. } = scope.kind {
break;
}
if scope.kind == ScopeKind::Loop {
log::debug!("preventing inline as it's declared in a loop");
self.scope.prevent_inline(&id);
break;
}
cur = scope.parent;
}
}
//
let barrier_works = match kind {
VarType::Param => false,
_ if alias_of == Some(VarType::Param) => false,
_ => true,
};
if barrier_works {
if let Some((value_idx, vi)) = value_idx {
log::trace!("\tdeclare: {} -> {}", idx, value_idx);
let barrier_exists = (|| {
for &blocker in self.scope.inline_barriers.borrow().iter() {
if value_idx <= blocker && blocker <= idx {
return true;
} else if idx <= blocker && blocker <= value_idx {
return true;
}
}
false
})();
if value_idx > idx || barrier_exists {
log::trace!("Variable use before declaration: {:?}", id);
self.scope.prevent_inline(&id);
self.scope.prevent_inline(&vi)
}
} else {
log::trace!("\tdeclare: value idx is none");
}
}
}
}
#[derive(Debug, Default)]
pub(super) struct Scope<'a> {
pub parent: Option<&'a Scope<'a>>,
pub kind: ScopeKind,
inline_barriers: RefCell<VecDeque<usize>>,
bindings: IndexMap<Id, VarInfo, FxBuildHasher>,
unresolved_usages: FxHashSet<Id>,
/// Simple optimization. We don't need complex scope analysis.
pub constants: FxHashMap<Id, Option<Expr>>,
}
impl<'a> Scope<'a> {
pub fn new(parent: Option<&'a Scope<'a>>, kind: ScopeKind) -> Self {
Self {
parent,
kind,
..Default::default()
}
}
pub fn depth(&self) -> usize {
match self.parent {
None => 0,
Some(p) => p.depth() + 1,
}
}
fn should_prevent_inline_because_of_scope(&self, id: &Id) -> bool {
if self.unresolved_usages.contains(id) {
return true;
}
match self.parent {
None => false,
Some(p) => {
if p.should_prevent_inline_because_of_scope(id) {
return true;
}
if let Some(v) = p.find_binding(id) {
if v.hoisted.get() && v.is_inline_prevented() {
return true;
}
}
false
}
}
}
/// True if the returned scope is self
fn scope_for(&self, id: &Id) -> (&Scope, bool) {
if let Some(..) = self.constants.get(id) {
return (self, true);
}
if let Some(..) = self.find_binding_from_current(id) {
return (self, true);
}
match self.parent {
None => (self, true),
Some(ref p) => {
let (s, _) = p.scope_for(id);
(s, false)
}
}
}
pub fn read_cnt(&self, id: &Id) -> Option<usize> {
if let Some(var) = self.find_binding(id) {
return Some(var.read_cnt.get());
}
None
}
fn read_prevents_inlining(&self, id: &Id) -> bool {
log::trace!("read_prevents_inlining({:?})", id);
if let Some(v) = self.find_binding(id) {
match v.kind {
// Reading parameter is ok.
VarType::Param => return false,
VarType::Var(VarDeclKind::Let) | VarType::Var(VarDeclKind::Const) => return false,
_ => {}
}
// If it's already hoisted, it means that it is already processed by child
// scope.
if v.hoisted.get() {
return false;
}
}
{
let mut cur = Some(self);
while let Some(scope) = cur {
let found = scope.find_binding_from_current(id).is_some();
if found {
log::trace!("found");
break;
}
log::debug!("({}): {}: kind = {:?}", scope.depth(), id.0, scope.kind);
match scope.kind {
ScopeKind::Fn { .. } => {
log::debug!("{}: variable access from a nested function detected", id.0);
return true;
}
ScopeKind::Loop | ScopeKind::Cond => {
return true;
}
_ => {}
}
cur = scope.parent;
}
}
false
}
pub fn add_read(&mut self, id: &Id) {
if self.read_prevents_inlining(id) {
log::debug!("prevent inlining because of read: {}", id.0);
self.prevent_inline(id)
}
if id.0 == js_word!("arguments") {
self.prevent_inline_of_params();
}
if let Some(var_info) = self.find_binding(id) {
var_info.read_cnt.set(var_info.read_cnt.get() + 1);
if var_info.hoisted.get() {
var_info.inline_prevented.set(true);
}
} else {
log::trace!("({}): Unresolved usage.: {:?}", self.depth(), id);
self.unresolved_usages.insert(id.clone());
}
let (scope, is_self) = self.scope_for(id);
if !is_self {
if let Some(var_info) = scope.find_binding_from_current(id) {
var_info.read_from_nested_scope.set(true);
}
}
}
fn write_prevents_inline(&self, id: &Id) -> bool {
log::trace!("write_prevents_inline({})", id.0);
{
let mut cur = Some(self);
while let Some(scope) = cur {
let found = scope.find_binding_from_current(id).is_some();
if found {
break;
}
log::debug!("({}): {}: kind = {:?}", scope.depth(), id.0, scope.kind);
match scope.kind {
ScopeKind::Fn { .. } => {
log::debug!("{}: variable access from a nested function detected", id.0);
return true;
}
ScopeKind::Loop | ScopeKind::Cond => {
return true;
}
_ => {}
}
cur = scope.parent;
}
}
false
}
pub fn add_write(&mut self, id: &Id, force_no_inline: bool) {
if self.write_prevents_inline(id) {
log::debug!("prevent inlining because of write: {}", id.0);
self.prevent_inline(id)
}
if id.0 == js_word!("arguments") {
self.prevent_inline_of_params();
}
let (scope, is_self) = self.scope_for(id);
if let Some(var_info) = scope.find_binding_from_current(id) {
var_info.is_undefined.set(false);
if !is_self || force_no_inline {
self.prevent_inline(id)
}
} else if self.has_constant(id) {
// noop
} else {
log::trace!(
"({}): Unresolved. (scope = ({})): {:?}",
self.depth(),
scope.depth(),
id
);
self.bindings.insert(
id.clone(),
VarInfo {
kind: VarType::Var(VarDeclKind::Var),
alias_of: None,
read_from_nested_scope: Cell::new(false),
read_cnt: Cell::new(0),
inline_prevented: Cell::new(force_no_inline),
value: RefCell::new(None),
is_undefined: Cell::new(false),
this_sensitive: Cell::new(false),
hoisted: Cell::new(false),
},
);
}
}
pub fn find_binding(&self, id: &Id) -> Option<&VarInfo> {
if let Some(e) = self.find_binding_from_current(id) {
return Some(e);
}
self.parent.and_then(|parent| parent.find_binding(id))
}
/// Searches only for current scope.
fn idx_val(&self, id: &Id) -> Option<(usize, &VarInfo)> {
self.bindings.iter().enumerate().find_map(
|(idx, (k, v))| {
if k == id {
Some((idx, v))
} else {
None
}
},
)
}
pub fn find_binding_from_current(&self, id: &Id) -> Option<&VarInfo> {
let (_, v) = self.idx_val(id)?;
Some(v)
}
fn has_constant(&self, id: &Id) -> bool {
if let Some(..) = self.constants.get(id) {
return true;
}
self.parent
.map(|parent| parent.has_constant(id))
.unwrap_or(false)
}
pub fn find_constant(&self, id: &Id) -> Option<&Expr> {
if let Some(Some(e)) = self.constants.get(id) {
return Some(e);
}
self.parent.and_then(|parent| parent.find_constant(id))
}
pub fn mark_this_sensitive(&self, callee: &Expr) {
match callee {
Expr::Ident(ref i) => {
if let Some(v) = self.find_binding(&i.to_id()) {
v.this_sensitive.set(true);
}
}
_ => {}
}
}
pub fn store_inline_barrier(&self, phase: Phase) {
log::trace!("store_inline_barrier({:?})", phase);
match phase {
Phase::Analysis => {
let idx = self.bindings.len();
self.inline_barriers.borrow_mut().push_back(idx);
}
Phase::Inlining => {
//if let Some(idx) =
// self.inline_barriers.borrow_mut().pop_front() {
// for i in 0..idx {
// if let Some((id, _)) = self.bindings.get_index(i) {
// self.prevent_inline(id);
// }
// }
//}
}
}
match self.parent {
None => {}
Some(p) => p.store_inline_barrier(phase),
}
}
fn prevent_inline_of_params(&self) {
for (_, v) in self.bindings.iter() {
let v: &VarInfo = v;
if v.is_param() {
v.inline_prevented.set(true);
}
}
if let ScopeKind::Fn { .. } = self.kind {
return;
}
match self.parent {
Some(p) => p.prevent_inline_of_params(),
None => {}
}
}
pub fn prevent_inline(&self, id: &Id) {
log::debug!("({}) Prevent inlining: {:?}", self.depth(), id);
if let Some(v) = self.find_binding_from_current(id) {
v.inline_prevented.set(true);
}
for (_, v) in self.bindings.iter() {
match v.value.borrow().as_ref() {
Some(&Expr::Ident(ref i)) => {
if i.sym == id.0 && i.span.ctxt() == id.1 {
v.inline_prevented.set(true);
}
}
_ => {}
}
}
match self.parent {
None => {}
Some(p) => p.prevent_inline(id),
}
}
pub fn is_inline_prevented(&self, e: &Expr) -> bool {
match *e {
Expr::Ident(ref ri) => {
if let Some(v) = self.find_binding_from_current(&ri.to_id()) {
return v.inline_prevented.get();
}
}
Expr::Member(MemberExpr {
obj: ExprOrSuper::Expr(box Expr::Ident(ref ri)),
..
}) => {
if let Some(v) = self.find_binding_from_current(&ri.to_id()) {
return v.inline_prevented.get();
}
}
Expr::Update(..) => return true,
// TODO: Remove this
Expr::Paren(..) | Expr::Call(..) | Expr::New(..) => return true,
_ => {}
}
match self.parent {
None => false,
Some(p) => p.is_inline_prevented(e),
}
}
pub fn has_same_this(&self, id: &Id, init: Option<&Expr>) -> bool {
if let Some(v) = self.find_binding(id) {
if v.this_sensitive.get() {
match init {
Some(&Expr::Member(..)) => return false,
_ => {}
}
}
}
true
}
}
#[derive(Debug)]
pub(super) struct VarInfo {
pub kind: VarType,
alias_of: Option<VarType>,
read_from_nested_scope: Cell<bool>,
read_cnt: Cell<usize>,
inline_prevented: Cell<bool>,
this_sensitive: Cell<bool>,
pub value: RefCell<Option<Expr>>,
pub is_undefined: Cell<bool>,
hoisted: Cell<bool>,
}
impl VarInfo {
pub fn is_param(&self) -> bool {
self.kind == VarType::Param
}
pub fn is_inline_prevented(&self) -> bool {
if self.inline_prevented.get() {
return true;
}
if self.this_sensitive.get() {
match *self.value.borrow() {
Some(Expr::Member(..)) => return true,
_ => {}
}
}
false
}
}

View File

@ -0,0 +1,26 @@
//! Ported from closure compiler.
pub use self::{branch::dead_branch_remover, expr::expr_simplifier};
use crate::pass::RepeatedJsPass;
use swc_common::{chain, pass::Repeat};
mod branch;
pub mod dce;
mod expr;
pub mod inlining;
#[derive(Debug, Default)]
pub struct Config<'a> {
pub dce: dce::Config<'a>,
pub inlining: inlining::Config,
}
/// Performs simplify-expr, inlining, remove-dead-branch and dce until nothing
/// changes.
pub fn simplifier<'a>(c: Config<'a>) -> impl RepeatedJsPass + 'a {
Repeat::new(chain!(
expr_simplifier(),
inlining::inlining(c.inlining),
dead_branch_remover(),
dce::dce(c.dce)
))
}

View File

@ -1,5 +1,8 @@
use std::marker::PhantomData;
use swc_common::{Fold, FoldWith};
use swc_common::{
pass::{Repeated, RepeatedPass},
Fold, FoldWith,
};
use swc_ecma_ast::*;
pub fn noop() -> impl Pass {
@ -210,3 +213,10 @@ where
node.fold_children(self)
}
}
pub trait RepeatedJsPass:
Repeated + RepeatedPass<Program> + RepeatedPass<Module> + RepeatedPass<Script> + Pass
{
}
impl<P> RepeatedJsPass for P where P: Repeated + Pass {}

View File

@ -360,6 +360,28 @@ impl<'a> Fold<Ident> for Resolver<'a> {
if cfg!(debug_assertions) && LOG {
eprintln!("\t -> Unresolved");
}
let mark = {
let mut mark = self.mark;
let mut cur = Some(&self.current);
while let Some(scope) = cur {
cur = scope.parent;
if cur.is_none() {
break;
}
mark = mark.parent();
}
mark
};
let span = span.apply_mark(mark);
if cfg!(debug_assertions) && LOG {
eprintln!("\t -> {:?}", span.ctxt());
}
// Support hoisting
self.fold_binding_ident(Ident { sym, span, ..i })
}

View File

@ -1044,3 +1044,12 @@ test!(
Object1.defineProperty();
}"
);
identical!(
hoisting,
"function foo() {
return XXX
}
var XXX = 1;
"
);

View File

@ -200,6 +200,7 @@ where
let expected =
tester.apply_transform(::testing::DropSpan, "output.js", syntax, expected)?;
println!(">>>>> Orig <<<<<\n{}", input);
println!("----- Actual -----");
let tr = make_tr("actual", tr, tester);
@ -236,7 +237,6 @@ where
return Err(());
}
println!(">>>>> Orig <<<<<\n{}", input);
println!(">>>>> Code <<<<<\n{}", actual_src);
if actual_src != expected_src {
panic!(

View File

@ -111,7 +111,7 @@ struct MyHandlers;
impl swc_ecma_codegen::Handlers for MyHandlers {}
fn error_tests(tests: &mut Vec<TestDescAndFn>) -> Result<(), io::Error> {
fn identity_tests(tests: &mut Vec<TestDescAndFn>) -> Result<(), io::Error> {
let dir = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
@ -269,7 +269,7 @@ fn error_tests(tests: &mut Vec<TestDescAndFn>) -> Result<(), io::Error> {
fn identity() {
let args: Vec<_> = env::args().collect();
let mut tests = Vec::new();
error_tests(&mut tests).expect("failed to load testss");
identity_tests(&mut tests).expect("failed to load testss");
test_main(&args, tests, Some(Options::new()));
}

View File

@ -113,7 +113,7 @@ test!(
decorators(Default::default()),
class_properties(),
export(),
simplifier(),
simplifier(Default::default()),
compat::es2018(),
compat::es2017(),
compat::es2016(),

View File

@ -0,0 +1,448 @@
//! Copied from PeepholeIntegrationTest from the google closure compiler.
#![feature(box_syntax)]
#![feature(test)]
#![feature(box_patterns)]
#![feature(specialization)]
use swc_common::chain;
use swc_ecma_transforms::{optimization::simplifier, resolver};
#[macro_use]
mod common;
fn test(src: &str, expected: &str) {
test_transform!(
::swc_ecma_parser::Syntax::default(),
|_| chain!(resolver(), simplifier(Default::default())),
src,
expected,
true
)
}
fn test_same(src: &str) {
test(src, src)
}
macro_rules! to {
($name:ident, $src:expr, $expected:expr) => {
test!(
Default::default(),
|_| chain!(resolver(), simplifier(Default::default())),
$name,
$src,
$expected
);
};
}
macro_rules! optimized_out {
($name:ident, $src:expr) => {
to!($name, $src, "");
};
}
optimized_out!(
single_pass,
"
const a = 1;
if (a) {
const b = 2;
}
"
);
optimized_out!(issue_607, "let a");
to!(
multi_run,
"
let b = 2;
let a = 1;
if (b) { // Removed by first run of remove_dead_branch
a = 2; // It becomes `flat assignment` to a on second run of inlining
}
let c;
if (a) { // Removed by second run of remove_dead_branch
c = 3; // It becomes `flat assignment` to c on third run of inlining.
}
console.log(c); // Prevent optimizing out.
",
"console.log(3)"
);
#[test]
#[ignore] // TODO
fn test_fold_one_child_blocks_integration() {
test(
"function f(){switch(foo()){default:{break}}}",
"function f(){foo()}",
);
test(
"function f(){switch(x){default:{break}}} use(f);",
"function f(){} use(f);",
);
test(
"function f(){switch(x){default:x;case 1:return 2}} use(f);",
"function f(){switch(x){default:case 1:return 2}} use(f);",
);
// ensure that block folding does not break hook ifs
test(
"if(x){if(true){foo();foo()}else{bar();bar()}}",
"if(x){foo();foo()}",
);
test(
"if(x){if(false){foo();foo()}else{bar();bar()}}",
"if(x){bar();bar()}",
);
// Cases where the then clause has no side effects.
test("if(x()){}", "x()");
test("if(x()){} else {x()}", "x()||x()");
test("if(x){}", ""); // Even the condition has no side effect.
test(
"if(a()){A()} else if (b()) {} else {C()}",
"a()?A():b()||C()",
);
test(
"if(a()){} else if (b()) {} else {C()}",
"a() || (b() || C())",
);
test(
"if(a()){A()} else if (b()) {} else if (c()) {} else{D()}",
"a() ? A() : b() || (c() || D())",
);
test(
"if(a()){} else if (b()) {} else if (c()) {} else{D()}",
"a() || (b() || (c() || D()))",
);
test(
"if(a()){A()} else if (b()) {} else if (c()) {} else{}",
"a()?A():b()||c()",
);
// Verify that non-global scope works.
test("function foo(){if(x()){}}", "function foo(){x()}");
}
#[test]
#[ignore] // swc optimizes out everything
fn test_fold_one_child_blocks_string_compare() {
test(
"if (x) {if (y) { var x; } } else{ var z; }",
"if (x) { if (y) var x } else var z",
);
}
#[test]
fn test_necessary_dangling_else() {
test(
"if (x) if (y){ y(); z() } else; else x()",
"if (x) { if(y) { y(); z() } } else x()",
);
}
#[test]
#[ignore] // TODO
fn test_fold_returns_integration() {
// if-then-else duplicate statement removal handles this case:
test("function f(){if(x)return;else return}", "function f(){}");
}
#[test]
fn test_bug1059649() {
// ensure that folding blocks with a single var node doesn't explode
test(
"if(x){var y=3;}var z=5; use(y, z)",
"if(x)var y=3; use(y, 5)",
);
test(
"for(var i=0;i<10;i++){var y=3;}var z=5; use(y, z)",
"for(var i=0;i<10;i++)var y=3; use(y, 5)",
);
test(
"for(var i in x){var y=3;}var z=5; use(y, z)",
"for(var i in x)var y=3; use(y, 5)",
);
test(
"do{var y=3;}while(x);var z=5; use(y, z)",
"do var y=3;while(x); use(y, 5)",
);
}
#[test]
#[ignore] // TODO
fn test_hook_if_integration() {
test(
"if (false){ x = 1; } else if (cond) { x = 2; } else { x = 3; }",
"x=cond?2:3",
);
test("x?void 0:y()", "x||y()");
test("!x?void 0:y()", "x&&y()");
test("x?y():void 0", "x&&y()");
}
#[test]
#[ignore] // normalize
fn test_remove_duplicate_statements_integration() {
test(
concat!(
"function z() {if (a) { return true }",
"else if (b) { return true }",
"else { return true }}",
),
"function z() {return true;}",
);
test(
concat!(
"function z() {if (a()) { return true }",
"else if (b()) { return true }",
"else { return true }}",
),
"function z() {a()||b();return true;}",
);
}
#[test]
#[ignore] // TODO
fn test_fold_logical_op_integration() {
test("if(x && true) z()", "x&&z()");
test("if(x && false) z()", "");
test("if(x || 3) z()", "z()");
test("if(x || false) z()", "x&&z()");
test("if(x==y && false) z()", "");
test("if(y() || x || 3) z()", "y();z()");
}
#[test]
#[ignore] // TODO: This is a bug, but does anyone write code like this?
fn test_fold_bitwise_op_string_compare_integration() {
test("for (;-1 | 0;) {}", "for (;;);");
}
#[test]
fn test_var_lifting_integration() {
test("if(true);else var a;", "");
test("if(false) foo();else var a;", "");
test("if(true)var a;else;", "");
test("if(false)var a;else;", "");
test("if(false)var a,b;", "");
test("if(false){var a;var a;}", "");
test("if(false)var a=function(){var b};", "");
test("if(a)if(false)var a;else var b;", "");
}
#[test]
fn test_bug1438784() {
test_same("for(var i=0;i<10;i++)if(x)x.y;");
}
#[test]
fn test_fold_useless_for_integration() {
test("for(;!true;) { foo() }", "");
test("for(;void 0;) { foo() }", "");
test("for(;undefined;) { foo() }", "");
test("for(;1;) foo()", "for(;;) foo()");
test("for(;!void 0;) foo()", "for(;;) foo()");
// Make sure proper empty nodes are inserted.
// test("if(foo())for(;false;){foo()}else bar()", "foo()||bar()");
test(
"if(foo())for(;false;){foo()}else bar()",
"if (foo()); else bar();",
);
}
#[test]
fn test_fold_useless_do_integration() {
test("do { foo() } while(!true);", "foo()");
test("do { foo() } while(void 0);", "foo()");
test("do { foo() } while(undefined);", "foo()");
test("do { foo() } while(!void 0);", "for(;;)foo();");
// Make sure proper empty nodes are inserted.
// test(
// "if(foo())do {foo()} while(false) else bar()",
// "foo()?foo():bar()",
// );
test(
"if(foo())do {foo()} while(false) else bar()",
"if (foo()) foo(); else bar();",
);
}
#[test]
#[ignore] // TODO
fn test_minimize_expr() {
test("!!true", "");
test("!!x()", "x()");
test("!(!x()&&!y())", "x()||y()");
test("x()||!!y()", "x()||y()");
/* This is similar to the !!true case */
test("!!x()&&y()", "x()&&y()");
}
#[test]
fn test_bug_issue3() {
test_same(concat!(
"function foo() {",
" if(sections.length != 1) children[i] = 0;",
" else var selectedid = children[i]",
"}",
));
}
#[test]
fn test_bug_issue43() {
test_same("function foo() {\n if (a) var b = bar(); else a.b = 1; \n} use(foo);");
}
#[test]
fn test_fold_negative_bug() {
test("for (;-3;){};", "for (;;);");
}
#[test]
#[ignore] // normalize
fn test_no_normalize_labeled_expr() {
test_same("var x; foo:{x = 3;}");
test_same("var x; foo:x = 3;");
}
#[test]
fn test_short_circuit1() {
test("1 && a()", "a()");
}
#[test]
fn test_short_circuit2() {
test("1 && a() && 2", "a()");
}
#[test]
fn test_short_circuit3() {
test("a() && 1 && 2", "a()");
}
#[test]
#[ignore]
fn test_short_circuit4() {
test("a() && (1 && b())", "a() && b()");
test("a() && 1 && b()", "a() && b()");
test("(a() && 1) && b()", "a() && b()");
}
#[test]
#[ignore] // TODO
fn test_minimize_expr_condition() {
test("(x || true) && y()", "y()");
test("(x || false) && y()", "x&&y()");
test("(x && true) && y()", "x && y()");
test("(x && false) && y()", "");
test("a = x || false ? b : c", "a=x?b:c");
test("do {x()} while((x && false) && y())", "x()");
}
// A few miscellaneous cases where one of the peephole passes increases the
// size, but it does it in such a way that a later pass can decrease it.
// Test to make sure the overall change is a decrease, not an increase.
#[test]
#[ignore] // TODO
fn test_misc() {
test("use(x = [foo()] && x)", "use(x = (foo(),x))");
test("x = foo() && false || bar()", "x = (foo(), bar())");
test("if(foo() && false) z()", "foo()");
}
#[test]
#[ignore] // swc strips out whole of this
fn test_comma_spliting_constant_condition() {
test("(b=0,b=1);if(b)x=b;", "b=0;b=1;x=b;");
test("(b=0,b=1);if(b)x=b;", "b=0;b=1;x=b;");
}
#[test]
#[ignore]
fn test_avoid_comma_splitting() {
test("x(),y(),z()", "x();y();z()");
}
#[test]
fn test_object_literal() {
test("({})", "");
test("({a:1})", "");
test("({a:foo()})", "foo()");
test("({'a':foo()})", "foo()");
}
#[test]
fn test_array_literal() {
test("([])", "");
test("([1])", "");
test("([a])", "");
test("([foo()])", "foo()");
}
#[test]
#[ignore] // TODO
fn test_fold_ifs1() {
test(
"function f() {if (x) return 1; else if (y) return 1;}",
"function f() {if (x||y) return 1;}",
);
test(
"function f() {if (x) return 1; else {if (y) return 1; else foo();}}",
"function f() {if (x||y) return 1; foo();}",
);
}
#[test]
#[ignore] // TODO
fn test_fold_ifs2() {
test(
"function f() {if (x) { a(); } else if (y) { a() }}",
"function f() {x?a():y&&a();}",
);
}
#[test]
#[ignore] // TODO
fn test_fold_hook2() {
test(
"function f(a) {if (!a) return a; else return a;}",
"function f(a) {return a}",
);
}
#[test]
#[ignore] // TODO
fn test_template_strings_known_methods() {
test("x = `abcdef`.indexOf('b')", "x = 1");
test("x = [`a`, `b`, `c`].join(``)", "x='abc'");
test("x = `abcdef`.substr(0,2)", "x = 'ab'");
test("x = `abcdef`.substring(0,2)", "x = 'ab'");
test("x = `abcdef`.slice(0,2)", "x = 'ab'");
test("x = `abcdef`.charAt(0)", "x = 'a'");
test("x = `abcdef`.charCodeAt(0)", "x = 97");
test("x = `abc`.toUpperCase()", "x = 'ABC'");
test("x = `ABC`.toLowerCase()", "x = 'abc'");
//test("x = `\t\n\uFEFF\t asd foo bar \r\n`.trim()", "x = 'asd foo bar'");
test("x = parseInt(`123`)", "x = 123");
test("x = parseFloat(`1.23`)", "x = 1.23");
}

View File

@ -0,0 +1,106 @@
#![feature(box_syntax)]
#![feature(test)]
#![feature(box_patterns)]
#![feature(specialization)]
use swc_common::chain;
use swc_ecma_transforms::{optimization::simplify::dce::dce, resolver};
#[macro_use]
mod common;
macro_rules! to {
($name:ident, $src:expr, $expected:expr) => {
test!(
Default::default(),
|_| chain!(resolver(), dce(Default::default())),
$name,
$src,
$expected
);
};
}
macro_rules! optimized_out {
($name:ident, $src:expr) => {
to!($name, $src, "");
};
}
macro_rules! noop {
($name:ident, $src:expr) => {
to!($name, $src, $src);
};
}
optimized_out!(
single_pass,
"
const a = 1;
if (a) {
const b = 2;
}
"
);
optimized_out!(issue_607, "let a");
noop!(
noop_1,
"
let b = 2;
let a = 1;
if (b) {
a = 2;
}
let c;
if (a) {
c = 3;
}
console.log(c);
"
);
noop!(
noop_2,
"
switch (1){
case 1:
a = '1';
}
console.log(a);
"
);
noop!(
noop_3,
"
try {
console.log(foo())
} catch (e) {
console.error(e);
}"
);
to!(
custom_loop_2,
"let b = 2;
let a = 1;
a = 2;
let c;
if (2) c = 3
console.log(c)",
"let c;
if (2) c = 3;
console.log(c);"
);
optimized_out!(simple_const, "{const x = 1}");
noop!(assign_op, "x *= 2; use(x)");

File diff suppressed because it is too large Load Diff

View File

@ -477,6 +477,25 @@ pub trait ExprExt {
v
}
Expr::Bin(BinExpr {
ref left,
op: op!("||"),
ref right,
..
}) => {
let (lp, lv) = left.as_bool();
if let Known(true) = lv {
return (lp, lv);
}
let (rp, rv) = right.as_bool();
if let Known(true) = rv {
return (lp + rp, rv);
}
Unknown
}
Expr::Fn(..) | Expr::Class(..) | Expr::New(..) | Expr::Array(..) | Expr::Object(..) => {
Known(true)
}
@ -1353,7 +1372,7 @@ pub fn undefined(span: Span) -> Box<Expr> {
})
}
/// inject `stmt` after directives
/// inject `branch` after directives
#[inline(never)]
pub fn prepend<T: StmtLike>(stmts: &mut Vec<T>, stmt: T) {
let idx = stmts

View File

@ -1,40 +0,0 @@
#!/bin/bash
set -eu
crate_name() {
POSITIONAL=()
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
--crate-name)
CRATE_NAME="$2"
shift # past argument
shift # past value
;;
*) # unknown option
POSITIONAL+=("$1") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
echo "$CRATE_NAME"
}
cr=$(crate_name "$@")
if [[ $cr == swc* ]]; then
# We use this instead of --document-private-items to
# make output simillar to usage from outside.
#
# e.g. this inlines self::stmt::*, and when we're using ecmascript::ast,
# we can't use ecmascript::ast::stmt because it's private.
# rustdoc --passes strip-hidden,unindent-comments,\
# collapse-docs,strip-priv-imports,propagate-doc-cfg $@
rustdoc --document-private-items $@
else
rustdoc $@
fi

View File

@ -206,7 +206,7 @@ impl Options {
export(),
syntax.export_default_from() || syntax.export_namespace_from()
),
Optional::new(simplifier(), enable_optimizer),
Optional::new(simplifier(Default::default()), enable_optimizer),
json_parse_pass
);

View File

@ -1,6 +1,6 @@
[package]
name = "testing"
version = "0.5.0"
version = "0.5.1"
authors = ["강동윤 <kdy1997.dev@gmail.com>"]
license = "Apache-2.0/MIT"
repository = "https://github.com/swc-project/swc.git"
@ -14,3 +14,6 @@ once_cell = "1"
regex = "1"
relative-path = "1"
difference = "2"
log = "0.4"
env_logger = "0.7.1"
ansi_term = "0.12.1"

View File

@ -8,6 +8,7 @@ use difference::Changeset;
use once_cell::sync::Lazy;
use regex::Regex;
use std::{
fmt,
fmt::Debug,
fs::{create_dir_all, File},
io::Write,
@ -27,11 +28,48 @@ mod output;
mod paths;
mod string_errors;
/// Configures logger
pub fn init() {
use ansi_term::Color;
struct Padded<T> {
value: T,
width: usize,
}
impl<T: fmt::Display> fmt::Display for Padded<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{: <width$}", self.value, width = self.width)
}
}
fn colored_level<'a>(level: log::Level) -> String {
match level {
log::Level::Trace => Color::Cyan.paint("TRACE").to_string(),
log::Level::Debug => Color::Blue.paint("TRACE").to_string(),
log::Level::Info => Color::Green.paint("INFO ").to_string(),
log::Level::Warn => Color::Yellow.paint("WARN ").to_string(),
log::Level::Error => Color::Red.paint("ERROR").to_string(),
}
}
let _ = env_logger::Builder::from_default_env()
.is_test(true)
.format(|f, record| {
let level = colored_level(record.level());
writeln!(f, " {} > {}", level, record.args(),)
})
.try_init();
}
/// Run test and print errors.
pub fn run_test<F, Ret>(treat_err_as_bug: bool, op: F) -> Result<Ret, StdErr>
where
F: FnOnce(Arc<SourceMap>, &Handler) -> Result<Ret, ()>,
{
init();
let cm = Arc::new(SourceMap::new(FilePathMapping::empty()));
let (handler, errors) = self::string_errors::new_handler(cm.clone(), treat_err_as_bug);
let result = swc_common::GLOBALS.set(&swc_common::Globals::new(), || op(cm, &handler));
@ -47,6 +85,8 @@ pub fn run_test2<F, Ret>(treat_err_as_bug: bool, op: F) -> Result<Ret, StdErr>
where
F: FnOnce(Arc<SourceMap>, Handler) -> Result<Ret, ()>,
{
init();
let cm = Arc::new(SourceMap::new(FilePathMapping::empty()));
let (handler, errors) = self::string_errors::new_handler(cm.clone(), treat_err_as_bug);
let result = swc_common::GLOBALS.set(&swc_common::Globals::new(), || op(cm, handler));
@ -64,6 +104,8 @@ pub struct Tester {
impl Tester {
pub fn new() -> Self {
init();
Tester {
cm: Arc::new(SourceMap::new(FilePathMapping::empty())),
globals: swc_common::Globals::new(),

View File

@ -1 +1,2 @@
let a = { b: 'c' };
use(a)