├── README.md ├── .gitattributes ├── src ├── anl │ ├── tests │ │ ├── mod.rs │ │ ├── fns.rs │ │ └── refs.rs │ ├── fns.rs │ └── refs.rs ├── anl.rs ├── tests │ ├── mod.rs │ ├── snapshots │ │ ├── jsssa__tests__parse__parse error.snap │ │ ├── jsssa__tests__parse__parse error immediate.snap │ │ └── jsssa__tests__parse__string_has_escape_behavior.snap │ ├── parse.rs │ ├── emit.rs │ ├── collections.rs │ └── ir2ast.rs ├── swc_globals.rs ├── err.rs ├── opt_ast.rs ├── opt_ast │ ├── swc.rs │ ├── tests │ │ ├── swc.rs │ │ ├── mod.rs │ │ ├── merge_vars.rs │ │ ├── if2cond.rs │ │ └── resugar_loops.rs │ ├── resugar_loops.rs │ ├── merge_vars.rs │ └── if2cond.rs ├── emit.rs ├── opt │ ├── tests │ │ ├── mod.rs │ │ ├── writeonly.rs │ │ ├── constant.rs │ │ ├── forward.rs │ │ ├── mut2ssa.rs │ │ ├── dce.rs │ │ ├── inline.rs │ │ └── unroll.rs │ ├── writeonly.rs │ ├── dce.rs │ ├── forward.rs │ ├── mut2ssa.rs │ ├── unroll.rs │ ├── constant.rs │ └── inline.rs ├── cli.rs ├── opt.rs ├── parse.rs ├── main.rs ├── ir │ ├── ref_.rs │ ├── float.rs │ └── scope.rs ├── ir.rs ├── utils.rs ├── collections.rs ├── ast2ir │ └── hoist.rs └── ir2ast │ └── ssa.rs ├── Cargo.toml ├── LICENSE ├── .gitignore └── .github └── workflows └── ci.yml /README.md: -------------------------------------------------------------------------------- 1 | # jsssa -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf 2 | -------------------------------------------------------------------------------- /src/anl/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod fns; 2 | mod refs; 3 | -------------------------------------------------------------------------------- /src/anl.rs: -------------------------------------------------------------------------------- 1 | pub mod fns; 2 | pub mod refs; 3 | 4 | #[cfg(test)] 5 | mod tests; 6 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod all_phases; 2 | mod ast2ir; 3 | mod collections; 4 | mod emit; 5 | mod ir2ast; 6 | mod parse; 7 | -------------------------------------------------------------------------------- /src/tests/snapshots/jsssa__tests__parse__parse error.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/tests/parse.rs 3 | expression: err 4 | --- 5 | Parse error: Expected a semicolon 6 | --> :1:7 7 | | 8 | 1 | var ab-cd = 1; 9 | | ^ 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/swc_globals.rs: -------------------------------------------------------------------------------- 1 | use swc_common::{Globals, GLOBALS}; 2 | 3 | /// A token proving that SWC's globals are initialized. 4 | pub struct Initialized(()); 5 | 6 | /// Initializes the globals used by SWC and provides a token as proof. 7 | /// 8 | /// A higher-rank lifetime ensures the token cannot escape the closure. 9 | pub fn with(f: impl FnOnce(&'_ Initialized) -> R) -> R { 10 | GLOBALS.set(&Globals::new(), || f(&Initialized(()))) 11 | } 12 | -------------------------------------------------------------------------------- /src/tests/snapshots/jsssa__tests__parse__parse error immediate.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/tests/parse.rs 3 | expression: err 4 | --- 5 | Parse error: Unexpected token ``. Expected this, import, async, function, [ for array literal, { for object literal, @ for decorator, function, class, null, true, false, number, bigint, string, regexp, ` for template literal, (, or an identifier 6 | --> :1:1 7 | | 8 | 1 | * 9 | | ^ 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/err.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Debug, Display}; 2 | 3 | pub type Error = Box; 4 | 5 | /// Delegates `Debug` to `Display`, since `Termination` always uses `Debug` to print errors 6 | pub struct NiceError(Error); 7 | 8 | impl Debug for NiceError { 9 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 10 | Display::fmt(&self.0, f) 11 | } 12 | } 13 | 14 | impl> From for NiceError { 15 | fn from(display: T) -> Self { 16 | NiceError(display.into()) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jsssa" 3 | version = "0.0.4" 4 | description = "JS optimizer with SSA-based IR" 5 | edition = "2018" 6 | 7 | [dependencies] 8 | env_logger = { version = "0.8", default-features = false } 9 | log = "0.4" 10 | structopt = { version = "0.3", default-features = false } 11 | swc_atoms = "0.2" 12 | swc_common = { version = "0.10", features = ["tty-emitter"] } 13 | swc_ecma_ast = "0.37" 14 | swc_ecma_codegen = "0.43" 15 | swc_ecma_parser = "0.45" 16 | swc_ecma_transforms = { version = "0.33", features = ["swc_ecma_transforms_optimization"] } 17 | swc_ecma_visit = "0.23" 18 | 19 | [dev-dependencies] 20 | insta = "1.5" 21 | 22 | [profile.dev] 23 | opt-level = 1 24 | 25 | [profile.release] 26 | panic = "abort" 27 | lto = true 28 | codegen-units = 1 29 | debug = 1 30 | -------------------------------------------------------------------------------- /src/opt_ast.rs: -------------------------------------------------------------------------------- 1 | use swc_common::chain; 2 | use swc_ecma_ast as ast; 3 | use swc_ecma_visit::FoldWith; 4 | 5 | use crate::swc_globals; 6 | 7 | mod if2cond; 8 | mod merge_vars; 9 | mod resugar_loops; 10 | mod swc; 11 | 12 | #[cfg(test)] 13 | mod tests; 14 | 15 | pub struct Opt { 16 | pub minify: bool, 17 | } 18 | 19 | #[inline(never)] // for better profiling 20 | pub fn run(g: &swc_globals::Initialized, ast: ast::Program, options: Opt) -> ast::Program { 21 | let ast = swc::run_passes(g, ast); 22 | 23 | let ast = ast.fold_with(&mut chain!(if2cond::If2Cond, resugar_loops::ResugarLoops)); 24 | 25 | let ast = if options.minify { 26 | ast.fold_with(&mut merge_vars::MergeVars) 27 | } else { 28 | ast 29 | }; 30 | 31 | swc::run_passes(g, ast) 32 | } 33 | -------------------------------------------------------------------------------- /src/opt_ast/swc.rs: -------------------------------------------------------------------------------- 1 | use swc_common::chain; 2 | use swc_ecma_ast as ast; 3 | use swc_ecma_transforms as transforms; 4 | use swc_ecma_visit::FoldWith; 5 | 6 | use crate::swc_globals; 7 | 8 | pub fn run_passes(_: &swc_globals::Initialized, ast: ast::Program) -> ast::Program { 9 | // ideally we would use the following transform, but it removes assignments to global variables 10 | // so instead we use the components of it that don't cause problems 11 | let _ideally_we_would_use_this = transforms::optimization::simplifier(Default::default()); 12 | 13 | ast.fold_with(&mut chain!( 14 | transforms::resolver(), 15 | transforms::optimization::simplify::expr_simplifier(), 16 | transforms::optimization::simplify::inlining::inlining(Default::default()), 17 | transforms::optimization::simplify::dead_branch_remover(), 18 | //transforms::optimization::simplify::dce::dce(Default::default()) 19 | )) 20 | } 21 | -------------------------------------------------------------------------------- /src/opt_ast/tests/swc.rs: -------------------------------------------------------------------------------- 1 | case!(basic_empty_if, all_passes, r#" 2 | if (x) { 3 | console.log(1); 4 | } else { 5 | console.log(2); 6 | console.log(3); 7 | } 8 | "#, @r###" 9 | if (x) console.log(1); 10 | else { 11 | console.log(2); 12 | console.log(3); 13 | } 14 | "###); 15 | 16 | case!(chained_single_stmt_ifs, all_passes, r#" 17 | if (x) { 18 | if (y) { 19 | console.log(1); 20 | } else if (z) { 21 | console.log(2); 22 | } 23 | // this else should not get attached to the inner if-elseif 24 | } else { 25 | console.log(3); 26 | } 27 | "#, @r###" 28 | if (x) { 29 | if (y) console.log(1); 30 | else if (z) console.log(2); 31 | } else console.log(3); 32 | "###); 33 | 34 | case!(if_zero_bitor, all_passes, r#" 35 | if (0 | x) { 36 | first(); 37 | } else { 38 | second(); 39 | } 40 | "#, @r###" 41 | if (0 | x) first(); 42 | else second(); 43 | "###); 44 | -------------------------------------------------------------------------------- /src/emit.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use swc_common::SourceMap; 4 | use swc_ecma_ast as ast; 5 | use swc_ecma_codegen::{text_writer::JsWriter, Config, Emitter}; 6 | use swc_ecma_transforms as transforms; 7 | use swc_ecma_visit::FoldWith; 8 | 9 | use crate::err::Error; 10 | use crate::swc_globals; 11 | 12 | pub struct Opt { 13 | pub minify: bool, 14 | } 15 | 16 | #[inline(never)] // for better profiling 17 | pub fn emit( 18 | _: &swc_globals::Initialized, 19 | ast: ast::Program, 20 | files: Rc, 21 | options: Opt, 22 | ) -> Result { 23 | let mut wr = vec![]; 24 | 25 | let fixed_ast = ast.fold_with(&mut transforms::fixer(None)); 26 | 27 | { 28 | let mut emitter = Emitter { 29 | cfg: Config { 30 | minify: options.minify, 31 | }, 32 | cm: files.clone(), 33 | wr: Box::new(JsWriter::new(files, "\n", &mut wr, None)), 34 | comments: None, 35 | }; 36 | emitter.emit_program(&fixed_ast)?; 37 | } 38 | 39 | Ok(String::from_utf8_lossy(&wr).into_owned()) 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Erik Desjardins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | /target/ 61 | **/*.rs.bk 62 | 63 | *.iml 64 | /.idea 65 | 66 | *.pending-snap 67 | -------------------------------------------------------------------------------- /src/opt_ast/tests/mod.rs: -------------------------------------------------------------------------------- 1 | macro_rules! case { 2 | ( $name:ident, || $fold_expr:expr, $string:expr, @ $expected:literal ) => { 3 | #[test] 4 | fn $name() -> Result<(), crate::err::NiceError> { 5 | use swc_ecma_visit::FoldWith; 6 | use crate::{emit, parse, swc_globals}; 7 | swc_globals::with(|g| { 8 | let (ast, files) = parse::parse(g, $string)?; 9 | let ast = ast.fold_with(&mut $fold_expr); 10 | let js = emit::emit(g, ast, files, emit::Opt { minify: false })?; 11 | insta::assert_snapshot!(js, @ $expected); 12 | Ok(()) 13 | }) 14 | } 15 | }; 16 | ( $name:ident, all_passes, $string:expr, @ $expected:literal ) => { 17 | #[test] 18 | fn $name() -> Result<(), crate::err::NiceError> { 19 | use crate::{emit, opt_ast, parse, swc_globals}; 20 | swc_globals::with(|g| { 21 | let (ast, files) = parse::parse(g, $string)?; 22 | let ast = opt_ast::run(g, ast, opt_ast::Opt { minify: false }); 23 | let js = emit::emit(g, ast, files, emit::Opt { minify: false })?; 24 | insta::assert_snapshot!(js, @ $expected); 25 | Ok(()) 26 | }) 27 | } 28 | }; 29 | } 30 | 31 | mod if2cond; 32 | mod merge_vars; 33 | mod resugar_loops; 34 | mod swc; 35 | -------------------------------------------------------------------------------- /src/opt_ast/tests/merge_vars.rs: -------------------------------------------------------------------------------- 1 | use crate::opt_ast::merge_vars; 2 | 3 | case!(basic, || merge_vars::MergeVars, r#" 4 | var x; 5 | var y; 6 | var z; 7 | let a; 8 | let b; 9 | let c; 10 | const d; 11 | const e; 12 | const f; 13 | "#, @r###" 14 | var x, y, z; 15 | let a, b, c; 16 | const d, e, f; 17 | "###); 18 | 19 | case!(basic_values, || merge_vars::MergeVars, r#" 20 | var x; 21 | var y = 1; 22 | var z; 23 | let a; 24 | let b = 2; 25 | let c; 26 | const d; 27 | const e = 3; 28 | const f; 29 | "#, @r###" 30 | var x, y = 1, z; 31 | let a, b = 2, c; 32 | const d, e = 3, f; 33 | "###); 34 | 35 | case!(inner_scopes, || merge_vars::MergeVars, r#" 36 | if (foo) { 37 | var a; 38 | var b = 1; 39 | } 40 | "#, @r###" 41 | if (foo) { 42 | var a, b = 1; 43 | } 44 | "###); 45 | 46 | case!(inner_fn_scopes, || merge_vars::MergeVars, r#" 47 | function foo() { 48 | var a; 49 | var b = 1; 50 | } 51 | "#, @r###" 52 | function foo() { 53 | var a, b = 1; 54 | } 55 | "###); 56 | 57 | case!(bail_nondecl, || merge_vars::MergeVars, r#" 58 | var a; 59 | foo(); 60 | var b = 1; 61 | "#, @r###" 62 | var a; 63 | foo(); 64 | var b = 1; 65 | "###); 66 | 67 | case!(bail_different_types, || merge_vars::MergeVars, r#" 68 | var x; 69 | let y; 70 | const z; 71 | var a; 72 | "#, @r###" 73 | var x; 74 | let y; 75 | const z; 76 | var a; 77 | "###); 78 | -------------------------------------------------------------------------------- /src/opt/tests/mod.rs: -------------------------------------------------------------------------------- 1 | macro_rules! case { 2 | ( $name:ident, |$cx:ident| $cx_expr:expr, $string:expr, @ $expected:literal ) => { 3 | #[test] 4 | fn $name() -> Result<(), crate::err::NiceError> { 5 | use crate::{ast2ir, ir, opt, parse, swc_globals}; 6 | swc_globals::with(|g| { 7 | let (ast, _) = parse::parse(g, $string)?; 8 | let ir = ast2ir::convert(g, ast); 9 | let $cx = opt::OptCx::new(ir); 10 | let ir = opt::OptCx::into_inner($cx_expr); 11 | let ppr = ir::print(g, &ir); 12 | insta::assert_snapshot!(ppr, @ $expected); 13 | Ok(()) 14 | }) 15 | } 16 | }; 17 | ( $name:ident, all_passes, $string:expr, @ $expected:literal ) => { 18 | #[test] 19 | fn $name() -> Result<(), crate::err::NiceError> { 20 | use crate::{ast2ir, ir, opt, parse, swc_globals}; 21 | swc_globals::with(|g| { 22 | let (ast, _) = parse::parse(g, $string)?; 23 | let ir = ast2ir::convert(g, ast); 24 | let ir = opt::run_passes(g, ir); 25 | let ppr = ir::print(g, &ir); 26 | insta::assert_snapshot!(ppr, @ $expected); 27 | Ok(()) 28 | }) 29 | } 30 | }; 31 | } 32 | 33 | mod constant; 34 | mod dce; 35 | mod forward; 36 | mod inline; 37 | mod mut2ssa; 38 | mod redundant; 39 | mod redundant_obj; 40 | mod sroa; 41 | mod unroll; 42 | mod writeonly; 43 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use structopt::StructOpt; 2 | 3 | #[derive(StructOpt, Debug)] 4 | #[structopt(about)] 5 | pub struct Options { 6 | /// Logging verbosity (-v info, -vv debug, -vvv trace) 7 | #[structopt(short = "v", long = "verbose", parse(from_occurrences), global = true)] 8 | pub verbose: u8, 9 | 10 | /// Input file ("-" means stdin) 11 | #[structopt(default_value = "-")] 12 | pub input: String, 13 | 14 | /// Output file ("-" means stdout) 15 | #[structopt(short = "o", default_value = "-")] 16 | pub output: String, 17 | 18 | /// Minify when emitting JS 19 | #[structopt(short = "M", long = "minify")] 20 | pub minify: bool, 21 | 22 | /// Run optimizations (implies all --opt-* flags) 23 | #[structopt(short = "O", long = "optimize")] 24 | pub optimize: bool, 25 | 26 | /// Run optimization passes on IR 27 | #[structopt(long = "opt-ir")] 28 | pub opt_ir: bool, 29 | 30 | /// Inline SSA values when emitting JS 31 | #[structopt(long = "opt-inline-ssa")] 32 | pub opt_inline_ssa: bool, 33 | 34 | /// Run optimization passes on AST 35 | #[structopt(long = "opt-ast")] 36 | pub opt_ast: bool, 37 | 38 | /// Output IR instead of JS 39 | #[structopt(long = "emit-ir")] 40 | pub emit_ir: bool, 41 | } 42 | 43 | impl Options { 44 | pub fn from_args() -> Self { 45 | let mut this: Self = StructOpt::from_args(); 46 | 47 | this.opt_ir |= this.optimize; 48 | this.opt_inline_ssa |= this.optimize; 49 | this.opt_ast |= this.optimize; 50 | 51 | this 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/tests/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::err::NiceError; 2 | use crate::parse; 3 | use crate::swc_globals; 4 | 5 | macro_rules! case { 6 | ( $name:ident, $string:expr ) => { 7 | #[test] 8 | fn $name() -> Result<(), NiceError> { 9 | swc_globals::with(|g| { 10 | let (ast, _) = parse::parse(g, $string)?; 11 | insta::assert_debug_snapshot!(stringify!($name), ast); 12 | Ok(()) 13 | }) 14 | } 15 | }; 16 | } 17 | 18 | case!( 19 | basic, 20 | r#" 21 | function f(x) { 22 | while (true); 23 | x = y.bar; 24 | z.foo = x ? true : 'hi'; 25 | return +[1 || x, { x }, f + 1, ++g]; 26 | } 27 | f(1), true; 28 | "# 29 | ); 30 | 31 | #[test] 32 | fn parse_error() { 33 | // swc successfully parses `var ab` and returns that AST, along with emitting an error 34 | swc_globals::with(|g| match parse::parse(g, "var ab-cd = 1;").err() { 35 | Some(err) => insta::assert_display_snapshot!("parse error", err), 36 | None => panic!("parse unexpectedly succeeded"), 37 | }); 38 | } 39 | 40 | #[test] 41 | fn parse_error_immediate() { 42 | // swc can't parse anything, making this different from `parse_error` 43 | swc_globals::with(|g| match parse::parse(g, "*").err() { 44 | Some(err) => insta::assert_display_snapshot!("parse error immediate", err), 45 | None => panic!("parse unexpectedly succeeded"), 46 | }); 47 | } 48 | 49 | case!( 50 | string_has_escape_behavior, 51 | r#" 52 | "foo"; 53 | "ba\r"; 54 | "ba\\z"; 55 | "# 56 | ); 57 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - v*.*.* 9 | pull_request: 10 | 11 | jobs: 12 | fmt: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v1 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: stable 19 | - run: rustup component add rustfmt 20 | - run: cargo fmt --all -- --check 21 | 22 | clippy: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v1 26 | - uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: stable 29 | - run: rustup component add clippy 30 | - run: RUSTFLAGS="-D warnings" cargo clippy 31 | 32 | test: 33 | runs-on: ubuntu-latest 34 | steps: 35 | - uses: actions/checkout@v1 36 | - uses: actions-rs/toolchain@v1 37 | with: 38 | toolchain: stable 39 | - run: cargo test 40 | 41 | build: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v1 45 | - uses: actions-rs/toolchain@v1 46 | with: 47 | toolchain: stable 48 | target: x86_64-unknown-linux-musl 49 | - run: cargo build --release --target=x86_64-unknown-linux-musl 50 | - run: strip target/x86_64-unknown-linux-musl/release/jsssa 51 | - run: ls -lh target/x86_64-unknown-linux-musl/release/jsssa 52 | - uses: softprops/action-gh-release@v1 53 | if: startsWith(github.ref, 'refs/tags/') 54 | with: 55 | files: target/x86_64-unknown-linux-musl/release/jsssa 56 | env: 57 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 | -------------------------------------------------------------------------------- /src/opt_ast/resugar_loops.rs: -------------------------------------------------------------------------------- 1 | use swc_ecma_ast as ast; 2 | use swc_ecma_visit::{Fold, FoldWith}; 3 | 4 | /// Moves loop conditions back into loop headers. 5 | pub struct ResugarLoops; 6 | 7 | impl Fold for ResugarLoops { 8 | fn fold_for_stmt(&mut self, stmt: ast::ForStmt) -> ast::ForStmt { 9 | let mut stmt = stmt.fold_children_with(self); 10 | 11 | match &mut stmt { 12 | ast::ForStmt { 13 | init: _, 14 | test: for_test @ None, 15 | update: _, 16 | body, 17 | span: _, 18 | } => match body.as_mut() { 19 | ast::Stmt::Block(ast::BlockStmt { stmts, span: _ }) => match stmts.get(0) { 20 | Some(ast::Stmt::If(ast::IfStmt { 21 | test: _, 22 | cons, 23 | alt: Some(alt), 24 | span: _, 25 | })) => match (cons.as_ref(), alt.as_ref()) { 26 | ( 27 | ast::Stmt::Empty(ast::EmptyStmt { span: _ }), 28 | ast::Stmt::Break(ast::BreakStmt { 29 | label: None, 30 | span: _, 31 | }), 32 | ) => match stmts.remove(0) { 33 | ast::Stmt::If(ast::IfStmt { test, .. }) => { 34 | *for_test = Some(test); 35 | stmt 36 | } 37 | _ => unreachable!(), 38 | }, 39 | _ => stmt, 40 | }, 41 | _ => stmt, 42 | }, 43 | _ => stmt, 44 | }, 45 | _ => stmt, 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/opt_ast/merge_vars.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | use swc_ecma_ast as ast; 4 | use swc_ecma_visit::{Fold, FoldWith}; 5 | 6 | /// Merges adjacent variable declarations. 7 | pub struct MergeVars; 8 | 9 | impl Fold for MergeVars { 10 | fn fold_stmts(&mut self, stmts: Vec) -> Vec { 11 | let stmts = stmts.fold_children_with(self); 12 | 13 | let var_to_stmt = |var| ast::Stmt::Decl(ast::Decl::Var(var)); 14 | 15 | let mut out = Vec::with_capacity(stmts.len()); 16 | let mut buffered_var = None; 17 | for stmt in stmts { 18 | match (stmt, &mut buffered_var) { 19 | (ast::Stmt::Decl(ast::Decl::Var(cur)), buf @ None) => { 20 | // no buffer yet, buffer this decl 21 | *buf = Some(cur); 22 | } 23 | (ast::Stmt::Decl(ast::Decl::Var(cur)), Some(buf)) if cur.kind == buf.kind => { 24 | // same kind, add to buffer 25 | buf.decls.extend(cur.decls); 26 | } 27 | (ast::Stmt::Decl(ast::Decl::Var(cur)), Some(_)) => { 28 | // different kinds, swap into buffer 29 | let buffered_var = mem::replace(&mut buffered_var, Some(cur)); 30 | if let Some(buf) = buffered_var { 31 | out.push(var_to_stmt(buf)); 32 | } 33 | } 34 | (stmt, _) => { 35 | // not a var decl, flush buffer 36 | if let Some(buf) = buffered_var.take() { 37 | out.push(var_to_stmt(buf)); 38 | } 39 | out.push(stmt); 40 | } 41 | } 42 | } 43 | if let Some(buf) = buffered_var { 44 | out.push(var_to_stmt(buf)); 45 | } 46 | out 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/opt_ast/tests/if2cond.rs: -------------------------------------------------------------------------------- 1 | use crate::opt_ast::if2cond; 2 | 3 | case!(basic, || if2cond::If2Cond, r#" 4 | var x; 5 | if (foo) x = 1; 6 | else x = 2; 7 | "#, @r###" 8 | var x; 9 | x = foo ? 1 : 2; 10 | "###); 11 | 12 | case!(bail_different_var, || if2cond::If2Cond, r#" 13 | var x; 14 | if (foo) x = 1; 15 | else y = 2; 16 | "#, @r###" 17 | var x; 18 | if (foo) x = 1; 19 | else y = 2; 20 | "###); 21 | 22 | case!(bail_different_var_declared, || if2cond::If2Cond, r#" 23 | var x, y; 24 | if (foo) x = 1; 25 | else y = 2; 26 | "#, @r###" 27 | var x, y; 28 | if (foo) x = 1; 29 | else y = 2; 30 | "###); 31 | 32 | case!(bail_multiple_exprs, || if2cond::If2Cond, r#" 33 | var x; 34 | if (foo) { x = 1; foo(); } 35 | else x = 2; 36 | "#, @r###" 37 | var x; 38 | if (foo) { 39 | x = 1; 40 | foo(); 41 | } else x = 2; 42 | "###); 43 | 44 | case!(bail_multiple_exprs2, || if2cond::If2Cond, r#" 45 | var x; 46 | if (foo) x = 1; 47 | else { x = 2; foo(); } 48 | "#, @r###" 49 | var x; 50 | if (foo) x = 1; 51 | else { 52 | x = 2; 53 | foo(); 54 | } 55 | "###); 56 | 57 | case!(bail_comma, || if2cond::If2Cond, r#" 58 | var x; 59 | if (foo) (x = 1, foo()); 60 | else x = 2; 61 | "#, @r###" 62 | var x; 63 | if (foo) x = 1, foo(); 64 | else x = 2; 65 | "###); 66 | 67 | case!(bail_comma2, || if2cond::If2Cond, r#" 68 | var x; 69 | if (foo) x = 1; 70 | else (x = 2, foo()); 71 | "#, @r###" 72 | var x; 73 | if (foo) x = 1; 74 | else x = 2, foo(); 75 | "###); 76 | 77 | case!(bail_special_op, || if2cond::If2Cond, r#" 78 | var x; 79 | if (foo) x += 1; 80 | else x = 2; 81 | "#, @r###" 82 | var x; 83 | if (foo) x += 1; 84 | else x = 2; 85 | "###); 86 | 87 | case!(bail_special_op2, || if2cond::If2Cond, r#" 88 | var x; 89 | if (foo) x = 1; 90 | else x += 2; 91 | "#, @r###" 92 | var x; 93 | if (foo) x = 1; 94 | else x += 2; 95 | "###); 96 | -------------------------------------------------------------------------------- /src/opt_ast/tests/resugar_loops.rs: -------------------------------------------------------------------------------- 1 | use crate::opt_ast::resugar_loops; 2 | 3 | case!(basic, || resugar_loops::ResugarLoops, r#" 4 | for(;;){ 5 | if (a < A.length); 6 | else break; 7 | a += 1; 8 | } 9 | "#, @r###" 10 | for(; a < A.length;){ 11 | a += 1; 12 | } 13 | "###); 14 | 15 | case!(nested, || resugar_loops::ResugarLoops, r#" 16 | for(;;){ 17 | if (a < A.length); 18 | else break; 19 | a += 1; 20 | 21 | for(;;){ 22 | if (b < B.length); 23 | else break; 24 | b += 1; 25 | } 26 | } 27 | "#, @r###" 28 | for(; a < A.length;){ 29 | a += 1; 30 | for(; b < B.length;){ 31 | b += 1; 32 | } 33 | } 34 | "###); 35 | 36 | case!(bail_existing_test, || resugar_loops::ResugarLoops, r#" 37 | for(;b < 1;){ 38 | if (a < A.length); 39 | else break; 40 | a += 1; 41 | } 42 | "#, @r###" 43 | for(; b < 1;){ 44 | if (a < A.length) ; 45 | else break; 46 | a += 1; 47 | } 48 | "###); 49 | 50 | case!(bail_labelled, || resugar_loops::ResugarLoops, r#" 51 | outer: for (;;) { 52 | for(;;){ 53 | if (a < A.length); 54 | else break outer; 55 | a += 1; 56 | } 57 | } 58 | "#, @r###" 59 | outer: for(;;){ 60 | for(;;){ 61 | if (a < A.length) ; 62 | else break outer; 63 | a += 1; 64 | } 65 | } 66 | "###); 67 | 68 | case!(bail_consequent, || resugar_loops::ResugarLoops, r#" 69 | for(;;){ 70 | if (a < A.length) log(); 71 | else break; 72 | a += 1; 73 | } 74 | "#, @r###" 75 | for(;;){ 76 | if (a < A.length) log(); 77 | else break; 78 | a += 1; 79 | } 80 | "###); 81 | 82 | case!(bail_alternate, || resugar_loops::ResugarLoops, r#" 83 | for(;;){ 84 | if (a < A.length) ; 85 | else log(); 86 | a += 1; 87 | } 88 | "#, @r###" 89 | for(;;){ 90 | if (a < A.length) ; 91 | else log(); 92 | a += 1; 93 | } 94 | "###); 95 | -------------------------------------------------------------------------------- /src/tests/emit.rs: -------------------------------------------------------------------------------- 1 | use crate::emit; 2 | use crate::err::NiceError; 3 | use crate::parse; 4 | use crate::swc_globals; 5 | 6 | macro_rules! case { 7 | ( $name:ident, $string:expr, @ $expected:literal ) => { 8 | #[test] 9 | fn $name() -> Result<(), NiceError> { 10 | swc_globals::with(|g| { 11 | let (ast, files) = parse::parse(g, $string)?; 12 | let js = emit::emit(g, ast, files, emit::Opt { minify: false })?; 13 | insta::assert_snapshot!(js, @ $expected); 14 | Ok(()) 15 | }) 16 | } 17 | }; 18 | } 19 | 20 | case!( 21 | basic, 22 | r#" 23 | function f(x) { 24 | while (true); 25 | x = y.bar; 26 | z.foo = x ? true : 'hi'; 27 | return +[1 || x, { x }, f + 1, ++g]; 28 | } 29 | f(2), true; 30 | "#, 31 | @r###" 32 | function f(x) { 33 | while(true); 34 | x = y.bar; 35 | z.foo = x ? true : 'hi'; 36 | return +[ 37 | 1 || x, 38 | { 39 | x 40 | }, 41 | f + 1, 42 | ++g 43 | ]; 44 | } 45 | f(2), true; 46 | "### 47 | ); 48 | 49 | #[test] 50 | fn no_octal_escapes() -> Result<(), NiceError> { 51 | swc_globals::with(|g| { 52 | let (ast, files) = parse::parse( 53 | g, 54 | r#" 55 | "\x001"; // === "\0" + "1" 56 | "\x008"; // === "\0" + "8" 57 | "#, 58 | )?; 59 | let js = emit::emit(g, ast, files, emit::Opt { minify: false })?; 60 | assert_eq!( 61 | js, 62 | r#""\x001"; 63 | "\x008"; 64 | "# 65 | ); 66 | Ok(()) 67 | }) 68 | } 69 | 70 | #[test] 71 | fn minify() -> Result<(), NiceError> { 72 | swc_globals::with(|g| { 73 | let (ast, files) = parse::parse( 74 | g, 75 | r#" 76 | if (x) { 77 | y; 78 | } 79 | "#, 80 | )?; 81 | let js = emit::emit(g, ast, files, emit::Opt { minify: true })?; 82 | insta::assert_snapshot!(js, @"if(x){y;}"); 83 | Ok(()) 84 | }) 85 | } 86 | -------------------------------------------------------------------------------- /src/opt/tests/writeonly.rs: -------------------------------------------------------------------------------- 1 | use crate::opt::dce; 2 | use crate::opt::forward; 3 | use crate::opt::mut2ssa; 4 | use crate::opt::writeonly; 5 | 6 | macro_rules! passes { 7 | ( $cx:ident ) => { 8 | $cx.run::("mut2ssa") 9 | .run::("forward-reads-redundancy") 10 | .converge::("dce-forwarded-reads") 11 | .converge::("writeonly-objects") 12 | }; 13 | } 14 | 15 | case!( 16 | basic, 17 | |cx| cx.converge::("writeonly-objects"), 18 | r#" 19 | ({}).x = 1; 20 | "#, 21 | @r###" 22 | = "x" 23 | _val = 1 24 | = _val 25 | "###); 26 | 27 | case!( 28 | basic_var, 29 | |cx| passes!(cx), 30 | r#" 31 | const x = {}; 32 | log(); 33 | x.z = 3; 34 | "#, 35 | @r###" 36 | _fun = 37 | = _fun() 38 | = "z" 39 | = 3 40 | "###); 41 | 42 | case!( 43 | basic_bail, 44 | |cx| passes!(cx), 45 | r#" 46 | const x = {}; 47 | log(); 48 | x.z = 3; 49 | g = function f() { 50 | x.y; 51 | } 52 | "#, 53 | @r###" 54 | x = { } 55 | _fun = 56 | = _fun() 57 | _prp = "z" 58 | _val = 3 59 | x[_prp] <- _val 60 | _val$1 = : 61 | _prp$1 = "y" 62 | = x[_prp$1] 63 | <- _val$1 64 | "###); 65 | 66 | case!( 67 | bail_escape, 68 | |cx| passes!(cx), 69 | r#" 70 | const x = {}; 71 | log(); 72 | x.z = 3; 73 | g = function f() { 74 | n = x; 75 | }; 76 | "#, 77 | @r###" 78 | x = { } 79 | _fun = 80 | = _fun() 81 | _prp = "z" 82 | _val = 3 83 | x[_prp] <- _val 84 | _val$1 = : 85 | <- x 86 | <- _val$1 87 | "###); 88 | 89 | case!( 90 | bail_other_index, 91 | |cx| passes!(cx), 92 | r#" 93 | const x = {}; 94 | log(); 95 | z[1] = x; 96 | "#, 97 | @r###" 98 | x = { } 99 | _fun = 100 | = _fun() 101 | _obj = 102 | _prp = 1 103 | _obj[_prp] <- x 104 | "###); 105 | 106 | case!( 107 | bail_other_index2, 108 | |cx| passes!(cx), 109 | r#" 110 | const x = {}; 111 | log(); 112 | z[x] = 1; 113 | "#, 114 | @r###" 115 | x = { } 116 | _fun = 117 | = _fun() 118 | _obj = 119 | _val = 1 120 | _obj[x] <- _val 121 | "###); 122 | 123 | case!( 124 | bail_not_an_object, 125 | |cx| passes!(cx), 126 | r#" 127 | window.x = 1; 128 | "#, 129 | @r###" 130 | _obj = 131 | _prp = "x" 132 | _val = 1 133 | _obj[_prp] <- _val 134 | "###); 135 | -------------------------------------------------------------------------------- /src/opt.rs: -------------------------------------------------------------------------------- 1 | use crate::ir; 2 | use crate::ir::traverse::{Folder, RunFolder}; 3 | use crate::swc_globals; 4 | use crate::utils::default_hash; 5 | 6 | mod constant; 7 | mod dce; 8 | mod forward; 9 | mod inline; 10 | mod mut2ssa; 11 | mod redundant; 12 | mod redundant_obj; 13 | mod sroa; 14 | mod unroll; 15 | mod writeonly; 16 | 17 | #[cfg(test)] 18 | mod tests; 19 | 20 | pub fn run_passes(_: &swc_globals::Initialized, ir: ir::Block) -> ir::Block { 21 | OptCx::new(ir) 22 | .converge_with("main-opt-loop", |cx| { 23 | cx.converge::("dce") 24 | .run::("redundant-load-store") 25 | .run::("mut2ssa") 26 | .run::("forward-reads-redundancy") 27 | .converge::("dce-forwarded-reads") 28 | .run::("unroll-loops") 29 | .run::("scalar-replace") 30 | .run::("redundant-obj") 31 | .run::("writeonly-objects") 32 | .run::("const-prop") 33 | .run::("inline") 34 | }) 35 | .into_inner() 36 | } 37 | 38 | struct OptCx(ir::Block); 39 | 40 | impl OptCx { 41 | fn new(block: ir::Block) -> Self { 42 | Self(block) 43 | } 44 | 45 | fn into_inner(self) -> ir::Block { 46 | self.0 47 | } 48 | 49 | #[inline(never)] // for better profiling 50 | fn run(self, name: &str) -> Self { 51 | log::debug!("{}: running single pass", name); 52 | Self(F::default().run_folder(self.0)) 53 | } 54 | 55 | #[inline(never)] // for better profiling 56 | fn converge(self, name: &str) -> Self { 57 | self.converge_with(name, |cx| cx.run::(name)) 58 | } 59 | 60 | fn converge_with(self, name: &str, mut f: impl FnMut(Self) -> Self) -> Self { 61 | let mut this = self; 62 | let mut last_hash = default_hash(&this.0); 63 | log::debug!("{}: starting opt-to-convergence, hash {}", name, last_hash); 64 | let mut iter = 0u64; 65 | loop { 66 | iter += 1; 67 | this = f(this); 68 | let hash = default_hash(&this.0); 69 | if hash == last_hash { 70 | log::debug!("{}: stopping opt-to-convergence, iteration {}", name, iter); 71 | return this; 72 | } else { 73 | log::debug!("{}: continuing opt-to-convergence, hash {}", name, hash); 74 | } 75 | last_hash = hash; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/anl/fns.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::mem; 3 | 4 | use crate::ir; 5 | use crate::ir::traverse::{RunVisitor, ScopeTy, Visitor}; 6 | 7 | pub fn without_this(ir: &ir::Block) -> HashSet<&ir::Ref> { 8 | let mut collector = WithoutThis::default(); 9 | collector.run_visitor(ir); 10 | collector.fns_without_this 11 | } 12 | 13 | #[derive(Debug, Default)] 14 | pub struct WithoutThis<'a> { 15 | fns_without_this: HashSet<&'a ir::Ref>, 16 | about_to_enter_arrow_fn: bool, 17 | about_to_enter_fn: Option<&'a ir::Ref>, 18 | current_fn: Option<&'a ir::Ref>, 19 | used_this: bool, 20 | } 21 | 22 | impl<'a> Visitor<'a> for WithoutThis<'a> { 23 | fn wrap_scope( 24 | &mut self, 25 | ty: &ScopeTy, 26 | block: &'a ir::Block, 27 | enter: impl FnOnce(&mut Self, &'a ir::Block) -> R, 28 | ) -> R { 29 | match ty { 30 | ScopeTy::Function => { 31 | let is_arrow_fn = mem::replace(&mut self.about_to_enter_arrow_fn, false); 32 | let mut inner = Self::default(); 33 | mem::swap(&mut inner.fns_without_this, &mut self.fns_without_this); 34 | inner.current_fn = self.about_to_enter_fn.take(); 35 | let r = enter(&mut inner, block); 36 | mem::swap(&mut inner.fns_without_this, &mut self.fns_without_this); 37 | // if fn hasn't been invalidated, it's good 38 | self.fns_without_this.extend(inner.current_fn); 39 | // propagate `this` from arrow functions 40 | if is_arrow_fn && inner.used_this { 41 | self.current_fn = None; 42 | self.used_this = true; 43 | } 44 | r 45 | } 46 | ScopeTy::Normal | ScopeTy::Toplevel | ScopeTy::Nonlinear => enter(self, block), 47 | } 48 | } 49 | 50 | fn visit(&mut self, stmt: &'a ir::Stmt) { 51 | match stmt { 52 | ir::Stmt::Expr { 53 | target, 54 | expr: ir::Expr::Function { kind, body: _ }, 55 | } => { 56 | match kind { 57 | ir::FnKind::Arrow { .. } => { 58 | self.about_to_enter_arrow_fn = true; 59 | } 60 | ir::FnKind::Func { .. } => {} 61 | } 62 | self.about_to_enter_fn = Some(target); 63 | } 64 | ir::Stmt::Expr { 65 | target: _, 66 | expr: ir::Expr::This, 67 | } => { 68 | self.current_fn = None; 69 | self.used_this = true; 70 | } 71 | _ => {} 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::io; 3 | use std::io::Write; 4 | use std::rc::Rc; 5 | use std::sync::{Arc, Mutex}; 6 | 7 | use swc_common::errors::emitter::EmitterWriter; 8 | use swc_common::errors::Handler; 9 | use swc_common::{FileName, FilePathMapping, SourceMap}; 10 | use swc_ecma_ast as ast; 11 | use swc_ecma_parser::{Parser, StringInput, Syntax}; 12 | 13 | use crate::swc_globals; 14 | 15 | /// Parse a given ES6+ script into SWC's AST. 16 | #[inline(never)] // for better profiling 17 | pub fn parse( 18 | _: &swc_globals::Initialized, 19 | js: impl Into, 20 | ) -> Result<(ast::Program, Rc), ParseError> { 21 | let files = Rc::new(SourceMap::new(FilePathMapping::empty())); 22 | 23 | let error = BufferedError::default(); 24 | let emitter = EmitterWriter::new(Box::new(error.clone()), Some(files.clone()), false, false); 25 | let handler = Handler::with_emitter_and_flags(Box::new(emitter), Default::default()); 26 | 27 | let file = files.new_source_file(FileName::Anon, js.into()); 28 | 29 | let mut parser = Parser::new( 30 | Syntax::Es(Default::default()), 31 | StringInput::from(file.as_ref()), 32 | None, 33 | ); 34 | 35 | let ast = match parser.parse_script() { 36 | Ok(script) => { 37 | // we may still receive an AST for partial parse results, so check for errors 38 | for e in parser.take_errors() { 39 | e.into_diagnostic(&handler).emit(); 40 | } 41 | Some(ast::Program::Script(script)) 42 | } 43 | Err(e) => { 44 | e.into_diagnostic(&handler).emit(); 45 | None 46 | } 47 | }; 48 | 49 | let err = error.read_msg(); 50 | 51 | match (ast, err) { 52 | (_, Some(err)) => Err(ParseError(err)), 53 | (Some(ast), None) => Ok((ast, files)), 54 | (None, None) => unreachable!("parse failed, but no error message was emitted"), 55 | } 56 | } 57 | 58 | #[derive(Debug)] 59 | pub struct ParseError(String); 60 | 61 | impl std::error::Error for ParseError {} 62 | 63 | impl Display for ParseError { 64 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 65 | write!(f, "Parse {}", self.0) 66 | } 67 | } 68 | 69 | #[derive(Clone, Default)] 70 | struct BufferedError(Arc>>>); 71 | 72 | impl Write for BufferedError { 73 | fn write(&mut self, d: &[u8]) -> io::Result { 74 | self.0.lock().unwrap().get_or_insert_with(Vec::new).write(d) 75 | } 76 | fn flush(&mut self) -> io::Result<()> { 77 | Ok(()) 78 | } 79 | } 80 | 81 | impl BufferedError { 82 | fn read_msg(&self) -> Option { 83 | self.0 84 | .lock() 85 | .unwrap() 86 | .as_ref() 87 | .map(|v| String::from_utf8_lossy(v).into_owned()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/opt/tests/constant.rs: -------------------------------------------------------------------------------- 1 | use crate::opt::constant; 2 | 3 | case!( 4 | basic, 5 | |cx| cx.run::("const-prop"), 6 | r#" 7 | (typeof (1 + 1 + 1 + 1)); 8 | "#, 9 | @r###" 10 | = 1 11 | = 1 12 | = 2 13 | = 1 14 | = 3 15 | = 1 16 | = 4 17 | = "number" 18 | "###); 19 | 20 | case!( 21 | basic_bail, 22 | |cx| cx.run::("const-prop"), 23 | r#" 24 | (typeof (1 + 1 + null + 1)); 25 | "#, 26 | @r###" 27 | = 1 28 | = 1 29 | _lhs = 2 30 | _rhs = 31 | _lhs$1 = _lhs + _rhs 32 | _rhs$1 = 1 33 | _una = _lhs$1 + _rhs$1 34 | = _una 35 | "###); 36 | 37 | case!( 38 | dead_if, 39 | |cx| cx.run::("const-prop"), 40 | r#" 41 | if (true) good; 42 | else bad; 43 | if (0) bad; 44 | else good; 45 | "#, 46 | @r###" 47 | = true 48 | = 49 | = 0 50 | = 51 | "###); 52 | 53 | case!( 54 | nan_and_undefined_magic_globals, 55 | |cx| cx.run::("const-prop"), 56 | r#" 57 | g1 = NaN; 58 | g2 = undefined; 59 | { 60 | let NaN = 1; 61 | let undefined = 2; 62 | g3 = NaN; 63 | g4 = undefined; 64 | } 65 | "#, 66 | @r###" 67 | _val = +NaN 68 | <- _val 69 | = _val 70 | _val$1 = 71 | <- _val$1 72 | = _val$1 73 | _ini = 1 74 | NaN <= _ini 75 | _ini$1 = 2 76 | undefined <= _ini$1 77 | _val$2 = *NaN 78 | <- _val$2 79 | = _val$2 80 | _val$3 = *undefined 81 | <- _val$3 82 | = _val$3 83 | "###); 84 | 85 | case!( 86 | precompute, 87 | all_passes, 88 | r#" 89 | x = 1 + 1 + 1 + 1; 90 | y = "foo" + " " + "bar"; 91 | "#, 92 | @r###" 93 | _val = 4 94 | <- _val 95 | _val$1 = "foo bar" 96 | <- _val$1 97 | "###); 98 | 99 | case!( 100 | string_eq, 101 | |cx| cx.run::("const-prop"), 102 | r#" 103 | "foo" == "bar"; 104 | "foo" != "bar"; 105 | "#, 106 | @r###" 107 | = "foo" 108 | = "bar" 109 | = false 110 | = "foo" 111 | = "bar" 112 | = true 113 | "###); 114 | 115 | case!( 116 | not, 117 | |cx| cx.run::("const-prop"), 118 | r#" 119 | !void 0 120 | ![] 121 | "#, 122 | @r###" 123 | = 0 124 | = 125 | = true 126 | = [] 127 | = false 128 | "###); 129 | 130 | case!( 131 | string_length, 132 | |cx| cx.run::("const-prop"), 133 | r#" 134 | 'foo'.length 135 | 'A\uD87E\uDC04Z'.length 136 | "#, 137 | @r###" 138 | = "foo" 139 | = "length" 140 | = 3 141 | = "A\u{d87e}\u{dc04}Z" 142 | = "length" 143 | = 4 144 | "###); 145 | -------------------------------------------------------------------------------- /src/opt/writeonly.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use crate::ir; 4 | use crate::ir::traverse::{Folder, RunVisitor, ScopeTy, Visitor}; 5 | 6 | /// Remove arrays and objects which are only written to. 7 | /// 8 | /// Does not profit from multiple passes. 9 | /// May profit from DCE running first; may create opportunities for DCE. 10 | #[derive(Debug, Default)] 11 | pub struct Objects { 12 | objects_to_remove: HashSet>, 13 | } 14 | 15 | #[derive(Debug, Default)] 16 | struct CollectObjWriteInfo<'a> { 17 | obj_ops: HashMap<&'a ir::Ref, State>, 18 | last_use_was_safe: bool, 19 | } 20 | 21 | #[derive(Debug)] 22 | enum State { 23 | WriteOnly, 24 | Invalid, 25 | } 26 | 27 | impl<'a> Visitor<'a> for CollectObjWriteInfo<'a> { 28 | fn visit(&mut self, stmt: &'a ir::Stmt) { 29 | self.last_use_was_safe = false; 30 | 31 | match stmt { 32 | ir::Stmt::Expr { 33 | target, 34 | expr: ir::Expr::Object { .. }, 35 | } => { 36 | self.obj_ops.entry(target).or_insert(State::WriteOnly); 37 | } 38 | ir::Stmt::WriteMember { obj, prop, val } => { 39 | self.last_use_was_safe = true; 40 | let _ = obj; 41 | self.obj_ops.insert(prop, State::Invalid); 42 | self.obj_ops.insert(val, State::Invalid); 43 | } 44 | _ => {} 45 | } 46 | } 47 | 48 | fn visit_ref_use(&mut self, ref_: &'a ir::Ref) { 49 | if !self.last_use_was_safe { 50 | self.obj_ops.insert(ref_, State::Invalid); 51 | } 52 | } 53 | } 54 | 55 | impl Folder for Objects { 56 | type Output = Option; 57 | 58 | fn wrap_scope( 59 | &mut self, 60 | ty: &ScopeTy, 61 | block: ir::Block, 62 | enter: impl FnOnce(&mut Self, ir::Block) -> R, 63 | ) -> R { 64 | if let ScopeTy::Toplevel = ty { 65 | let mut collector = CollectObjWriteInfo::default(); 66 | collector.run_visitor(&block); 67 | self.objects_to_remove = collector 68 | .obj_ops 69 | .into_iter() 70 | .filter_map(|(ref_, state)| match state { 71 | State::WriteOnly => Some(ref_.weak()), 72 | State::Invalid => None, 73 | }) 74 | .collect(); 75 | } 76 | 77 | enter(self, block) 78 | } 79 | 80 | fn fold(&mut self, stmt: ir::Stmt) -> Self::Output { 81 | match stmt { 82 | ir::Stmt::Expr { 83 | target: ref obj, 84 | expr: ir::Expr::Object { .. }, 85 | } 86 | | ir::Stmt::WriteMember { 87 | ref obj, 88 | prop: _, 89 | val: _, 90 | } if self.objects_to_remove.contains(&obj.weak()) => None, 91 | _ => Some(stmt), 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/opt/dce.rs: -------------------------------------------------------------------------------- 1 | use crate::ir; 2 | use crate::ir::traverse::{Folder, ScopeTy}; 3 | 4 | /// Dead code elimination. 5 | /// 6 | /// May profit from multiple passes. 7 | #[derive(Debug, Default)] 8 | pub struct Dce { 9 | dropping_after_jump: bool, 10 | } 11 | 12 | impl Folder for Dce { 13 | type Output = Option; 14 | 15 | fn wrap_scope( 16 | &mut self, 17 | _: &ScopeTy, 18 | block: ir::Block, 19 | enter: impl FnOnce(&mut Self, ir::Block) -> R, 20 | ) -> R { 21 | let r = enter(self, block); 22 | // stop dropping when we leave the current scope... 23 | self.dropping_after_jump = false; 24 | r 25 | } 26 | 27 | fn fold(&mut self, stmt: ir::Stmt) -> Self::Output { 28 | // ...or when encountering a case statement 29 | if let ir::Stmt::SwitchCase { .. } = &stmt { 30 | self.dropping_after_jump = false; 31 | } 32 | 33 | if self.dropping_after_jump { 34 | return None; 35 | } 36 | 37 | match stmt { 38 | ir::Stmt::Expr { 39 | ref target, 40 | ref expr, 41 | } if target.used().is_never() => match expr { 42 | ir::Expr::Function { .. } 43 | | ir::Expr::Bool { .. } 44 | | ir::Expr::Number { .. } 45 | | ir::Expr::String { .. } 46 | | ir::Expr::Null 47 | | ir::Expr::Undefined 48 | | ir::Expr::This 49 | | ir::Expr::Read { .. } 50 | | ir::Expr::ReadMutable { .. } 51 | | ir::Expr::ReadGlobal { .. } 52 | | ir::Expr::Array { .. } 53 | | ir::Expr::Object { .. } 54 | | ir::Expr::RegExp { .. } 55 | | ir::Expr::Unary { .. } 56 | | ir::Expr::Binary { .. } 57 | | ir::Expr::CurrentFunction 58 | | ir::Expr::Argument { .. } => None, 59 | ir::Expr::ReadMember { .. } 60 | | ir::Expr::Delete { .. } 61 | | ir::Expr::Yield { .. } 62 | | ir::Expr::Await { .. } 63 | | ir::Expr::Call { .. } => Some(stmt), 64 | }, 65 | ir::Stmt::DeclareMutable { ref target, val: _ } if target.used().is_never() => None, 66 | ir::Stmt::Return { .. } 67 | | ir::Stmt::Throw { .. } 68 | | ir::Stmt::Break { .. } 69 | | ir::Stmt::Continue { .. } => { 70 | self.dropping_after_jump = true; 71 | Some(stmt) 72 | } 73 | ir::Stmt::IfElse { 74 | cond: _, 75 | ref cons, 76 | ref alt, 77 | } if cons.0.is_empty() && alt.0.is_empty() => None, 78 | ir::Stmt::Try { 79 | ref body, 80 | catch: _, 81 | ref finally, 82 | } if body.0.is_empty() && finally.0.is_empty() => None, 83 | _ => Some(stmt), 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(clippy::dbg_macro, clippy::print_stdout)] 2 | #![allow( 3 | clippy::unneeded_field_pattern, 4 | clippy::cognitive_complexity, 5 | clippy::option_map_unit_fn, 6 | clippy::map_clone, 7 | clippy::match_bool, 8 | clippy::match_like_matches_macro, 9 | clippy::single_match 10 | )] 11 | #![allow(unstable_name_collisions)] 12 | 13 | use std::fs; 14 | use std::io; 15 | use std::io::{Read, Write}; 16 | use std::time::Instant; 17 | 18 | use crate::err::NiceError; 19 | use crate::utils::Time; 20 | 21 | mod anl; 22 | mod ast2ir; 23 | mod cli; 24 | mod collections; 25 | mod emit; 26 | mod err; 27 | mod ir; 28 | mod ir2ast; 29 | mod opt; 30 | mod opt_ast; 31 | mod parse; 32 | mod swc_globals; 33 | mod utils; 34 | 35 | #[cfg(test)] 36 | mod tests; 37 | 38 | fn main() -> Result<(), NiceError> { 39 | let cli::Options { 40 | verbose, 41 | input, 42 | output, 43 | minify, 44 | optimize: _, 45 | opt_ir, 46 | opt_inline_ssa, 47 | opt_ast, 48 | emit_ir, 49 | } = cli::Options::from_args(); 50 | 51 | env_logger::Builder::new() 52 | .filter_level(match verbose { 53 | 0 => log::LevelFilter::Warn, 54 | 1 => log::LevelFilter::Info, 55 | 2 => log::LevelFilter::Debug, 56 | _ => log::LevelFilter::Trace, 57 | }) 58 | .init(); 59 | 60 | swc_globals::with(|g| { 61 | let start = Instant::now(); 62 | 63 | let input_string = if input == "-" { 64 | let mut s = String::new(); 65 | io::stdin().read_to_string(&mut s)?; 66 | s 67 | } else { 68 | fs::read_to_string(input)? 69 | }; 70 | log::info!("Done reading @ {}", Time(start.elapsed())); 71 | 72 | let (ast, files) = parse::parse(g, input_string)?; 73 | log::info!("Done parsing @ {}", Time(start.elapsed())); 74 | 75 | let ir = ast2ir::convert(g, ast); 76 | log::info!("Done ast2ir @ {}", Time(start.elapsed())); 77 | 78 | let ir = if opt_ir { 79 | let ir = opt::run_passes(g, ir); 80 | log::info!("Done optimization @ {}", Time(start.elapsed())); 81 | ir 82 | } else { 83 | ir 84 | }; 85 | 86 | let output_string = if emit_ir { 87 | let ppr = ir::print(g, &ir); 88 | log::info!("Done printing @ {}", Time(start.elapsed())); 89 | ppr 90 | } else { 91 | let ast = ir2ast::convert( 92 | g, 93 | ir, 94 | ir2ast::Opt { 95 | inline: opt_inline_ssa, 96 | minify, 97 | }, 98 | ); 99 | log::info!("Done ir2ast @ {}", Time(start.elapsed())); 100 | 101 | let ast = if opt_ast { 102 | let ast = opt_ast::run(g, ast, opt_ast::Opt { minify }); 103 | log::info!("Done ast optimization @ {}", Time(start.elapsed())); 104 | ast 105 | } else { 106 | ast 107 | }; 108 | 109 | let js = emit::emit(g, ast, files, emit::Opt { minify })?; 110 | log::info!("Done emitting @ {}", Time(start.elapsed())); 111 | js 112 | }; 113 | 114 | if output == "-" { 115 | io::stdout().write_all(output_string.as_bytes())?; 116 | } else { 117 | fs::write(output, output_string)?; 118 | } 119 | log::info!("Done writing @ {}", Time(start.elapsed())); 120 | 121 | Ok(()) 122 | }) 123 | } 124 | -------------------------------------------------------------------------------- /src/ir/ref_.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Debug}; 2 | use std::hash::{Hash, Hasher}; 3 | use std::marker::PhantomData; 4 | use std::rc::Rc; 5 | use std::sync::atomic::{AtomicUsize, Ordering}; 6 | 7 | use swc_atoms::JsWord; 8 | 9 | /// SSA references 10 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 11 | pub enum Ssa {} 12 | 13 | /// Mutable references 14 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 15 | pub enum Mut {} 16 | 17 | /// Labels 18 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 19 | pub enum Lbl {} 20 | 21 | pub trait RefType: Clone + PartialEq + Eq + Hash { 22 | const NAME: &'static str; 23 | } 24 | 25 | impl RefType for Ssa { 26 | const NAME: &'static str = "Ssa"; 27 | } 28 | 29 | impl RefType for Mut { 30 | const NAME: &'static str = "Mut"; 31 | } 32 | 33 | impl RefType for Lbl { 34 | const NAME: &'static str = "Lbl"; 35 | } 36 | 37 | /// A unique, statically-resolved reference 38 | #[derive(Clone, PartialEq, Eq, Hash)] 39 | pub struct Ref { 40 | inner: Rc>, 41 | } 42 | 43 | #[derive(Clone, PartialEq, Eq, Hash)] 44 | pub struct WeakRef { 45 | id: usize, 46 | _t: PhantomData, 47 | } 48 | 49 | struct RefInner { 50 | id: usize, 51 | name_hint: JsWord, 52 | _t: PhantomData, 53 | } 54 | 55 | impl Ref { 56 | pub fn new(name_hint: impl Into) -> Self { 57 | static ID_COUNTER: AtomicUsize = AtomicUsize::new(1); 58 | 59 | Self { 60 | inner: Rc::new(RefInner { 61 | id: ID_COUNTER.fetch_add(1, Ordering::Relaxed), 62 | name_hint: name_hint.into(), 63 | _t: PhantomData, 64 | }), 65 | } 66 | } 67 | 68 | pub fn dead() -> Self { 69 | Self::new("_") 70 | } 71 | 72 | pub fn name_hint(&self) -> &JsWord { 73 | &self.inner.name_hint 74 | } 75 | 76 | pub fn used(&self) -> Used { 77 | match Rc::strong_count(&self.inner) { 78 | 0 /* impossible */ | 1 => Used::Never, 79 | 2 => Used::Once, 80 | _ => Used::Mult, 81 | } 82 | } 83 | 84 | pub fn weak(&self) -> WeakRef { 85 | WeakRef { 86 | id: self.inner.id, 87 | _t: PhantomData, 88 | } 89 | } 90 | } 91 | 92 | impl Debug for Ref { 93 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 94 | if self.used().is_never() { 95 | write!(f, "Ref<{}>({}, )", T::NAME, self.inner.id) 96 | } else { 97 | write!( 98 | f, 99 | "Ref<{}>({}, \"{}\")", 100 | T::NAME, 101 | self.inner.id, 102 | self.inner.name_hint 103 | ) 104 | } 105 | } 106 | } 107 | 108 | impl Debug for WeakRef { 109 | fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { 110 | write!(f, "WeakRef({})", self.id) 111 | } 112 | } 113 | 114 | impl PartialEq for RefInner { 115 | fn eq(&self, other: &Self) -> bool { 116 | // compare only by id, which is unique by construction 117 | self.id == other.id 118 | } 119 | } 120 | 121 | impl Eq for RefInner {} 122 | 123 | impl Hash for RefInner { 124 | fn hash(&self, state: &mut H) { 125 | state.write_usize(self.id) 126 | } 127 | } 128 | 129 | pub enum Used { 130 | Never, 131 | Once, 132 | Mult, 133 | } 134 | 135 | impl Used { 136 | pub fn is_never(&self) -> bool { 137 | match self { 138 | Used::Never => true, 139 | _ => false, 140 | } 141 | } 142 | 143 | pub fn is_once(&self) -> bool { 144 | match self { 145 | Used::Once => true, 146 | _ => false, 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/anl/refs.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::ir; 4 | use crate::ir::traverse::{RunVisitor, ScopeTy, Visitor}; 5 | 6 | pub fn used_in_only_one_fn_scope<'a, K>(ir: &'a ir::Block) -> impl Iterator> 7 | where 8 | K: ir::RefType + 'a, 9 | UsedInOnlyOneFnScope<'a, K>: Visitor<'a>, 10 | { 11 | let mut collector = UsedInOnlyOneFnScope::default(); 12 | collector.run_visitor(ir); 13 | collector 14 | .refs 15 | .into_iter() 16 | .filter_map(|(ref_, state)| match state { 17 | State::Valid(_) => Some(ref_), 18 | State::Invalid => None, 19 | }) 20 | } 21 | 22 | type LevelNumber = u64; 23 | 24 | #[derive(Debug)] 25 | pub struct UsedInOnlyOneFnScope<'a, K: ir::RefType> { 26 | refs: HashMap<&'a ir::Ref, State>, 27 | cur_level: LevelNumber, 28 | } 29 | 30 | #[derive(Debug)] 31 | enum State { 32 | Valid(LevelNumber), 33 | Invalid, 34 | } 35 | 36 | impl<'a, K: ir::RefType> Default for UsedInOnlyOneFnScope<'a, K> { 37 | fn default() -> Self { 38 | Self { 39 | refs: Default::default(), 40 | cur_level: Default::default(), 41 | } 42 | } 43 | } 44 | 45 | impl<'a, K: ir::RefType> UsedInOnlyOneFnScope<'a, K> { 46 | fn visit_ref(&mut self, ref_: &'a ir::Ref) { 47 | let state = self 48 | .refs 49 | .entry(ref_) 50 | .or_insert(State::Valid(self.cur_level)); 51 | match state { 52 | State::Valid(level) if *level != self.cur_level => { 53 | *state = State::Invalid; 54 | } 55 | State::Valid(_) | State::Invalid => { 56 | // only used in current scope or already invalid 57 | } 58 | } 59 | } 60 | } 61 | 62 | impl<'a> Visitor<'a> for UsedInOnlyOneFnScope<'a, ir::Ssa> { 63 | fn wrap_scope( 64 | &mut self, 65 | ty: &ScopeTy, 66 | block: &'a ir::Block, 67 | enter: impl FnOnce(&mut Self, &'a ir::Block) -> R, 68 | ) -> R { 69 | match ty { 70 | ScopeTy::Function => { 71 | self.cur_level += 1; 72 | let r = enter(self, block); 73 | self.cur_level -= 1; 74 | r 75 | } 76 | ScopeTy::Toplevel | ScopeTy::Normal | ScopeTy::Nonlinear => enter(self, block), 77 | } 78 | } 79 | 80 | fn visit(&mut self, stmt: &'a ir::Stmt) { 81 | match stmt { 82 | ir::Stmt::Expr { target, expr: _ } => { 83 | self.visit_ref(target); 84 | } 85 | _ => {} 86 | } 87 | } 88 | 89 | fn visit_ref_use(&mut self, ref_: &'a ir::Ref) { 90 | self.visit_ref(ref_); 91 | } 92 | } 93 | 94 | impl<'a> Visitor<'a> for UsedInOnlyOneFnScope<'a, ir::Mut> { 95 | fn wrap_scope( 96 | &mut self, 97 | ty: &ScopeTy, 98 | block: &'a ir::Block, 99 | enter: impl FnOnce(&mut Self, &'a ir::Block) -> R, 100 | ) -> R { 101 | match ty { 102 | ScopeTy::Function => { 103 | self.cur_level += 1; 104 | let r = enter(self, block); 105 | self.cur_level -= 1; 106 | r 107 | } 108 | ScopeTy::Toplevel | ScopeTy::Normal | ScopeTy::Nonlinear => enter(self, block), 109 | } 110 | } 111 | 112 | fn visit(&mut self, stmt: &'a ir::Stmt) { 113 | match stmt { 114 | ir::Stmt::Expr { 115 | target: _, 116 | expr: ir::Expr::ReadMutable { source: ref_ }, 117 | } 118 | | ir::Stmt::DeclareMutable { 119 | target: ref_, 120 | val: _, 121 | } 122 | | ir::Stmt::WriteMutable { 123 | target: ref_, 124 | val: _, 125 | } => { 126 | self.visit_ref(ref_); 127 | } 128 | _ => {} 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/opt_ast/if2cond.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | use swc_ecma_ast as ast; 4 | use swc_ecma_visit::{Fold, FoldWith}; 5 | 6 | /// Converts if-statements where both sides assign to the same variable into conditional expressions. 7 | pub struct If2Cond; 8 | 9 | impl Fold for If2Cond { 10 | fn fold_stmt(&mut self, stmt: ast::Stmt) -> ast::Stmt { 11 | let mut stmt = stmt.fold_children_with(self); 12 | 13 | match &mut stmt { 14 | ast::Stmt::If(ast::IfStmt { 15 | test, 16 | cons, 17 | alt: Some(alt), 18 | span, 19 | }) => match (cons.as_mut(), alt.as_mut()) { 20 | ( 21 | ast::Stmt::Expr(ast::ExprStmt { 22 | expr: cons_expr, 23 | span: _, 24 | }), 25 | ast::Stmt::Expr(ast::ExprStmt { 26 | expr: alt_expr, 27 | span: _, 28 | }), 29 | ) => match (cons_expr.as_mut(), alt_expr.as_mut()) { 30 | ( 31 | ast::Expr::Assign(ast::AssignExpr { 32 | op: ast::AssignOp::Assign, 33 | left: ast::PatOrExpr::Pat(left_pat), 34 | right: left_val, 35 | span: _, 36 | }), 37 | ast::Expr::Assign(ast::AssignExpr { 38 | op: ast::AssignOp::Assign, 39 | left: ast::PatOrExpr::Pat(right_pat), 40 | right: right_val, 41 | span: _, 42 | }), 43 | ) => match (left_pat.as_ref(), right_pat.as_ref()) { 44 | (ast::Pat::Ident(left_ident), ast::Pat::Ident(right_ident)) 45 | if left_ident.sym == right_ident.sym => 46 | { 47 | ast::Stmt::Expr(ast::ExprStmt { 48 | span: *span, 49 | expr: Box::new(ast::Expr::Assign(ast::AssignExpr { 50 | span: *span, 51 | op: ast::AssignOp::Assign, 52 | left: ast::PatOrExpr::Pat(mem::replace( 53 | left_pat, 54 | Box::new(ast::Pat::Invalid(ast::Invalid { span: *span })), 55 | )), 56 | right: Box::new(ast::Expr::Cond(ast::CondExpr { 57 | span: *span, 58 | test: mem::replace( 59 | test, 60 | Box::new(ast::Expr::Invalid(ast::Invalid { 61 | span: *span, 62 | })), 63 | ), 64 | cons: mem::replace( 65 | left_val, 66 | Box::new(ast::Expr::Invalid(ast::Invalid { 67 | span: *span, 68 | })), 69 | ), 70 | alt: mem::replace( 71 | right_val, 72 | Box::new(ast::Expr::Invalid(ast::Invalid { 73 | span: *span, 74 | })), 75 | ), 76 | })), 77 | })), 78 | }) 79 | } 80 | _ => stmt, 81 | }, 82 | _ => stmt, 83 | }, 84 | _ => stmt, 85 | }, 86 | _ => stmt, 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/tests/collections.rs: -------------------------------------------------------------------------------- 1 | mod stacked_map { 2 | use crate::collections::StackedMap; 3 | 4 | #[test] 5 | fn basic() { 6 | let mut m = StackedMap::default(); 7 | m.insert_self(0, 1); 8 | assert_eq!(m.get_self(&0), Some(&1)); 9 | assert_eq!(m.get_all(&0), Some(&1)); 10 | assert_eq!(m.get_self(&2), None); 11 | assert_eq!(m.get_all(&2), None); 12 | m.remove_self(&0); 13 | assert_eq!(m.get_self(&0), None); 14 | assert_eq!(m.get_all(&0), None); 15 | } 16 | 17 | #[test] 18 | fn child() { 19 | let mut m = StackedMap::default(); 20 | m.insert_self(0, 0); 21 | let mut m1 = m.child(); 22 | m1.insert_self(1, 1); 23 | let mut m2 = m1.child(); 24 | m2.insert_self(2, 2); 25 | assert_eq!(m2.get_self(&0), None); 26 | assert_eq!(m2.get_self(&1), None); 27 | assert_eq!(m2.get_self(&2), Some(&2)); 28 | assert_eq!(m2.get_all(&0), Some(&0)); 29 | assert_eq!(m2.get_all(&1), Some(&1)); 30 | assert_eq!(m2.get_self(&2), Some(&2)); 31 | m2.clear_self(); 32 | assert_eq!(m2.get_all(&0), Some(&0)); 33 | assert_eq!(m2.get_all(&1), Some(&1)); 34 | assert_eq!(m2.get_self(&2), None); 35 | m1.remove_self(&0); 36 | assert_eq!(m1.get_all(&0), Some(&0)); 37 | assert_eq!(m1.get_all(&1), Some(&1)); 38 | m1.remove_self(&1); 39 | assert_eq!(m1.get_all(&0), Some(&0)); 40 | assert_eq!(m1.get_all(&1), None); 41 | } 42 | 43 | #[test] 44 | fn child_limited_lifetime() { 45 | let m = StackedMap::default(); 46 | let mut m1 = m.child(); 47 | { 48 | let mut m2 = m1.child(); 49 | m2.insert_self(2, 2); 50 | } 51 | m1.insert_self(1, 1); 52 | } 53 | } 54 | 55 | mod small_map { 56 | use crate::collections::SmallMap; 57 | 58 | #[test] 59 | fn insert() { 60 | let mut m = SmallMap::default(); 61 | 62 | assert_eq!(m.get(&0), None); 63 | 64 | assert_eq!(m.insert(0, 1), None); 65 | assert_eq!(m.get(&0), Some(&1)); 66 | assert_eq!(m.get(&1), None); 67 | 68 | assert_eq!(m.insert(0, 2), Some(1)); 69 | assert_eq!(m.get(&0), Some(&2)); 70 | assert_eq!(m.get(&1), None); 71 | 72 | assert_eq!(m.insert(1, 3), None); 73 | assert_eq!(m.get(&0), Some(&2)); 74 | assert_eq!(m.get(&1), Some(&3)); 75 | assert_eq!(m.get(&2), None); 76 | 77 | assert_eq!(m.insert(1, 4), Some(3)); 78 | assert_eq!(m.get(&0), Some(&2)); 79 | assert_eq!(m.get(&1), Some(&4)); 80 | assert_eq!(m.get(&2), None); 81 | 82 | assert_eq!(m.insert(2, 5), None); 83 | assert_eq!(m.get(&0), Some(&2)); 84 | assert_eq!(m.get(&1), Some(&4)); 85 | assert_eq!(m.get(&2), Some(&5)); 86 | assert_eq!(m.get(&3), None); 87 | 88 | assert_eq!(m.insert(2, 6), Some(5)); 89 | assert_eq!(m.get(&0), Some(&2)); 90 | assert_eq!(m.get(&1), Some(&4)); 91 | assert_eq!(m.get(&2), Some(&6)); 92 | assert_eq!(m.get(&3), None); 93 | } 94 | 95 | #[test] 96 | fn remove() { 97 | let mut m = SmallMap::default(); 98 | 99 | assert_eq!(m.remove(&0), None); 100 | 101 | assert_eq!(m.insert(0, 1), None); 102 | assert_eq!(m.remove(&1), None); 103 | assert_eq!(m.remove(&0), Some(1)); 104 | 105 | assert_eq!(m.insert(0, 2), None); 106 | assert_eq!(m.insert(1, 3), None); 107 | assert_eq!(m.remove(&2), None); 108 | assert_eq!(m.remove(&1), Some(3)); 109 | assert_eq!(m.remove(&0), Some(2)); 110 | } 111 | 112 | #[test] 113 | fn clear() { 114 | let mut m = SmallMap::default(); 115 | 116 | m.clear(); 117 | assert_eq!(m.get(&0), None); 118 | 119 | m.insert(0, 1); 120 | m.clear(); 121 | assert_eq!(m.get(&0), None); 122 | 123 | m.insert(0, 2); 124 | m.insert(1, 3); 125 | m.clear(); 126 | assert_eq!(m.get(&0), None); 127 | assert_eq!(m.get(&1), None); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/opt/tests/forward.rs: -------------------------------------------------------------------------------- 1 | use crate::ir; 2 | use crate::opt::{self, forward}; 3 | use crate::swc_globals; 4 | 5 | #[test] 6 | fn basic() { 7 | swc_globals::with(|g| { 8 | #[rustfmt::skip] 9 | let ir = { 10 | let a = ir::Ref::new("a"); 11 | let b = ir::Ref::new("b"); 12 | let c = ir::Ref::new("c"); 13 | let d = ir::Ref::new("d"); 14 | ir::Block(vec![ 15 | ir::Stmt::Expr { target: a.clone(), expr: ir::Expr::Null }, 16 | ir::Stmt::Expr { target: b.clone(), expr: ir::Expr::Read { source: a } }, 17 | ir::Stmt::Expr { target: c.clone(), expr: ir::Expr::Read { source: b } }, 18 | ir::Stmt::Expr { 19 | target: d, 20 | expr: ir::Expr::Binary { op: ir::BinaryOp::Add, left: c.clone(), right: c }, 21 | }, 22 | ]) 23 | }; 24 | 25 | let ir = opt::OptCx::new(ir) 26 | .run::("forward-reads") 27 | .into_inner(); 28 | let ppr = ir::print(g, &ir); 29 | insta::assert_snapshot!(ppr, @r###" 30 | c = 31 | = c + c 32 | "###); 33 | }) 34 | } 35 | 36 | #[test] 37 | fn move_down() { 38 | swc_globals::with(|g| { 39 | #[rustfmt::skip] 40 | let ir = { 41 | let a = ir::Ref::new("a"); 42 | let b = ir::Ref::new("b"); 43 | // imagine this is a downleveled mut ref: 44 | // then we want it to declare the value itself, and remove the initializer, for two reasons: 45 | // 1: better naming 46 | // if we have: 47 | // _ini = 1 48 | // var_name = _ini 49 | // = var_name + 1 50 | // we prefer: 51 | // var_name = 1 52 | // = var_name + 1 53 | // instead of: 54 | // _ini = 1 55 | // = _ini + 1 56 | // 2: avoiding pessimization of time-travelling downleveled refs 57 | // if we have: 58 | // g = function() { var_name }; 59 | // _ini = 1 60 | // var_name = _ini 61 | // var_name + 1 62 | // we prefer: 63 | // g = function() { var_name }; 64 | // var_name = 1 65 | // var_name + 1 66 | // instead of: 67 | // g = function() { var_name }; 68 | // _ini = 1 69 | // var_name = 1 70 | // _ini + 1 71 | let dwn = ir::Ref::new("downleveled"); 72 | ir::Block(vec![ 73 | ir::Stmt::Expr { target: a.clone(), expr: ir::Expr::Null }, 74 | ir::Stmt::Expr { target: b.clone(), expr: ir::Expr::Read { source: a } }, 75 | ir::Stmt::Expr { target: dwn, expr: ir::Expr::Read { source: b } }, 76 | ]) 77 | }; 78 | 79 | let ir = opt::OptCx::new(ir) 80 | .run::("forward-reads") 81 | .into_inner(); 82 | let ppr = ir::print(g, &ir); 83 | insta::assert_snapshot!(ppr, @" = "); 84 | }) 85 | } 86 | 87 | #[test] 88 | fn dont_move_down_past_effects() { 89 | swc_globals::with(|g| { 90 | #[rustfmt::skip] 91 | let ir = { 92 | let a = ir::Ref::new("a"); 93 | let b = ir::Ref::new("b"); 94 | let dwn = ir::Ref::new("downleveled"); 95 | ir::Block(vec![ 96 | ir::Stmt::Expr { target: a.clone(), expr: ir::Expr::Null }, 97 | ir::Stmt::Break { label: None }, 98 | ir::Stmt::Expr { target: b.clone(), expr: ir::Expr::Read { source: a } }, 99 | ir::Stmt::Expr { target: dwn, expr: ir::Expr::Read { source: b } }, 100 | ]) 101 | }; 102 | 103 | let ir = opt::OptCx::new(ir) 104 | .run::("forward-reads") 105 | .into_inner(); 106 | let ppr = ir::print(g, &ir); 107 | insta::assert_snapshot!(ppr, @r###" 108 | a = 109 | 110 | = a 111 | "###); 112 | }) 113 | } 114 | -------------------------------------------------------------------------------- /src/anl/tests/fns.rs: -------------------------------------------------------------------------------- 1 | use crate::anl::fns; 2 | use crate::ir; 3 | 4 | macro_rules! case { 5 | ( $name:ident, $ir_and_expected:expr ) => { 6 | #[test] 7 | fn $name() { 8 | let (ir, expected): (ir::Block, Vec>) = $ir_and_expected; 9 | let refs = fns::without_this(&ir); 10 | assert_eq!(refs, expected.iter().collect()) 11 | } 12 | }; 13 | } 14 | 15 | case!(basic, { 16 | let x = ir::Ref::new("x"); 17 | #[rustfmt::skip] 18 | let ir = ir::Block(vec![ 19 | ir::Stmt::Expr { target: x.clone(), expr: ir::Expr::Function { 20 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 21 | body: ir::Block(vec![ 22 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::Null }, 23 | ]) 24 | } }, 25 | ]); 26 | (ir, vec![x]) 27 | }); 28 | 29 | case!(basic_bail, { 30 | let x = ir::Ref::new("x"); 31 | #[rustfmt::skip] 32 | let ir = ir::Block(vec![ 33 | ir::Stmt::Expr { target: x, expr: ir::Expr::Function { 34 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 35 | body: ir::Block(vec![ 36 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::This }, 37 | ]) 38 | } }, 39 | ]); 40 | (ir, vec![]) 41 | }); 42 | 43 | case!(bail_containing_arrow_this, { 44 | let x = ir::Ref::new("x"); 45 | let y = ir::Ref::new("y"); 46 | #[rustfmt::skip] 47 | let ir = ir::Block(vec![ 48 | ir::Stmt::Expr { target: x, expr: ir::Expr::Function { 49 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 50 | body: ir::Block(vec![ 51 | ir::Stmt::Expr { target: y, expr: ir::Expr::Function { 52 | kind: ir::FnKind::Arrow { is_async: false }, 53 | body: ir::Block(vec![ 54 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::This }, 55 | ]) 56 | } } 57 | ]) 58 | } }, 59 | ]); 60 | (ir, vec![]) 61 | }); 62 | 63 | case!(partial_bail_containing_arrow_this, { 64 | let x = ir::Ref::new("x"); 65 | let y = ir::Ref::new("y"); 66 | let z = ir::Ref::new("z"); 67 | #[rustfmt::skip] 68 | let ir = ir::Block(vec![ 69 | ir::Stmt::Expr { target: x.clone(), expr: ir::Expr::Function { 70 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 71 | body: ir::Block(vec![ 72 | ir::Stmt::Expr { target: y, expr: ir::Expr::Function { 73 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 74 | body: ir::Block(vec![ 75 | ir::Stmt::Expr { target: z, expr: ir::Expr::Function { 76 | kind: ir::FnKind::Arrow { is_async: false }, 77 | body: ir::Block(vec![ 78 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::This }, 79 | ]) 80 | } } 81 | ]) 82 | } }, 83 | ]) 84 | } }, 85 | ]); 86 | (ir, vec![x]) 87 | }); 88 | 89 | case!(bail_containing_arrow_this_deep, { 90 | let x = ir::Ref::new("x"); 91 | let y = ir::Ref::new("y"); 92 | let z = ir::Ref::new("z"); 93 | #[rustfmt::skip] 94 | let ir = ir::Block(vec![ 95 | ir::Stmt::Expr { target: x, expr: ir::Expr::Function { 96 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 97 | body: ir::Block(vec![ 98 | ir::Stmt::Expr { target: y, expr: ir::Expr::Function { 99 | kind: ir::FnKind::Arrow { is_async: false }, 100 | body: ir::Block(vec![ 101 | ir::Stmt::Expr { target: z, expr: ir::Expr::Function { 102 | kind: ir::FnKind::Arrow { is_async: false }, 103 | body: ir::Block(vec![ 104 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::This }, 105 | ]) 106 | } } 107 | ]) 108 | } }, 109 | ]) 110 | } }, 111 | ]); 112 | (ir, vec![]) 113 | }); 114 | -------------------------------------------------------------------------------- /src/tests/snapshots/jsssa__tests__parse__string_has_escape_behavior.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: src/tests/parse.rs 3 | expression: ast 4 | --- 5 | Script( 6 | Script { 7 | span: Span { 8 | lo: BytePos( 9 | 5, 10 | ), 11 | hi: BytePos( 12 | 36, 13 | ), 14 | ctxt: #0, 15 | }, 16 | body: [ 17 | Expr( 18 | ExprStmt { 19 | span: Span { 20 | lo: BytePos( 21 | 5, 22 | ), 23 | hi: BytePos( 24 | 11, 25 | ), 26 | ctxt: #0, 27 | }, 28 | expr: Lit( 29 | Str( 30 | Str { 31 | span: Span { 32 | lo: BytePos( 33 | 5, 34 | ), 35 | hi: BytePos( 36 | 10, 37 | ), 38 | ctxt: #0, 39 | }, 40 | value: Atom('foo' type=inline), 41 | has_escape: false, 42 | kind: Normal { 43 | contains_quote: true, 44 | }, 45 | }, 46 | ), 47 | ), 48 | }, 49 | ), 50 | Expr( 51 | ExprStmt { 52 | span: Span { 53 | lo: BytePos( 54 | 16, 55 | ), 56 | hi: BytePos( 57 | 23, 58 | ), 59 | ctxt: #0, 60 | }, 61 | expr: Lit( 62 | Str( 63 | Str { 64 | span: Span { 65 | lo: BytePos( 66 | 16, 67 | ), 68 | hi: BytePos( 69 | 22, 70 | ), 71 | ctxt: #0, 72 | }, 73 | value: Atom('ba ' type=inline), 74 | has_escape: true, 75 | kind: Normal { 76 | contains_quote: true, 77 | }, 78 | }, 79 | ), 80 | ), 81 | }, 82 | ), 83 | Expr( 84 | ExprStmt { 85 | span: Span { 86 | lo: BytePos( 87 | 28, 88 | ), 89 | hi: BytePos( 90 | 36, 91 | ), 92 | ctxt: #0, 93 | }, 94 | expr: Lit( 95 | Str( 96 | Str { 97 | span: Span { 98 | lo: BytePos( 99 | 28, 100 | ), 101 | hi: BytePos( 102 | 35, 103 | ), 104 | ctxt: #0, 105 | }, 106 | value: Atom('ba\z' type=inline), 107 | has_escape: true, 108 | kind: Normal { 109 | contains_quote: true, 110 | }, 111 | }, 112 | ), 113 | ), 114 | }, 115 | ), 116 | ], 117 | shebang: None, 118 | }, 119 | ) 120 | -------------------------------------------------------------------------------- /src/opt/tests/mut2ssa.rs: -------------------------------------------------------------------------------- 1 | use crate::opt::mut2ssa; 2 | 3 | case!( 4 | basic, 5 | |cx| cx.run::("mut2ssa"), 6 | r#" 7 | let x = 1; 8 | x = 2; 9 | x = 3; 10 | 11 | let y = 10; 12 | log(y); 13 | log(y + 1); 14 | "#, 15 | @r###" 16 | = 1 17 | _val = 2 18 | = _val 19 | _val$1 = 3 20 | = _val$1 21 | _ini = 10 22 | y = _ini 23 | _fun = 24 | _arg = y 25 | = _fun(_arg) 26 | _fun$1 = 27 | _lhs = y 28 | _rhs = 1 29 | _arg$1 = _lhs + _rhs 30 | = _fun$1(_arg$1) 31 | "###); 32 | 33 | case!( 34 | basic_bail, 35 | |cx| cx.run::("mut2ssa"), 36 | r#" 37 | let x = 1; 38 | x = 2; 39 | x = 3; 40 | if (foo) log(x); 41 | 42 | let y = 10; 43 | log(y); 44 | log(y + 1); 45 | if (bar) (function() { 46 | y = 5; 47 | })(); 48 | "#, 49 | @r###" 50 | _ini = 1 51 | x <= _ini 52 | _val = 2 53 | x <- _val 54 | = _val 55 | _val$1 = 3 56 | x <- _val$1 57 | = _val$1 58 | _iff = 59 | _iff: 60 | _fun$2 = 61 | _arg$2 = *x 62 | = _fun$2(_arg$2) 63 | : 64 | 65 | _ini$1 = 10 66 | y <= _ini$1 67 | _fun = 68 | _arg = *y 69 | = _fun(_arg) 70 | _fun$1 = 71 | _lhs = *y 72 | _rhs = 1 73 | _arg$1 = _lhs + _rhs 74 | = _fun$1(_arg$1) 75 | _iff$1 = 76 | _iff$1: 77 | _fun$2 = : 78 | _val$2 = 5 79 | y <- _val$2 80 | = _val$2 81 | = _fun$2() 82 | : 83 | 84 | "###); 85 | 86 | case!( 87 | downleveling, 88 | all_passes, 89 | r#" 90 | let x = 1; 91 | x = 2; 92 | x = 3; 93 | 94 | let y = 10; 95 | log(y); 96 | log(y + 1); 97 | "#, 98 | @r###" 99 | _ini = 10 100 | _fun = 101 | = _fun(_ini) 102 | _fun$1 = 103 | _arg = 11 104 | = _fun$1(_arg) 105 | "###); 106 | 107 | case!( 108 | downleveling_bail, 109 | all_passes, 110 | r#" 111 | let x = 1; 112 | x = 2; 113 | x = 3; 114 | if (foo) log(x); 115 | 116 | let y = 10; 117 | log(y); 118 | log(y + 1); 119 | bar = function() { 120 | y = 5; 121 | }; 122 | "#, 123 | @r###" 124 | _val = 3 125 | _iff = 126 | _iff: 127 | _fun$2 = 128 | = _fun$2(_val) 129 | : 130 | 131 | _ini = 10 132 | y <= _ini 133 | _fun = 134 | = _fun(_ini) 135 | _fun$1 = 136 | _lhs = *y 137 | _rhs = 1 138 | _arg = _lhs + _rhs 139 | = _fun$1(_arg) 140 | _val$1 = : 141 | _val$2 = 5 142 | y <- _val$2 143 | <- _val$1 144 | "###); 145 | 146 | case!( 147 | no_time_travel, 148 | |cx| cx.run::("mut2ssa"), 149 | r#" 150 | x; 151 | let x = 1; 152 | "#, 153 | @r###" 154 | = *x 155 | _ini = 1 156 | x <= _ini 157 | "###); 158 | 159 | case!( 160 | time_travel_into_function_scope, 161 | |cx| cx.run::("mut2ssa"), 162 | r#" 163 | g = function() { return x }; 164 | let x = 1; 165 | "#, 166 | @r###" 167 | _val = : 168 | _ret = x 169 | _ret 170 | <- _val 171 | = _val 172 | _ini = 1 173 | x = _ini 174 | "###); 175 | 176 | case!( 177 | no_cross_case, 178 | |cx| cx.run::("mut2ssa"), 179 | r#" 180 | switch (foo) { 181 | case 0: 182 | let x = 1; 183 | default: 184 | g = function() { return x }; 185 | } 186 | "#, 187 | @r###" 188 | _swi = 189 | _tst = 0 190 | _swi: 191 | _tst: 192 | _ini = 1 193 | x <= _ini 194 | : 195 | _val = : 196 | _ret = *x 197 | _ret 198 | <- _val 199 | = _val 200 | "###); 201 | 202 | case!( 203 | remove_writeonly_cross_case, 204 | |cx| cx.run::("mut2ssa"), 205 | r#" 206 | switch (foo) { 207 | case 0: 208 | let x = 1; 209 | default: 210 | g = function() { x = 2 }; 211 | } 212 | "#, 213 | @r###" 214 | _swi = 215 | _tst = 0 216 | _swi: 217 | _tst: 218 | = 1 219 | : 220 | _val = : 221 | _val$1 = 2 222 | = _val$1 223 | <- _val 224 | = _val 225 | "###); 226 | -------------------------------------------------------------------------------- /src/opt/forward.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::ir; 4 | use crate::ir::traverse::{visit_with, Folder, ScopeTy}; 5 | 6 | /// Forward `ir::Expr::Read` to the source SSA ref. 7 | /// 8 | /// Does not profit from multiple passes. 9 | /// Does not profit from DCE running first; may create opportunities for DCE. 10 | #[derive(Debug, Default)] 11 | pub struct Reads { 12 | ssa_remappings: HashMap, What>, 13 | prev_expr_if_moving_to_next: Option, 14 | } 15 | 16 | #[derive(Debug)] 17 | enum What { 18 | MoveToNext, 19 | ForwardTo(ir::Ref), 20 | } 21 | 22 | impl Folder for Reads { 23 | type Output = Option; 24 | 25 | fn wrap_scope( 26 | &mut self, 27 | ty: &ScopeTy, 28 | block: ir::Block, 29 | enter: impl FnOnce(&mut Self, ir::Block) -> R, 30 | ) -> R { 31 | if let ScopeTy::Toplevel = ty { 32 | let mut prev_ref_if_single_use = None; 33 | visit_with(&block, |stmt: &ir::Stmt| { 34 | prev_ref_if_single_use = match stmt { 35 | ir::Stmt::Expr { target, expr } => { 36 | if let ir::Expr::Read { source } = expr { 37 | if prev_ref_if_single_use == Some(source) { 38 | // move prev ref down to here, removing this read 39 | self.ssa_remappings.insert(source.weak(), What::MoveToNext); 40 | } else { 41 | // forward uses of this read to its source, or its source's source 42 | match self.ssa_remappings.get(&source.weak()) { 43 | Some(What::MoveToNext) => { 44 | unreachable!("MoveToNext should imply single use") 45 | } 46 | Some(What::ForwardTo(orig_ref)) => { 47 | let orig_ref = orig_ref.clone(); 48 | self.ssa_remappings 49 | .insert(target.weak(), What::ForwardTo(orig_ref)); 50 | } 51 | None => { 52 | self.ssa_remappings 53 | .insert(target.weak(), What::ForwardTo(source.clone())); 54 | } 55 | } 56 | } 57 | } 58 | if target.used().is_once() { 59 | Some(target) 60 | } else { 61 | None 62 | } 63 | } 64 | _ => None, 65 | }; 66 | }); 67 | } 68 | 69 | enter(self, block) 70 | } 71 | 72 | fn fold(&mut self, stmt: ir::Stmt) -> Self::Output { 73 | match stmt { 74 | ir::Stmt::Expr { target, mut expr } => { 75 | match self.prev_expr_if_moving_to_next.take() { 76 | Some(prev_expr) => { 77 | match &expr { 78 | ir::Expr::Read { source: _ } => {} 79 | e => unreachable!("target isn't a read: {:?}", e), 80 | } 81 | expr = prev_expr; 82 | } 83 | None => {} 84 | } 85 | 86 | match self.ssa_remappings.get(&target.weak()) { 87 | Some(What::MoveToNext) => { 88 | self.prev_expr_if_moving_to_next = Some(expr); 89 | None 90 | } 91 | Some(What::ForwardTo(_)) | None => { 92 | // keep this expr, since it may have non-forwarded uses 93 | Some(ir::Stmt::Expr { target, expr }) 94 | } 95 | } 96 | } 97 | _ => { 98 | assert!(self.prev_expr_if_moving_to_next.is_none()); 99 | Some(stmt) 100 | } 101 | } 102 | } 103 | 104 | fn fold_ref_use(&mut self, ref_: ir::Ref) -> ir::Ref { 105 | match self.ssa_remappings.get(&ref_.weak()) { 106 | Some(What::MoveToNext) => unreachable!("MoveToNext should imply single use"), 107 | Some(What::ForwardTo(orig_ref)) => orig_ref.clone(), 108 | None => ref_, 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/ir/float.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::cast_lossless)] 2 | 3 | use std::f64; 4 | use std::fmt::{self, Display}; 5 | use std::hash::{Hash, Hasher}; 6 | use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Not, Rem, Sub}; 7 | 8 | /// f64 wrapper with JS semantics 9 | #[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] 10 | pub struct F64(f64); 11 | 12 | impl F64 { 13 | pub const NAN: F64 = F64(f64::NAN); 14 | 15 | pub fn is_nan(self) -> bool { 16 | self.0.is_nan() 17 | } 18 | 19 | pub fn is_truthy(self) -> bool { 20 | self.0 != 0. && !self.0.is_nan() 21 | } 22 | 23 | pub fn into_inner(self) -> f64 { 24 | self.0 25 | } 26 | } 27 | 28 | impl From for F64 { 29 | fn from(x: f64) -> F64 { 30 | F64(x) 31 | } 32 | } 33 | 34 | impl Display for F64 { 35 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 36 | Display::fmt(&self.0, f) 37 | } 38 | } 39 | 40 | #[allow(clippy::derive_hash_xor_eq)] 41 | impl Hash for F64 { 42 | fn hash(&self, state: &mut H) { 43 | if self.0.is_nan() { 44 | // use one specific NaN representation 45 | state.write_u64(f64::NAN.to_bits()); 46 | } else { 47 | state.write_u64(self.0.to_bits()); 48 | } 49 | } 50 | } 51 | 52 | impl Neg for F64 { 53 | type Output = F64; 54 | fn neg(self) -> F64 { 55 | F64(-self.0) 56 | } 57 | } 58 | 59 | impl Not for F64 { 60 | type Output = F64; 61 | fn not(self) -> F64 { 62 | F64(!(self.0 as i32) as f64) 63 | } 64 | } 65 | 66 | impl Add for F64 { 67 | type Output = F64; 68 | fn add(self, rhs: F64) -> F64 { 69 | F64(self.0 + rhs.0) 70 | } 71 | } 72 | 73 | impl Sub for F64 { 74 | type Output = F64; 75 | fn sub(self, rhs: F64) -> F64 { 76 | F64(self.0 - rhs.0) 77 | } 78 | } 79 | 80 | impl Mul for F64 { 81 | type Output = F64; 82 | fn mul(self, rhs: F64) -> F64 { 83 | F64(self.0 * rhs.0) 84 | } 85 | } 86 | 87 | impl Div for F64 { 88 | type Output = F64; 89 | fn div(self, rhs: F64) -> F64 { 90 | F64(self.0 / rhs.0) 91 | } 92 | } 93 | 94 | impl Rem for F64 { 95 | type Output = F64; 96 | fn rem(self, rhs: F64) -> F64 { 97 | F64(self.0 % rhs.0) 98 | } 99 | } 100 | 101 | impl BitAnd for F64 { 102 | type Output = F64; 103 | fn bitand(self, rhs: F64) -> F64 { 104 | F64(((self.0 as i32) & (rhs.0 as i32)) as f64) 105 | } 106 | } 107 | 108 | impl BitOr for F64 { 109 | type Output = F64; 110 | fn bitor(self, rhs: F64) -> F64 { 111 | F64(((self.0 as i32) | (rhs.0 as i32)) as f64) 112 | } 113 | } 114 | 115 | impl BitXor for F64 { 116 | type Output = F64; 117 | fn bitxor(self, rhs: F64) -> F64 { 118 | F64(((self.0 as i32) ^ (rhs.0 as i32)) as f64) 119 | } 120 | } 121 | 122 | impl F64 { 123 | pub fn shl(self, rhs: F64) -> F64 { 124 | F64(((self.0 as i32) << rhs.0 as i32) as f64) 125 | } 126 | 127 | pub fn shr(self, rhs: F64) -> F64 { 128 | F64(((self.0 as i32) >> rhs.0 as i32) as f64) 129 | } 130 | 131 | pub fn shr_zero(self, rhs: F64) -> F64 { 132 | F64(((self.0 as i32 as u32) >> rhs.0 as i32) as f64) 133 | } 134 | 135 | pub fn powf(self, rhs: F64) -> F64 { 136 | F64(self.0.powf(rhs.0)) 137 | } 138 | } 139 | 140 | #[cfg(test)] 141 | mod tests { 142 | use super::*; 143 | 144 | #[test] 145 | fn basic_ops() { 146 | let zero = F64::from(0.0); 147 | let one = F64::from(1.0); 148 | let two = F64::from(2.0); 149 | let three = F64::from(3.0); 150 | 151 | assert!(F64::NAN.is_nan()); 152 | assert!(!zero.is_nan()); 153 | 154 | assert!(!F64::NAN.is_truthy()); 155 | assert!(!zero.is_truthy()); 156 | assert!(one.is_truthy()); 157 | 158 | assert_eq!(-one, F64::from(-1.)); 159 | assert_eq!(!one, F64::from(-2.)); 160 | 161 | assert_eq!(one + two, three); 162 | assert_eq!(three - one, two); 163 | assert_eq!(three / two, F64::from(3. / 2.)); 164 | assert_eq!(three * two, F64::from(6.)); 165 | assert_eq!(three % one, zero); 166 | 167 | assert_eq!(three & one, one); 168 | assert_eq!(two & one, zero); 169 | assert_eq!(three | one, three); 170 | assert_eq!(two | one, three); 171 | assert_eq!(two ^ one, three); 172 | assert_eq!(two ^ two, zero); 173 | 174 | assert_eq!(one.shl(one), two); 175 | assert_eq!(two.shr(one), one); 176 | assert_eq!((-two).shr(one), -one); 177 | assert_eq!(two.shr_zero(one), one); 178 | assert_eq!((-two).shr_zero(one), F64::from(2147483647.)); 179 | assert_eq!(two.powf(two), F64::from(4.)); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/ir.rs: -------------------------------------------------------------------------------- 1 | use swc_atoms::JsWord; 2 | 3 | pub use self::float::F64; 4 | pub use self::print::print; 5 | pub use self::ref_::{Lbl, Mut, Ref, RefType, Ssa, Used, WeakRef}; 6 | 7 | mod float; 8 | mod print; 9 | mod ref_; 10 | pub mod scope; 11 | pub mod traverse; 12 | 13 | #[derive(Debug, Hash)] 14 | pub struct Block(pub Vec); 15 | 16 | #[derive(Debug, Hash)] 17 | pub enum Stmt { 18 | Expr { 19 | target: Ref, 20 | expr: Expr, 21 | }, 22 | DeclareMutable { 23 | target: Ref, 24 | val: Ref, 25 | }, 26 | WriteMutable { 27 | target: Ref, 28 | val: Ref, 29 | }, 30 | WriteGlobal { 31 | target: JsWord, 32 | val: Ref, 33 | }, 34 | WriteMember { 35 | obj: Ref, 36 | prop: Ref, 37 | val: Ref, 38 | }, 39 | Return { 40 | val: Ref, 41 | }, 42 | Throw { 43 | val: Ref, 44 | }, 45 | Break { 46 | label: Option>, 47 | }, 48 | Continue { 49 | label: Option>, 50 | }, 51 | Debugger, 52 | Label { 53 | label: Ref, 54 | body: Block, 55 | }, 56 | Loop { 57 | body: Block, 58 | }, 59 | ForEach { 60 | kind: ForKind, 61 | init: Ref, 62 | body: Block, 63 | }, 64 | IfElse { 65 | cond: Ref, 66 | cons: Block, 67 | alt: Block, 68 | }, 69 | Switch { 70 | discr: Ref, 71 | body: Block, 72 | }, 73 | SwitchCase { 74 | val: Option>, 75 | }, 76 | Try { 77 | body: Block, 78 | catch: Block, 79 | finally: Box, 80 | }, 81 | } 82 | 83 | #[derive(Debug, Hash)] 84 | pub enum Expr { 85 | Bool { 86 | value: bool, 87 | }, 88 | Number { 89 | value: F64, 90 | }, 91 | String { 92 | value: JsWord, 93 | }, 94 | Null, 95 | Undefined, 96 | This, 97 | Read { 98 | source: Ref, 99 | }, 100 | ReadMutable { 101 | source: Ref, 102 | }, 103 | ReadGlobal { 104 | source: JsWord, 105 | }, 106 | ReadMember { 107 | obj: Ref, 108 | prop: Ref, 109 | }, 110 | Array { 111 | elems: Vec)>>, 112 | }, 113 | Object { 114 | props: Vec<(PropKind, Ref, Ref)>, 115 | }, 116 | RegExp { 117 | regex: JsWord, 118 | flags: JsWord, 119 | }, 120 | Unary { 121 | op: UnaryOp, 122 | val: Ref, 123 | }, 124 | Binary { 125 | op: BinaryOp, 126 | left: Ref, 127 | right: Ref, 128 | }, 129 | Delete { 130 | obj: Ref, 131 | prop: Ref, 132 | }, 133 | Yield { 134 | kind: YieldKind, 135 | val: Ref, 136 | }, 137 | Await { 138 | val: Ref, 139 | }, 140 | Call { 141 | kind: CallKind, 142 | base: Ref, 143 | prop: Option>, 144 | args: Vec<(EleKind, Ref)>, 145 | }, 146 | Function { 147 | kind: FnKind, 148 | body: Block, 149 | }, 150 | CurrentFunction, 151 | Argument { 152 | index: usize, 153 | }, 154 | } 155 | 156 | #[derive(Debug, Clone, Hash)] 157 | pub enum UnaryOp { 158 | Plus, 159 | Minus, 160 | Not, 161 | Tilde, 162 | Typeof, 163 | Void, 164 | } 165 | 166 | #[derive(Debug, Clone, Hash)] 167 | pub enum BinaryOp { 168 | EqEq, 169 | NotEq, 170 | StrictEq, 171 | NotStrictEq, 172 | Lt, 173 | LtEq, 174 | Gt, 175 | GtEq, 176 | ShiftLeft, 177 | ShiftRight, 178 | ShiftRightZero, 179 | Add, 180 | Sub, 181 | Mul, 182 | Div, 183 | Mod, 184 | BitOr, 185 | BitXor, 186 | BitAnd, 187 | Exp, 188 | In, 189 | Instanceof, 190 | } 191 | 192 | #[derive(Debug, Clone, Hash)] 193 | pub enum ForKind { 194 | In, 195 | Of, 196 | } 197 | 198 | #[derive(Debug, Clone, Hash)] 199 | pub enum EleKind { 200 | Single, 201 | Spread, 202 | } 203 | 204 | #[derive(Debug, Clone, Hash)] 205 | pub enum PropKind { 206 | Simple, 207 | Get, 208 | Set, 209 | } 210 | 211 | #[derive(Debug, Clone, Hash)] 212 | pub enum CallKind { 213 | Call, 214 | New, 215 | } 216 | 217 | #[derive(Debug, Clone, Hash)] 218 | pub enum FnKind { 219 | Func { is_async: bool, is_generator: bool }, 220 | Arrow { is_async: bool }, 221 | } 222 | 223 | #[derive(Debug, Clone, Hash)] 224 | pub enum YieldKind { 225 | Single, 226 | Delegate, 227 | } 228 | 229 | #[cfg(test)] 230 | mod tests { 231 | use super::*; 232 | 233 | #[test] 234 | fn stmt_is_512_bits() { 235 | assert_eq!(std::mem::size_of::(), 64); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::DefaultHasher; 2 | use std::fmt::{self, Display}; 3 | use std::hash::{Hash, Hasher}; 4 | use std::time::Duration; 5 | 6 | /// Shorthand for `Box::new` without `feature(box_syntax)` 7 | #[allow(non_snake_case)] 8 | pub fn P(x: T) -> Box { 9 | Box::new(x) 10 | } 11 | 12 | /// Pretty-printing wrapper for `Duration`, outputs "1.234s" 13 | pub struct Time(pub Duration); 14 | 15 | impl Display for Time { 16 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 17 | write!(f, "{}.{:0>3}s", self.0.as_secs(), self.0.subsec_millis()) 18 | } 19 | } 20 | 21 | /// Hash a value with the default hasher 22 | pub fn default_hash(h: &H) -> u64 { 23 | let mut hasher = DefaultHasher::new(); 24 | h.hash(&mut hasher); 25 | hasher.finish() 26 | } 27 | 28 | pub trait VecExt { 29 | fn drain_filter(&mut self, filter: F) -> DrainFilter<'_, T, F> 30 | where 31 | F: FnMut(&mut T) -> bool; 32 | } 33 | 34 | impl VecExt for Vec { 35 | fn drain_filter(&mut self, filter: F) -> DrainFilter<'_, T, F> 36 | where 37 | F: FnMut(&mut T) -> bool, 38 | { 39 | let old_len = self.len(); 40 | 41 | // Guard against us getting leaked (leak amplification) 42 | unsafe { 43 | self.set_len(0); 44 | } 45 | 46 | DrainFilter { 47 | vec: self, 48 | idx: 0, 49 | del: 0, 50 | old_len, 51 | pred: filter, 52 | panic_flag: false, 53 | } 54 | } 55 | } 56 | 57 | pub struct DrainFilter<'a, T, F> 58 | where 59 | F: FnMut(&mut T) -> bool, 60 | { 61 | vec: &'a mut Vec, 62 | idx: usize, 63 | del: usize, 64 | old_len: usize, 65 | pred: F, 66 | panic_flag: bool, 67 | } 68 | 69 | impl Iterator for DrainFilter<'_, T, F> 70 | where 71 | F: FnMut(&mut T) -> bool, 72 | { 73 | type Item = T; 74 | 75 | fn next(&mut self) -> Option { 76 | unsafe { 77 | while self.idx < self.old_len { 78 | let i = self.idx; 79 | let v = std::slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len); 80 | self.panic_flag = true; 81 | let drained = (self.pred)(&mut v[i]); 82 | self.panic_flag = false; 83 | // Update the index *after* the predicate is called. If the index 84 | // is updated prior and the predicate panics, the element at this 85 | // index would be leaked. 86 | self.idx += 1; 87 | if drained { 88 | self.del += 1; 89 | return Some(std::ptr::read(&v[i])); 90 | } else if self.del > 0 { 91 | let del = self.del; 92 | let src: *const T = &v[i]; 93 | let dst: *mut T = &mut v[i - del]; 94 | std::ptr::copy_nonoverlapping(src, dst, 1); 95 | } 96 | } 97 | None 98 | } 99 | } 100 | 101 | fn size_hint(&self) -> (usize, Option) { 102 | (0, Some(self.old_len - self.idx)) 103 | } 104 | } 105 | 106 | impl Drop for DrainFilter<'_, T, F> 107 | where 108 | F: FnMut(&mut T) -> bool, 109 | { 110 | fn drop(&mut self) { 111 | struct BackshiftOnDrop<'a, 'b, T, F> 112 | where 113 | F: FnMut(&mut T) -> bool, 114 | { 115 | drain: &'b mut DrainFilter<'a, T, F>, 116 | } 117 | 118 | impl<'a, 'b, T, F> Drop for BackshiftOnDrop<'a, 'b, T, F> 119 | where 120 | F: FnMut(&mut T) -> bool, 121 | { 122 | fn drop(&mut self) { 123 | unsafe { 124 | if self.drain.idx < self.drain.old_len && self.drain.del > 0 { 125 | // This is a pretty messed up state, and there isn't really an 126 | // obviously right thing to do. We don't want to keep trying 127 | // to execute `pred`, so we just backshift all the unprocessed 128 | // elements and tell the vec that they still exist. The backshift 129 | // is required to prevent a double-drop of the last successfully 130 | // drained item prior to a panic in the predicate. 131 | let ptr = self.drain.vec.as_mut_ptr(); 132 | let src = ptr.add(self.drain.idx); 133 | let dst = src.sub(self.drain.del); 134 | let tail_len = self.drain.old_len - self.drain.idx; 135 | src.copy_to(dst, tail_len); 136 | } 137 | self.drain.vec.set_len(self.drain.old_len - self.drain.del); 138 | } 139 | } 140 | } 141 | 142 | let backshift = BackshiftOnDrop { drain: self }; 143 | 144 | // Attempt to consume any remaining elements if the filter predicate 145 | // has not yet panicked. We'll backshift any remaining elements 146 | // whether we've already panicked or if the consumption here panics. 147 | if !backshift.drain.panic_flag { 148 | backshift.drain.for_each(drop); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/opt/tests/dce.rs: -------------------------------------------------------------------------------- 1 | use crate::opt::dce; 2 | 3 | case!( 4 | basic, 5 | |cx| cx.converge::("dce"), 6 | r#" 7 | 1; 8 | true; 9 | (function() {}); 10 | []; 11 | ({}); 12 | "#, 13 | @""); 14 | 15 | case!( 16 | basic_bail, 17 | |cx| cx.converge::("dce"), 18 | r#" 19 | const foo; 20 | foo.bar; 21 | delete foo.bar; 22 | foo(); 23 | (function* baz() { 24 | yield; 25 | })(); 26 | (async function baz2() { 27 | await 1; 28 | })(); 29 | "#, 30 | @r###" 31 | _ini = 32 | foo <= _ini 33 | _obj = *foo 34 | _prp = "bar" 35 | = _obj[_prp] 36 | _obj$1 = *foo 37 | _prp$1 = "bar" 38 | = _obj$1[_prp$1] 39 | _fun = *foo 40 | = _fun() 41 | _fun$1 = : 42 | _yld = 43 | = _yld 44 | = _fun$1() 45 | _fun$2 = : 46 | _awa = 1 47 | = _awa 48 | = _fun$2() 49 | "###); 50 | 51 | case!( 52 | bindings, 53 | |cx| cx.converge::("dce"), 54 | r#" 55 | var x = 1; 56 | const y = 1; 57 | function z() {} 58 | "#, 59 | @r###" 60 | _ini = 61 | z <= _ini 62 | _ini$1 = 63 | x <= _ini$1 64 | _fun = : 65 | 66 | z <- _fun 67 | _ini$2 = 1 68 | x <- _ini$2 69 | "###); 70 | 71 | case!( 72 | nested_effects, 73 | |cx| cx.converge::("dce"), 74 | r#" 75 | [{ x: call() }]; 76 | "#, 77 | @r###" 78 | _fun = 79 | = _fun() 80 | "###); 81 | 82 | case!( 83 | drop_after_jumps_1, 84 | |cx| cx.converge::("dce"), 85 | r#" 86 | (function() { 87 | good(); 88 | if (x) { 89 | good(); 90 | throw 1; 91 | bad; 92 | } 93 | good(); 94 | return 2; 95 | bad; 96 | })(); 97 | "#, 98 | @r###" 99 | _fun = : 100 | _fun$1 = 101 | = _fun$1() 102 | _iff = 103 | _iff: 104 | _fun$3 = 105 | = _fun$3() 106 | _thr = 1 107 | _thr 108 | : 109 | 110 | _fun$2 = 111 | = _fun$2() 112 | _ret = 2 113 | _ret 114 | = _fun() 115 | "###); 116 | 117 | case!( 118 | drop_after_jumps_2, 119 | |cx| cx.converge::("dce"), 120 | r#" 121 | for (;;) { 122 | good(); 123 | if (x) { 124 | good(); 125 | continue; 126 | bad; 127 | } 128 | good(); 129 | if (y) { 130 | good(); 131 | break; 132 | bad; 133 | } 134 | good(); 135 | } 136 | "#, 137 | @r###" 138 | : 139 | _fun = 140 | = _fun() 141 | _iff = 142 | _iff: 143 | _fun$3 = 144 | = _fun$3() 145 | 146 | : 147 | 148 | _fun$1 = 149 | = _fun$1() 150 | _iff$1 = 151 | _iff$1: 152 | _fun$3 = 153 | = _fun$3() 154 | 155 | : 156 | 157 | _fun$2 = 158 | = _fun$2() 159 | "###); 160 | 161 | case!( 162 | drop_after_jumps_depth, 163 | |cx| cx.converge::("dce"), 164 | r#" 165 | (function() { 166 | good(); 167 | return 2; 168 | (function() { bad; })(); 169 | if (x) { 170 | bad; 171 | } 172 | bad; 173 | })(); 174 | "#, 175 | @r###" 176 | _fun = : 177 | _fun$1 = 178 | = _fun$1() 179 | _ret = 2 180 | _ret 181 | = _fun() 182 | "###); 183 | 184 | case!( 185 | dont_drop_hoisted_fns, 186 | |cx| cx.converge::("dce"), 187 | r#" 188 | (function() { 189 | foo(); 190 | return; 191 | function foo() { 192 | log(); 193 | } 194 | })(); 195 | "#, 196 | @r###" 197 | _fun = : 198 | _ini = 199 | foo <= _ini 200 | _fun$1 = : 201 | _fun$3 = 202 | = _fun$3() 203 | foo <- _fun$1 204 | _fun$2 = *foo 205 | = _fun$2() 206 | _ret = 207 | _ret 208 | = _fun() 209 | "###); 210 | 211 | case!( 212 | empty_blocks, 213 | |cx| cx.converge::("dce"), 214 | r#" 215 | if (x()) {} else {} 216 | try {} catch (e) { bad(e); } finally {} 217 | "#, 218 | @r###" 219 | _fun = 220 | = _fun() 221 | "###); 222 | 223 | case!( 224 | dont_drop_after_switch_break, 225 | |cx| cx.converge::("dce"), 226 | r#" 227 | switch (x) { 228 | case 1: 229 | break; 230 | drop_me(); 231 | case 2: 232 | log(); 233 | } 234 | "#, 235 | @r###" 236 | _swi = 237 | _tst = 1 238 | _tst$1 = 2 239 | _swi: 240 | _tst: 241 | 242 | _tst$1: 243 | _fun = 244 | = _fun() 245 | "###); 246 | 247 | case!( 248 | do_not_eliminate_for_in_with_assignments, 249 | all_passes, 250 | r#" 251 | let x = {}; 252 | x.y = 1; 253 | for (let k in x) log(k); 254 | "#, 255 | @r###" 256 | _ini = { } 257 | _prp = "y" 258 | _val = 1 259 | _ini[_prp] <- _val 260 | _ini: 261 | _for = 262 | _fun = 263 | = _fun(_for) 264 | "###); 265 | -------------------------------------------------------------------------------- /src/collections.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Borrow; 2 | use std::collections::HashMap; 3 | use std::hash::Hash; 4 | use std::iter::{FromIterator, FusedIterator}; 5 | use std::mem; 6 | use std::option; 7 | use std::vec; 8 | 9 | /// Container holding 0/1/n items, avoiding allocation in the 0/1 cases 10 | #[derive(Debug)] 11 | pub enum ZeroOneMany { 12 | Zero, 13 | One(T), 14 | Many(Vec), 15 | } 16 | 17 | impl IntoIterator for ZeroOneMany { 18 | type Item = T; 19 | type IntoIter = ZeroOneManyIter; 20 | 21 | fn into_iter(self) -> Self::IntoIter { 22 | match self { 23 | ZeroOneMany::Zero => ZeroOneManyIter::Option(None.into_iter()), 24 | ZeroOneMany::One(x) => ZeroOneManyIter::Option(Some(x).into_iter()), 25 | ZeroOneMany::Many(v) => ZeroOneManyIter::Vec(v.into_iter()), 26 | } 27 | } 28 | } 29 | 30 | pub enum ZeroOneManyIter { 31 | Option(option::IntoIter), 32 | Vec(vec::IntoIter), 33 | } 34 | 35 | impl Iterator for ZeroOneManyIter { 36 | type Item = T; 37 | 38 | fn next(&mut self) -> Option { 39 | match self { 40 | ZeroOneManyIter::Option(o) => o.next(), 41 | ZeroOneManyIter::Vec(v) => v.next(), 42 | } 43 | } 44 | 45 | fn size_hint(&self) -> (usize, Option) { 46 | match self { 47 | ZeroOneManyIter::Option(o) => o.size_hint(), 48 | ZeroOneManyIter::Vec(v) => v.size_hint(), 49 | } 50 | } 51 | } 52 | 53 | impl DoubleEndedIterator for ZeroOneManyIter { 54 | fn next_back(&mut self) -> Option { 55 | match self { 56 | ZeroOneManyIter::Option(o) => o.next_back(), 57 | ZeroOneManyIter::Vec(v) => v.next_back(), 58 | } 59 | } 60 | } 61 | 62 | impl ExactSizeIterator for ZeroOneManyIter {} 63 | 64 | impl FusedIterator for ZeroOneManyIter {} 65 | 66 | /// The fundamental sum type. 67 | #[derive(Debug)] 68 | pub enum Either { 69 | A(A), 70 | B(B), 71 | } 72 | 73 | /// A HashMap holding a reference to its parent, allowing efficient scope-based lookup. 74 | #[derive(Debug)] 75 | pub struct StackedMap<'a, K, V> 76 | where 77 | K: Eq + Hash, 78 | { 79 | parent: Option<&'a Self>, 80 | map: HashMap, 81 | } 82 | 83 | impl<'a, K, V> Default for StackedMap<'a, K, V> 84 | where 85 | K: Eq + Hash, 86 | { 87 | fn default() -> Self { 88 | Self { 89 | parent: None, 90 | map: Default::default(), 91 | } 92 | } 93 | } 94 | 95 | #[allow(dead_code)] 96 | impl<'a, K, V> StackedMap<'a, K, V> 97 | where 98 | K: Eq + Hash, 99 | { 100 | pub fn child(&self) -> StackedMap<'_, K, V> { 101 | StackedMap { 102 | parent: Some(self), 103 | map: Default::default(), 104 | } 105 | } 106 | 107 | pub fn get_all(&self, k: &Q) -> Option<&V> 108 | where 109 | K: Borrow, 110 | Q: Hash + Eq + ?Sized, 111 | { 112 | self.get_self(k) 113 | .or_else(|| self.parent.and_then(|p| p.get_all(k))) 114 | } 115 | 116 | pub fn get_self(&self, k: &Q) -> Option<&V> 117 | where 118 | K: Borrow, 119 | Q: Hash + Eq + ?Sized, 120 | { 121 | self.map.get(k) 122 | } 123 | 124 | pub fn insert_self(&mut self, k: K, v: V) -> Option { 125 | self.map.insert(k, v) 126 | } 127 | 128 | pub fn remove_self(&mut self, k: &Q) -> Option 129 | where 130 | K: Borrow, 131 | Q: Hash + Eq + ?Sized, 132 | { 133 | self.map.remove(k) 134 | } 135 | 136 | pub fn clear_self(&mut self) { 137 | self.map.clear(); 138 | } 139 | } 140 | 141 | impl<'a, K, V> FromIterator<(K, V)> for StackedMap<'a, K, V> 142 | where 143 | K: Eq + Hash, 144 | { 145 | fn from_iter>(iter: T) -> Self { 146 | Self { 147 | parent: None, 148 | map: HashMap::from_iter(iter), 149 | } 150 | } 151 | } 152 | 153 | /// A HashMap that avoids allocation for empty and singleton maps. 154 | #[derive(Debug, Clone)] 155 | pub enum SmallMap 156 | where 157 | K: Eq + Hash, 158 | { 159 | Zero, 160 | One(K, V), 161 | Many(HashMap), 162 | } 163 | 164 | impl Default for SmallMap 165 | where 166 | K: Eq + Hash, 167 | { 168 | fn default() -> Self { 169 | Self::Zero 170 | } 171 | } 172 | 173 | #[allow(dead_code)] 174 | impl SmallMap 175 | where 176 | K: Eq + Hash, 177 | { 178 | pub fn get(&self, q: &Q) -> Option<&V> 179 | where 180 | K: Borrow, 181 | Q: Hash + Eq + ?Sized, 182 | { 183 | match self { 184 | Self::Zero => None, 185 | Self::One(k, v) if k.borrow() == q => Some(v), 186 | Self::One(_, _) => None, 187 | Self::Many(map) => map.get(q), 188 | } 189 | } 190 | 191 | pub fn insert(&mut self, k: K, v: V) -> Option { 192 | match self { 193 | zero @ Self::Zero => { 194 | *zero = Self::One(k, v); 195 | None 196 | } 197 | Self::One(old_k, old_v) if old_k == &k => { 198 | let old_v = mem::replace(old_v, v); 199 | Some(old_v) 200 | } 201 | one @ Self::One(_, _) => { 202 | let (old_k, old_v) = match mem::take(one) { 203 | Self::One(old_k, old_v) => (old_k, old_v), 204 | _ => unreachable!(), 205 | }; 206 | let mut map = HashMap::with_capacity(2); 207 | map.insert(old_k, old_v); 208 | map.insert(k, v); 209 | *one = Self::Many(map); 210 | None 211 | } 212 | Self::Many(map) => map.insert(k, v), 213 | } 214 | } 215 | 216 | pub fn remove(&mut self, q: &Q) -> Option 217 | where 218 | K: Borrow, 219 | Q: Hash + Eq + ?Sized, 220 | { 221 | match self { 222 | Self::Zero => None, 223 | Self::One(k, _) if (&*k).borrow() == q => match mem::replace(self, Self::Zero) { 224 | Self::One(_, v) => Some(v), 225 | _ => unreachable!(), 226 | }, 227 | Self::One(_, _) => None, 228 | Self::Many(map) => map.remove(q), 229 | } 230 | } 231 | 232 | pub fn clear(&mut self) { 233 | match self { 234 | Self::Zero => {} 235 | one @ Self::One(_, _) => *one = Self::Zero, 236 | Self::Many(map) => map.clear(), 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/tests/ir2ast.rs: -------------------------------------------------------------------------------- 1 | use crate::ast2ir; 2 | use crate::emit; 3 | use crate::err::NiceError; 4 | use crate::ir2ast; 5 | use crate::parse; 6 | use crate::swc_globals; 7 | 8 | macro_rules! case { 9 | ( $name:ident, $string:expr, @ $expected:literal ) => { 10 | #[test] 11 | fn $name() -> Result<(), NiceError> { 12 | swc_globals::with(|g| { 13 | let (ast, files) = parse::parse(g, $string)?; 14 | let ir = ast2ir::convert(g, ast); 15 | let ast2 = ir2ast::convert( 16 | g, 17 | ir, 18 | ir2ast::Opt { 19 | inline: true, 20 | minify: false, 21 | }, 22 | ); 23 | let js = emit::emit(g, ast2, files, emit::Opt { minify: false })?; 24 | insta::assert_snapshot!(js, @ $expected); 25 | Ok(()) 26 | }) 27 | } 28 | }; 29 | } 30 | 31 | case!( 32 | basic, 33 | r#" 34 | function f(x) { 35 | while (true); 36 | x = y.bar; 37 | z.foo = x ? true : 'hi'; 38 | return +[1 || x, { x }, f + 1, ++g]; 39 | } 40 | f(1), true; 41 | "#, 42 | @r###" 43 | var f; 44 | f = function f$1(x) { 45 | var f$2 = f$1; 46 | var x$1 = x; 47 | for(;;){ 48 | if (true) { 49 | } else { 50 | break; 51 | } 52 | } 53 | var _val = y.bar; 54 | x$1 = _val; 55 | _val; 56 | var _obj = z; 57 | var _val$1; 58 | if (x$1) { 59 | _val$1 = true; 60 | } else { 61 | _val$1 = "hi"; 62 | } 63 | var _val$2 = _val$1; 64 | _obj.foo = _val$2; 65 | _val$2; 66 | var _log = 1; 67 | if (1) { 68 | } else { 69 | _log = x$1; 70 | } 71 | var _ele = _log; 72 | var _ele$1 = { 73 | x: x$1 74 | }; 75 | var _ele$2 = f$2 + 1; 76 | var _wri = g + 1; 77 | g = _wri; 78 | return +[ 79 | _ele, 80 | _ele$1, 81 | _ele$2, 82 | _wri 83 | ]; 84 | }; 85 | f(1); 86 | true; 87 | "###); 88 | 89 | case!(simple_inlining, "let x = y;", @"var x = y; 90 | "); 91 | 92 | #[test] 93 | fn no_inlining() -> Result<(), NiceError> { 94 | swc_globals::with(|g| { 95 | let (ast, files) = parse::parse(g, "let x = y;")?; 96 | let ir = ast2ir::convert(g, ast); 97 | let ast2 = ir2ast::convert( 98 | g, 99 | ir, 100 | ir2ast::Opt { 101 | inline: false, 102 | minify: false, 103 | }, 104 | ); 105 | let js = emit::emit(g, ast2, files, emit::Opt { minify: false })?; 106 | insta::assert_snapshot!(js, @r###" 107 | var _ini = y; 108 | var x = _ini; 109 | "###); 110 | Ok(()) 111 | }) 112 | } 113 | 114 | #[test] 115 | fn minify_names() -> Result<(), NiceError> { 116 | swc_globals::with(|g| { 117 | let (ast, files) = parse::parse( 118 | g, 119 | r#" 120 | var x = 1; 121 | if (x) { 122 | let x = 3; 123 | log(x); 124 | } else { 125 | let x = 7; 126 | log(x); 127 | } 128 | log(x); 129 | "#, 130 | )?; 131 | let ir = ast2ir::convert(g, ast); 132 | let ast2 = ir2ast::convert( 133 | g, 134 | ir, 135 | ir2ast::Opt { 136 | inline: true, 137 | minify: true, 138 | }, 139 | ); 140 | let js = emit::emit(g, ast2, files, emit::Opt { minify: false })?; 141 | insta::assert_snapshot!(js, @r###" 142 | var a; 143 | a = 1; 144 | if (a) { 145 | var b = 3; 146 | log(b); 147 | } else { 148 | var b = 7; 149 | log(b); 150 | } 151 | log(a); 152 | "###); 153 | Ok(()) 154 | }) 155 | } 156 | 157 | case!( 158 | deep_scopes, 159 | r#" 160 | var x = 1; 161 | (function() { 162 | (function() { 163 | x = 2; 164 | }); 165 | }); 166 | "#, 167 | @r###" 168 | var x; 169 | x = 1; 170 | (function() { 171 | (function() { 172 | x = 2; 173 | 2; 174 | }); 175 | }); 176 | "###); 177 | 178 | case!( 179 | deconflict, 180 | r#" 181 | var x = 1; 182 | var x$1 = 1; 183 | (function() { 184 | var x = 1; 185 | }) 186 | "#, 187 | @r###" 188 | var x; 189 | var x$1; 190 | x = 1; 191 | x$1 = 1; 192 | (function() { 193 | var x$1$1; 194 | x$1$1 = 1; 195 | }); 196 | "###); 197 | 198 | case!( 199 | deconflict_globals, 200 | r#" 201 | var x = 1; 202 | (function() { 203 | var x = 1; 204 | x$1; 205 | x$2; 206 | }) 207 | "#, 208 | @r###" 209 | var x; 210 | x = 1; 211 | (function() { 212 | var x$1$1; 213 | x$1$1 = 1; 214 | x$1; 215 | x$2; 216 | }); 217 | "###); 218 | 219 | case!( 220 | deconflict_for_in, 221 | r#" 222 | { 223 | const x; 224 | } 225 | for (x in 1); 226 | "#, 227 | @r###" 228 | var x$1; 229 | for(var _for in 1){ 230 | x = _for; 231 | } 232 | "###); 233 | 234 | case!( 235 | empty_blocks, 236 | r#" 237 | if (x) { 238 | good; 239 | } else {} 240 | 241 | try { good; } catch {} finally {} 242 | 243 | try { good; } catch {} finally { good; } 244 | "#, 245 | @r###" 246 | if (x) { 247 | good; 248 | } 249 | try { 250 | good; 251 | } catch { 252 | } 253 | try { 254 | good; 255 | } finally{ 256 | good; 257 | } 258 | "###); 259 | 260 | case!( 261 | labels, 262 | r#" 263 | outer: for (;;) { 264 | inner: for (;;) { 265 | if (foo) continue inner; 266 | if (bar) break outer; 267 | } 268 | } 269 | "#, 270 | @r###" 271 | outer: { 272 | for(;;){ 273 | inner: { 274 | for(;;){ 275 | if (foo) { 276 | continue inner; 277 | } 278 | if (bar) { 279 | break outer; 280 | } 281 | } 282 | } 283 | } 284 | } 285 | "###); 286 | 287 | case!( 288 | string_has_escape_behavior, 289 | r#" 290 | "foo"; 291 | "ba\r"; 292 | "ba\\z"; 293 | "#, 294 | @r###" 295 | "foo"; 296 | "ba\r"; 297 | "ba\\z"; 298 | "###); 299 | 300 | case!( 301 | regex_has_escape_behavior, 302 | r#" 303 | /foo/; 304 | /bar\./; 305 | /ba\/z/; 306 | "#, 307 | @r###" 308 | /foo/; 309 | /bar\./; 310 | /ba\/z/; 311 | "###); 312 | 313 | case!( 314 | no_void_0_initializer, 315 | r#" 316 | var x; 317 | let y; 318 | "#, 319 | @r###" 320 | var x; 321 | x = void 0; 322 | var y; 323 | "###); 324 | 325 | case!( 326 | object_props, 327 | r#" 328 | var obj = { 329 | x: 1, 330 | ['bad>']: 2, 331 | 0: 7, 332 | }; 333 | obj.y = 3; 334 | obj['#bad'] = 4; 335 | obj.z; 336 | obj['%bad']; 337 | delete obj.w; 338 | delete obj['^bad']; 339 | obj.some(); 340 | obj['*bad'](); 341 | "#, 342 | @r###" 343 | var obj; 344 | obj = { 345 | x: 1, 346 | "bad>": 2, 347 | 0: 7 348 | }; 349 | obj.y = 3; 350 | 3; 351 | obj["#bad"] = 4; 352 | 4; 353 | obj.z; 354 | obj["%bad"]; 355 | delete obj.w; 356 | delete obj["^bad"]; 357 | obj.some(); 358 | obj["*bad"](); 359 | "###); 360 | -------------------------------------------------------------------------------- /src/opt/mut2ssa.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::mem; 3 | 4 | use crate::ir; 5 | use crate::ir::traverse::{Folder, RunVisitor, ScopeTy, Visitor}; 6 | 7 | /// Converts read-only mutable vars to SSA, and removes write-only mutable vars. 8 | /// 9 | /// Does not profit from multiple passes. 10 | /// May profit from DCE running first; may create opportunities for DCE. 11 | /// May create opportunities for read forwarding. 12 | #[derive(Debug, Default)] 13 | pub struct Mut2Ssa { 14 | mut_vars_to_replace: HashMap, What>, 15 | } 16 | 17 | #[derive(Debug)] 18 | enum What { 19 | Convert(ir::Ref), 20 | Remove, 21 | } 22 | 23 | #[derive(Debug, Default)] 24 | struct CollectMutOpInfo<'a> { 25 | mut_ops: HashMap<&'a ir::Ref, State>, 26 | reads_in_scope: HashSet<&'a ir::Ref>, 27 | declared_at_toplevel_of_switch: HashSet<&'a ir::Ref>, 28 | at_toplevel_of_switch: bool, 29 | about_to_enter_switch: bool, 30 | } 31 | 32 | #[derive(Debug, PartialEq)] 33 | enum State { 34 | ReadOnly { frozen: bool }, 35 | WriteOnly, 36 | Invalid, 37 | } 38 | 39 | impl<'a> Visitor<'a> for CollectMutOpInfo<'a> { 40 | fn wrap_scope( 41 | &mut self, 42 | ty: &ScopeTy, 43 | block: &'a ir::Block, 44 | enter: impl FnOnce(&mut Self, &'a ir::Block) -> R, 45 | ) -> R { 46 | match ty { 47 | ScopeTy::Function => { 48 | // functions are analyzed separately, since they can read ssa refs 49 | // before they are lexically declared 50 | let mut inner = Self::default(); 51 | mem::swap(&mut inner.mut_ops, &mut self.mut_ops); 52 | let r = enter(&mut inner, block); 53 | mem::swap(&mut inner.mut_ops, &mut self.mut_ops); 54 | r 55 | } 56 | ScopeTy::Normal | ScopeTy::Toplevel | ScopeTy::Nonlinear => { 57 | let mut inner = Self::default(); 58 | mem::swap(&mut inner.mut_ops, &mut self.mut_ops); 59 | mem::swap(&mut inner.reads_in_scope, &mut self.reads_in_scope); 60 | inner.at_toplevel_of_switch = self.about_to_enter_switch; 61 | let r = enter(&mut inner, block); 62 | mem::swap(&mut inner.mut_ops, &mut self.mut_ops); 63 | mem::swap(&mut inner.reads_in_scope, &mut self.reads_in_scope); 64 | self.about_to_enter_switch = false; 65 | r 66 | } 67 | } 68 | } 69 | 70 | fn visit(&mut self, stmt: &'a ir::Stmt) { 71 | match stmt { 72 | ir::Stmt::Expr { 73 | target: _, 74 | expr: ir::Expr::ReadMutable { source }, 75 | } => { 76 | let our_state = State::ReadOnly { frozen: false }; 77 | self.mut_ops 78 | .entry(source) 79 | .and_modify(|state| { 80 | if state != &our_state { 81 | *state = State::Invalid; 82 | } 83 | }) 84 | .or_insert(our_state); 85 | self.reads_in_scope.insert(source); 86 | } 87 | ir::Stmt::WriteMutable { target, val: _ } => { 88 | let our_state = State::WriteOnly; 89 | self.mut_ops 90 | .entry(target) 91 | .and_modify(|state| { 92 | if state != &our_state { 93 | *state = State::Invalid; 94 | } 95 | }) 96 | .or_insert(our_state); 97 | } 98 | ir::Stmt::DeclareMutable { target, val: _ } => { 99 | if self.reads_in_scope.contains(&target) { 100 | // read before declaration 101 | self.mut_ops.insert(target, State::Invalid); 102 | } 103 | if self.at_toplevel_of_switch { 104 | self.declared_at_toplevel_of_switch.insert(target); 105 | } 106 | } 107 | ir::Stmt::Switch { .. } => { 108 | self.about_to_enter_switch = true; 109 | } 110 | ir::Stmt::SwitchCase { .. } => { 111 | for ref_ in self.declared_at_toplevel_of_switch.drain() { 112 | match self.mut_ops.entry(ref_).or_insert(State::WriteOnly) { 113 | State::ReadOnly { frozen } => { 114 | *frozen = true; 115 | } 116 | State::WriteOnly | State::Invalid => { 117 | // future reads already disallowed 118 | } 119 | } 120 | } 121 | } 122 | _ => {} 123 | } 124 | } 125 | } 126 | 127 | impl Folder for Mut2Ssa { 128 | type Output = Option; 129 | 130 | fn wrap_scope( 131 | &mut self, 132 | ty: &ScopeTy, 133 | block: ir::Block, 134 | enter: impl FnOnce(&mut Self, ir::Block) -> R, 135 | ) -> R { 136 | if let ScopeTy::Toplevel = ty { 137 | let mut collector = CollectMutOpInfo::default(); 138 | collector.run_visitor(&block); 139 | self.mut_vars_to_replace = collector 140 | .mut_ops 141 | .into_iter() 142 | .filter_map(|(ref_, saw)| match saw { 143 | State::ReadOnly { frozen: _ } => { 144 | Some((ref_.weak(), What::Convert(ir::Ref::new(ref_.name_hint())))) 145 | } 146 | State::WriteOnly => Some((ref_.weak(), What::Remove)), 147 | State::Invalid => None, 148 | }) 149 | .collect(); 150 | } 151 | 152 | enter(self, block) 153 | } 154 | 155 | fn fold(&mut self, stmt: ir::Stmt) -> Self::Output { 156 | match stmt { 157 | ir::Stmt::Expr { 158 | target, 159 | expr: ir::Expr::ReadMutable { source }, 160 | } => match self.mut_vars_to_replace.get(&source.weak()) { 161 | Some(What::Convert(ssa_ref)) => Some(ir::Stmt::Expr { 162 | target, 163 | expr: ir::Expr::Read { 164 | source: ssa_ref.clone(), 165 | }, 166 | }), 167 | Some(What::Remove) => unreachable!("removing mut read: {:?}", source), 168 | None => Some(ir::Stmt::Expr { 169 | target, 170 | expr: ir::Expr::ReadMutable { source }, 171 | }), 172 | }, 173 | ir::Stmt::DeclareMutable { target, val } => { 174 | match self.mut_vars_to_replace.get(&target.weak()) { 175 | Some(What::Convert(ssa_ref)) => Some(ir::Stmt::Expr { 176 | target: ssa_ref.clone(), 177 | expr: ir::Expr::Read { source: val }, 178 | }), 179 | Some(What::Remove) => None, 180 | None => Some(ir::Stmt::DeclareMutable { target, val }), 181 | } 182 | } 183 | ir::Stmt::WriteMutable { target, val } => { 184 | match self.mut_vars_to_replace.get(&target.weak()) { 185 | Some(What::Convert(_)) => unreachable!("converting mut write: {:?}", target), 186 | Some(What::Remove) => None, 187 | None => Some(ir::Stmt::WriteMutable { target, val }), 188 | } 189 | } 190 | _ => Some(stmt), 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/anl/tests/refs.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use crate::anl::refs; 4 | use crate::ir; 5 | 6 | macro_rules! case { 7 | ( $name:ident, $kind:ty, $ir_and_expected:expr ) => { 8 | #[test] 9 | fn $name() { 10 | let (ir, expected): (ir::Block, Vec>) = $ir_and_expected; 11 | let refs = refs::used_in_only_one_fn_scope(&ir).collect::>(); 12 | assert_eq!(refs, expected.iter().collect()) 13 | } 14 | }; 15 | } 16 | 17 | case!(basic, ir::Ssa, { 18 | let x = ir::Ref::new("x"); 19 | let y = ir::Ref::new("y"); 20 | let d1 = ir::Ref::dead(); 21 | let d2 = ir::Ref::dead(); 22 | let d3 = ir::Ref::dead(); 23 | #[rustfmt::skip] 24 | let ir = ir::Block(vec![ 25 | ir::Stmt::Expr { target: x.clone(), expr: ir::Expr::Null }, 26 | ir::Stmt::Expr { target: d1.clone(), expr: ir::Expr::Function { 27 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 28 | body: ir::Block(vec![ 29 | ir::Stmt::Expr { target: y.clone(), expr: ir::Expr::Null }, 30 | ir::Stmt::Expr { target: d2.clone(), expr: ir::Expr::Read { source: y.clone() } }, 31 | ]) 32 | } }, 33 | ir::Stmt::Expr { target: d3.clone(), expr: ir::Expr::Read { source: x.clone() } }, 34 | ]); 35 | (ir, vec![x, y, d1, d2, d3]) 36 | }); 37 | 38 | case!(basic_bail, ir::Ssa, { 39 | let x = ir::Ref::new("x"); 40 | let d1 = ir::Ref::dead(); 41 | let d2 = ir::Ref::dead(); 42 | let d3 = ir::Ref::dead(); 43 | #[rustfmt::skip] 44 | let ir = ir::Block(vec![ 45 | ir::Stmt::Expr { target: x.clone(), expr: ir::Expr::Null }, 46 | ir::Stmt::Expr { target: d1.clone(), expr: ir::Expr::Function { 47 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 48 | body: ir::Block(vec![ 49 | ir::Stmt::Expr { target: d2.clone(), expr: ir::Expr::Read { source: x.clone() } }, 50 | ]) 51 | } }, 52 | ir::Stmt::Expr { target: d3.clone(), expr: ir::Expr::Read { source: x.clone() } }, 53 | ]); 54 | (ir, vec![d1, d2, d3]) 55 | }); 56 | 57 | case!(bail_deep, ir::Ssa, { 58 | let x = ir::Ref::new("x"); 59 | let d1 = ir::Ref::dead(); 60 | let d2 = ir::Ref::dead(); 61 | let d3 = ir::Ref::dead(); 62 | #[rustfmt::skip] 63 | let ir = ir::Block(vec![ 64 | ir::Stmt::Expr { target: x.clone(), expr: ir::Expr::Null }, 65 | ir::Stmt::Expr { target: d1.clone(), expr: ir::Expr::Function { 66 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 67 | body: ir::Block(vec![ 68 | ir::Stmt::Expr { target: d2.clone(), expr: ir::Expr::Function { 69 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 70 | body: ir::Block(vec![ 71 | ir::Stmt::Expr { target: d3.clone(), expr: ir::Expr::Read { source: x.clone() } }, 72 | ]) 73 | } }, 74 | ]) 75 | } }, 76 | ]); 77 | (ir, vec![d1, d2, d3]) 78 | }); 79 | 80 | case!(time_travel, ir::Ssa, { 81 | let x = ir::Ref::new("x"); 82 | let d1 = ir::Ref::dead(); 83 | let d2 = ir::Ref::dead(); 84 | #[rustfmt::skip] 85 | let ir = ir::Block(vec![ 86 | ir::Stmt::Expr { target: d1.clone(), expr: ir::Expr::Function { 87 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 88 | body: ir::Block(vec![ 89 | ir::Stmt::Expr { target: d2.clone(), expr: ir::Expr::Read { source: x.clone() } }, 90 | ]) 91 | } }, 92 | ir::Stmt::Expr { target: x.clone(), expr: ir::Expr::Null }, 93 | ]); 94 | (ir, vec![d1, d2]) 95 | }); 96 | 97 | case!(mut_basic, ir::Mut, { 98 | let void = ir::Ref::new("void"); 99 | let x = ir::Ref::new("x"); 100 | let y = ir::Ref::new("y"); 101 | #[rustfmt::skip] 102 | let ir = ir::Block(vec![ 103 | ir::Stmt::Expr { target: void.clone(), expr: ir::Expr::Null }, 104 | ir::Stmt::DeclareMutable { target: x.clone(), val: void.clone() }, 105 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::Function { 106 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 107 | body: ir::Block(vec![ 108 | ir::Stmt::DeclareMutable { target: y.clone(), val: void.clone() }, 109 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::ReadMutable { source: y.clone() } }, 110 | ]) 111 | } }, 112 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::ReadMutable { source: x.clone() } }, 113 | ]); 114 | (ir, vec![x, y]) 115 | }); 116 | 117 | case!(mut_basic_bail, ir::Mut, { 118 | let void = ir::Ref::new("void"); 119 | let x = ir::Ref::new("x"); 120 | #[rustfmt::skip] 121 | let ir = ir::Block(vec![ 122 | ir::Stmt::Expr { target: void.clone(), expr: ir::Expr::Null }, 123 | ir::Stmt::DeclareMutable { target: x.clone(), val: void.clone() }, 124 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::Function { 125 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 126 | body: ir::Block(vec![ 127 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::ReadMutable { source: x.clone() } }, 128 | ]) 129 | } }, 130 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::ReadMutable { source: x.clone() } }, 131 | ]); 132 | (ir, vec![]) 133 | }); 134 | 135 | case!(mut_basic_bail_write, ir::Mut, { 136 | let void = ir::Ref::new("void"); 137 | let x = ir::Ref::new("x"); 138 | #[rustfmt::skip] 139 | let ir = ir::Block(vec![ 140 | ir::Stmt::Expr { target: void.clone(), expr: ir::Expr::Null }, 141 | ir::Stmt::DeclareMutable { target: x.clone(), val: void.clone() }, 142 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::Function { 143 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 144 | body: ir::Block(vec![ 145 | ir::Stmt::WriteMutable { target: x.clone(), val: void.clone() }, 146 | ]) 147 | } }, 148 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::ReadMutable { source: x.clone() } }, 149 | ]); 150 | (ir, vec![]) 151 | }); 152 | 153 | case!(mut_bail_deep, ir::Mut, { 154 | let void = ir::Ref::new("void"); 155 | let x = ir::Ref::new("x"); 156 | #[rustfmt::skip] 157 | let ir = ir::Block(vec![ 158 | ir::Stmt::Expr { target: void.clone(), expr: ir::Expr::Null }, 159 | ir::Stmt::DeclareMutable { target: x.clone(), val: void.clone() }, 160 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::Function { 161 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 162 | body: ir::Block(vec![ 163 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::Function { 164 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 165 | body: ir::Block(vec![ 166 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::ReadMutable { source: x.clone() } }, 167 | ]) 168 | } }, 169 | ]) 170 | } }, 171 | ]); 172 | (ir, vec![]) 173 | }); 174 | 175 | case!(mut_time_travel, ir::Mut, { 176 | let void = ir::Ref::new("void"); 177 | let x = ir::Ref::new("x"); 178 | #[rustfmt::skip] 179 | let ir = ir::Block(vec![ 180 | ir::Stmt::Expr { target: void.clone(), expr: ir::Expr::Null }, 181 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::Function { 182 | kind: ir::FnKind::Func { is_async: false, is_generator: false }, 183 | body: ir::Block(vec![ 184 | ir::Stmt::Expr { target: ir::Ref::dead(), expr: ir::Expr::ReadMutable { source: x.clone() } }, 185 | ]) 186 | } }, 187 | ir::Stmt::DeclareMutable { target: x.clone(), val: void.clone() }, 188 | ]); 189 | (ir, vec![]) 190 | }); 191 | -------------------------------------------------------------------------------- /src/opt/tests/inline.rs: -------------------------------------------------------------------------------- 1 | use crate::opt::inline; 2 | 3 | case!( 4 | basic, 5 | |cx| cx.converge::("inline"), 6 | r#" 7 | (function() { 8 | foo; 9 | })(); 10 | (() => { 11 | foo; 12 | })(); 13 | "#, 14 | @r###" 15 | _mis = 16 | = 17 | = _mis 18 | _mis$1 = 19 | = 20 | = _mis$1 21 | "###); 22 | 23 | case!( 24 | bail_async_gen, 25 | |cx| cx.converge::("inline"), 26 | r#" 27 | (function*() { 28 | foo; 29 | })(); 30 | (async function() { 31 | foo; 32 | })(); 33 | (async () => { 34 | foo; 35 | })(); 36 | "#, 37 | @r###" 38 | _fun = : 39 | = 40 | = _fun() 41 | _fun$1 = : 42 | = 43 | = _fun$1() 44 | _fun$2 = : 45 | = 46 | = _fun$2() 47 | "###); 48 | 49 | case!( 50 | bail_props, 51 | |cx| cx.converge::("inline"), 52 | r#" 53 | (function() { 54 | foo; 55 | }).x(); 56 | "#, 57 | @r###" 58 | _obj = : 59 | = 60 | _prp = "x" 61 | = _obj[_prp]() 62 | "###); 63 | 64 | case!( 65 | bail_new, 66 | |cx| cx.converge::("inline"), 67 | r#" 68 | new (function() { 69 | foo; 70 | })(); 71 | "#, 72 | @r###" 73 | _fun = : 74 | = 75 | = _fun() 76 | "###); 77 | 78 | case!( 79 | bail_this, 80 | |cx| cx.converge::("inline"), 81 | r#" 82 | (function() { 83 | if (foo) { this; } 84 | })(); 85 | "#, 86 | @r###" 87 | _fun = : 88 | _iff = 89 | _iff: 90 | = 91 | : 92 | 93 | = _fun() 94 | "###); 95 | 96 | case!( 97 | bail_recursive, 98 | |cx| cx.converge::("inline"), 99 | r#" 100 | (function f() { 101 | if (foo) { f(); } 102 | })(); 103 | "#, 104 | @r###" 105 | _fun = : 106 | f = 107 | f$1 <= f 108 | _iff = 109 | _iff: 110 | _fun$1 = *f$1 111 | = _fun$1() 112 | : 113 | 114 | = _fun() 115 | "###); 116 | 117 | case!( 118 | bail_bad_return, 119 | |cx| cx.converge::("inline"), 120 | r#" 121 | (function() { 122 | if (foo) { return; } 123 | })(); 124 | "#, 125 | @r###" 126 | _fun = : 127 | _iff = 128 | _iff: 129 | _ret = 130 | _ret 131 | : 132 | 133 | = _fun() 134 | "###); 135 | 136 | case!( 137 | bail_containing_arrow_this, 138 | |cx| cx.converge::("inline"), 139 | r#" 140 | (function() { 141 | return () => this; 142 | })(); 143 | "#, 144 | @r###" 145 | _fun = : 146 | _ret = : 147 | _arr = 148 | _arr 149 | _ret 150 | = _fun() 151 | "###); 152 | 153 | case!( 154 | partial_bail_containing_arrow_this, 155 | |cx| cx.converge::("inline"), 156 | r#" 157 | (function() { 158 | (function() { 159 | return () => this; 160 | })(); 161 | })(); 162 | "#, 163 | @r###" 164 | _mis = 165 | _fun = : 166 | _ret = : 167 | _arr = 168 | _arr 169 | _ret 170 | = _fun() 171 | = _mis 172 | "###); 173 | 174 | case!( 175 | bail_containing_arrow_this_deep, 176 | |cx| cx.converge::("inline"), 177 | r#" 178 | (function() { 179 | return () => () => this; 180 | })(); 181 | "#, 182 | @r###" 183 | _fun = : 184 | _ret = : 185 | _arr = : 186 | _arr$1 = 187 | _arr$1 188 | _arr 189 | _ret 190 | = _fun() 191 | "###); 192 | 193 | case!( 194 | bail_containing_async_arrow_this, 195 | |cx| cx.converge::("inline"), 196 | r#" 197 | (function() { 198 | return async () => this; 199 | })(); 200 | "#, 201 | @r###" 202 | _fun = : 203 | _ret = : 204 | _arr = 205 | _arr 206 | _ret 207 | = _fun() 208 | "###); 209 | 210 | case!( 211 | bail_containing_async_arrow_this_deep, 212 | |cx| cx.converge::("inline"), 213 | r#" 214 | (function() { 215 | return () => async () => this; 216 | })(); 217 | "#, 218 | @r###" 219 | _fun = : 220 | _ret = : 221 | _arr = : 222 | _arr$1 = 223 | _arr$1 224 | _arr 225 | _ret 226 | = _fun() 227 | "###); 228 | 229 | case!( 230 | dont_bail_containing_innocuous_async_arrow, 231 | |cx| cx.converge::("inline"), 232 | r#" 233 | (function() { 234 | return async () => { await foo }; 235 | })(); 236 | "#, 237 | @r###" 238 | = 239 | _ret = : 240 | _awa = 241 | = _awa 242 | = _ret 243 | "###); 244 | 245 | case!( 246 | basic_constructor, 247 | |cx| cx.converge::("inline"), 248 | r#" 249 | new function() { 250 | return { foo }; 251 | }(); 252 | "#, 253 | @r###" 254 | = 255 | _key = "foo" 256 | _val = 257 | _ret = { [_key]: _val } 258 | = _ret 259 | "###); 260 | 261 | case!( 262 | bail_constructor_arrow, 263 | |cx| cx.converge::("inline"), 264 | r#" 265 | new (() => { 266 | return {}; 267 | })(); 268 | "#, 269 | @r###" 270 | _fun = : 271 | _ret = { } 272 | _ret 273 | = _fun() 274 | "###); 275 | 276 | case!( 277 | bail_constructor_no_return, 278 | |cx| cx.converge::("inline"), 279 | r#" 280 | new function() { 281 | }(); 282 | "#, 283 | @r###" 284 | _fun = : 285 | 286 | = _fun() 287 | "###); 288 | 289 | case!( 290 | bail_constructor_return_bad, 291 | |cx| cx.converge::("inline"), 292 | r#" 293 | new function() { 294 | return 1; 295 | }(); 296 | "#, 297 | @r###" 298 | _fun = : 299 | _ret = 1 300 | _ret 301 | = _fun() 302 | "###); 303 | 304 | case!( 305 | bail_constructor_this, 306 | |cx| cx.converge::("inline"), 307 | r#" 308 | new function() { 309 | this; 310 | return {}; 311 | }(); 312 | "#, 313 | @r###" 314 | _fun = : 315 | = 316 | _ret = { } 317 | _ret 318 | = _fun() 319 | "###); 320 | 321 | case!( 322 | bail_constructor_conditional_return, 323 | |cx| cx.converge::("inline"), 324 | r#" 325 | new function() { 326 | if (foo) ; 327 | else return {}; 328 | }(); 329 | "#, 330 | @r###" 331 | _fun = : 332 | _iff = 333 | _iff: 334 | 335 | : 336 | _ret = { } 337 | _ret 338 | = _fun() 339 | "###); 340 | 341 | case!( 342 | more_complex, 343 | all_passes, 344 | r#" 345 | g = (function f(a, b, c) { 346 | log(); 347 | return a + b + c; 348 | })(1, 2); 349 | "#, 350 | @r###" 351 | _mis = 352 | _fun = 353 | = _fun() 354 | _lhs = 3 355 | _val = _lhs + _mis 356 | <- _val 357 | "###); 358 | 359 | case!( 360 | do_not_inline_multi_use, 361 | all_passes, 362 | r#" 363 | const f = () => { foo(); }; 364 | f(); 365 | f(); 366 | "#, 367 | @r###" 368 | _ini = : 369 | _fun = 370 | = _fun() 371 | = _ini() 372 | = _ini() 373 | "###); 374 | 375 | case!( 376 | basic_inlining, 377 | all_passes, 378 | r#" 379 | function f(a, b, c) { 380 | log(); 381 | return a + b + c + 4; 382 | } 383 | g = f(1, 2, 3); 384 | "#, 385 | @r###" 386 | _fun = 387 | = _fun() 388 | _val = 10 389 | <- _val 390 | "###); 391 | 392 | case!( 393 | no_stmts_after_return, 394 | all_passes, 395 | r#" 396 | function f(a, b, c) { 397 | log(); 398 | return a + b + c + 4; 399 | console.log('drop me'); 400 | } 401 | g = f(1, 2, 3); 402 | "#, 403 | @r###" 404 | _fun = 405 | = _fun() 406 | _val = 10 407 | <- _val 408 | "###); 409 | -------------------------------------------------------------------------------- /src/ir/scope.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use swc_atoms::JsWord; 4 | 5 | use crate::collections::StackedMap; 6 | use crate::ir::{Lbl, Mut, Ref, Ssa}; 7 | use crate::utils::default_hash; 8 | 9 | #[derive(Default)] 10 | pub struct Ast<'a> { 11 | ident_to_mut_ref: StackedMap<'a, JsWord, Ref>, 12 | ident_to_label_ref: StackedMap<'a, JsWord, Ref>, 13 | } 14 | 15 | impl<'a> Ast<'a> { 16 | pub fn nested(&'a self) -> Ast<'a> { 17 | Self { 18 | ident_to_mut_ref: self.ident_to_mut_ref.child(), 19 | ident_to_label_ref: self.ident_to_label_ref.child(), 20 | } 21 | } 22 | 23 | pub fn get_mutable(&self, ident: &JsWord) -> Option<&Ref> { 24 | self.ident_to_mut_ref.get_all(ident) 25 | } 26 | 27 | pub fn get_mutable_in_current(&self, ident: &JsWord) -> Option<&Ref> { 28 | self.ident_to_mut_ref.get_self(ident) 29 | } 30 | 31 | pub fn declare_mutable(&mut self, ident: JsWord) -> Ref { 32 | let ref_ = Ref::new(&ident); 33 | let old_ref = self.ident_to_mut_ref.insert_self(ident, ref_.clone()); 34 | assert!(old_ref.is_none(), "mutable vars can only be declared once"); 35 | ref_ 36 | } 37 | 38 | pub fn get_label(&self, ident: &JsWord) -> Option<&Ref> { 39 | self.ident_to_label_ref.get_all(ident) 40 | } 41 | 42 | pub fn declare_label(&mut self, ident: JsWord) -> Ref { 43 | let ref_ = Ref::new(&ident); 44 | let old_ref = self.ident_to_label_ref.insert_self(ident, ref_.clone()); 45 | assert!(old_ref.is_none(), "labels can only be declared once"); 46 | ref_ 47 | } 48 | } 49 | 50 | pub struct Ir<'a> { 51 | minified_name_counter: Option, 52 | seen_prefix_hashes: StackedMap<'a, u64, u64>, 53 | mutable_names: StackedMap<'a, Ref, JsWord>, 54 | label_names: StackedMap<'a, Ref, JsWord>, 55 | ssa_names: StackedMap<'a, Ref, JsWord>, 56 | } 57 | 58 | pub struct Opt { 59 | pub minify: bool, 60 | } 61 | 62 | impl<'a> Ir<'a> { 63 | pub fn with_globals(globals: HashSet<&str>, options: Opt) -> Self { 64 | Self { 65 | minified_name_counter: match options.minify { 66 | true => Some(0), 67 | false => None, 68 | }, 69 | seen_prefix_hashes: globals 70 | .into_iter() 71 | .map(|name| (default_hash(name), 1)) 72 | .collect(), 73 | mutable_names: Default::default(), 74 | label_names: Default::default(), 75 | ssa_names: Default::default(), 76 | } 77 | } 78 | 79 | pub fn no_globals() -> Self { 80 | Self { 81 | minified_name_counter: None, 82 | seen_prefix_hashes: Default::default(), 83 | mutable_names: Default::default(), 84 | label_names: Default::default(), 85 | ssa_names: Default::default(), 86 | } 87 | } 88 | 89 | pub fn nested(&'a self) -> Ir<'a> { 90 | Self { 91 | minified_name_counter: self.minified_name_counter, 92 | seen_prefix_hashes: self.seen_prefix_hashes.child(), 93 | mutable_names: self.mutable_names.child(), 94 | label_names: self.label_names.child(), 95 | ssa_names: self.ssa_names.child(), 96 | } 97 | } 98 | 99 | pub fn get_mutable(&self, ref_: &Ref) -> Option { 100 | self.mutable_names.get_all(ref_).cloned() 101 | } 102 | 103 | pub fn declare_mutable(&mut self, ref_: Ref) -> JsWord { 104 | let name = self.unique_name(ref_.name_hint()); 105 | let old_name = self.mutable_names.insert_self(ref_, name.clone()); 106 | assert!(old_name.is_none(), "mutable vars can only be declared once"); 107 | name 108 | } 109 | 110 | pub fn get_label(&self, ref_: &Ref) -> Option { 111 | self.label_names.get_all(ref_).cloned() 112 | } 113 | 114 | pub fn declare_label(&mut self, ref_: Ref) -> JsWord { 115 | let name = self.unique_name(ref_.name_hint()); 116 | let old_name = self.label_names.insert_self(ref_, name.clone()); 117 | assert!(old_name.is_none(), "labels can only be declared once"); 118 | name 119 | } 120 | 121 | pub fn get_ssa(&self, ref_: &Ref) -> Option { 122 | self.ssa_names.get_all(ref_).cloned() 123 | } 124 | 125 | pub fn declare_ssa(&mut self, ref_: Ref) -> JsWord { 126 | let name = self.unique_name(ref_.name_hint()); 127 | let old_name = self.ssa_names.insert_self(ref_, name.clone()); 128 | assert!(old_name.is_none(), "ssa vars can only be declared once"); 129 | name 130 | } 131 | 132 | fn minified_name(mut index: usize) -> String { 133 | #[rustfmt::skip] 134 | const FIRST_CHAR: [char; 26 * 2 + 2] = [ 135 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 136 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 137 | '_', '$' 138 | ]; 139 | #[rustfmt::skip] 140 | const OTHER_CHARS: [char; 26 * 2 + 10 + 2] = [ 141 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 142 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 143 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 144 | '_', '$' 145 | ]; 146 | 147 | let mut name = String::with_capacity(4); 148 | name.push(FIRST_CHAR[index % FIRST_CHAR.len()]); 149 | index /= FIRST_CHAR.len(); 150 | while index > OTHER_CHARS.len() { 151 | name.push(OTHER_CHARS[index % OTHER_CHARS.len()]); 152 | index /= OTHER_CHARS.len(); 153 | } 154 | if index > 0 { 155 | name.push(OTHER_CHARS[index - 1]); 156 | } 157 | name 158 | } 159 | 160 | fn unique_name(&mut self, prefix: &str) -> JsWord { 161 | if let Some(counter) = &mut self.minified_name_counter { 162 | let name = loop { 163 | let name = Ir::minified_name(*counter); 164 | if self 165 | .seen_prefix_hashes 166 | .get_all(&default_hash(name.as_str())) 167 | .is_none() 168 | { 169 | break name; 170 | } 171 | *counter += 1; 172 | }; 173 | *counter += 1; 174 | JsWord::from(name) 175 | } else { 176 | let prefix = match prefix { 177 | "" => "_", 178 | _ => prefix, 179 | }; 180 | 181 | let suffix_counter = { 182 | // hash collisions are fine; we'll just end up being overly conservative 183 | let hash = default_hash(prefix); 184 | let old_value = *self.seen_prefix_hashes.get_all(&hash).unwrap_or(&0); 185 | self.seen_prefix_hashes.insert_self(hash, old_value + 1); 186 | old_value 187 | }; 188 | 189 | if suffix_counter == 0 { 190 | // if this prefix has never been seen, emit it directly 191 | JsWord::from(prefix) 192 | } else { 193 | self.unique_name(&format!("{}${}", prefix, suffix_counter)) 194 | } 195 | } 196 | } 197 | } 198 | 199 | #[cfg(test)] 200 | mod tests { 201 | use super::*; 202 | 203 | #[test] 204 | fn basic() { 205 | let mut p = Ir::no_globals(); 206 | assert_eq!(p.unique_name("foo").as_ref(), "foo"); 207 | assert_eq!(p.unique_name("foo").as_ref(), "foo$1"); 208 | assert_eq!(p.unique_name("foo").as_ref(), "foo$2"); 209 | assert_eq!(p.unique_name("foo").as_ref(), "foo$3"); 210 | } 211 | 212 | #[test] 213 | fn replacement_overlap1() { 214 | let mut p = Ir::no_globals(); 215 | assert_eq!(p.unique_name("foo").as_ref(), "foo"); 216 | assert_eq!(p.unique_name("foo$1").as_ref(), "foo$1"); 217 | assert_eq!(p.unique_name("foo").as_ref(), "foo$1$1"); 218 | } 219 | 220 | #[test] 221 | fn replacement_overlap2() { 222 | let mut p = Ir::no_globals(); 223 | assert_eq!(p.unique_name("foo").as_ref(), "foo"); 224 | assert_eq!(p.unique_name("foo").as_ref(), "foo$1"); 225 | assert_eq!(p.unique_name("foo$1").as_ref(), "foo$1$1"); 226 | } 227 | 228 | #[test] 229 | fn empty_string() { 230 | let mut p = Ir::no_globals(); 231 | assert_eq!(p.unique_name("").as_ref(), "_"); 232 | assert_eq!(p.unique_name("").as_ref(), "_$1"); 233 | } 234 | 235 | #[test] 236 | fn minify_deconflict() { 237 | let mut p = Ir::with_globals(Some("b").into_iter().collect(), Opt { minify: true }); 238 | assert_eq!(p.unique_name("").as_ref(), "a"); 239 | assert_eq!(p.unique_name("").as_ref(), "c"); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /src/opt/unroll.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::mem; 3 | 4 | use crate::anl; 5 | use crate::collections::ZeroOneMany::{self, Many, One, Zero}; 6 | use crate::ir; 7 | use crate::ir::traverse::{Folder, ScopeTy}; 8 | 9 | /// Loop unrolling for 0/1 iteration loops. 10 | /// 11 | /// Does not profit from multiple passes. 12 | /// May profit from DCE running first; may create opportunities for DCE. 13 | /// May create opportunities for read forwarding. 14 | /// May create opportunities for SROA. 15 | #[derive(Debug, Default)] 16 | pub struct Loops { 17 | refs_used_in_only_one_fn_scope: HashSet>, 18 | local: Objects, 19 | nonlocal: Objects, 20 | } 21 | 22 | #[derive(Debug, Default)] 23 | struct Objects { 24 | small_objects: HashMap, Option>>, 25 | invalid_in_parent: Invalid, 26 | } 27 | 28 | #[derive(Debug)] 29 | enum Invalid { 30 | All, 31 | Objects(HashSet>), 32 | } 33 | 34 | impl Default for Invalid { 35 | fn default() -> Self { 36 | Self::Objects(Default::default()) 37 | } 38 | } 39 | 40 | impl Invalid { 41 | fn insert_obj(&mut self, obj: ir::WeakRef) { 42 | match self { 43 | Self::All => { 44 | // all objects already invalid 45 | } 46 | Self::Objects(objects) => { 47 | objects.insert(obj); 48 | } 49 | } 50 | } 51 | } 52 | 53 | impl Loops { 54 | fn invalidate_from_child(&mut self, child: Self) { 55 | fn invalidate(this: &mut Objects, child: Objects) { 56 | match child.invalid_in_parent { 57 | Invalid::All => { 58 | this.small_objects.clear(); 59 | this.invalid_in_parent = Invalid::All; 60 | } 61 | Invalid::Objects(objects) => { 62 | for obj in objects { 63 | this.small_objects.remove(&obj); 64 | this.invalid_in_parent.insert_obj(obj); 65 | } 66 | } 67 | } 68 | } 69 | invalidate(&mut self.local, child.local); 70 | invalidate(&mut self.nonlocal, child.nonlocal); 71 | } 72 | 73 | fn invalidate_everything_used_across_fn_scopes(&mut self) { 74 | self.nonlocal.small_objects.clear(); 75 | self.nonlocal.invalid_in_parent = Invalid::All; 76 | } 77 | 78 | fn invalidate_ref(&mut self, ref_: ir::WeakRef) { 79 | let objects = if self.refs_used_in_only_one_fn_scope.contains(&ref_) { 80 | &mut self.local 81 | } else { 82 | &mut self.nonlocal 83 | }; 84 | objects.small_objects.remove(&ref_); 85 | objects.invalid_in_parent.insert_obj(ref_); 86 | } 87 | 88 | fn declare_small_object(&mut self, ref_: ir::WeakRef, prop: Option>) { 89 | let objects = if self.refs_used_in_only_one_fn_scope.contains(&ref_) { 90 | &mut self.local 91 | } else { 92 | &mut self.nonlocal 93 | }; 94 | objects.small_objects.insert(ref_, prop); 95 | } 96 | 97 | fn get_small_object(&self, ref_: &ir::WeakRef) -> Option<&Option>> { 98 | self.local 99 | .small_objects 100 | .get(ref_) 101 | .or_else(|| self.nonlocal.small_objects.get(ref_)) 102 | } 103 | } 104 | 105 | impl Folder for Loops { 106 | type Output = ZeroOneMany; 107 | 108 | fn wrap_scope( 109 | &mut self, 110 | ty: &ScopeTy, 111 | block: ir::Block, 112 | enter: impl FnOnce(&mut Self, ir::Block) -> R, 113 | ) -> R { 114 | if let ScopeTy::Toplevel = ty { 115 | self.refs_used_in_only_one_fn_scope = anl::refs::used_in_only_one_fn_scope(&block) 116 | .map(ir::Ref::weak) 117 | .collect(); 118 | } 119 | 120 | match ty { 121 | ScopeTy::Function => { 122 | // functions are analyzed separately 123 | let mut inner = Self::default(); 124 | mem::swap( 125 | &mut inner.refs_used_in_only_one_fn_scope, 126 | &mut self.refs_used_in_only_one_fn_scope, 127 | ); 128 | let r = enter(&mut inner, block); 129 | mem::swap( 130 | &mut inner.refs_used_in_only_one_fn_scope, 131 | &mut self.refs_used_in_only_one_fn_scope, 132 | ); 133 | r 134 | } 135 | ScopeTy::Normal | ScopeTy::Toplevel => { 136 | // we deal only with ssa refs, so it doesn't matter if usage info escapes normal scopes, 137 | // since there can be no uses of an ssa ref in a parent scope 138 | enter(self, block) 139 | } 140 | ScopeTy::Nonlinear => { 141 | // no information can be carried into a nonlinear scope, but invalidations must be applied 142 | let mut inner = Self::default(); 143 | mem::swap( 144 | &mut inner.refs_used_in_only_one_fn_scope, 145 | &mut self.refs_used_in_only_one_fn_scope, 146 | ); 147 | let r = enter(&mut inner, block); 148 | mem::swap( 149 | &mut inner.refs_used_in_only_one_fn_scope, 150 | &mut self.refs_used_in_only_one_fn_scope, 151 | ); 152 | self.invalidate_from_child(inner); 153 | r 154 | } 155 | } 156 | } 157 | 158 | fn fold(&mut self, stmt: ir::Stmt) -> Self::Output { 159 | match stmt { 160 | ir::Stmt::Expr { 161 | ref target, 162 | ref expr, 163 | } => match expr { 164 | ir::Expr::Object { props } => { 165 | let mut props = props.iter(); 166 | if let (maybe_first, None) = (props.next(), props.next()) { 167 | let maybe_key = maybe_first.map(|(_kind, key, _val)| key.clone()); 168 | self.declare_small_object(target.weak(), maybe_key); 169 | } 170 | One(stmt) 171 | } 172 | ir::Expr::Yield { .. } | ir::Expr::Await { .. } | ir::Expr::Call { .. } => { 173 | self.invalidate_everything_used_across_fn_scopes(); 174 | One(stmt) 175 | } 176 | ir::Expr::Bool { .. } 177 | | ir::Expr::Number { .. } 178 | | ir::Expr::String { .. } 179 | | ir::Expr::Null 180 | | ir::Expr::Undefined 181 | | ir::Expr::This 182 | | ir::Expr::Read { .. } 183 | | ir::Expr::ReadMutable { .. } 184 | | ir::Expr::ReadGlobal { .. } 185 | | ir::Expr::ReadMember { .. } 186 | | ir::Expr::Array { .. } 187 | | ir::Expr::RegExp { .. } 188 | | ir::Expr::Unary { .. } 189 | | ir::Expr::Binary { .. } 190 | | ir::Expr::Delete { .. } 191 | | ir::Expr::Function { .. } 192 | | ir::Expr::CurrentFunction { .. } 193 | | ir::Expr::Argument { .. } => One(stmt), 194 | }, 195 | ir::Stmt::ForEach { 196 | kind: kind @ ir::ForKind::In, 197 | init, 198 | body, 199 | } => match self.get_small_object(&init.weak()) { 200 | Some(maybe_key) => match maybe_key { 201 | Some(single_key) => { 202 | let ir::Block(children) = body; 203 | let single_iteration = children 204 | .into_iter() 205 | .map(|stmt| match stmt { 206 | ir::Stmt::Expr { 207 | target, 208 | expr: ir::Expr::Argument { index: 0 }, 209 | } => ir::Stmt::Expr { 210 | target, 211 | expr: ir::Expr::Read { 212 | source: single_key.clone(), 213 | }, 214 | }, 215 | _ => stmt, 216 | }) 217 | .collect(); 218 | Many(single_iteration) 219 | } 220 | None => Zero, 221 | }, 222 | None => One(ir::Stmt::ForEach { kind, init, body }), 223 | }, 224 | ir::Stmt::DeclareMutable { .. } 225 | | ir::Stmt::WriteMutable { .. } 226 | | ir::Stmt::WriteGlobal { .. } 227 | | ir::Stmt::WriteMember { .. } 228 | | ir::Stmt::Return { .. } 229 | | ir::Stmt::Throw { .. } 230 | | ir::Stmt::Break { .. } 231 | | ir::Stmt::Continue { .. } 232 | | ir::Stmt::Debugger 233 | | ir::Stmt::Label { .. } 234 | | ir::Stmt::Loop { .. } 235 | | ir::Stmt::ForEach { .. } 236 | | ir::Stmt::IfElse { .. } 237 | | ir::Stmt::Switch { .. } 238 | | ir::Stmt::SwitchCase { .. } 239 | | ir::Stmt::Try { .. } => One(stmt), 240 | } 241 | } 242 | 243 | fn fold_ref_use(&mut self, ref_: ir::Ref) -> ir::Ref { 244 | // todo inefficient: this invalidates every single ref, even if it's not an object 245 | self.invalidate_ref(ref_.weak()); 246 | ref_ 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/opt/constant.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use swc_atoms::JsWord; 4 | 5 | use crate::collections::ZeroOneMany::{self, Many, One}; 6 | use crate::ir; 7 | use crate::ir::traverse::Folder; 8 | use crate::ir::F64; 9 | 10 | /// Constant propagation / precompute. 11 | /// 12 | /// Does not profit from multiple passes. 13 | /// Does not profit from DCE running first; may create opportunities for DCE. 14 | #[derive(Debug, Default)] 15 | pub struct ConstProp { 16 | shallow_values: HashMap, Expr>, 17 | } 18 | 19 | /// Cached expr with limited information. 20 | #[derive(Debug)] 21 | enum Expr { 22 | Bool(bool), 23 | Number(F64), 24 | String(JsWord), 25 | Null, 26 | Undefined, 27 | This, 28 | Array, 29 | Object, 30 | RegExp, 31 | Function, 32 | CurrentFunction, 33 | Argument, 34 | } 35 | 36 | impl ConstProp { 37 | fn maybe_shallow_cache_expr(&mut self, ref_: &ir::Ref, expr: &ir::Expr) { 38 | // avoid cloning refs, which wastes time on refcounts and make tracing ref drops harder 39 | // and CERTAINLY avoid deep cloning blocks 40 | let shallow_clone = match expr { 41 | ir::Expr::Bool { value } => Expr::Bool(*value), 42 | ir::Expr::Number { value } => Expr::Number(*value), 43 | ir::Expr::String { value } => Expr::String(value.clone()), 44 | ir::Expr::Null => Expr::Null, 45 | ir::Expr::Undefined => Expr::Undefined, 46 | ir::Expr::This => Expr::This, 47 | // avoid cloning refs inside array 48 | ir::Expr::Array { elems: _ } => Expr::Array, 49 | // avoid cloning refs inside object 50 | ir::Expr::Object { props: _ } => Expr::Object, 51 | ir::Expr::RegExp { regex: _, flags: _ } => Expr::RegExp, 52 | // avoid cloning function body 53 | ir::Expr::Function { kind: _, body: _ } => Expr::Function, 54 | ir::Expr::CurrentFunction => Expr::CurrentFunction, 55 | ir::Expr::Argument { index: _ } => Expr::Argument, 56 | _ => return, 57 | }; 58 | 59 | self.shallow_values.insert(ref_.weak(), shallow_clone); 60 | } 61 | } 62 | 63 | impl Folder for ConstProp { 64 | type Output = ZeroOneMany; 65 | 66 | #[rustfmt::skip] 67 | fn fold(&mut self, stmt: ir::Stmt) -> Self::Output { 68 | use ir::BinaryOp::*; 69 | use ir::Expr::*; 70 | use ir::UnaryOp::*; 71 | 72 | match stmt { 73 | ir::Stmt::Expr { 74 | target, 75 | expr, 76 | } => { 77 | let expr = match expr { 78 | ReadGlobal { ref source } => match source.as_ref() { 79 | "NaN" => Number { value: F64::NAN }, 80 | "undefined" => Undefined, 81 | _ => expr, 82 | }, 83 | ReadMember { ref obj, ref prop } => match (self.shallow_values.get(&obj.weak()), self.shallow_values.get(&prop.weak())) { 84 | (Some(obj), Some(Expr::String(prop))) => match (obj, prop.as_ref()) { 85 | (Expr::String(value), "length") => Number { value: F64::from(value.chars().map(char::len_utf16).sum::() as f64) }, 86 | _ => expr, 87 | }, 88 | _ => expr, 89 | } 90 | Unary { 91 | ref op, 92 | ref val, 93 | } => match self.shallow_values.get(&val.weak()) { 94 | Some(val_val) => match (op, val_val) { 95 | (Plus, Expr::Number(value)) => Number { value: *value }, 96 | (Minus, Expr::Number(value)) => Number { value: -*value }, 97 | (Not, Expr::Bool(value)) => Bool { value: !*value }, 98 | (Not, Expr::Number(value)) => Bool { value: !value.is_truthy() }, 99 | (Not, Expr::String(value)) => Bool { value: value == "" }, 100 | (Not, Expr::Null) 101 | | (Not, Expr::Undefined) => Bool { value: true }, 102 | (Not, Expr::Array) 103 | | (Not, Expr::Object) 104 | | (Not, Expr::RegExp) 105 | | (Not, Expr::Function) 106 | | (Not, Expr::CurrentFunction) => Bool { value: false }, 107 | (Tilde, Expr::Number(value)) => Number { value: !*value }, 108 | (Typeof, Expr::Bool(_)) => String { value: "boolean".into() }, 109 | (Typeof, Expr::Number(_)) => String { value: "number".into() }, 110 | (Typeof, Expr::String(_)) => String { value: "string".into() }, 111 | (Typeof, Expr::Null) => String { value: "object".into() }, 112 | (Typeof, Expr::Undefined) => String { value: "undefined".into() }, 113 | (Typeof, Expr::Array) 114 | | (Typeof, Expr::Object) 115 | | (Typeof, Expr::RegExp) => String { value: "object".into() }, 116 | (Typeof, Expr::Function) 117 | | (Typeof, Expr::CurrentFunction) => String { value: "function".into() }, 118 | (Void, _) => Undefined, 119 | _ => expr, 120 | }, 121 | None => expr, 122 | }, 123 | Binary { 124 | ref op, 125 | ref left, 126 | ref right, 127 | } => match (self.shallow_values.get(&left.weak()), self.shallow_values.get(&right.weak())) { 128 | (Some(left_val), Some(right_val)) => match (op, left_val, right_val) { 129 | (EqEq, _, _) 130 | | (StrictEq, _, _) if left == right => Bool { value: true }, 131 | (EqEq, Expr::Bool(a), Expr::Bool(b)) 132 | | (StrictEq, Expr::Bool(a), Expr::Bool(b)) => Bool { value: a == b }, 133 | (EqEq, Expr::Number(a), Expr::Number(b)) 134 | | (StrictEq, Expr::Number(a), Expr::Number(b)) => Bool { value: a == b }, 135 | (EqEq, Expr::String(a), Expr::String(b)) 136 | | (StrictEq, Expr::String(a), Expr::String(b)) => Bool { value: a == b }, 137 | (NotEq, Expr::Bool(a), Expr::Bool(b)) 138 | | (NotStrictEq, Expr::Bool(a), Expr::Bool(b)) => Bool { value: a != b }, 139 | (NotEq, Expr::Number(a), Expr::Number(b)) 140 | | (NotStrictEq, Expr::Number(a), Expr::Number(b)) => Bool { value: a != b }, 141 | (NotEq, Expr::String(a), Expr::String(b)) 142 | | (NotStrictEq, Expr::String(a), Expr::String(b)) => Bool { value: a != b }, 143 | (Lt, Expr::Number(a), Expr::Number(b)) => Bool { value: a < b }, 144 | (LtEq, Expr::Number(a), Expr::Number(b)) => Bool { value: a <= b }, 145 | (Gt, Expr::Number(a), Expr::Number(b)) => Bool { value: a > b }, 146 | (GtEq, Expr::Number(a), Expr::Number(b)) => Bool { value: a >= b }, 147 | (ShiftLeft, Expr::Number(a), Expr::Number(b)) => Number { value: a.shl(*b) }, 148 | (ShiftRight, Expr::Number(a), Expr::Number(b)) => Number { value: a.shr(*b) }, 149 | (ShiftRightZero, Expr::Number(a), Expr::Number(b)) => Number { value: a.shr_zero(*b) }, 150 | (Add, Expr::Number(a), Expr::Number(b)) => Number { value: *a + *b }, 151 | (Sub, Expr::Number(a), Expr::Number(b)) => Number { value: *a - *b }, 152 | (Mul, Expr::Number(a), Expr::Number(b)) => Number { value: *a * *b }, 153 | (Div, Expr::Number(a), Expr::Number(b)) => Number { value: *a / *b }, 154 | (Mod, Expr::Number(a), Expr::Number(b)) => Number { value: *a % *b }, 155 | (BitOr, Expr::Number(a), Expr::Number(b)) => Number { value: *a | *b }, 156 | (BitXor, Expr::Number(a), Expr::Number(b)) => Number { value: *a ^ *b }, 157 | (BitAnd, Expr::Number(a), Expr::Number(b)) => Number { value: *a & *b }, 158 | (Exp, Expr::Number(a), Expr::Number(b)) => Number { value: a.powf(*b) }, 159 | (Add, Expr::String(a), Expr::String(b)) => String { value: (a.to_string() + b).into() }, 160 | _ => expr, 161 | }, 162 | _ => expr, 163 | }, 164 | _ => expr, 165 | }; 166 | 167 | self.maybe_shallow_cache_expr(&target, &expr); 168 | 169 | One(ir::Stmt::Expr { target, expr }) 170 | } 171 | ir::Stmt::IfElse { 172 | cond, 173 | cons, 174 | alt, 175 | } => match self.shallow_values.get(&cond.weak()) { 176 | Some(cond_val) => match cond_val { 177 | Expr::Bool(true) 178 | | Expr::Array 179 | | Expr::Object 180 | | Expr::RegExp 181 | | Expr::Function => Many(cons.0), 182 | Expr::Bool(false) 183 | | Expr::Null 184 | | Expr::Undefined => Many(alt.0), 185 | Expr::Number(value) => if value.is_truthy() { Many(cons.0) } else { Many(alt.0) }, 186 | Expr::String(value) => if value != "" { Many(cons.0) } else { Many(alt.0) }, 187 | _ => One(ir::Stmt::IfElse { cond, cons, alt }), 188 | }, 189 | None => One(ir::Stmt::IfElse { cond, cons, alt }), 190 | }, 191 | _ => One(stmt), 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/ast2ir/hoist.rs: -------------------------------------------------------------------------------- 1 | use swc_atoms::JsWord; 2 | use swc_ecma_ast as ast; 3 | 4 | use crate::ir; 5 | use crate::ir::scope; 6 | 7 | pub enum Hoist { 8 | Everything, 9 | OnlyLetConst, 10 | } 11 | 12 | #[inline(never)] // for better profiling 13 | pub fn hoist_fn_decls(block: &mut [ast::Stmt]) { 14 | // manually hoist function declarations at the toplevel/fn scopes 15 | block.sort_by_key(|stmt| match stmt { 16 | ast::Stmt::Decl(ast::Decl::Fn(_)) => 0, 17 | ast::Stmt::Labeled(ast::LabeledStmt { body, .. }) => match body.as_ref() { 18 | ast::Stmt::Decl(ast::Decl::Fn(_)) => 0, 19 | _ => 1, 20 | }, 21 | _ => 1, 22 | }); 23 | } 24 | 25 | #[inline(never)] // for better profiling 26 | pub fn hoist_vars(block: &[ast::Stmt], scope: &mut scope::Ast, hoist: Hoist) -> Vec { 27 | hoist_block(block, scope, &hoist, Level::First) 28 | } 29 | 30 | enum Level { 31 | First, 32 | Below, 33 | } 34 | 35 | fn hoist_block( 36 | body: &[ast::Stmt], 37 | scope: &mut scope::Ast, 38 | hoist: &Hoist, 39 | level: Level, 40 | ) -> Vec { 41 | match (hoist, &level) { 42 | (Hoist::Everything, _) | (Hoist::OnlyLetConst, Level::First) => body 43 | .iter() 44 | .flat_map(|stmt| hoist_statement(stmt, scope, hoist, &level)) 45 | .collect(), 46 | (Hoist::OnlyLetConst, Level::Below) => vec![], 47 | } 48 | } 49 | 50 | fn hoist_statement( 51 | stmt: &ast::Stmt, 52 | scope: &mut scope::Ast, 53 | hoist: &Hoist, 54 | level: &Level, 55 | ) -> Vec { 56 | match stmt { 57 | ast::Stmt::Block(ast::BlockStmt { stmts, span: _ }) => { 58 | hoist_block(stmts, scope, hoist, Level::Below) 59 | } 60 | ast::Stmt::With(ast::WithStmt { 61 | obj: _, 62 | body, 63 | span: _, 64 | }) => hoist_statement(body, scope, hoist, &Level::Below), 65 | ast::Stmt::Labeled(ast::LabeledStmt { 66 | label: _, 67 | body, 68 | span: _, 69 | }) => hoist_statement(body, scope, hoist, level), 70 | ast::Stmt::If(ast::IfStmt { 71 | test: _, 72 | cons, 73 | alt, 74 | span: _, 75 | }) => { 76 | let mut decls = hoist_statement(cons, scope, hoist, &Level::Below); 77 | if let Some(alt) = alt { 78 | decls.extend(hoist_statement(alt, scope, hoist, &Level::Below)); 79 | } 80 | decls 81 | } 82 | ast::Stmt::Switch(ast::SwitchStmt { 83 | discriminant: _, 84 | cases, 85 | span: _, 86 | }) => cases 87 | .iter() 88 | .flat_map( 89 | |ast::SwitchCase { 90 | test: _, 91 | cons, 92 | span: _, 93 | }| { 94 | cons.iter() 95 | .flat_map(|stmt| hoist_statement(stmt, scope, hoist, &Level::Below)) 96 | .collect::>() 97 | }, 98 | ) 99 | .collect(), 100 | ast::Stmt::Try(ast::TryStmt { 101 | block: ast::BlockStmt { stmts, span: _ }, 102 | handler, 103 | finalizer, 104 | span: _, 105 | }) => { 106 | let mut decls = hoist_block(stmts, scope, hoist, Level::Below); 107 | if let Some(ast::CatchClause { 108 | param: _, 109 | body: ast::BlockStmt { stmts, span: _ }, 110 | span: _, 111 | }) = handler 112 | { 113 | decls.extend(hoist_block(stmts, scope, hoist, Level::Below)); 114 | } 115 | if let Some(ast::BlockStmt { stmts, span: _ }) = finalizer { 116 | decls.extend(hoist_block(stmts, scope, hoist, Level::Below)); 117 | } 118 | decls 119 | } 120 | ast::Stmt::While(ast::WhileStmt { 121 | test: _, 122 | body, 123 | span: _, 124 | }) 125 | | ast::Stmt::DoWhile(ast::DoWhileStmt { 126 | test: _, 127 | body, 128 | span: _, 129 | }) => hoist_statement(body, scope, hoist, &Level::Below), 130 | ast::Stmt::For(ast::ForStmt { 131 | init, 132 | test: _, 133 | update: _, 134 | body, 135 | span: _, 136 | }) => { 137 | let mut decls = match init { 138 | Some(ast::VarDeclOrExpr::VarDecl(var_decl)) => { 139 | hoist_variable_declaration(var_decl, scope, hoist, &Level::Below) 140 | } 141 | Some(ast::VarDeclOrExpr::Expr(_)) | None => vec![], 142 | }; 143 | decls.extend(hoist_statement(body, scope, hoist, &Level::Below)); 144 | decls 145 | } 146 | ast::Stmt::ForIn(ast::ForInStmt { 147 | left, 148 | right: _, 149 | body, 150 | span: _, 151 | }) 152 | | ast::Stmt::ForOf(ast::ForOfStmt { 153 | left, 154 | right: _, 155 | body, 156 | await_token: _, 157 | span: _, 158 | }) => { 159 | let mut decls = match left { 160 | ast::VarDeclOrPat::VarDecl(var_decl) => { 161 | hoist_variable_declaration(var_decl, scope, hoist, &Level::Below) 162 | } 163 | // bare patterns can't introduce variables 164 | ast::VarDeclOrPat::Pat(_) => vec![], 165 | }; 166 | decls.extend(hoist_statement(body, scope, hoist, &Level::Below)); 167 | decls 168 | } 169 | ast::Stmt::Decl(decl) => match decl { 170 | ast::Decl::Fn(fn_decl) => hoist_function_declaration(fn_decl, scope, hoist), 171 | ast::Decl::Var(var_decl) => hoist_variable_declaration(var_decl, scope, hoist, level), 172 | ast::Decl::Class(_) => unimplemented!("classes not supported"), 173 | ast::Decl::TsInterface(_) 174 | | ast::Decl::TsTypeAlias(_) 175 | | ast::Decl::TsEnum(_) 176 | | ast::Decl::TsModule(_) => unreachable!(), 177 | }, 178 | ast::Stmt::Expr(_) 179 | | ast::Stmt::Empty(_) 180 | | ast::Stmt::Debugger(_) 181 | | ast::Stmt::Return(_) 182 | | ast::Stmt::Break(_) 183 | | ast::Stmt::Continue(_) 184 | | ast::Stmt::Throw(_) => vec![], 185 | } 186 | } 187 | 188 | fn hoist_function_declaration( 189 | fn_decl: &ast::FnDecl, 190 | scope: &mut scope::Ast, 191 | hoist: &Hoist, 192 | ) -> Vec { 193 | let ast::FnDecl { 194 | ident: 195 | ast::Ident { 196 | sym, 197 | span: _, 198 | type_ann: _, 199 | optional: _, 200 | }, 201 | function: _, 202 | declare: _, 203 | } = fn_decl; 204 | 205 | match hoist { 206 | Hoist::Everything => { 207 | let fn_ref = scope.declare_mutable(sym.clone()); 208 | let init_ref = ir::Ref::new("_ini"); 209 | vec![ 210 | ir::Stmt::Expr { 211 | target: init_ref.clone(), 212 | expr: ir::Expr::Undefined, 213 | }, 214 | ir::Stmt::DeclareMutable { 215 | target: fn_ref, 216 | val: init_ref, 217 | }, 218 | ] 219 | } 220 | Hoist::OnlyLetConst => vec![], 221 | } 222 | } 223 | 224 | fn hoist_variable_declaration( 225 | var_decl: &ast::VarDecl, 226 | scope: &mut scope::Ast, 227 | hoist: &Hoist, 228 | level: &Level, 229 | ) -> Vec { 230 | let ast::VarDecl { 231 | kind, 232 | decls, 233 | span: _, 234 | declare: _, 235 | } = var_decl; 236 | match (hoist, level, kind) { 237 | (Hoist::Everything, _, ast::VarDeclKind::Var) => decls 238 | .iter() 239 | .flat_map( 240 | |ast::VarDeclarator { 241 | name, 242 | init: _, 243 | span: _, 244 | definite: _, 245 | }| { 246 | let name = pat_to_ident(name); 247 | match scope.get_mutable_in_current(name) { 248 | None => { 249 | let var_ref = scope.declare_mutable(name.clone()); 250 | let init_ref = ir::Ref::new("_ini"); 251 | vec![ 252 | ir::Stmt::Expr { 253 | target: init_ref.clone(), 254 | expr: ir::Expr::Undefined, 255 | }, 256 | ir::Stmt::DeclareMutable { 257 | target: var_ref, 258 | val: init_ref, 259 | }, 260 | ] 261 | } 262 | Some(_) => vec![], 263 | } 264 | }, 265 | ) 266 | .collect(), 267 | (_, Level::First, ast::VarDeclKind::Let) | (_, Level::First, ast::VarDeclKind::Const) => { 268 | decls.iter().for_each( 269 | |ast::VarDeclarator { 270 | name, 271 | init: _, 272 | span: _, 273 | definite: _, 274 | }| { 275 | let name = pat_to_ident(name); 276 | // declare to reserve name, in case some function above the declaration uses it 277 | scope.declare_mutable(name.clone()); 278 | }, 279 | ); 280 | vec![] 281 | } 282 | (Hoist::OnlyLetConst, _, ast::VarDeclKind::Var) 283 | | (_, Level::Below, ast::VarDeclKind::Let) 284 | | (_, Level::Below, ast::VarDeclKind::Const) => vec![], 285 | } 286 | } 287 | 288 | fn pat_to_ident(pat: &ast::Pat) -> &JsWord { 289 | match pat { 290 | ast::Pat::Ident(ast::Ident { 291 | sym, 292 | span: _, 293 | type_ann: _, 294 | optional: _, 295 | }) => sym, 296 | ast::Pat::Array(_) 297 | | ast::Pat::Object(_) 298 | | ast::Pat::Rest(_) 299 | | ast::Pat::Assign(_) 300 | | ast::Pat::Expr(_) => unimplemented!("complex patterns not supported"), 301 | ast::Pat::Invalid(_) => unreachable!(), 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /src/opt/inline.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::iter; 3 | use std::mem; 4 | 5 | use crate::collections::ZeroOneMany::{self, Many, One, Zero}; 6 | use crate::ir; 7 | use crate::ir::traverse::{Folder, RunVisitor, ScopeTy, Visitor}; 8 | 9 | /// Inline functions called a single time. 10 | /// 11 | /// Does not profit from multiple passes. 12 | /// May profit from DCE running first; may create opportunities for DCE. 13 | /// May create opportunities for _everything_; in particular, read forwarding. 14 | #[derive(Debug, Default)] 15 | pub struct Inline { 16 | fns_to_inline: HashSet>, 17 | fn_bodies: HashMap, ir::Block>, 18 | } 19 | 20 | impl Drop for Inline { 21 | fn drop(&mut self) { 22 | assert!( 23 | self.fn_bodies.is_empty(), 24 | "should have inlined fns: {:?}", 25 | self.fn_bodies 26 | ); 27 | } 28 | } 29 | 30 | #[derive(Debug, Default)] 31 | struct CollectFnCallInfo<'a> { 32 | known_objs: HashSet<&'a ir::Ref>, 33 | fns_to_inline: HashSet<&'a ir::Ref>, 34 | fn_def_is_good: HashMap<&'a ir::Ref, FnInfo>, 35 | about_to_enter_arrow_fn: bool, 36 | about_to_enter_fn: Option<(&'a ir::Ref, FnInfo)>, 37 | current_fn: Option<(&'a ir::Ref, FnInfo)>, 38 | depth_in_current_fn: u32, 39 | used_this: bool, 40 | } 41 | 42 | #[derive(Debug)] 43 | struct FnInfo { 44 | is_arrow: bool, 45 | returns_an_object: bool, 46 | } 47 | 48 | impl<'a> Visitor<'a> for CollectFnCallInfo<'a> { 49 | fn wrap_scope( 50 | &mut self, 51 | ty: &ScopeTy, 52 | block: &'a ir::Block, 53 | enter: impl FnOnce(&mut Self, &'a ir::Block) -> R, 54 | ) -> R { 55 | match ty { 56 | ScopeTy::Function => { 57 | let is_arrow_fn = mem::replace(&mut self.about_to_enter_arrow_fn, false); 58 | let mut inner = Self::default(); 59 | mem::swap(&mut inner.fns_to_inline, &mut self.fns_to_inline); 60 | mem::swap(&mut inner.fn_def_is_good, &mut self.fn_def_is_good); 61 | inner.current_fn = self.about_to_enter_fn.take(); 62 | let r = enter(&mut inner, block); 63 | mem::swap(&mut inner.fns_to_inline, &mut self.fns_to_inline); 64 | mem::swap(&mut inner.fn_def_is_good, &mut self.fn_def_is_good); 65 | // if fn hasn't been invalidated, it's good 66 | self.fn_def_is_good.extend(inner.current_fn); 67 | // propagate `this` from arrow functions 68 | if is_arrow_fn && inner.used_this { 69 | self.current_fn = None; 70 | self.used_this = true; 71 | } 72 | r 73 | } 74 | ScopeTy::Normal | ScopeTy::Toplevel | ScopeTy::Nonlinear => { 75 | self.depth_in_current_fn += 1; 76 | let r = enter(self, block); 77 | self.depth_in_current_fn -= 1; 78 | r 79 | } 80 | } 81 | } 82 | 83 | fn visit(&mut self, stmt: &'a ir::Stmt) { 84 | match stmt { 85 | ir::Stmt::Expr { target, expr } => match expr { 86 | ir::Expr::Object { .. } => { 87 | self.known_objs.insert(target); 88 | } 89 | ir::Expr::Function { 90 | kind: 91 | ir::FnKind::Func { 92 | is_async: false, 93 | is_generator: false, 94 | }, 95 | body: _, 96 | } if target.used().is_once() => { 97 | // fn is simple, used once: def is good if its body is good 98 | self.about_to_enter_fn = Some(( 99 | target, 100 | FnInfo { 101 | is_arrow: false, 102 | returns_an_object: false, 103 | }, 104 | )); 105 | } 106 | ir::Expr::Function { kind, body: _ } => { 107 | self.about_to_enter_arrow_fn = true; 108 | match kind { 109 | ir::FnKind::Arrow { is_async: false } if target.used().is_once() => { 110 | // fn is simple, used once: def is good if its body is good 111 | self.about_to_enter_fn = Some(( 112 | target, 113 | FnInfo { 114 | is_arrow: true, 115 | returns_an_object: false, 116 | }, 117 | )); 118 | } 119 | _ => {} 120 | } 121 | } 122 | ir::Expr::Call { 123 | kind, 124 | base, 125 | prop: None, 126 | args, 127 | } => { 128 | if let Some(info) = self.fn_def_is_good.get(base) { 129 | let kind_is_good = match kind { 130 | ir::CallKind::Call => true, 131 | ir::CallKind::New => !info.is_arrow && info.returns_an_object, 132 | }; 133 | let args_are_good = args.iter().all(|(kind, _)| match kind { 134 | ir::EleKind::Single => true, 135 | ir::EleKind::Spread => false, 136 | }); 137 | if kind_is_good && args_are_good { 138 | // fn def is good, and call is simple: mark for inlining 139 | self.fns_to_inline.insert(base); 140 | } 141 | } 142 | } 143 | ir::Expr::This => { 144 | // fn body uses `this`: invalidate 145 | self.current_fn = None; 146 | self.used_this = true; 147 | } 148 | ir::Expr::CurrentFunction => { 149 | // fn is possibly recursive: invalidate 150 | self.current_fn = None; 151 | } 152 | _ => {} 153 | }, 154 | ir::Stmt::Return { val } => { 155 | if self.depth_in_current_fn > 0 { 156 | // return statement not at top level: invalidate 157 | self.current_fn = None; 158 | } else { 159 | // return statement is good: check whether we can inline constructor calls 160 | if let Some((_, info)) = &mut self.current_fn { 161 | info.returns_an_object = self.known_objs.contains(val); 162 | } 163 | } 164 | } 165 | _ => {} 166 | } 167 | } 168 | } 169 | 170 | impl Folder for Inline { 171 | type Output = ZeroOneMany; 172 | 173 | fn wrap_scope( 174 | &mut self, 175 | ty: &ScopeTy, 176 | block: ir::Block, 177 | enter: impl FnOnce(&mut Self, ir::Block) -> R, 178 | ) -> R { 179 | if let ScopeTy::Toplevel = ty { 180 | let mut collector = CollectFnCallInfo::default(); 181 | collector.run_visitor(&block); 182 | self.fns_to_inline = collector 183 | .fns_to_inline 184 | .into_iter() 185 | .map(ir::Ref::weak) 186 | .collect(); 187 | } 188 | 189 | enter(self, block) 190 | } 191 | 192 | fn fold(&mut self, stmt: ir::Stmt) -> Self::Output { 193 | match stmt { 194 | ir::Stmt::Expr { 195 | target, 196 | expr: ir::Expr::Function { kind, body }, 197 | } => { 198 | if self.fns_to_inline.contains(&target.weak()) { 199 | self.fn_bodies.insert(target.weak(), body); 200 | Zero 201 | } else { 202 | One(ir::Stmt::Expr { 203 | target, 204 | expr: ir::Expr::Function { kind, body }, 205 | }) 206 | } 207 | } 208 | ir::Stmt::Expr { 209 | target, 210 | expr: 211 | ir::Expr::Call { 212 | kind, 213 | base, 214 | prop: None, 215 | args, 216 | }, 217 | } => { 218 | if let Some(ir::Block(body)) = self.fn_bodies.remove(&base.weak()) { 219 | let undef_ref = ir::Ref::new("_mis"); 220 | let undef = ir::Stmt::Expr { 221 | target: undef_ref.clone(), 222 | expr: ir::Expr::Undefined, 223 | }; 224 | let mut return_ref = None; 225 | let body = body 226 | .into_iter() 227 | .flat_map(|stmt| match stmt { 228 | ir::Stmt::Expr { 229 | target: _, 230 | expr: ir::Expr::CurrentFunction, 231 | } => unreachable!("curfn should have been removed"), 232 | ir::Stmt::Expr { 233 | target, 234 | expr: ir::Expr::Argument { index }, 235 | } => Some(ir::Stmt::Expr { 236 | target, 237 | expr: ir::Expr::Read { 238 | source: args 239 | .get(index) 240 | .map(|(_, arg)| arg.clone()) 241 | .unwrap_or_else(|| undef_ref.clone()), 242 | }, 243 | }), 244 | ir::Stmt::Return { val } => { 245 | assert!(return_ref.is_none(), "should only have 1 return"); 246 | return_ref = Some(val); 247 | None 248 | } 249 | _ => Some(stmt), 250 | }) 251 | .collect::>(); 252 | let ret = ir::Stmt::Expr { 253 | target, 254 | expr: ir::Expr::Read { 255 | source: return_ref.unwrap_or(undef_ref), 256 | }, 257 | }; 258 | Many( 259 | iter::once(undef) 260 | .chain(body) 261 | .chain(iter::once(ret)) 262 | .collect(), 263 | ) 264 | } else { 265 | One(ir::Stmt::Expr { 266 | target, 267 | expr: ir::Expr::Call { 268 | kind, 269 | base, 270 | prop: None, 271 | args, 272 | }, 273 | }) 274 | } 275 | } 276 | _ => One(stmt), 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /src/opt/tests/unroll.rs: -------------------------------------------------------------------------------- 1 | use crate::opt::dce; 2 | use crate::opt::forward; 3 | use crate::opt::mut2ssa; 4 | use crate::opt::unroll; 5 | 6 | macro_rules! passes { 7 | ( $cx:ident ) => { 8 | $cx.run::("mut2ssa") 9 | .run::("forward-reads-redundancy") 10 | .converge::("dce-forwarded-reads") 11 | .run::("unroll-loops") 12 | }; 13 | } 14 | 15 | case!( 16 | basic_zero, 17 | |cx| passes!(cx), 18 | r#" 19 | let something = {}; 20 | for (var x in something) { 21 | log(x); 22 | } 23 | "#, 24 | @r###" 25 | _ini = 26 | x <= _ini 27 | = { } 28 | "###); 29 | 30 | case!( 31 | basic_one, 32 | |cx| passes!(cx), 33 | r#" 34 | let something = { x: 1 }; 35 | for (var x in something) { 36 | log(x); 37 | } 38 | "#, 39 | @r###" 40 | _ini = 41 | x <= _ini 42 | _key = "x" 43 | _val = 1 44 | = { [_key]: _val } 45 | _for = _key 46 | x <- _for 47 | _fun = 48 | _arg = *x 49 | = _fun(_arg) 50 | "###); 51 | 52 | case!( 53 | other_ops_ok, 54 | |cx| passes!(cx), 55 | r#" 56 | let something = { x: 1 }; 57 | g = 1; 58 | y.x = 2; 59 | for (var x in something) { 60 | log(x); 61 | } 62 | "#, 63 | @r###" 64 | _ini = 65 | x <= _ini 66 | _key = "x" 67 | _val = 1 68 | = { [_key]: _val } 69 | _val$1 = 1 70 | <- _val$1 71 | _obj = 72 | _prp = "x" 73 | _val$2 = 2 74 | _obj[_prp] <- _val$2 75 | _for = _key 76 | x <- _for 77 | _fun = 78 | _arg = *x 79 | = _fun(_arg) 80 | "###); 81 | 82 | case!( 83 | bail_mutate, 84 | |cx| passes!(cx), 85 | r#" 86 | let something = {}; 87 | something.x = 1; 88 | for (var x in something) { 89 | log(x); 90 | } 91 | "#, 92 | @r###" 93 | _ini = 94 | x <= _ini 95 | something = { } 96 | _prp = "x" 97 | _val = 1 98 | something[_prp] <- _val 99 | something: 100 | _for = 101 | x <- _for 102 | _fun = 103 | _arg = *x 104 | = _fun(_arg) 105 | "###); 106 | 107 | case!( 108 | bail_escape, 109 | |cx| passes!(cx), 110 | r#" 111 | let something = {}; 112 | g = something; 113 | for (var x in something) { 114 | log(x); 115 | } 116 | "#, 117 | @r###" 118 | _ini = 119 | x <= _ini 120 | something = { } 121 | <- something 122 | something: 123 | _for = 124 | x <- _for 125 | _fun = 126 | _arg = *x 127 | = _fun(_arg) 128 | "###); 129 | 130 | case!( 131 | bail_call, 132 | |cx| passes!(cx), 133 | r#" 134 | let something = {}; 135 | let foo = function() { g = something; }; 136 | foo(); 137 | for (var x in something) { 138 | log(x); 139 | } 140 | "#, 141 | @r###" 142 | _ini = 143 | x <= _ini 144 | something = { } 145 | _ini$1 = : 146 | <- something 147 | _fun = _ini$1 148 | = _fun() 149 | something: 150 | _for = 151 | x <- _for 152 | _fun$1 = 153 | _arg = *x 154 | = _fun$1(_arg) 155 | "###); 156 | 157 | case!( 158 | bail_call_in_nonlinear, 159 | |cx| passes!(cx), 160 | r#" 161 | let something = {}; 162 | let foo = function() { g = something; }; 163 | while (g) 164 | foo(); 165 | for (var x in something) { 166 | log(x); 167 | } 168 | "#, 169 | @r###" 170 | _ini = 171 | x <= _ini 172 | something = { } 173 | _ini$1 = : 174 | <- something 175 | : 176 | _whl = 177 | _whl: 178 | 179 | : 180 | 181 | = _ini$1() 182 | something: 183 | _for = 184 | x <- _for 185 | _fun = 186 | _arg = *x 187 | = _fun(_arg) 188 | "###); 189 | 190 | case!( 191 | bail_call_in_nonlinear_deep, 192 | |cx| passes!(cx), 193 | r#" 194 | let something = {}; 195 | let foo = function() { g = something; }; 196 | while (g) 197 | while (g) 198 | foo(); 199 | for (var x in something) { 200 | log(x); 201 | } 202 | "#, 203 | @r###" 204 | _ini = 205 | x <= _ini 206 | something = { } 207 | _ini$1 = : 208 | <- something 209 | : 210 | _whl = 211 | _whl: 212 | 213 | : 214 | 215 | : 216 | _whl$1 = 217 | _whl$1: 218 | 219 | : 220 | 221 | = _ini$1() 222 | something: 223 | _for = 224 | x <- _for 225 | _fun = 226 | _arg = *x 227 | = _fun(_arg) 228 | "###); 229 | 230 | case!( 231 | bail_call_cross_scope, 232 | |cx| passes!(cx), 233 | r#" 234 | let something = {}; 235 | (function() { 236 | foo(); 237 | for (var x in something) { 238 | log(x); 239 | } 240 | })(); 241 | "#, 242 | @r###" 243 | something = { } 244 | _fun = : 245 | _ini = 246 | x <= _ini 247 | _fun$1 = 248 | = _fun$1() 249 | something: 250 | _for = 251 | x <- _for 252 | _fun$2 = 253 | _arg = *x 254 | = _fun$2(_arg) 255 | = _fun() 256 | "###); 257 | 258 | case!( 259 | call_no_cross_scope, 260 | |cx| passes!(cx), 261 | r#" 262 | let something = {}; 263 | foo(); 264 | for (var x in something) { 265 | log(x); 266 | } 267 | "#, 268 | @r###" 269 | _ini = 270 | x <= _ini 271 | = { } 272 | _fun = 273 | = _fun() 274 | "###); 275 | 276 | case!( 277 | call_no_cross_scope_inner, 278 | |cx| passes!(cx), 279 | r#" 280 | (function() { 281 | let something = {}; 282 | foo(); 283 | for (var x in something) { 284 | log(x); 285 | } 286 | })(); 287 | "#, 288 | @r###" 289 | _fun = : 290 | _ini = 291 | x <= _ini 292 | = { } 293 | _fun$1 = 294 | = _fun$1() 295 | = _fun() 296 | "###); 297 | 298 | case!( 299 | cross_scope_after_call, 300 | |cx| passes!(cx), 301 | r#" 302 | foo(); 303 | let something = {}; 304 | for (var x in something) { 305 | log(x); 306 | } 307 | (function() { 308 | g = something; 309 | })(); 310 | "#, 311 | @r###" 312 | _ini = 313 | x <= _ini 314 | _fun = 315 | = _fun() 316 | something = { } 317 | _fun$1 = : 318 | <- something 319 | = _fun$1() 320 | "###); 321 | 322 | case!( 323 | bail_fn_scope, 324 | |cx| passes!(cx), 325 | r#" 326 | let something = {}; 327 | g = function() { 328 | for (var x in something) { 329 | log(x); 330 | } 331 | }; 332 | "#, 333 | @r###" 334 | something = { } 335 | _val = : 336 | _ini = 337 | x <= _ini 338 | something: 339 | _for = 340 | x <- _for 341 | _fun = 342 | _arg = *x 343 | = _fun(_arg) 344 | <- _val 345 | "###); 346 | 347 | case!( 348 | bail_nonlinear_scope, 349 | |cx| passes!(cx), 350 | r#" 351 | let something = {}; 352 | for (;;) { 353 | for (var x in something) { 354 | log(x); 355 | } 356 | something.x = 1; 357 | } 358 | "#, 359 | @r###" 360 | _ini = 361 | x <= _ini 362 | something = { } 363 | : 364 | something: 365 | _for = 366 | x <- _for 367 | _fun = 368 | _arg = *x 369 | = _fun(_arg) 370 | _prp = "x" 371 | _val = 1 372 | something[_prp] <- _val 373 | "###); 374 | 375 | case!( 376 | bail_across_nonlinear_scope, 377 | |cx| passes!(cx), 378 | r#" 379 | let something = {}; 380 | for (;;) { 381 | something.x = 1; 382 | } 383 | for (var x in something) { 384 | log(x); 385 | } 386 | "#, 387 | @r###" 388 | _ini = 389 | x <= _ini 390 | something = { } 391 | : 392 | _prp = "x" 393 | _val = 1 394 | something[_prp] <- _val 395 | something: 396 | _for = 397 | x <- _for 398 | _fun = 399 | _arg = *x 400 | = _fun(_arg) 401 | "###); 402 | 403 | case!( 404 | bail_deep_nonlinear_scopes, 405 | |cx| passes!(cx), 406 | r#" 407 | let something = {}; 408 | for (;;) { 409 | for (;;) { 410 | something.x = 1; 411 | } 412 | } 413 | for (var x in something) { 414 | log(x); 415 | } 416 | "#, 417 | @r###" 418 | _ini = 419 | x <= _ini 420 | something = { } 421 | : 422 | : 423 | _prp = "x" 424 | _val = 1 425 | something[_prp] <- _val 426 | something: 427 | _for = 428 | x <- _for 429 | _fun = 430 | _arg = *x 431 | = _fun(_arg) 432 | "###); 433 | 434 | case!( 435 | across_safe_nonlinear_scope, 436 | |cx| passes!(cx), 437 | r#" 438 | let something = {}; 439 | for (;;) { 440 | g = log; 441 | } 442 | for (var x in something) { 443 | log(x); 444 | } 445 | "#, 446 | @r###" 447 | _ini = 448 | x <= _ini 449 | = { } 450 | : 451 | _val = 452 | <- _val 453 | "###); 454 | 455 | case!( 456 | into_linear_scope, 457 | |cx| passes!(cx), 458 | r#" 459 | let something = {}; 460 | if (g) { 461 | for (var x in something) { 462 | log(x); 463 | } 464 | } 465 | "#, 466 | @r###" 467 | _ini = 468 | x <= _ini 469 | = { } 470 | _iff = 471 | _iff: 472 | 473 | : 474 | 475 | "###); 476 | 477 | case!( 478 | across_linear_scope, 479 | |cx| passes!(cx), 480 | r#" 481 | let something = {}; 482 | if (g) { 483 | g = log; 484 | } 485 | for (var x in something) { 486 | log(x); 487 | } 488 | "#, 489 | @r###" 490 | _ini = 491 | x <= _ini 492 | = { } 493 | _iff = 494 | _iff: 495 | _val = 496 | <- _val 497 | : 498 | 499 | "###); 500 | 501 | case!( 502 | bail_across_switch_case, 503 | |cx| passes!(cx), 504 | r#" 505 | switch (foo) { 506 | case 1: 507 | let something = {}; 508 | default: 509 | for (var x in something) { 510 | log(x); 511 | } 512 | } 513 | "#, 514 | @r###" 515 | _ini = 516 | x <= _ini 517 | _swi = 518 | _tst = 1 519 | _swi: 520 | _tst: 521 | _ini$1 = { } 522 | something <= _ini$1 523 | : 524 | _rhs = *something 525 | _rhs: 526 | _for = 527 | x <- _for 528 | _fun = 529 | _arg = *x 530 | = _fun(_arg) 531 | "###); 532 | 533 | case!( 534 | bail_on_second_usage, 535 | |cx| passes!(cx), 536 | r#" 537 | let something = {}; 538 | for (let x in something) { 539 | g = x; 540 | } 541 | something.x = 1; 542 | for (let x in something) { 543 | g = x; 544 | } 545 | "#, 546 | @r###" 547 | something = { } 548 | _prp = "x" 549 | _val = 1 550 | something[_prp] <- _val 551 | something: 552 | _val$1 = 553 | <- _val$1 554 | "###); 555 | -------------------------------------------------------------------------------- /src/ir2ast/ssa.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::iter; 3 | use std::mem; 4 | 5 | use swc_ecma_ast as ast; 6 | 7 | use crate::ir; 8 | use crate::ir::traverse::{RunVisitor, ScopeTy, Visitor}; 9 | 10 | pub struct Cache { 11 | can_inline_at_use: HashSet>, 12 | expr_cache: HashMap, ast::Expr>, 13 | to_do_at_declaration: HashMap, ToDo>, 14 | } 15 | 16 | impl Cache { 17 | #[inline(never)] // for better profiling 18 | pub fn prepare_for_inlining(ir: &ir::Block) -> Self { 19 | let mut collector = CollectSingleUseInliningInfo::default(); 20 | collector.run_visitor(&ir); 21 | Self { 22 | can_inline_at_use: collector 23 | .can_inline_at_use 24 | .into_iter() 25 | .map(ir::Ref::weak) 26 | .collect(), 27 | expr_cache: Default::default(), 28 | to_do_at_declaration: Default::default(), 29 | } 30 | } 31 | 32 | pub fn empty() -> Self { 33 | Self { 34 | can_inline_at_use: Default::default(), 35 | expr_cache: Default::default(), 36 | to_do_at_declaration: Default::default(), 37 | } 38 | } 39 | } 40 | 41 | pub enum ToDo { 42 | EmitForSideEffects, 43 | AddToCache, 44 | DropAlreadyCached, 45 | DeclareVar, 46 | } 47 | 48 | impl Cache { 49 | pub fn can_be_inlined_forwards(&self, ssa_ref: &ir::Ref) -> bool { 50 | self.can_inline_at_use.contains(&ssa_ref.weak()) 51 | } 52 | 53 | pub fn cache(&mut self, ssa_ref: &ir::Ref, expr: ast::Expr) { 54 | let old_value = self.expr_cache.insert(ssa_ref.weak(), expr); 55 | assert!(old_value.is_none(), "cached multiple times: {:?}", ssa_ref); 56 | } 57 | 58 | pub fn get_cached(&self, ssa_ref: &ir::Ref) -> Option<&ast::Expr> { 59 | self.expr_cache.get(&ssa_ref.weak()) 60 | } 61 | 62 | pub fn do_at_declaration(&mut self, ssa_ref: &ir::Ref, to_do: ToDo) { 63 | let old_value = self.to_do_at_declaration.insert(ssa_ref.weak(), to_do); 64 | assert!(old_value.is_none(), "multiple todos for ref: {:?}", ssa_ref); 65 | } 66 | 67 | pub fn what_to_do(&self, ssa_ref: &ir::Ref) -> Option<&ToDo> { 68 | self.to_do_at_declaration.get(&ssa_ref.weak()) 69 | } 70 | } 71 | 72 | #[derive(Debug, Default)] 73 | struct CollectSingleUseInliningInfo<'a> { 74 | can_inline_at_use: HashSet<&'a ir::Ref>, 75 | pure_refs: HashSet<&'a ir::Ref>, 76 | read_refs: HashSet<&'a ir::Ref>, 77 | write_refs: HashSet<&'a ir::Ref>, 78 | largest_effect: Effect, 79 | } 80 | 81 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 82 | enum Effect { 83 | Pure, 84 | Read, 85 | Write, 86 | } 87 | 88 | impl Default for Effect { 89 | fn default() -> Self { 90 | Effect::Pure 91 | } 92 | } 93 | 94 | impl<'a> CollectSingleUseInliningInfo<'a> { 95 | fn declare_ref(&mut self, ref_: &'a ir::Ref, eff: Effect) { 96 | let added_to_set = match eff { 97 | Effect::Pure => self.pure_refs.insert(ref_), 98 | Effect::Read => self.read_refs.insert(ref_), 99 | Effect::Write => self.write_refs.insert(ref_), 100 | }; 101 | assert!(added_to_set, "refs can only be declared once"); 102 | } 103 | 104 | fn use_ref(&mut self, own_eff: Effect, ref_: &'a ir::Ref) -> Effect { 105 | self.use_refs(own_eff, iter::once(ref_)) 106 | } 107 | 108 | fn use_refs_<'b>( 109 | &mut self, 110 | own_eff: Effect, 111 | refs: impl IntoIterator>, 112 | ) -> Effect 113 | where 114 | 'a: 'b, 115 | { 116 | self.use_refs(own_eff, refs.into_iter().map(|r| *r)) 117 | } 118 | 119 | fn use_refs( 120 | &mut self, 121 | own_eff: Effect, 122 | refs: impl IntoIterator>, 123 | ) -> Effect { 124 | refs.into_iter() 125 | .filter_map(|ref_| { 126 | if self.pure_refs.remove(&ref_) { 127 | self.can_inline_at_use.insert(ref_); 128 | Some(Effect::Pure) 129 | } else if self.read_refs.remove(&ref_) { 130 | self.can_inline_at_use.insert(ref_); 131 | Some(Effect::Read) 132 | } else if self.write_refs.remove(&ref_) { 133 | self.can_inline_at_use.insert(ref_); 134 | Some(Effect::Write) 135 | } else { 136 | None 137 | } 138 | }) 139 | .chain(iter::once(own_eff)) 140 | .max() 141 | .unwrap_or(Effect::Write) 142 | } 143 | 144 | fn side_effect(&mut self, eff: Effect) { 145 | match &eff { 146 | Effect::Pure => {} 147 | Effect::Read => { 148 | self.write_refs.clear(); 149 | } 150 | Effect::Write => { 151 | self.read_refs.clear(); 152 | self.write_refs.clear(); 153 | } 154 | } 155 | if eff > self.largest_effect { 156 | self.largest_effect = eff; 157 | } 158 | } 159 | } 160 | 161 | impl<'a> Visitor<'a> for CollectSingleUseInliningInfo<'a> { 162 | fn wrap_scope( 163 | &mut self, 164 | ty: &ScopeTy, 165 | block: &'a ir::Block, 166 | enter: impl FnOnce(&mut Self, &'a ir::Block) -> R, 167 | ) -> R { 168 | match ty { 169 | ScopeTy::Function => { 170 | // each function is analyzed separately, but keep the map of results 171 | let mut inner = Self::default(); 172 | mem::swap(&mut inner.can_inline_at_use, &mut self.can_inline_at_use); 173 | let r = enter(&mut inner, block); 174 | mem::swap(&mut inner.can_inline_at_use, &mut self.can_inline_at_use); 175 | r 176 | } 177 | ScopeTy::Normal | ScopeTy::Toplevel => { 178 | // write refs cannot be inlined into normal scopes (because they may not execute) 179 | let mut inner = Self::default(); 180 | mem::swap(&mut inner.can_inline_at_use, &mut self.can_inline_at_use); 181 | mem::swap(&mut inner.pure_refs, &mut self.pure_refs); 182 | mem::swap(&mut inner.read_refs, &mut self.read_refs); 183 | let r = enter(&mut inner, block); 184 | mem::swap(&mut inner.can_inline_at_use, &mut self.can_inline_at_use); 185 | mem::swap(&mut inner.pure_refs, &mut self.pure_refs); 186 | mem::swap(&mut inner.read_refs, &mut self.read_refs); 187 | // apply side effect from inner scope 188 | self.side_effect(inner.largest_effect); 189 | r 190 | } 191 | ScopeTy::Nonlinear => { 192 | // read and write refs cannot be inlined into nonlinear scopes 193 | let mut inner = Self::default(); 194 | mem::swap(&mut inner.can_inline_at_use, &mut self.can_inline_at_use); 195 | mem::swap(&mut inner.pure_refs, &mut self.pure_refs); 196 | let r = enter(&mut inner, block); 197 | mem::swap(&mut inner.can_inline_at_use, &mut self.can_inline_at_use); 198 | mem::swap(&mut inner.pure_refs, &mut self.pure_refs); 199 | // apply side effect from inner scope 200 | self.side_effect(inner.largest_effect); 201 | r 202 | } 203 | } 204 | } 205 | 206 | fn visit(&mut self, stmt: &'a ir::Stmt) { 207 | match stmt { 208 | ir::Stmt::Expr { target, expr } => { 209 | let eff = match expr { 210 | ir::Expr::Bool { .. } 211 | | ir::Expr::Number { .. } 212 | | ir::Expr::String { .. } 213 | | ir::Expr::Null 214 | | ir::Expr::Undefined => Effect::Pure, 215 | ir::Expr::This => Effect::Pure, 216 | ir::Expr::Read { source } => self.use_ref(Effect::Read, source), 217 | ir::Expr::ReadMutable { .. } => Effect::Read, 218 | ir::Expr::ReadGlobal { .. } => Effect::Read, 219 | ir::Expr::ReadMember { obj, prop } => { 220 | self.use_refs_(Effect::Read, &[obj, prop]) 221 | } 222 | ir::Expr::Array { elems } => { 223 | self.use_refs(Effect::Pure, elems.iter().flatten().map(|(_, elem)| elem)) 224 | } 225 | ir::Expr::Object { props } => self.use_refs( 226 | Effect::Pure, 227 | props 228 | .iter() 229 | .flat_map(|(_, key, val)| iter::once(key).chain(iter::once(val))), 230 | ), 231 | ir::Expr::RegExp { .. } => Effect::Read, 232 | ir::Expr::Unary { op: _, val } => self.use_ref(Effect::Pure, val), 233 | ir::Expr::Binary { op: _, left, right } => { 234 | self.use_refs_(Effect::Pure, &[left, right]) 235 | } 236 | ir::Expr::Delete { obj, prop } => self.use_refs_(Effect::Write, &[obj, prop]), 237 | ir::Expr::Yield { kind: _, val } => self.use_ref(Effect::Write, val), 238 | ir::Expr::Await { val } => self.use_ref(Effect::Write, val), 239 | ir::Expr::Call { 240 | kind: _, 241 | base, 242 | prop, 243 | args, 244 | } => self.use_refs( 245 | Effect::Write, 246 | iter::once(base) 247 | .chain(prop) 248 | .chain(args.iter().map(|(_, arg)| arg)), 249 | ), 250 | ir::Expr::Function { .. } => Effect::Pure, 251 | ir::Expr::CurrentFunction | ir::Expr::Argument { .. } => Effect::Read, 252 | }; 253 | 254 | self.side_effect(eff.clone()); 255 | 256 | if let ir::Used::Once = target.used() { 257 | self.declare_ref(&target, eff); 258 | } 259 | } 260 | ir::Stmt::DeclareMutable { target: _, val } => { 261 | let eff = self.use_ref(Effect::Pure, val); 262 | self.side_effect(eff); 263 | } 264 | ir::Stmt::WriteMutable { target: _, val } => { 265 | let eff = self.use_ref(Effect::Write, val); 266 | self.side_effect(eff); 267 | } 268 | ir::Stmt::WriteGlobal { target: _, val } => { 269 | let eff = self.use_ref(Effect::Write, val); 270 | self.side_effect(eff); 271 | } 272 | ir::Stmt::WriteMember { obj, prop, val } => { 273 | let eff = self.use_refs_(Effect::Write, &[obj, prop, val]); 274 | self.side_effect(eff); 275 | } 276 | ir::Stmt::Return { val } | ir::Stmt::Throw { val } => { 277 | let eff = self.use_ref(Effect::Read, val); 278 | self.side_effect(eff); 279 | } 280 | ir::Stmt::Break { .. } | ir::Stmt::Continue { .. } => { 281 | self.side_effect(Effect::Read); 282 | } 283 | ir::Stmt::Debugger => { 284 | self.side_effect(Effect::Write); 285 | } 286 | ir::Stmt::Label { label: _, body: _ } => { 287 | self.side_effect(Effect::Pure); 288 | } 289 | ir::Stmt::Loop { body: _ } => { 290 | self.side_effect(Effect::Pure); 291 | } 292 | ir::Stmt::ForEach { 293 | kind: _, 294 | init, 295 | body: _, 296 | } => { 297 | let eff = self.use_ref(Effect::Pure, init); 298 | self.side_effect(eff); 299 | } 300 | ir::Stmt::IfElse { 301 | cond, 302 | cons: _, 303 | alt: _, 304 | } => { 305 | let eff = self.use_ref(Effect::Pure, cond); 306 | self.side_effect(eff); 307 | } 308 | ir::Stmt::Switch { discr, body: _ } => { 309 | let eff = self.use_ref(Effect::Pure, discr); 310 | self.side_effect(eff); 311 | } 312 | ir::Stmt::SwitchCase { val } => { 313 | let eff = self.use_refs(Effect::Read, val); 314 | self.side_effect(eff); 315 | } 316 | ir::Stmt::Try { 317 | body: _, 318 | catch: _, 319 | finally: _, 320 | } => { 321 | self.side_effect(Effect::Pure); 322 | } 323 | } 324 | } 325 | } 326 | --------------------------------------------------------------------------------