diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/js-optimizer.js | 32 | ||||
-rw-r--r-- | tools/js_optimizer.py | 31 | ||||
-rw-r--r-- | tools/shared.py | 40 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-pre-output.js | 10 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-pre.js | 12 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-relocate-output.js | 8 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-relocate.js | 10 |
7 files changed, 107 insertions, 36 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index d04807a7..1de06e4c 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -405,13 +405,16 @@ function removeUnneededLabelSettings(ast) { // Various expression simplifications. Pre run before closure (where we still have metadata), Post run after. var USEFUL_BINARY_OPS = set('<<', '>>', '|', '&', '^'); +var COMPARE_OPS = set('<', '<=', '>', '>=', '==', '===', '!='); function simplifyExpressionsPre(ast) { - // Look for (x&A)<<B>>B and replace it with X&A if possible. - function simplifySignExtends(ast) { + // Simplify common expressions used to perform integer conversion operations + // in cases where no conversion is needed. + function simplifyIntegerConversions(ast) { traverse(ast, function(node, type) { if (type === 'binary' && node[1] === '>>' && node[3][0] === 'num' && node[2][0] === 'binary' && node[2][1] === '<<' && node[2][3][0] === 'num' && node[3][1] === node[2][3][1]) { + // Transform (x&A)<<B>>B to X&A. var innerNode = node[2][2]; var shifts = node[3][1]; if (innerNode[0] === 'binary' && innerNode[1] === '&' && innerNode[3][0] === 'num') { @@ -420,6 +423,15 @@ function simplifyExpressionsPre(ast) { return innerNode; } } + } else if (type === 'binary' && node[1] === '&' && node[3][0] === 'num') { + // Rewrite (X < Y) & 1 to (X < Y)|0. (Subsequent passes will eliminate + // the |0 if possible.) + var input = node[2]; + var amount = node[3][1]; + if (input[0] === 'binary' && (input[1] in COMPARE_OPS) && amount == 1) { + node[1] = '|'; + node[3][1] = 0; + } } }); } @@ -763,7 +775,7 @@ function simplifyExpressionsPre(ast) { } traverseGeneratedFunctions(ast, function(func) { - simplifySignExtends(func); + simplifyIntegerConversions(func); simplifyBitops(func); joinAdditions(func); // simplifyZeroComp(func); TODO: investigate performance @@ -2730,8 +2742,9 @@ function relocate(ast) { assert(asm); // we also assume we are normalized var replacements = extraInfo.replacements; - var fBase = extraInfo.fBase; + var fBases = extraInfo.fBases; var hBase = extraInfo.hBase; + var m; traverse(ast, function(node, type) { switch(type) { @@ -2743,13 +2756,14 @@ function relocate(ast) { case 'binary': { if (node[1] == '+' && node[2][0] == 'name') { var base = null; - if (node[2][1] == 'F_BASE') { - base = fBase; - } else if (node[2][1] == 'H_BASE') { + if (node[2][1] == 'H_BASE') { base = hBase; + } else if (m = /^F_BASE_(\w+)$/.exec(node[2][1])) { + base = fBases[m[1]] || 0; // 0 if the parent has no function table for this, but the child does. so no relocation needed } - if (base) { + if (base !== null) { var other = node[3]; + if (base === 0) return other; if (other[0] == 'num') { other[1] += base; return other; @@ -2887,7 +2901,7 @@ arguments_.slice(1).forEach(function(arg) { passes[arg](ast); }); if (asm && last) { - asmLoopOptimizer(ast); + asmLoopOptimizer(ast); // TODO: move out of last, to make last faster when done later (as in side modules) prepDotZero(ast); } var js = astToSrc(ast, minifyWhitespace), old; diff --git a/tools/js_optimizer.py b/tools/js_optimizer.py index 905ae835..a4e1ca6c 100644 --- a/tools/js_optimizer.py +++ b/tools/js_optimizer.py @@ -20,6 +20,7 @@ WINDOWS = sys.platform.startswith('win') DEBUG = os.environ.get('EMCC_DEBUG') func_sig = re.compile('( *)function ([_\w$]+)\(') +import_sig = re.compile('var ([_\w$]+) *=[^;]+;') class Minifier: ''' @@ -95,6 +96,11 @@ class Minifier: 'globals': self.globs }) +start_funcs_marker = '// EMSCRIPTEN_START_FUNCS\n' +end_funcs_marker = '// EMSCRIPTEN_END_FUNCS\n' +start_asm_marker = '// EMSCRIPTEN_START_ASM\n' +end_asm_marker = '// EMSCRIPTEN_END_ASM\n' + def run_on_chunk(command): filename = command[2] # XXX hackish #print >> sys.stderr, 'running js optimizer command', ' '.join(command), '""""', open(filename).read() @@ -107,7 +113,7 @@ def run_on_chunk(command): if DEBUG and not shared.WINDOWS: print >> sys.stderr, '.' # Skip debug progress indicator on Windows, since it doesn't buffer well with multiple threads printing to console. return filename -def run_on_js(filename, passes, js_engine, jcache, source_map=False): +def run_on_js(filename, passes, js_engine, jcache, source_map=False, extra_info=None): if isinstance(jcache, bool) and jcache: jcache = shared.JCache if jcache: shared.JCache.ensure() @@ -129,17 +135,14 @@ def run_on_js(filename, passes, js_engine, jcache, source_map=False): generated = set(eval(suffix[len(suffix_marker)+1:])) # Find markers - start_funcs_marker = '// EMSCRIPTEN_START_FUNCS\n' - end_funcs_marker = '// EMSCRIPTEN_END_FUNCS\n' start_funcs = js.find(start_funcs_marker) end_funcs = js.rfind(end_funcs_marker) - #assert (start_funcs >= 0) == (end_funcs >= 0) == (not not suffix) + + know_generated = suffix or start_funcs >= 0 minify_globals = 'registerizeAndMinify' in passes and 'asm' in passes if minify_globals: passes = map(lambda p: p if p != 'registerizeAndMinify' else 'registerize', passes) - start_asm_marker = '// EMSCRIPTEN_START_ASM\n' - end_asm_marker = '// EMSCRIPTEN_END_ASM\n' start_asm = js.find(start_asm_marker) end_asm = js.rfind(end_asm_marker) assert (start_asm >= 0) == (end_asm >= 0) @@ -148,14 +151,14 @@ def run_on_js(filename, passes, js_engine, jcache, source_map=False): if closure: passes = filter(lambda p: p != 'closure', passes) # we will do it manually - if not suffix and jcache: + if not know_generated and jcache: # JCache cannot be used without metadata, since it might reorder stuff, and that's dangerous since only generated can be reordered # This means jcache does not work after closure compiler runs, for example. But you won't get much benefit from jcache with closure # anyhow (since closure is likely the longest part of the build). if DEBUG: print >>sys.stderr, 'js optimizer: no metadata, so disabling jcache' jcache = False - if suffix: + if know_generated: if not minify_globals: pre = js[:start_funcs + len(start_funcs_marker)] post = js[end_funcs + len(end_funcs_marker):] @@ -189,7 +192,7 @@ EMSCRIPTEN_FUNCS(); minify_info = minifier.serialize() #if DEBUG: print >> sys.stderr, 'minify info:', minify_info # remove suffix if no longer needed - if 'last' in passes: + if suffix and 'last' in passes: suffix_start = post.find(suffix_marker) suffix_end = post.find('\n', suffix_start) post = post[:suffix_start] + post[suffix_end:] @@ -209,7 +212,7 @@ EMSCRIPTEN_FUNCS(); if m: ident = m.group(2) else: - if suffix: continue # ignore whitespace + if know_generated: continue # ignore whitespace ident = 'anon_%d' % i assert ident funcs.append((ident, func)) @@ -249,8 +252,12 @@ EMSCRIPTEN_FUNCS(); f.write(chunk) f.write(suffix_marker) if minify_globals: + assert not extra_info f.write('\n') f.write('// EXTRA_INFO:' + minify_info) + elif extra_info: + f.write('\n') + f.write('// EXTRA_INFO:' + json.dumps(extra_info)) f.close() return temp_file filenames = [write_chunk(chunks[i], i) for i in range(len(chunks))] @@ -329,6 +336,6 @@ EMSCRIPTEN_FUNCS(); return filename -def run(filename, passes, js_engine, jcache, source_map=False): - return temp_files.run_and_clean(lambda: run_on_js(filename, passes, js_engine, jcache, source_map)) +def run(filename, passes, js_engine, jcache, source_map=False, extra_info=None): + return temp_files.run_and_clean(lambda: run_on_js(filename, passes, js_engine, jcache, source_map, extra_info)) diff --git a/tools/shared.py b/tools/shared.py index 776001cd..d35924c6 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -205,7 +205,7 @@ else: config_file = '\n'.join(config_file) # autodetect some default paths config_file = config_file.replace('{{{ EMSCRIPTEN_ROOT }}}', __rootpath__) - llvm_root = find_executable('llvm-dis') or '/usr/bin' + llvm_root = os.path.dirname(find_executable('llvm-dis') or '/usr/bin/llvm-dis') config_file = config_file.replace('{{{ LLVM_ROOT }}}', llvm_root) node = find_executable('node') or find_executable('nodejs') or 'node' config_file = config_file.replace('{{{ NODE }}}', node) @@ -400,6 +400,7 @@ EMAR = path_from_root('emar') EMRANLIB = path_from_root('emranlib') EMLIBTOOL = path_from_root('emlibtool') EMCONFIG = path_from_root('em-config') +EMLINK = path_from_root('emlink.py') EMMAKEN = path_from_root('tools', 'emmaken.py') AUTODEBUGGER = path_from_root('tools', 'autodebugger.py') BINDINGS_GENERATOR = path_from_root('tools', 'bindings_generator.py') @@ -866,7 +867,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e return generated_libs @staticmethod - def link(files, target): + def link(files, target, force_archive_contents=False): actual_files = [] unresolved_symbols = set(['main']) # tracking unresolveds is necessary for .a linking, see below. (and main is always a necessary symbol) resolved_symbols = set() @@ -917,7 +918,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e # Link in the .o if it provides symbols, *or* this is a singleton archive (which is apparently an exception in gcc ld) #print >> sys.stderr, 'need', content, '?', unresolved_symbols, 'and we can supply', new_symbols.defs #print >> sys.stderr, content, 'DEF', new_symbols.defs, '\n' - if new_symbols.defs.intersection(unresolved_symbols) or len(files) == 1: + if new_symbols.defs.intersection(unresolved_symbols) or len(files) == 1 or force_archive_contents: if Building.is_bitcode(content): #print >> sys.stderr, ' adding object', content, '\n' resolved_symbols = resolved_symbols.union(new_symbols.defs) @@ -1100,6 +1101,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e @staticmethod def get_safe_internalize(): + if not Building.can_build_standalone(): return [] # do not internalize anything exps = expand_response(Settings.EXPORTED_FUNCTIONS) if '_malloc' not in exps: exps.append('_malloc') # needed internally, even if user did not add to EXPORTED_FUNCTIONS exports = ','.join(map(lambda exp: exp[1:], exps)) @@ -1209,8 +1211,8 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e return opts @staticmethod - def js_optimizer(filename, passes, jcache, debug): - return js_optimizer.run(filename, passes, listify(NODE_JS), jcache, debug) + def js_optimizer(filename, passes, jcache=False, debug=False, extra_info=None): + return js_optimizer.run(filename, passes, listify(NODE_JS), jcache, debug, extra_info) @staticmethod def closure_compiler(filename, pretty=True): @@ -1350,9 +1352,35 @@ JCache = cache.JCache(Cache) chunkify = cache.chunkify class JS: + memory_initializer_pattern = '/\* memory initializer \*/ allocate\(([\d,\.concat\(\)\[\]\\n ]+)"i8", ALLOC_NONE, ([\dRuntime\.GLOBAL_BASE+]+)\)' + no_memory_initializer_pattern = '/\* no memory initializer \*/' + + memory_staticbump_pattern = 'STATICTOP = STATIC_BASE \+ (\d+);' + + global_initializers_pattern = '/\* global initializers \*/ __ATINIT__.push\((.+)\);' + @staticmethod def to_nice_ident(ident): # limited version of the JS function toNiceIdent - return ident.replace('%', '$').replace('@', '_'); + return ident.replace('%', '$').replace('@', '_') + + @staticmethod + def make_invoke(sig, named=True): + args = ','.join(['a' + str(i) for i in range(1, len(sig))]) + args = 'index' + (',' if args else '') + args + # C++ exceptions are numbers, and longjmp is a string 'longjmp' + return '''function%s(%s) { + try { + %sModule["dynCall_%s"](%s); + } catch(e) { + if (typeof e !== 'number' && e !== 'longjmp') throw e; + asm["setThrew"](1, 0); + } +}''' % ((' invoke_' + sig) if named else '', args, 'return ' if sig[0] != 'v' else '', sig, args) + + @staticmethod + def align(x, by): + while x % by != 0: x += 1 + return x # Compression of code and data for smaller downloads class Compression: diff --git a/tools/test-js-optimizer-asm-pre-output.js b/tools/test-js-optimizer-asm-pre-output.js index 301a2ec8..59a42010 100644 --- a/tools/test-js-optimizer-asm-pre-output.js +++ b/tools/test-js-optimizer-asm-pre-output.js @@ -119,6 +119,16 @@ function sign_extension_simplification() { print(5); } } +function compare_result_simplification() { + HEAP32[$4] = HEAP32[$5] < HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] > HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] <= HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] <= HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] == HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] === HEAP32[$6]; + HEAP32[$4] = HEAP32[$5] != HEAP32[$6]; + var x = HEAP32[$5] != HEAP32[$6] | 0; +} function tempDoublePtr($45, $14, $28, $42) { $45 = $45 | 0; $14 = $14 | 0; diff --git a/tools/test-js-optimizer-asm-pre.js b/tools/test-js-optimizer-asm-pre.js index c7c92124..7ca941fa 100644 --- a/tools/test-js-optimizer-asm-pre.js +++ b/tools/test-js-optimizer-asm-pre.js @@ -122,6 +122,18 @@ function sign_extension_simplification() { print(5); } } +function compare_result_simplification() { + // Eliminate these '&1's. + HEAP32[$4] = (HEAP32[$5] < HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] > HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] <= HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] <= HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] == HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] === HEAP32[$6]) & 1; + HEAP32[$4] = (HEAP32[$5] != HEAP32[$6]) & 1; + // Convert the &1 to |0 here, since we don't get an implicit coersion. + var x = (HEAP32[$5] != HEAP32[$6]) & 1; +} function tempDoublePtr($45, $14, $28, $42) { $45 = $45 | 0; $14 = $14 | 0; diff --git a/tools/test-js-optimizer-asm-relocate-output.js b/tools/test-js-optimizer-asm-relocate-output.js index 6a197e81..2f8294c5 100644 --- a/tools/test-js-optimizer-asm-relocate-output.js +++ b/tools/test-js-optimizer-asm-relocate-output.js @@ -1,9 +1,9 @@ function leaveMeAlone(c) {} function fixed(a, b) {} function a(x, y) { - fixed(34, 12); - fixed(34 | 0, 12 | 0); - leaveMeAlone(10 + x, 33 + y); - leaveMeAlone(10 + x | 0, 33 + y | 0); + fixed(34, 4); + fixed(34 | 0, 102 | 0); + leaveMeAlone(2 + x, 33 + y); + leaveMeAlone(x | 0, 33 + y | 0); } diff --git a/tools/test-js-optimizer-asm-relocate.js b/tools/test-js-optimizer-asm-relocate.js index a45bc2f0..5402c9f6 100644 --- a/tools/test-js-optimizer-asm-relocate.js +++ b/tools/test-js-optimizer-asm-relocate.js @@ -3,10 +3,10 @@ function leaveMeAlone(c) { function replaceMe(a, b) { } function a(x, y) { - replaceMe(H_BASE + 1, F_BASE + 2); - replaceMe(H_BASE + 1 | 0, F_BASE + 2 | 0); - leaveMeAlone(F_BASE + x, H_BASE + y); - leaveMeAlone(F_BASE + x | 0, H_BASE + y | 0); + replaceMe(H_BASE + 1, F_BASE_vii + 2); + replaceMe(H_BASE + 1 | 0, F_BASE_vi + 2 | 0); + leaveMeAlone(F_BASE_vii + x, H_BASE + y); + leaveMeAlone(F_BASE_vUNKNOWN + x | 0, H_BASE + y | 0); } // EMSCRIPTEN_GENERATED_FUNCTIONS -// EXTRA_INFO: { "replacements": { "replaceMe": "fixed" }, "hBase": 33, "fBase": 10 } +// EXTRA_INFO: { "replacements": { "replaceMe": "fixed" }, "hBase": 33, "fBases": { "vii": 2, "vi": 100, "v": 20 } } |