diff options
Diffstat (limited to 'tools/shared.py')
-rw-r--r-- | tools/shared.py | 139 |
1 files changed, 136 insertions, 3 deletions
diff --git a/tools/shared.py b/tools/shared.py index 8f630ad6..6367b41a 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -1,4 +1,4 @@ -import shutil, time, os +import shutil, time, os, json from subprocess import Popen, PIPE, STDOUT __rootpath__ = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) @@ -10,7 +10,7 @@ if not os.path.exists(CONFIG_FILE): shutil.copy(path_from_root('settings.py'), CONFIG_FILE) exec(open(CONFIG_FILE, 'r').read()) -# Tools +# Tools/paths CLANG=os.path.expanduser(os.path.join(LLVM_ROOT, 'clang++')) LLVM_LINK=os.path.join(LLVM_ROOT, 'llvm-link') @@ -21,9 +21,17 @@ LLVM_DIS=os.path.expanduser(os.path.join(LLVM_ROOT, 'llvm-dis')) LLVM_DIS_OPTS = ['-show-annotations'] # For LLVM 2.8+. For 2.7, you may need to do just [] LLVM_INTERPRETER=os.path.expanduser(os.path.join(LLVM_ROOT, 'lli')) LLVM_COMPILER=os.path.expanduser(os.path.join(LLVM_ROOT, 'llc')) - +COFFEESCRIPT = path_from_root('tools', 'eliminator', 'node_modules', 'coffee-script', 'bin', 'coffee') + +EMSCRIPTEN = path_from_root('emscripten.py') +DEMANGLER = path_from_root('third_party', 'demangler.py') +NAMESPACER = path_from_root('tools', 'namespacer.py') +EMMAKEN = path_from_root('tools', 'emmaken.py') +AUTODEBUGGER = path_from_root('tools', 'autodebugger.py') +DFE = path_from_root('tools', 'dead_function_eliminator.py') BINDINGS_GENERATOR = path_from_root('tools', 'bindings_generator.py') EXEC_LLVM = path_from_root('tools', 'exec_llvm.py') +VARIABLE_ELIMINATOR = path_from_root('tools', 'eliminator', 'eliminator.coffee') # Additional compiler options @@ -41,6 +49,8 @@ if USE_EMSDK: '-I' + path_from_root('system', 'include', 'gfx'), '-I' + path_from_root('system', 'include', 'net'), '-I' + path_from_root('system', 'include', 'SDL'), +] + [ + '-U__APPLE__' ] # Engine tweaks @@ -196,3 +206,126 @@ def read_auto_optimize_data(filename): 'overflows_lines': overflows_lines } +# Settings + +class Dummy: pass + +Settings = Dummy() # A global singleton. Not pretty, but nicer than passing |, settings| everywhere + +# Building + +class Building: + COMPILER = CLANG + LLVM_OPTS = False + COMPILER_TEST_OPTS = [] + + @staticmethod + def build_library(name, build_dir, output_dir, generated_libs, configure=['./configure'], configure_args=[], make=['make'], make_args=['-j', '2'], cache=None, cache_name=None, copy_project=False): + ''' Build a library into a .bc file. We build the .bc file once and cache it for all our tests. (We cache in + memory since the test directory is destroyed and recreated for each test. Note that we cache separately + for different compilers) ''' + + if type(generated_libs) is not list: generated_libs = [generated_libs] + + temp_dir = build_dir + if copy_project: + project_dir = os.path.join(temp_dir, name) + shutil.copytree(path_from_root('tests', name), project_dir) # Useful in debugging sometimes to comment this out + else: + project_dir = build_dir + try: + old_dir = os.getcwd() + except: + old_dir = None + os.chdir(project_dir) + env = os.environ.copy() + env['RANLIB'] = env['AR'] = env['CXX'] = env['CC'] = env['LIBTOOL'] = EMMAKEN + env['EMMAKEN_COMPILER'] = Building.COMPILER + env['EMSCRIPTEN_TOOLS'] = path_from_root('tools') + env['CFLAGS'] = env['EMMAKEN_CFLAGS'] = ' '.join(COMPILER_OPTS + Building.COMPILER_TEST_OPTS) # Normal CFLAGS is ignored by some configure's. + if configure: # Useful in debugging sometimes to comment this out (and the lines below up to and including the |make| call) + env['EMMAKEN_JUST_CONFIGURE'] = '1' + Popen(configure + configure_args, stdout=open(os.path.join(output_dir, 'configure'), 'w'), + stderr=open(os.path.join(output_dir, 'configure_err'), 'w'), env=env).communicate()[0] + del env['EMMAKEN_JUST_CONFIGURE'] + Popen(make + make_args, stdout=open(os.path.join(output_dir, 'make'), 'w'), + stderr=open(os.path.join(output_dir, 'make_err'), 'w'), env=env).communicate()[0] + bc_file = os.path.join(project_dir, 'bc.bc') + Building.link(map(lambda lib: os.path.join(project_dir, lib), generated_libs), bc_file) + if cache is not None: + cache[cache_name] = open(bc_file, 'rb').read() + if old_dir: + os.chdir(old_dir) + return bc_file + + @staticmethod + def link(files, target): + output = Popen([LLVM_LINK] + files + ['-o', target], stdout=PIPE, stderr=STDOUT).communicate()[0] + assert not os.path.exists(target) or output is None or 'Could not open input file' not in output, 'Linking error: ' + output + + # Emscripten optimizations that we run on the .ll file + @staticmethod + def ll_opts(filename): + # Remove target info. This helps LLVM opts, if we run them later + cleaned = filter(lambda line: not line.startswith('target datalayout = ') and not line.startswith('target triple = '), + open(filename + '.o.ll', 'r').readlines()) + os.unlink(filename + '.o.ll') + open(filename + '.o.ll.orig', 'w').write(''.join(cleaned)) + + output = Popen(['python', DFE, filename + '.o.ll.orig', filename + '.o.ll'], stdout=PIPE, stderr=STDOUT).communicate()[0] + assert os.path.exists(filename + '.o.ll'), 'Failed to run ll optimizations' + + # Optional LLVM optimizations + @staticmethod + def llvm_opts(filename): + if Building.LLVM_OPTS: + shutil.move(filename + '.o', filename + '.o.pre') + output = Popen([LLVM_OPT, filename + '.o.pre'] + LLVM_OPT_OPTS + ['-o=' + filename + '.o'], stdout=PIPE, stderr=STDOUT).communicate()[0] + assert os.path.exists(filename + '.o'), 'Failed to run llvm optimizations: ' + output + + @staticmethod + def llvm_dis(filename): + # LLVM binary ==> LLVM assembly + try: + os.remove(filename + '.o.ll') + except: + pass + output = Popen([LLVM_DIS, filename + '.o'] + LLVM_DIS_OPTS + ['-o=' + filename + '.o.ll'], stdout=PIPE, stderr=STDOUT).communicate()[0] + assert os.path.exists(filename + '.o.ll'), 'Could not create .ll file: ' + output + + @staticmethod + def llvm_as(source, target): + # LLVM assembly ==> LLVM binary + try: + os.remove(target) + except: + pass + output = Popen([LLVM_AS, source, '-o=' + target], stdout=PIPE, stderr=STDOUT).communicate()[0] + assert os.path.exists(target), 'Could not create bc file: ' + output + + @staticmethod + def emscripten(filename, output_processor=None, append_ext=True, extra_args=[]): + # Add some headers by default. TODO: remove manually adding these in each test + if '-H' not in extra_args: + extra_args += ['-H', 'libc/fcntl.h,libc/sys/unistd.h,poll.h,libc/math.h,libc/langinfo.h,libc/time.h'] + + # Run Emscripten + exported_settings = {} + for setting in ['QUANTUM_SIZE', 'RELOOP', 'OPTIMIZE', 'ASSERTIONS', 'USE_TYPED_ARRAYS', 'SAFE_HEAP', 'CHECK_OVERFLOWS', 'CORRECT_OVERFLOWS', 'CORRECT_SIGNS', 'CHECK_SIGNS', 'CORRECT_OVERFLOWS_LINES', 'CORRECT_SIGNS_LINES', 'CORRECT_ROUNDINGS', 'CORRECT_ROUNDINGS_LINES', 'INVOKE_RUN', 'SAFE_HEAP_LINES', 'INIT_STACK', 'AUTO_OPTIMIZE', 'EXPORTED_FUNCTIONS', 'EXPORTED_GLOBALS', 'BUILD_AS_SHARED_LIB', 'INCLUDE_FULL_LIBRARY', 'RUNTIME_TYPE_INFO', 'DISABLE_EXCEPTION_CATCHING', 'TOTAL_MEMORY', 'FAST_MEMORY', 'EXCEPTION_DEBUG', 'PROFILE']: + try: + value = eval('Settings.' + setting) + if value is not None: + exported_settings[setting] = value + except: + pass + settings = ['-s %s=%s' % (k, json.dumps(v)) for k, v in exported_settings.items()] + compiler_output = timeout_run(Popen(['python', EMSCRIPTEN, filename + ('.o.ll' if append_ext else ''), '-o', filename + '.o.js'] + settings + extra_args, stdout=PIPE, stderr=STDOUT), TIMEOUT, 'Compiling') + #print compiler_output + + # Detect compilation crashes and errors + if compiler_output is not None and 'Traceback' in compiler_output and 'in test_' in compiler_output: print compiler_output; assert 0 + assert os.path.exists(filename + '.o.js') and len(open(filename + '.o.js', 'r').read()) > 0, 'Emscripten failed to generate .js: ' + str(compiler_output) + + if output_processor is not None: + output_processor(open(filename + '.o.js').read()) + |