From 25999c92d3d2711b93ec9f8772521c8526129e30 Mon Sep 17 00:00:00 2001 From: Aidan Hobson Sayers Date: Sat, 7 Mar 2015 15:29:59 +0000 Subject: [PATCH] Make DEAD_FUNCTIONS work, add test --- emcc | 8 ++++++++ tests/test_core.py | 32 ++++++++++++++++++++++++++++++++ tools/js-optimizer.js | 18 ++++++++++++++++++ tools/js_optimizer.py | 2 +- tools/optimizer/optimizer.cpp | 24 ++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 1 deletion(-) diff --git a/emcc b/emcc index 586968632..ade759083 100755 --- a/emcc +++ b/emcc @@ -1070,6 +1070,10 @@ try: memory_init_file = True assert closure is not 2, 'EMTERPRETIFY requires valid asm.js, and is incompatible with closure 2 which disables that' + if shared.Settings.DEAD_FUNCTIONS and not js_opts: + js_opts = True + logging.warning('enabling js opts for DEAD_FUNCTIONS') + if proxy_to_worker: shared.Settings.PROXY_TO_WORKER = 1 @@ -1491,6 +1495,10 @@ try: js_optimizer_queue = [] js_optimizer_extra_info = {} + if shared.Settings.DEAD_FUNCTIONS: + js_optimizer_queue += ['eliminateDeadFuncs'] + js_optimizer_extra_info['dead_functions'] = shared.Settings.DEAD_FUNCTIONS + if opt_level >= 1 and js_opts: logging.debug('running js post-opts') diff --git a/tests/test_core.py b/tests/test_core.py index 936b8217d..c4574de95 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -6125,6 +6125,38 @@ def process(filename): self.emcc_args += ['--closure', '1'] self.do_run_from_file(src, output, post_build=post) + def test_dead_functions(self): + src = r''' + #include + extern "C" { + __attribute__((noinline)) int unused(int x) { + return x; + } + } + int main(int argc, char **argv) { + printf("*%d*\n", argc > 1 ? unused(1) : 2); + return 0; + } + ''' + def test(expected, args=[], no_build=False): + self.do_run(src, expected, args=args, no_build=no_build) + return open(self.in_dir('src.cpp.o.js')).read() + + # Sanity check that it works and the dead function is emitted + js = test('*1*', ['x']) + test('*2*', no_build=True) + if self.run_name in ['default', 'asm1', 'asm2g']: assert 'function _unused($' in js + + # Kill off the dead function, and check a code path using it aborts + Settings.DEAD_FUNCTIONS = ['_unused'] + test('*2*') + test('abort(-1) at Error', args=['x'], no_build=True) + + # Kill off a library function, check code aborts + Settings.DEAD_FUNCTIONS = ['_printf'] + test('abort(-1) at Error') + test('abort(-1) at Error', args=['x'], no_build=True) + def test_pgo(self): if Settings.ASM_JS: return self.skip('PGO does not work in asm mode') diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 733eb0d42..bbdd04b0f 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -7916,6 +7916,23 @@ function asmLastOpts(ast) { }); } +// Contrary to the name this does not eliminate actual dead functions, only +// those marked as such with DEAD_FUNCTIONS +function eliminateDeadFuncs(ast) { + assert(asm); + assert(extraInfo && extraInfo.dead_functions); + var deadFunctions = set(extraInfo.dead_functions); + traverseGeneratedFunctions(ast, function (fun, type) { + if (!(fun[1] in deadFunctions)) { + return; + } + var asmData = normalizeAsm(fun); + fun[3] = [['stat', ['call', ['name', 'abort'], [['num', -1]]]]]; + asmData.vars = {}; + denormalizeAsm(fun, asmData); + }); +} + // Passes table var minifyWhitespace = false, printMetadata = true, asm = false, asmPreciseF32 = false, emitJSON = false, last = false; @@ -7936,6 +7953,7 @@ var passes = { loopOptimizer: loopOptimizer, registerize: registerize, registerizeHarder: registerizeHarder, + eliminateDeadFuncs: eliminateDeadFuncs, eliminate: eliminate, eliminateMemSafe: eliminateMemSafe, aggressiveVariableElimination: aggressiveVariableElimination, diff --git a/tools/js_optimizer.py b/tools/js_optimizer.py index 915cfbc32..b6e24572b 100644 --- a/tools/js_optimizer.py +++ b/tools/js_optimizer.py @@ -9,7 +9,7 @@ __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) def path_from_root(*pathelems): return os.path.join(__rootpath__, *pathelems) -NATIVE_PASSES = set(['asm', 'asmPreciseF32', 'receiveJSON', 'emitJSON', 'eliminate', 'eliminateMemSafe', 'simplifyExpressions', 'simplifyIfs', 'optimizeFrounds', 'registerize', 'registerizeHarder', 'minifyNames', 'minifyLocals', 'minifyWhitespace', 'cleanup', 'asmLastOpts', 'last', 'noop', 'closure']) +NATIVE_PASSES = set(['asm', 'asmPreciseF32', 'receiveJSON', 'emitJSON', 'eliminateDeadFuncs', 'eliminate', 'eliminateMemSafe', 'simplifyExpressions', 'simplifyIfs', 'optimizeFrounds', 'registerize', 'registerizeHarder', 'minifyNames', 'minifyLocals', 'minifyWhitespace', 'cleanup', 'asmLastOpts', 'last', 'noop', 'closure']) JS_OPTIMIZER = path_from_root('tools', 'js-optimizer.js') diff --git a/tools/optimizer/optimizer.cpp b/tools/optimizer/optimizer.cpp index 6e63ac9b9..726d4f9f7 100644 --- a/tools/optimizer/optimizer.cpp +++ b/tools/optimizer/optimizer.cpp @@ -3869,6 +3869,29 @@ void asmLastOpts(Ref ast) { }); } +// Contrary to the name this does not eliminate actual dead functions, only +// those marked as such with DEAD_FUNCTIONS +void eliminateDeadFuncs(Ref ast) { + assert(!!extraInfo); + IString DEAD_FUNCTIONS("dead_functions"); + IString ABORT("abort"); + assert(extraInfo->has(DEAD_FUNCTIONS)); + StringSet deadFunctions; + for (size_t i = 0; i < extraInfo[DEAD_FUNCTIONS]->size(); i++) { + deadFunctions.insert(extraInfo[DEAD_FUNCTIONS][i]->getIString()); + } + traverseFunctions(ast, [&](Ref fun) { + if (!deadFunctions.has(fun[1].get()->getIString())) { + return; + } + AsmData asmData(fun); + fun[3]->setSize(1); + fun[3][0] = make1(STAT, make2(CALL, makeName(ABORT), &(makeArray())->push_back(makeNum(-1)))); + asmData.vars.clear(); + asmData.denormalize(); + }); +} + //================== // Main //================== @@ -3925,6 +3948,7 @@ int main(int argc, char **argv) { if (str == "asm") {} // the default for us else if (str == "asmPreciseF32") {} else if (str == "receiveJSON" || str == "emitJSON") {} + else if (str == "eliminateDeadFuncs") eliminateDeadFuncs(doc); else if (str == "eliminate") eliminate(doc); else if (str == "eliminateMemSafe") eliminateMemSafe(doc); else if (str == "simplifyExpressions") simplifyExpressions(doc);