diff --git a/.flake8 b/.flake8 index d30ccf4c608e2..95afe0baacf51 100644 --- a/.flake8 +++ b/.flake8 @@ -10,7 +10,6 @@ exclude = ./tools/emdump.py, ./tools/webidl_binder.py, ./tools/create_dom_pk_codes.py, - ./tools/asm_module.py, ./tools/ffdb.py, ./system/lib/, # system libraries ./cache/, # download/built content diff --git a/tools/asm_module.py b/tools/asm_module.py deleted file mode 100644 index 1993db469c944..0000000000000 --- a/tools/asm_module.py +++ /dev/null @@ -1,330 +0,0 @@ -# Copyright 2013 The Emscripten Authors. All rights reserved. -# Emscripten is available under two separate licenses, the MIT license and the -# University of Illinois/NCSA Open Source License. Both these licenses can be -# found in the LICENSE file. - -from __future__ import print_function -import sys, re, itertools - -from . import shared, js_optimizer, building - - -class AsmModule(): - def __init__(self, filename): - self.filename = filename - self.js = open(filename).read() - - self.start_asm = self.js.find(js_optimizer.start_asm_marker) - self.start_funcs = self.js.find(js_optimizer.start_funcs_marker) - self.end_funcs = self.js.rfind(js_optimizer.end_funcs_marker) - self.end_asm = self.js.rfind(js_optimizer.end_asm_marker) - - # pre and asm - self.pre_js = self.js[:self.start_asm] - self.asm_js = self.js[self.start_asm:self.end_asm] - - # heap initializer - try: - self.staticbump = int(re.search(shared.JS.memory_staticbump_pattern, self.pre_js).group(1)) - except: - self.staticbump = 0 - if self.staticbump: - try: - self.mem_init_js = re.search(shared.JS.memory_initializer_pattern, self.pre_js).group(0) - except: - self.mem_init_js = '' - - # global initializers - global_inits = re.search(shared.JS.global_initializers_pattern, self.pre_js) - if global_inits: - self.global_inits_js = global_inits.group(0) - self.global_inits = [init.split('{')[2][1:].split('(')[0] for init in global_inits.groups(0)[0].split(',')] - else: - self.global_inits_js = '' - self.global_inits = [] - - # imports (and global variables) - first_var = self.js.find('var ', self.js.find('var ', self.start_asm)+4) - self.pre_imports_js = self.js[self.start_asm:first_var] - self.imports_js = self.js[first_var:self.start_funcs] - self.imports = {} - for i in js_optimizer.import_sig.finditer(self.imports_js): - imp = i.group(2) - if ',' not in imp: - key, value = imp.split('=', 1) - self.imports[key.strip()] = value.strip() - else: - for part in imp.split(','): - assert part.count('(') == part.count(')') # we must not break ',' in func(x, y)! - assert part.count('=') == 1 - key, value = part.split('=', 1) - self.imports[key.strip()] = value.strip() - - #print >> sys.stderr, 'imports', self.imports - - # funcs - self.funcs_js = self.js[self.start_funcs:self.end_funcs] - self.funcs = sorted(set([m.group(1) for m in js_optimizer.func_sig.finditer(self.funcs_js)])) - #print 'funcs', self.funcs - - # tables and exports - post_js = self.js[self.end_funcs:self.end_asm] - ret = post_js.find('return ') - self.tables_js = post_js[:ret] - self.exports_js = post_js[ret:] - self.tables = self.parse_tables(self.tables_js) - self.exports = set([export.strip() for export in self.exports_js[self.exports_js.find('{')+1:self.exports_js.find('}')].split(',')]) - - # post - self.post_js = self.js[self.end_asm:] - self.sendings = {} - for sending in [sending.strip() for sending in self.post_js[self.post_js.find('}, { ')+5:self.post_js.find(' }, buffer);')].split(',')]: - colon = sending.find(':') - self.sendings[sending[:colon].replace('"', '')] = sending[colon+1:].strip() - self.module_defs = set(re.findall(r'var [\w\d_$]+ = Module\["[\w\d_$]+"\] = asm\["[\w\d_$]+"\];\n', self.post_js)) - - self.extra_funcs_js = '' - - def set_pre_js(self, staticbump=None, js=None): - if staticbump is None: staticbump = self.staticbump - if js is None: js = self.mem_init_js - self.pre_js = re.sub(shared.JS.memory_staticbump_pattern, 'STATICTOP = STATIC_BASE + %d;\n' % (staticbump,) + js, self.pre_js, count=1) - - def relocate_into(self, main): - # heap initializer - if self.staticbump > 0: - new_mem_init = self.mem_init_js[:self.mem_init_js.rfind(', ')] + ', GLOBAL_BASE+%d)' % main.staticbump - main.set_pre_js(main.staticbump + self.staticbump, new_mem_init) - - # Find function name replacements TODO: do not rename duplicate names with duplicate contents, just merge them - replacements = {} - for func in self.funcs: - rep = func - while rep in main.funcs: - rep += '_' - replacements[func] = rep - #print >> sys.stderr, 'replacements:', replacements - - # sendings: add invokes for new tables - all_sendings = main.sendings - added_sending = False - for table in self.tables: - if table not in main.tables: - sig = table[table.rfind('_')+1:] - func = 'invoke_%s' % sig - all_sendings[func] = func - main.pre_js += 'var %s = %s;\n' % (func, shared.JS.make_invoke(sig, named=False)) - added_sending = True - - # imports - all_imports = main.imports - for key, value in self.imports.items(): - if key in self.funcs or key in main.funcs: continue # external function in one module, implemented in the other - value_concrete = '.' not in value # env.key means it is an import, an external value, and not a concrete one - main_value = main.imports.get(key) - main_value_concrete = main_value and '.' not in main_value - if value_concrete and main_value_concrete: continue # standard global var - if not main_value or value_concrete: - if '+' in value: - # relocate - value = value.replace('(', '').replace(')', '').replace('| 0', '').replace('|0', '').replace(' ', '') - left, right = value.split('+') - assert left == 'H_BASE' - value = str(main.staticbump + int(right)) - all_imports[key] = value - if (value_concrete or main_value_concrete) and key in all_sendings: - del all_sendings[key] # import of external value no longer needed - for key in all_imports.keys(): - if key in self.funcs: - del all_imports[key] # import in main, provided in side - main.imports_js = '\n'.join(['var %s = %s;' % (key, value) for key, value in all_imports.items()]) + '\n' - - # check for undefined references to global variables - def check_import(key, value): - if value.startswith('+') or value.endswith('|0'): # ignore functions - if key not in all_sendings: - print('warning: external variable %s is still not defined after linking' % key, file=sys.stderr) - all_sendings[key] = '0' - for key, value in all_imports.items(): check_import(key, value) - - if added_sending: - sendings_js = ', '.join(['%s: %s' % (key, value) for key, value in all_sendings.items()]) - sendings_start = main.post_js.find('}, { ')+5 - sendings_end = main.post_js.find(' }, buffer);') - main.post_js = main.post_js[:sendings_start] + sendings_js + main.post_js[sendings_end:] - - # tables - f_bases = {} - f_sizes = {} - for table, data in self.tables.items(): - main.tables[table] = self.merge_tables(table, main.tables.get(table), data, replacements, f_bases, f_sizes) - main.combine_tables() - #print >> sys.stderr, 'f bases', f_bases - - # relocate - temp = building.js_optimizer(self.filename, ['asm', 'relocate', 'last'], extra_info={ - 'replacements': replacements, - 'fBases': f_bases, - 'hBase': main.staticbump - }) - #print >> sys.stderr, 'relocated side into', temp - relocated_funcs = AsmModule(temp) - shared.try_delete(temp) - main.extra_funcs_js = relocated_funcs.funcs_js.replace(js_optimizer.start_funcs_marker, '\n') - - # update function table uses - ft_marker = 'FUNCTION_TABLE_' - - def update_fts(what): - updates = [] - i = 1 # avoid seeing marker in recursion - while 1: - i = what.find(ft_marker, i) - if i < 0: break; - start = i - end = what.find('[', start) - table = what[i:end] - if table not in f_sizes: - # table was not modified - i += len(ft_marker) - continue - nesting = 1 - while nesting > 0: - next = what.find(']', end+1) - nesting -= 1 - nesting += what.count('[', end+1, next) - end = next - assert end > 0 - mask = what.rfind('&', start, end) - assert mask > 0 and end - mask <= 13 - fixed = update_fts(what[start:mask+1] + str(f_sizes[table]-1) + ']') - updates.append((start, end, fixed)) - i = end # additional function table uses were done by recursion - # apply updates - if len(updates) == 0: return what - parts = [] - so_far = 0 - for i in range(len(updates)): - start, end, fixed = updates[i] - parts.append(what[so_far:start]) - parts.append(fixed) - so_far = end+1 - parts.append(what[so_far:]) - return ''.join(parts) - - main.funcs_js = update_fts(main.funcs_js) - main.extra_funcs_js = update_fts(main.extra_funcs_js) - - # global initializers - if self.global_inits: - my_global_inits = [replacements[init] if init in replacements else init for init in self.global_inits] - all_global_inits = ['function() { %s() }' % init for init in main.global_inits + my_global_inits] - all_global_inits_js = '/* global initializers */ __ATINIT__.push(' + ','.join(all_global_inits) + ');' - if main.global_inits: - target = main.global_inits_js - else: - target = '// === Body ===\n' - all_global_inits_js = target + all_global_inits_js - main.pre_js = main.pre_js.replace(target, all_global_inits_js) - - # exports - def rep_exp(export): - key, value = export.split(':') - if key in replacements: - repped = replacements[key] - return repped + ': ' + repped - return export - my_exports = list(map(rep_exp, self.exports)) - exports = main.exports.union(my_exports) - main.exports_js = 'return {' + ','.join(list(exports)) + '};\n})\n' - - # post - def rep_def(deff): - key = deff.split(' ')[1] - if key in replacements: - rep = replacements[key] - return 'var %s = Module["%s"] = asm["%s"];\n' % (rep, rep, rep) - return deff - my_module_defs = [rep_def(x) for x in self.module_defs] - new_module_defs = set(my_module_defs).difference(main.module_defs) - if len(new_module_defs): - position = main.post_js.find('Runtime.') # Runtime is the start of the hardcoded ones - main.post_js = main.post_js[:position] + ''.join(list(new_module_defs)) + '\n' + main.post_js[position:] - - def write(self, out): - f = open(out, 'w') - f.write(self.pre_js) - f.write(self.pre_imports_js) - f.write(self.imports_js) - f.write(self.funcs_js) - f.write(self.extra_funcs_js) - f.write(self.tables_js) - f.write(self.exports_js) - f.write(self.post_js) - f.close() - - # Utilities - - def parse_tables(self, js): - tables = {} - parts = js.split(';') - for part in parts: - if '=' not in part: continue - part = part.split('var ')[1] - name, data = part.split('=', 1) - tables[name.strip()] = data.strip() - return tables - - def merge_tables(self, table, main, side, replacements, f_bases, f_sizes): - sig = table.split('_')[-1] - side = side[1:-1].split(',') - side = [s.strip() for s in side] - side = [replacements[f] if f in replacements else f for f in side] - if not main: - f_bases[sig] = 0 - f_sizes[table] = len(side) - return '[' + ','.join(side) + ']' - main = main[1:-1].split(',') - main = [m.strip() for m in main] - # TODO: handle non-aliasing case too - assert len(main) % 2 == 0 - f_bases[sig] = len(main) - ret = main + side - size = 2 - while size < len(ret): size *= 2 - aborter = ret[1] # we can assume odd indexes have an aborting function with the right signature - ret = ret + [aborter]*(size - len(ret)) - assert len(ret) == size - f_sizes[table] = size - return '[' + ','.join(ret) + ']' - - def combine_tables(self): - self.tables_js = '// EMSCRIPTEN_END_FUNCS\n' - for table, data in self.tables.items(): - self.tables_js += 'var %s = %s;\n' % (table, data) - - def get_table_funcs(self): - return set(itertools.chain.from_iterable([[y.strip() for y in x[1:-1].split(',')] for x in self.tables.values()])) - - def get_import_type(self, imp): - def is_int(x): - try: - int(x) - return True - except: - return False - - def is_float(x): - try: - float(x) - return True - except: - return False - - if '|0' in imp or '| 0' in imp or (is_int(imp) and not '.0' in imp or '+' in imp): - return 'i' - elif '.0' in imp or '+' in imp or is_float(imp): - return 'd' - else: - return '?' - diff --git a/tools/building.py b/tools/building.py index 2516a82fa7e5e..4c00a639f6785 100644 --- a/tools/building.py +++ b/tools/building.py @@ -24,7 +24,7 @@ from .toolchain_profiler import ToolchainProfiler from .shared import Settings, CLANG_CC, CLANG_CXX, PYTHON from .shared import LLVM_NM, EMCC, EMAR, EMXX, EMRANLIB, NODE_JS, WASM_LD, LLVM_AR -from .shared import JS, LLVM_OPT, LLVM_LINK, LLVM_DIS, LLVM_AS, LLVM_OBJCOPY +from .shared import LLVM_OPT, LLVM_LINK, LLVM_DIS, LLVM_AS, LLVM_OBJCOPY from .shared import try_delete, run_process, check_call, exit_with_error from .shared import safe_move, configuration, path_from_root, EXPECTED_BINARYEN_VERSION from .shared import asmjs_mangle, DEBUG, WINDOWS, JAVA, CLOSURE_COMPILER, EM_CONFIG @@ -947,64 +947,6 @@ def eval_ctors(js_file, binary_file, binaryen_bin='', debug_info=False): # check_call(cmd) -def calculate_reachable_functions(infile, initial_list, can_reach=True): - with ToolchainProfiler.profile_block('calculate_reachable_functions'): - from . import asm_module - temp = configuration.get_temp_files().get('.js').name - js_optimizer(infile, ['dumpCallGraph'], output_filename=temp, just_concat=True) - asm = asm_module.AsmModule(temp) - lines = asm.funcs_js.split('\n') - can_call = {} - for i in range(len(lines)): - line = lines[i] - if line.startswith('// REACHABLE '): - curr = json.loads(line[len('// REACHABLE '):]) - func = curr[0] - targets = curr[2] - can_call[func] = set(targets) - # function tables too - treat a function all as a function that can call anything in it, which is effectively what it is - for name, funcs in asm.tables.items(): - can_call[name] = set([x.strip() for x in funcs[1:-1].split(',')]) - # print can_call - # Note: We ignore calls in from outside the asm module, so you could do emterpreted => outside => emterpreted, and we would - # miss the first one there. But this is acceptable to do, because we can't save such a stack anyhow, due to the outside! - # print 'can call', can_call, '\n!!!\n', asm.tables, '!' - reachable_from = {} - for func, targets in can_call.items(): - for target in targets: - if target not in reachable_from: - reachable_from[target] = set() - reachable_from[target].add(func) - # print 'reachable from', reachable_from - to_check = initial_list[:] - advised = set() - if can_reach: - # find all functions that can reach the initial list - while len(to_check): - curr = to_check.pop() - if curr in reachable_from: - for reacher in reachable_from[curr]: - if reacher not in advised: - if not JS.is_dyn_call(reacher) and not JS.is_function_table(reacher): - advised.add(str(reacher)) - to_check.append(reacher) - else: - # find all functions that are reachable from the initial list, including it - # all tables are assumed reachable, as they can be called from dyncall from outside - for name, funcs in asm.tables.items(): - to_check.append(name) - while len(to_check): - curr = to_check.pop() - if not JS.is_function_table(curr): - advised.add(curr) - if curr in can_call: - for target in can_call[curr]: - if target not in advised: - advised.add(str(target)) - to_check.append(target) - return {'reachable': list(advised), 'total_funcs': len(can_call)} - - def check_closure_compiler(args, env): if not os.path.exists(CLOSURE_COMPILER[-1]): exit_with_error('google-closure-compiler executable (%s) does not exist, check the paths in %s. To install closure compiler, run "npm install" in emscripten root directory.', CLOSURE_COMPILER[-1], EM_CONFIG) diff --git a/tools/ctor_evaller.py b/tools/ctor_evaller.py index 8d48d33e9d3de..ce83b6e407e3a 100755 --- a/tools/ctor_evaller.py +++ b/tools/ctor_evaller.py @@ -10,17 +10,14 @@ fail to parse a massive project, we operate on the text in python. """ -import json import logging import os import subprocess import sys -import time sys.path.insert(1, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from tools import shared, js_optimizer, building -from tools.tempfiles import try_delete +from tools import shared, js_optimizer js_file = sys.argv[1] @@ -68,243 +65,7 @@ def find_ctors_data(js, num): return ctors_start, ctors_end, all_ctors, ctors -def eval_ctors_js(js, mem_init, num): - - def kill_func(asm, name): - asm = asm.replace('function ' + name + '(', 'function KILLED_' + name + '(', 1) - return asm - - def add_func(asm, func): - before = len(asm) - asm = asm.replace('function ', ' ' + func + '\nfunction ', 1) - assert len(asm) > before - name = func[func.find(' ') + 1:func.find('(')] - asm = asm.replace('return {', 'return { ' + name + ': ' + name + ',') - return asm - - # Find the global ctors - ctors_start, ctors_end, all_ctors, ctors = find_ctors_data(js, num) - logging.debug('trying to eval ctors: ' + ', '.join(ctors)) - # Find the asm module, and receive the mem init. - asm = get_asm(js) - assert len(asm) - asm = asm.replace('use asm', 'not asm') # don't try to validate this - # Substitute sbrk with a failing stub: the dynamic heap memory area shouldn't get increased during static ctor initialization. - asm = asm.replace('function _sbrk(', 'function _sbrk(increment) { throw "no sbrk when evalling ctors!"; } function KILLED_sbrk(', 1) - # find all global vars, and provide only safe ones. Also add dumping for those. - pre_funcs_start = asm.find(';') + 1 - pre_funcs_end = asm.find('function ', pre_funcs_start) - pre_funcs_end = asm.rfind(';', pre_funcs_start, pre_funcs_end) + 1 - pre_funcs = asm[pre_funcs_start:pre_funcs_end] - parts = [x for x in [x.strip() for x in pre_funcs.split(';')] if x.startswith('var ')] - global_vars = [] - new_globals = '\n' - for part in parts: - part = part[4:] # skip 'var ' - bits = [x.strip() for x in part.split(',')] - for bit in bits: - name, value = [x.strip() for x in bit.split('=', 1)] - if value in ['0', '+0', '0.0'] or name in [ - 'STACKTOP', 'STACK_MAX', - 'HEAP8', 'HEAP16', 'HEAP32', - 'HEAPU8', 'HEAPU16', 'HEAPU32', - 'HEAPF32', 'HEAPF64', - 'Int8View', 'Int16View', 'Int32View', 'Uint8View', 'Uint16View', 'Uint32View', 'Float32View', 'Float64View', - 'nan', 'inf', - '_emscripten_memcpy_big', '___dso_handle', - '_atexit', '___cxa_atexit', - ] or name.startswith('Math_'): - if 'new ' not in value: - global_vars.append(name) - new_globals += ' var ' + name + ' = ' + value + ';\n' - asm = asm[:pre_funcs_start] + new_globals + asm[pre_funcs_end:] - asm = add_func(asm, 'function dumpGlobals() { return [ ' + ', '.join(global_vars) + '] }') - # find static bump. this is the maximum area we'll write to during startup. - static_bump_op = 'STATICTOP = STATIC_BASE + ' - static_bump_start = js.find(static_bump_op) - static_bump_end = js.find(';', static_bump_start) - static_bump = int(js[static_bump_start + len(static_bump_op):static_bump_end]) - # Generate a safe sandboxed environment. We replace all ffis with errors. Otherwise, - # asm.js can't call outside, so we are ok. -# if shared.DEBUG: -# temp_file = os.path.join(shared.CANONICAL_TEMP_DIR, 'ctorEval.js') -# shared.safe_ensure_dirs(shared.CANONICAL_TEMP_DIR) -# else: -# temp_file = config.get_temp_files().get('.ctorEval.js').name - with shared.configuration.get_temp_files().get_file('.ctorEval.js') as temp_file: - open(temp_file, 'w').write(''' -var totalMemory = %d; -var totalStack = %d; - -var buffer = new ArrayBuffer(totalMemory); -var heap = new Uint8Array(buffer); -var heapi32 = new Int32Array(buffer); - -var memInit = %s; - -var globalBase = %d; -var staticBump = %d; - -heap.set(memInit, globalBase); - -var staticTop = globalBase + staticBump; -var staticBase = staticTop; - -var stackTop = staticTop; -while (stackTop %% 16 !== 0) stackTop--; -var stackBase = stackTop; -var stackMax = stackTop + totalStack; -if (stackMax >= totalMemory) throw 'not enough room for stack'; - -var dynamicTopPtr = stackMax; -heapi32[dynamicTopPtr >> 2] = stackMax; - -if (!Math.imul) { - Math.imul = Math.imul || function(a, b) { - var ah = (a >>> 16) & 0xffff; - var al = a & 0xffff; - var bh = (b >>> 16) & 0xffff; - var bl = b & 0xffff; - // the shift by 0 fixes the sign on the high part - // the final |0 converts the unsigned value into a signed value - return ((al * bl) + (((ah * bl + al * bh) << 16) >>> 0)|0); - }; -} -if (!Math.fround) { - var froundBuffer = new Float32Array(1); - Math.fround = function(x) { froundBuffer[0] = x; return froundBuffer[0] }; -} - -var atexits = []; // we record and replay atexits - -var globalArg = { - Int8Array: Int8Array, - Int16Array: Int16Array, - Int32Array: Int32Array, - Uint8Array: Uint8Array, - Uint16Array: Uint16Array, - Uint32Array: Uint32Array, - Float32Array: Float32Array, - Float64Array: Float64Array, - NaN: NaN, - Infinity: Infinity, - Math: Math, -}; - -var libraryArg = { - STACKTOP: stackTop, - STACK_MAX: stackMax, - ___dso_handle: 0, // used by atexit, value doesn't matter - _emscripten_memcpy_big: function(dest, src, num) { - heap.set(heap.subarray(src, src+num), dest); - return dest; - }, - _atexit: function(x) { - atexits.push([x, 0]); - return 0; - }, - ___cxa_atexit: function(x, y) { - atexits.push([x, y]); - return 0; - }, -}; - -// Instantiate asm -%s -(globalArg, libraryArg, buffer); - -// Try to run the constructors - -var allCtors = %s; -var numSuccessful = 0; - -for (var i = 0; i < allCtors.length; i++) { - try { - var globalsBefore = asm['dumpGlobals'](); - - asm[allCtors[i]](); - - var globalsAfter = asm['dumpGlobals'](); - if (JSON.stringify(globalsBefore) !== JSON.stringify(globalsAfter)) { - console.warn('globals modified'); - break; - } - if (heapi32[dynamicTopPtr >> 2] !== stackMax) { - console.warn('dynamic allocation was performend'); - break; - } - - // this one was ok. - numSuccessful = i + 1; - - } catch (e) { - console.warn(e.stack); - break; - } -} - -// Write out new mem init. It might be bigger if we added to the zero section, look for zeros -var newSize = globalBase + staticBump; -while (newSize > globalBase && heap[newSize-1] == 0) newSize--; -console.log(JSON.stringify([numSuccessful, Array.prototype.slice.call(heap.subarray(globalBase, newSize)), atexits])); - -''' % (total_memory, total_stack, mem_init, global_base, static_bump, asm, json.dumps(ctors))) - - def read_and_delete(filename): - result = '' - try: - result = open(filename, 'r').read() - finally: - try_delete(filename) - return result - - # Execute the sandboxed code. If an error happened due to calling an ffi, that's fine, - # us exiting with an error tells the caller that we failed. If it times out, give up. - out_file = shared.configuration.get_temp_files().get('.out').name - err_file = shared.configuration.get_temp_files().get('.err').name - out_file_handle = open(out_file, 'w') - err_file_handle = open(err_file, 'w') - proc = subprocess.Popen(shared.NODE_JS + [temp_file], stdout=out_file_handle, stderr=err_file_handle, universal_newlines=True) - try: - shared.timeout_run(proc, timeout=10, full_output=True, check=False) - except Exception as e: - if 'Timed out' not in str(e): - raise - logger.debug('ctors timed out\n') - return (0, 0, 0, 0) - if shared.WINDOWS: - time.sleep(0.5) # On Windows, there is some kind of race condition with Popen output stream related functions, where file handles are still in use a short period after the process has finished. - out_file_handle.close() - err_file_handle.close() - out_result = read_and_delete(out_file) - err_result = read_and_delete(err_file) - if proc.returncode != 0: - # TODO(sbc): This should never happen under normal circumstances. - # switch to exit_with_error once we fix https://github.com/emscripten-core/emscripten/issues/7463 - logger.debug('unexpected error while trying to eval ctors:\n' + out_result + '\n' + err_result) - return (0, 0, 0, 0) - - # out contains the new mem init and other info - num_successful, mem_init_raw, atexits = json.loads(out_result) - mem_init = bytes(bytearray(mem_init_raw)) - total_ctors = len(all_ctors) - if num_successful < total_ctors: - logger.debug('not all ctors could be evalled, something was used that was not safe (and therefore was not defined, and caused an error):\n========\n' + err_result + '========') - # Remove the evalled ctors, add a new one for atexits if needed, and write that out - if len(ctors) == total_ctors and len(atexits) == 0: - new_ctors = '' - else: - elements = [] - if len(atexits): - elements.append('{ func: function() { %s } }' % '; '.join(['_atexit(' + str(x[0]) + ',' + str(x[1]) + ')' for x in atexits])) - for ctor in all_ctors[num:]: - elements.append('{ func: function() { %s() } }' % ctor) - new_ctors = '__ATINIT__.push(' + ', '.join(elements) + ');' - js = js[:ctors_start] + new_ctors + js[ctors_end:] - return (num_successful, js, mem_init, ctors) - - -def eval_ctors_wasm(js, wasm_file, num): +def eval_ctors(js, wasm_file, num): ctors_start, ctors_end, all_ctors, ctors = find_ctors_data(js, num) cmd = [os.path.join(binaryen_bin, 'wasm-ctor-eval'), wasm_file, '-o', wasm_file, '--ctors=' + ','.join(ctors)] cmd += extra_args @@ -350,65 +111,14 @@ def main(): num_ctors = ctors_text.count('function()') logger.debug('ctor_evaller: %d ctors, from |%s|' % (num_ctors, ctors_text)) - if not wasm: - # js path - mem_init_file = binary_file - if os.path.exists(mem_init_file): - mem_init = json.dumps(list(bytearray(open(mem_init_file, 'rb').read()))) - else: - mem_init = [] - - # find how many ctors we can remove, by bisection (if there are hundreds, running them sequentially is silly slow) - - logger.debug('ctor_evaller: trying to eval %d global constructors' % num_ctors) - num_successful, new_js, new_mem_init, removed = eval_ctors_js(js, mem_init, num_ctors) - if num_successful == 0: - logger.debug('ctor_evaller: not successful') - sys.exit(0) - - logger.debug('ctor_evaller: we managed to remove %d ctors' % num_successful) - if num_successful == num_ctors: - js = new_js - mem_init = new_mem_init - else: - logger.debug('ctor_evaller: final execution') - check, js, mem_init, removed = eval_ctors_js(js, mem_init, num_successful) - assert check == num_successful - open(js_file, 'w').write(js) - open(mem_init_file, 'wb').write(mem_init) - - # Dead function elimination can help us - - logger.debug('ctor_evaller: eliminate no longer needed functions after ctor elimination') - # find exports - asm = get_asm(open(js_file).read()) - exports_start = asm.find('return {') - exports_end = asm.find('};', exports_start) - exports_text = asm[asm.find('{', exports_start) + 1:exports_end] - exports = [x.split(':')[1].strip() for x in exports_text.replace(' ', '').split(',')] - for r in removed: - assert r in exports, 'global ctors were exported' - exports = [e for e in exports if e not in removed] - # fix up the exports - js = open(js_file).read() - absolute_exports_start = js.find(exports_text) - js = js[:absolute_exports_start] + ', '.join([e + ': ' + e for e in exports]) + js[absolute_exports_start + len(exports_text):] - open(js_file, 'w').write(js) - # find unreachable methods and remove them - reachable = building.calculate_reachable_functions(js_file, exports, can_reach=False)['reachable'] - for r in removed: - assert r not in reachable, 'removed ctors must NOT be reachable' - building.js_optimizer(js_file, ['removeFuncs'], extra_info={'keep': reachable}, output_filename=js_file) - else: - # wasm path - wasm_file = binary_file - logger.debug('ctor_evaller (wasm): trying to eval %d global constructors' % num_ctors) - num_successful, new_js = eval_ctors_wasm(js, wasm_file, num_ctors) - if num_successful == 0: - logger.debug('ctor_evaller: not successful') - sys.exit(0) - logger.debug('ctor_evaller: we managed to remove %d ctors' % num_successful) - open(js_file, 'w').write(new_js) + wasm_file = binary_file + logger.debug('ctor_evaller (wasm): trying to eval %d global constructors' % num_ctors) + num_successful, new_js = eval_ctors(js, wasm_file, num_ctors) + if num_successful == 0: + logger.debug('ctor_evaller: not successful') + sys.exit(0) + logger.debug('ctor_evaller: we managed to remove %d ctors' % num_successful) + open(js_file, 'w').write(new_js) if __name__ == '__main__':