From 65d2449627c2cfda77c18b5c10dc53c00a0fd85a Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Sun, 26 Nov 2017 19:15:39 -0800 Subject: [PATCH 1/8] run multiple iterations of JSDCE, as more may be removable --- tests/optimizer/JSDCE-output.js | 3 + tests/optimizer/JSDCE.js | 10 ++ tools/js-optimizer.js | 200 +++++++++++++++++--------------- 3 files changed, 118 insertions(+), 95 deletions(-) diff --git a/tests/optimizer/JSDCE-output.js b/tests/optimizer/JSDCE-output.js index efa7528d0975a..b4804b9429bf2 100644 --- a/tests/optimizer/JSDCE-output.js +++ b/tests/optimizer/JSDCE-output.js @@ -21,4 +21,7 @@ print(h(123)); } print(hh(123)); }))(); +function glue() { +} +glue(); diff --git a/tests/optimizer/JSDCE.js b/tests/optimizer/JSDCE.js index d496992bfe052..ca1c133929fee 100644 --- a/tests/optimizer/JSDCE.js +++ b/tests/optimizer/JSDCE.js @@ -49,3 +49,13 @@ print(h(123)); print(hh(123)); })(); +function glue() { + function lookup() { // 2 passes needed for this one + throw 1; + } + function removable() { // first remove this + lookup(); + } +} +glue(); + diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 8f12ea89e36f9..9738bfbeea181 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -7808,111 +7808,121 @@ function eliminateDeadGlobals(ast) { // export e.g. by Module['..'] = theThing; , or use it somewhere, otherwise // it goes away. function JSDCE(ast) { - var scopes = [{}]; // begin with empty toplevel scope - function DUMP() { - printErr('vvvvvvvvvvvvvv'); - for (var i = 0; i < scopes.length; i++) { - printErr(i + ' : ' + JSON.stringify(scopes[i])); + function iteration() { + var removed = false; + var scopes = [{}]; // begin with empty toplevel scope + function DUMP() { + printErr('vvvvvvvvvvvvvv'); + for (var i = 0; i < scopes.length; i++) { + printErr(i + ' : ' + JSON.stringify(scopes[i])); + } + printErr('^^^^^^^^^^^^^^'); + } + function ensureData(scope, name) { + if (Object.prototype.hasOwnProperty.call(scope, name)) return scope[name]; + scope[name] = { + def: 0, + use: 0, + param: 0 // true for function params, which cannot be eliminated + }; + return scope[name]; } - printErr('^^^^^^^^^^^^^^'); - } - function ensureData(scope, name) { - if (Object.prototype.hasOwnProperty.call(scope, name)) return scope[name]; - scope[name] = { - def: 0, - use: 0, - param: 0 // true for function params, which cannot be eliminated - }; - return scope[name]; - } - function cleanUp(ast, names) { + function cleanUp(ast, names) { + traverse(ast, function(node, type) { + if (type === 'defun' && Object.prototype.hasOwnProperty.call(names, node[1])) { + removed = true; + return emptyNode(); + } + if (type === 'defun' || type === 'function') return null; // do not enter other scopes + if (type === 'var') { + node[1] = node[1].filter(function(varItem, j) { + var curr = varItem[0]; + var value = varItem[1]; + var keep = !(curr in names) || (value && hasSideEffects(value)); + if (!keep) removed = true; + return keep; + }); + if (node[1].length === 0) return emptyNode(); + } + }); + return ast; + } + var isVarNameOrObjectKeys = []; + // isVarNameOrObjectKeys is a stack which saves the state the node is defining a variable or in an object literal. + // the second argument `type` passed into the callback function called by traverse() could be a variable name or object key name. + // You cannot distinguish the `type` is a real type or not without isVarNameOrObjectKeys. + // ex.) var name = true; // `type` can be 'name' + // var obj = { defun: true } // `type` can be 'defun' traverse(ast, function(node, type) { - if (type === 'defun' && Object.prototype.hasOwnProperty.call(names, node[1])) return emptyNode(); - if (type === 'defun' || type === 'function') return null; // do not enter other scopes + if (isVarNameOrObjectKeys[isVarNameOrObjectKeys.length - 1]) { // check parent node defines a variable or is an object literal + // `type` is a variable name or an object key name + isVarNameOrObjectKeys.push(false); // doesn't define a variable nor be an object literal + return; + } if (type === 'var') { - node[1] = node[1].filter(function(varItem, j) { - var curr = varItem[0]; - var value = varItem[1]; - return !(curr in names) || (value && hasSideEffects(value)); + node[1].forEach(function(varItem, j) { + var name = varItem[0]; + ensureData(scopes[scopes.length-1], name).def = 1; }); - if (node[1].length === 0) return emptyNode(); + isVarNameOrObjectKeys.push(true); // this `node` defines a varible + return; + } + if (type === 'object') { + isVarNameOrObjectKeys.push(true); // this `node` is an object literal + return; } - }); - return ast; - } - var isVarNameOrObjectKeys = []; - // isVarNameOrObjectKeys is a stack which saves the state the node is defining a variable or in an object literal. - // the second argument `type` passed into the callback function called by traverse() could be a variable name or object key name. - // You cannot distinguish the `type` is a real type or not without isVarNameOrObjectKeys. - // ex.) var name = true; // `type` can be 'name' - // var obj = { defun: true } // `type` can be 'defun' - traverse(ast, function(node, type) { - if (isVarNameOrObjectKeys[isVarNameOrObjectKeys.length - 1]) { // check parent node defines a variable or is an object literal - // `type` is a variable name or an object key name isVarNameOrObjectKeys.push(false); // doesn't define a variable nor be an object literal - return; - } - if (type === 'var') { - node[1].forEach(function(varItem, j) { - var name = varItem[0]; - ensureData(scopes[scopes.length-1], name).def = 1; - }); - isVarNameOrObjectKeys.push(true); // this `node` defines a varible - return; - } - if (type === 'object') { - isVarNameOrObjectKeys.push(true); // this `node` is an object literal - return; - } - isVarNameOrObjectKeys.push(false); // doesn't define a variable nor be an object literal - if (type === 'defun' || type === 'function') { - if (node[1]) ensureData(scopes[scopes.length-1], node[1]).def = 1; - var scope = {}; - node[2].forEach(function(param) { - ensureData(scope, param).def = 1; - scope[param].param = 1; - }); - scopes.push(scope); - return; - } - if (type === 'name') { - ensureData(scopes[scopes.length-1], node[1]).use = 1; - } - }, function(node, type) { - isVarNameOrObjectKeys.pop(); - if (isVarNameOrObjectKeys[isVarNameOrObjectKeys.length - 1]) return; // `type` is a variable name or an object key name - if (type === 'defun' || type === 'function') { - var scope = scopes.pop(); - var names = set(); - for (name in scope) { - var data = scope[name]; - if (data.use && !data.def) { - // this is used from a higher scope, propagate the use down - ensureData(scopes[scopes.length-1], name).use = 1; - continue; - } - if (data.def && !data.use && !data.param) { - // this is eliminateable! - names[name] = 0; + if (type === 'defun' || type === 'function') { + if (node[1]) ensureData(scopes[scopes.length-1], node[1]).def = 1; + var scope = {}; + node[2].forEach(function(param) { + ensureData(scope, param).def = 1; + scope[param].param = 1; + }); + scopes.push(scope); + return; + } + if (type === 'name') { + ensureData(scopes[scopes.length-1], node[1]).use = 1; + } + }, function(node, type) { + isVarNameOrObjectKeys.pop(); + if (isVarNameOrObjectKeys[isVarNameOrObjectKeys.length - 1]) return; // `type` is a variable name or an object key name + if (type === 'defun' || type === 'function') { + var scope = scopes.pop(); + var names = set(); + for (name in scope) { + var data = scope[name]; + if (data.use && !data.def) { + // this is used from a higher scope, propagate the use down + ensureData(scopes[scopes.length-1], name).use = 1; + continue; + } + if (data.def && !data.use && !data.param) { + // this is eliminateable! + names[name] = 0; + } } + cleanUp(node[3], names); + } + }); + // toplevel + var scope = scopes.pop(); + assert(scopes.length === 0); + + var names = set(); + for (var name in scope) { + var data = scope[name]; + if (data.def && !data.use) { + assert(!data.param); // can't be + // this is eliminateable! + names[name] = 0; } - cleanUp(node[3], names); - } - }); - // toplevel - var scope = scopes.pop(); - assert(scopes.length === 0); - - var names = set(); - for (var name in scope) { - var data = scope[name]; - if (data.def && !data.use) { - assert(!data.param); // can't be - // this is eliminateable! - names[name] = 0; } + cleanUp(ast, names); + return removed; } - cleanUp(ast, names); + while (iteration()) { } } function removeFuncs(ast) { From f8451bbe9d83c21ccb84570d899bc187d347d0df Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Sun, 3 Dec 2017 15:23:42 -0800 Subject: [PATCH 2/8] run JSDCE before closure, and add AJSDCE which does multiple iterations --- emcc.py | 18 ++++++++++-------- tests/optimizer/JSDCE-output.js | 3 +++ tests/test_other.py | 2 ++ tools/js-optimizer.js | 10 ++++++++-- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/emcc.py b/emcc.py index 7973465254a65..753f597bd8230 100755 --- a/emcc.py +++ b/emcc.py @@ -2368,20 +2368,22 @@ def do_binaryen(final, target, asm_target, options, memfile, wasm_binary_target, if options.opt_level >= 2: # minify the JS optimizer.do_minify() # calculate how to minify - if optimizer.cleanup_shell or optimizer.minify_whitespace or options.use_closure_compiler: - misc_temp_files.note(final) + if optimizer.cleanup_shell or options.use_closure_compiler: if DEBUG: save_intermediate('preclean', 'js') - if options.use_closure_compiler: - logging.debug('running closure on shell code') - final = shared.Building.closure_compiler(final, pretty=not optimizer.minify_whitespace) - else: - assert optimizer.cleanup_shell + if optimizer.cleanup_shell logging.debug('running cleanup on shell code') - passes = ['noPrintMetadata', 'JSDCE', 'last'] + # in -Os and -Oz, run AJSDCE (aggressive JS DCE, performs multiple iterations) + passes = ['noPrintMetadata', 'JSDCE' if options.shrink_level == 0 else 'AJSDCE', 'last'] if optimizer.minify_whitespace: passes.append('minifyWhitespace') + misc_temp_files.note(final) final = shared.Building.js_optimizer_no_asmjs(final, passes) if DEBUG: save_intermediate('postclean', 'js') + if options.use_closure_compiler: + logging.debug('running closure on shell code') + misc_temp_files.note(final) + final = shared.Building.closure_compiler(final, pretty=not optimizer.minify_whitespace) + if DEBUG: save_intermediate('postclosure', 'js') # replace placeholder strings with correct subresource locations if shared.Settings.SINGLE_FILE: f = open(final, 'r') diff --git a/tests/optimizer/JSDCE-output.js b/tests/optimizer/JSDCE-output.js index b4804b9429bf2..bece7cdd2eb48 100644 --- a/tests/optimizer/JSDCE-output.js +++ b/tests/optimizer/JSDCE-output.js @@ -22,6 +22,9 @@ print(h(123)); print(hh(123)); }))(); function glue() { + function lookup() { + throw 1; + } } glue(); diff --git a/tests/test_other.py b/tests/test_other.py index 665844510ce24..6bf3203d888a6 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -1971,6 +1971,8 @@ def test_js_optimizer(self): ['JSDCE']), (path_from_root('tests', 'optimizer', 'JSDCE-hasOwnProperty.js'), open(path_from_root('tests', 'optimizer', 'JSDCE-hasOwnProperty-output.js')).read(), ['JSDCE']), + (path_from_root('tests', 'optimizer', 'AJSDCE.js'), open(path_from_root('tests', 'optimizer', 'AJSDCE-output.js')).read(), + ['JSDCE']), ]: print(input, passes) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 9738bfbeea181..876e65d8a019c 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -7807,7 +7807,7 @@ function eliminateDeadGlobals(ast) { // Removes obviously-unused code. Similar to closure compiler in its rules - // export e.g. by Module['..'] = theThing; , or use it somewhere, otherwise // it goes away. -function JSDCE(ast) { +function JSDCE(ast, multipleIterations) { function iteration() { var removed = false; var scopes = [{}]; // begin with empty toplevel scope @@ -7922,7 +7922,12 @@ function JSDCE(ast) { cleanUp(ast, names); return removed; } - while (iteration()) { } + while (iteration() && multipleIterations) { } +} + +// Aggressive JSDCE - multiple iterations +function AJSCE(ast) { + JSDCE(ast, /* multipleIterations= */ true); } function removeFuncs(ast) { @@ -7971,6 +7976,7 @@ var passes = { dumpCallGraph: dumpCallGraph, asmLastOpts: asmLastOpts, JSDCE: JSDCE, + AJSDCE: AJSDCE, removeFuncs: removeFuncs, noop: function() {}, From ac783bc904526ee6f57f72612ed7f1f408ec0051 Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Sun, 3 Dec 2017 15:24:58 -0800 Subject: [PATCH 3/8] files --- tests/optimizer/AJSDCE-output.js | 27 ++++++++++++++ tests/optimizer/AJSDCE.js | 61 ++++++++++++++++++++++++++++++++ tests/optimizer/JSDCE-output.js | 2 +- 3 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 tests/optimizer/AJSDCE-output.js create mode 100644 tests/optimizer/AJSDCE.js diff --git a/tests/optimizer/AJSDCE-output.js b/tests/optimizer/AJSDCE-output.js new file mode 100644 index 0000000000000..b4804b9429bf2 --- /dev/null +++ b/tests/optimizer/AJSDCE-output.js @@ -0,0 +1,27 @@ + +var z = fleefl(); +var zz = fleefl(); +function g(a) { + return a + 1; +} +Module["g"] = g; +function h(a) { + return a + 1; +} +print(h(123)); +((function() { + var z = fleefl(); + var zz = fleefl(); + function g(a) { + return a + 1; + } + Module["g"] = g; + function hh(a) { + return a + 1; + } + print(hh(123)); +}))(); +function glue() { +} +glue(); + diff --git a/tests/optimizer/AJSDCE.js b/tests/optimizer/AJSDCE.js new file mode 100644 index 0000000000000..ca1c133929fee --- /dev/null +++ b/tests/optimizer/AJSDCE.js @@ -0,0 +1,61 @@ + +// all unused +var x; +var y = 1; +var z = fleefl(); +var xx, yy = 1, zz = fleefl(); // but zz must remain due to the side effects in the value +function f(x, y, z) { + // shadow the x,y,z + x = y; + y = z; +} + +// exported +function g(a) { + return a+1; +} +Module['g'] = g; + +// used +function h(a) { + var t; // unused + return a+1; +} +print(h(123)); + +// inner workings +(function() { + var x; + var y = 1; + var z = fleefl(); + var xx, yy = 1, zz = fleefl(); + function f(x, y, z) { + // shadow the x,y,z + x = y; + y = z; + } + + // exported + function g(a) { + return a+1; + } + Module['g'] = g; + + // used + function hh(a) { + var t; // unused + return a+1; + } + print(hh(123)); +})(); + +function glue() { + function lookup() { // 2 passes needed for this one + throw 1; + } + function removable() { // first remove this + lookup(); + } +} +glue(); + diff --git a/tests/optimizer/JSDCE-output.js b/tests/optimizer/JSDCE-output.js index bece7cdd2eb48..50194093bf33b 100644 --- a/tests/optimizer/JSDCE-output.js +++ b/tests/optimizer/JSDCE-output.js @@ -23,7 +23,7 @@ print(h(123)); }))(); function glue() { function lookup() { - throw 1; + throw 1; } } glue(); From 2cd85f4ce65c3975672f9bfcf32387e8db589902 Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Sun, 3 Dec 2017 15:25:30 -0800 Subject: [PATCH 4/8] fix --- tools/js-optimizer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 876e65d8a019c..5454c4d0e9fcd 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -7926,7 +7926,7 @@ function JSDCE(ast, multipleIterations) { } // Aggressive JSDCE - multiple iterations -function AJSCE(ast) { +function AJSDCE(ast) { JSDCE(ast, /* multipleIterations= */ true); } From 7d45166af7aaa6afe525b886a0e75a6ebf69a575 Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Sun, 3 Dec 2017 15:28:24 -0800 Subject: [PATCH 5/8] fix --- tests/test_other.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_other.py b/tests/test_other.py index 1ecb153a554ba..0f7fc24d82cc9 100644 --- a/tests/test_other.py +++ b/tests/test_other.py @@ -1974,7 +1974,7 @@ def test_js_optimizer(self): (path_from_root('tests', 'optimizer', 'JSDCE-hasOwnProperty.js'), open(path_from_root('tests', 'optimizer', 'JSDCE-hasOwnProperty-output.js')).read(), ['JSDCE']), (path_from_root('tests', 'optimizer', 'AJSDCE.js'), open(path_from_root('tests', 'optimizer', 'AJSDCE-output.js')).read(), - ['JSDCE']), + ['AJSDCE']), ]: print(input, passes) From 64a22d7aa7e5708da111604051e2370227b14ee2 Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Sun, 3 Dec 2017 15:30:18 -0800 Subject: [PATCH 6/8] logging and fix --- emcc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/emcc.py b/emcc.py index 99a004b764504..0a2b93e4776f4 100755 --- a/emcc.py +++ b/emcc.py @@ -2365,13 +2365,13 @@ def do_binaryen(final, target, asm_target, options, memfile, wasm_binary_target, optimizer.do_minify() # calculate how to minify if optimizer.cleanup_shell or options.use_closure_compiler: if DEBUG: save_intermediate('preclean', 'js') - if optimizer.cleanup_shell - logging.debug('running cleanup on shell code') + if optimizer.cleanup_shell: # in -Os and -Oz, run AJSDCE (aggressive JS DCE, performs multiple iterations) passes = ['noPrintMetadata', 'JSDCE' if options.shrink_level == 0 else 'AJSDCE', 'last'] if optimizer.minify_whitespace: passes.append('minifyWhitespace') misc_temp_files.note(final) + logging.debug('running cleanup on shell code: ' + ' '.join(passes)) final = shared.Building.js_optimizer_no_asmjs(final, passes) if DEBUG: save_intermediate('postclean', 'js') if options.use_closure_compiler: From 0c0da3a94b0008f87e57a3f30bb1e490ed3409d5 Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Sun, 3 Dec 2017 15:55:16 -0800 Subject: [PATCH 7/8] clean up extra closure testing in test_core.py, and use it on -Os as well as -O2 (so it happens on binaryen -Os testing --- tests/test_core.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 9ace433518354..c8486349ab354 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -55,6 +55,13 @@ def is_split_memory(self): def is_wasm(self): return 'BINARYEN' in str(self.emcc_args) or self.is_wasm_backend() + # Use closure in some tests for some additional coverage + def maybe_closure(self): + if '-O2' in self.emcc_args or '-Os' in self.emcc_args: + self.emcc_args += ['--closure', '1'] + return True + return False + def do_run_in_out_file_test(self, *path, **kwargs): test_path = path_from_root(*path) @@ -882,8 +889,7 @@ def test_exceptions(self): Settings.EXCEPTION_DEBUG = 1 Settings.DISABLE_EXCEPTION_CATCHING = 0 - if '-O2' in self.emcc_args: - self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage + self.maybe_closure() src = ''' #include @@ -4089,8 +4095,7 @@ def test_langinfo(self): def test_files(self): self.banned_js_engines = [SPIDERMONKEY_ENGINE] # closure can generate variables called 'gc', which pick up js shell stuff - if '-O2' in self.emcc_args: - self.emcc_args += ['--closure', '1'] # Use closure here, to test we don't break FS stuff + if self.maybe_closure(): # Use closure here, to test we don't break FS stuff self.emcc_args = [x for x in self.emcc_args if x != '-g'] # ensure we test --closure 1 --memory-init-file 1 (-g would disable closure) elif '-O3' in self.emcc_args and not self.is_wasm(): print('closure 2') @@ -5095,8 +5100,7 @@ def test_sse1(self): orig_args = self.emcc_args for mode in [[], ['-s', 'SIMD=1']]: self.emcc_args = orig_args + mode + ['-msse'] - if '-O2' in self.emcc_args: - self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage + self.maybe_closure() self.do_run(open(path_from_root('tests', 'test_sse1.cpp'), 'r').read(), 'Success!') @@ -5118,8 +5122,7 @@ def test_sse1_full(self): orig_args = self.emcc_args for mode in [[], ['-s', 'SIMD=1']]: self.emcc_args = orig_args + mode + ['-I' + path_from_root('tests'), '-msse'] - if '-O2' in self.emcc_args: - self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage + self.maybe_closure() self.do_run(open(path_from_root('tests', 'test_sse1_full.cpp'), 'r').read(), self.ignore_nans(native_result), output_nicerizer=self.ignore_nans) @@ -5141,8 +5144,7 @@ def test_sse2_full(self): orig_args = self.emcc_args for mode in [[], ['-s', 'SIMD=1']]: self.emcc_args = orig_args + mode + ['-I' + path_from_root('tests'), '-msse2'] + args - if '-O2' in self.emcc_args: - self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage + self.maybe_closure() self.do_run(open(path_from_root('tests', 'test_sse2_full.cpp'), 'r').read(), self.ignore_nans(native_result), output_nicerizer=self.ignore_nans) @@ -5451,8 +5453,7 @@ def test_sqlite(self): force_c=True) def test_zlib(self): - if '-O2' in self.emcc_args and 'ASM_JS=0' not in self.emcc_args: # without asm, closure minifies Math.imul badly - self.emcc_args += ['--closure', '1'] # Use closure here for some additional coverage + self.maybe_closure() assert 'asm2g' in test_modes if self.run_name == 'asm2g': From 7bbaca3f1db0561d9be5bf260b87b4f65d81de71 Mon Sep 17 00:00:00 2001 From: "Alon Zakai (kripken)" Date: Sun, 3 Dec 2017 16:27:37 -0800 Subject: [PATCH 8/8] run jsdce even when closure, it helps --- emcc.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/emcc.py b/emcc.py index 0a2b93e4776f4..e4a670ac0a33f 100755 --- a/emcc.py +++ b/emcc.py @@ -2365,14 +2365,13 @@ def do_binaryen(final, target, asm_target, options, memfile, wasm_binary_target, optimizer.do_minify() # calculate how to minify if optimizer.cleanup_shell or options.use_closure_compiler: if DEBUG: save_intermediate('preclean', 'js') - if optimizer.cleanup_shell: - # in -Os and -Oz, run AJSDCE (aggressive JS DCE, performs multiple iterations) - passes = ['noPrintMetadata', 'JSDCE' if options.shrink_level == 0 else 'AJSDCE', 'last'] - if optimizer.minify_whitespace: - passes.append('minifyWhitespace') - misc_temp_files.note(final) - logging.debug('running cleanup on shell code: ' + ' '.join(passes)) - final = shared.Building.js_optimizer_no_asmjs(final, passes) + # in -Os and -Oz, run AJSDCE (aggressive JS DCE, performs multiple iterations) + passes = ['noPrintMetadata', 'JSDCE' if options.shrink_level == 0 else 'AJSDCE', 'last'] + if optimizer.minify_whitespace: + passes.append('minifyWhitespace') + misc_temp_files.note(final) + logging.debug('running cleanup on shell code: ' + ' '.join(passes)) + final = shared.Building.js_optimizer_no_asmjs(final, passes) if DEBUG: save_intermediate('postclean', 'js') if options.use_closure_compiler: logging.debug('running closure on shell code')