mirror of
https://github.com/swc-project/swc.git
synced 2024-12-23 13:51:19 +03:00
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:
parent
82e73b1121
commit
348052b017
@ -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"
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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
85
common/src/pass.rs
Normal 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
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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" }
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
1493
ecmascript/transforms/src/optimization/simplify/branch/mod.rs
Normal file
1493
ecmascript/transforms/src/optimization/simplify/branch/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -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);",
|
||||
);
|
||||
}
|
67
ecmascript/transforms/src/optimization/simplify/dce/decl.rs
Normal file
67
ecmascript/transforms/src/optimization/simplify/dce/decl.rs
Normal 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
@ -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);
|
@ -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;
|
||||
}
|
||||
}
|
293
ecmascript/transforms/src/optimization/simplify/dce/stmt.rs
Normal file
293
ecmascript/transforms/src/optimization/simplify/dce/stmt.rs
Normal 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
1160
ecmascript/transforms/src/optimization/simplify/expr/mod.rs
Normal file
1160
ecmascript/transforms/src/optimization/simplify/expr/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -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;");
|
||||
|
850
ecmascript/transforms/src/optimization/simplify/inlining/mod.rs
Normal file
850
ecmascript/transforms/src/optimization/simplify/inlining/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
26
ecmascript/transforms/src/optimization/simplify/mod.rs
Normal file
26
ecmascript/transforms/src/optimization/simplify/mod.rs
Normal 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)
|
||||
))
|
||||
}
|
@ -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 {}
|
||||
|
@ -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 })
|
||||
}
|
||||
|
@ -1044,3 +1044,12 @@ test!(
|
||||
Object1.defineProperty();
|
||||
}"
|
||||
);
|
||||
|
||||
identical!(
|
||||
hoisting,
|
||||
"function foo() {
|
||||
return XXX
|
||||
}
|
||||
var XXX = 1;
|
||||
"
|
||||
);
|
||||
|
@ -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!(
|
||||
|
@ -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()));
|
||||
}
|
||||
|
||||
|
@ -113,7 +113,7 @@ test!(
|
||||
decorators(Default::default()),
|
||||
class_properties(),
|
||||
export(),
|
||||
simplifier(),
|
||||
simplifier(Default::default()),
|
||||
compat::es2018(),
|
||||
compat::es2017(),
|
||||
compat::es2016(),
|
||||
|
448
ecmascript/transforms/tests/optimization_simplify.rs
Normal file
448
ecmascript/transforms/tests/optimization_simplify.rs
Normal 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");
|
||||
}
|
106
ecmascript/transforms/tests/optimization_simplify_dce.rs
Normal file
106
ecmascript/transforms/tests/optimization_simplify_dce.rs
Normal 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)");
|
2138
ecmascript/transforms/tests/optimization_simplify_inlining.rs
Normal file
2138
ecmascript/transforms/tests/optimization_simplify_inlining.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -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"
|
@ -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(),
|
||||
|
@ -1 +1,2 @@
|
||||
let a = { b: 'c' };
|
||||
use(a)
|
Loading…
Reference in New Issue
Block a user