diff options
-rwxr-xr-x | emar | 16 | ||||
-rwxr-xr-x | emcc | 196 | ||||
-rwxr-xr-x | emconfigure | 11 | ||||
-rwxr-xr-x | emld | 28 | ||||
-rwxr-xr-x | emlibtool | 11 | ||||
-rw-r--r-- | src/library.js | 4 | ||||
-rw-r--r-- | src/library_sdl.js | 4 | ||||
-rw-r--r-- | tests/runner.py | 67 | ||||
-rwxr-xr-x | tools/emconfiguren.py | 2 | ||||
-rwxr-xr-x | tools/emmaken.py | 2 | ||||
-rw-r--r-- | tools/js-optimizer.js | 74 | ||||
-rw-r--r-- | tools/shared.py | 16 | ||||
-rw-r--r-- | tools/test-js-optimizer-output.js | 9 | ||||
-rw-r--r-- | tools/test-js-optimizer.js | 7 |
14 files changed, 328 insertions, 119 deletions
@@ -1,13 +1,21 @@ #!/usr/bin/env python ''' -emcc - ar helper script +emar - ar helper script ======================= This script acts as a frontend replacement for ar. See emcc. ''' -if set(sys.argv[1]).issubset(set('-cruqs')): # ar - sys.argv = sys.argv[:1] + sys.argv[3:] + ['-o='+sys.argv[2]] - assert use_linker, 'Linker should be used in this case' +import os, sys +from tools import shared + +DEBUG = os.environ.get('EMCC_DEBUG') + +newargs = [shared.EMLD] + sys.argv[3:] + ['-o='+sys.argv[2]] + +if DEBUG: + print >> sys.stderr, 'emar:', sys.argv, ' ==> ', newargs + +os.execvp(shared.EMLD, newargs) @@ -73,16 +73,15 @@ emcc can be influenced by a few environment variables: EMMAKEN_COMPILER - The compiler to be used, if you don't want the default clang. ''' -import os, sys, shutil +import os, sys, shutil, tempfile from subprocess import Popen, PIPE, STDOUT from tools import shared DEBUG = os.environ.get('EMCC_DEBUG') -SAVE_FILES = os.environ.get('EMCC_SAVE_FILES') # saves some of the intermediate files +TEMP_DIR = os.environ.get('EMCC_TEMP_DIR') ################### XXX print >> sys.stderr, '\n***This is a WORK IN PROGRESS***' -print >> sys.stderr, '***[%s]***\n' % str(sys.argv) ################### XXX if DEBUG: print >> sys.stderr, 'emcc: ', ' '.join(sys.argv) @@ -113,7 +112,11 @@ Options that are modified or new in %s include: -O0 No optimizations (default) -O1 Simple optimizations, including safe LLVM optimizations, and no runtime assertions - -O2 As -O1, plus code flow optimization (relooper) + or C++ exception catching (to re-enable + C++ exception catching, use + -s DISABLE_EXCEPTION_CATCHING=0 ) + -O2 As -O1, plus the relooper (loop recreation), + plus closure compiler Warning: Compiling with this takes a long time! -O3 As -O2, plus dangerous optimizations that may break the generated code! If that happens, try @@ -124,11 +127,14 @@ Options that are modified or new in %s include: --typed-arrays <mode> 0: No typed arrays 1: Parallel typed arrays 2: Shared (C-like) typed arrays (default) - --llvm-opts <level> 0: No LLVM optimizations (default) + --llvm-opts <level> 0: No LLVM optimizations (default in -O0) 1: Safe/portable LLVM optimizations + (default in -O1 and above) 2: Full, unsafe/unportable LLVM optimizations; this will almost certainly break the generated code! + --closure <on> 0: No closure compiler (default in -O0, -O1) + 1: Run closure compiler (default in -O2, -O3) The target file, if specified (-o <target>), defines what will be generated: @@ -150,7 +156,7 @@ CONFIGURE_CONFIG = os.environ.get('EMMAKEN_JUST_CONFIGURE') CMAKE_CONFIG = 'CMakeFiles/cmTryCompileExec.dir' in ' '.join(sys.argv)# or 'CMakeCCompilerId' in ' '.join(sys.argv) if CONFIGURE_CONFIG or CMAKE_CONFIG: compiler = 'g++' if 'CXXCompiler' in ' '.join(sys.argv) or os.environ.get('EMMAKEN_CXX') else 'gcc' - cmd = [compiler] + EMSDK_OPTS + sys.argv[1:] + cmd = [compiler] + shared.EMSDK_OPTS + sys.argv[1:] if DEBUG: print >> sys.stderr, 'emcc, just configuring: ', cmd exit(os.execvp(compiler, cmd)) @@ -172,6 +178,8 @@ if EMMAKEN_CFLAGS: CC_ADDITIONAL_ARGS += EMMAKEN_CFLAGS.split(' ') # ---------------- Utilities --------------- +SOURCE_SUFFIXES = ('.c', '.cpp', '.cxx', '.cc') + def unsuffixed(name): return '.'.join(name.split('.')[:-1]) @@ -207,7 +215,23 @@ for i in range(len(sys.argv)-1): sys.argv = sys.argv[:i] + sys.argv[i+2:] break -if not header: +if header: # header or such + if DEBUG: print >> sys.stderr, 'Just copy.' + shutil.copy(sys.argv[-1], sys.argv[-2]) + exit(0) + +if TEMP_DIR: + temp_dir = TEMP_DIR + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) # clear it + os.makedirs(temp_dir) +else: + temp_dir = tempfile.mkdtemp() + +def in_temp(name): + return os.path.join(temp_dir, name) + +try: call = CXX if use_cxx else CC ## Parse args @@ -215,7 +239,11 @@ if not header: newargs = sys.argv[1:] opt_level = 0 - llvm_opt_level = 0 + llvm_opt_level = None + closure = None + + def check_bad_eq(arg): + assert '=' not in arg, 'Invalid parameter (do not use "=" with "--" options)' for i in range(len(newargs)): if newargs[i].startswith('-O'): @@ -224,17 +252,23 @@ if not header: assert 0 <= opt_level <= 3 except: raise Exception('Invalid optimization level: ' + newargs[i]) - if opt_level >= 1: - llvm_opt_level = 1 newargs[i] = '' elif newargs[i].startswith('--llvm-opts'): - assert '=' not in newargs[i], 'Invalid llvm opts parameter (do not use "=")' + check_bad_eq(newargs[i]) llvm_opt_level = eval(newargs[i+1]) assert 0 <= llvm_opt_level <= 1, 'Only two levels of LLVM optimizations are supported so far, 0 (none) and 1 (safe)' newargs[i] = '' newargs[i+1] = '' + elif newargs[i].startswith('--closure'): + check_bad_eq(newargs[i]) + closure = int(newargs[i+1]) + newargs[i] = '' + newargs[i+1] = '' newargs = [ arg for arg in newargs if arg is not '' ] + if llvm_opt_level is None: llvm_opt_level = 1 if opt_level >= 1 else 0 + if closure is None: closure = 1 if opt_level >= 2 else 0 + settings_changes = [] for i in range(len(newargs)): if newargs[i] == '-s': @@ -248,12 +282,15 @@ if not header: newargs = [ arg for arg in newargs if arg is not '' ] input_files = [] + has_source_inputs = False for i in range(len(newargs)): # find input files XXX this a simple heuristic. we should really analyze based on a full understanding of gcc params, # right now we just assume that what is left contains no more |-x OPT| things arg = newargs[i] - if arg.endswith(('.c', '.cpp', '.cxx', '.bc', '.o')): # we already removed -o <target>, so all these should be inputs + if arg.endswith(SOURCE_SUFFIXES + ('.bc', '.o')): # we already removed -o <target>, so all these should be inputs input_files.append(arg) newargs[i] = '' + if arg.endswith(SOURCE_SUFFIXES): + has_source_inputs = True newargs = [ arg for arg in newargs if arg is not '' ] assert len(input_files) > 0, 'emcc: no input files specified' @@ -265,62 +302,74 @@ if not header: target_basename = unsuffixed_basename(target) - if '-c' in newargs: # -c means do not link in gcc, and for us, the parallel is to not go all the way to JS, but stop at bitcode - target = target_basename + '.bc' + # -c means do not link in gcc, and for us, the parallel is to not go all the way to JS, but stop at bitcode + has_dash_c = '-c' in newargs + if has_dash_c: + assert has_source_inputs, 'Must have source code inputs to use -c' + target = target_basename + '.o' final_suffix = target.split('.')[-1] # Apply optimization level settings if opt_level >= 1: shared.Settings.ASSERTIONS = 0 + shared.Settings.DISABLE_EXCEPTION_CATCHING = 1 if opt_level >= 2: shared.Settings.RELOOP = 1 - print >> sys.stderr, 'Warning: The relooper optimization can be very slow.' if opt_level >= 3: shared.Settings.CORRECT_SIGNS = 0 shared.Settings.CORRECT_OVERFLOWS = 0 shared.Settings.CORRECT_ROUNDINGS = 0 shared.Settings.I64_MODE = 0 shared.Settings.DOUBLE_MODE = 0 - shared.Settings.DISABLE_EXCEPTION_CATCHING = 1 print >> sys.stderr, 'Warning: Applying some potentially unsafe optimizations! (Use -O2 if this fails.)' ## Compile source code to bitcode + if DEBUG: print >> sys.stderr, 'emcc: compiling to bitcode' + # First, generate LLVM bitcode. For each input file, we get base.o with bitcode newargs = newargs + ['-emit-llvm', '-c'] for input_file in input_files: - if input_file.endswith(('.c', '.cpp', '.cxx')): - if DEBUG: print >> sys.stderr, "Running:", call, ' '.join(newargs) - Popen([call] + newargs + [input_file]).communicate() + if input_file.endswith(SOURCE_SUFFIXES): + args = newargs + [input_file, '-o', in_temp(unsuffixed_basename(input_file) + '.o')] + if DEBUG: print >> sys.stderr, "emcc running:", call, ' '.join(args) + Popen([call] + args).communicate() else: - shutil.copyfile(input_file, unsuffixed_basename(input_file) + '.o') + shutil.copyfile(input_file, in_temp(unsuffixed_basename(input_file) + '.o')) # Optimize, if asked to if llvm_opt_level > 0: + if DEBUG: print >> sys.stderr, 'emcc: LLVM opts' for input_file in input_files: - shared.Building.llvm_opt(unsuffixed_basename(input_file) + '.o', 2, safe=llvm_opt_level < 2) + shared.Building.llvm_opt(in_temp(unsuffixed_basename(input_file) + '.o'), 2, safe=llvm_opt_level < 2) # If we were just asked to generate bitcode, stop there - if final_suffix in ['o', 'bc']: - if final_suffix == 'bc': + if final_suffix not in ['js', 'html']: + if not specified_target: for input_file in input_files: - shutil.move(unsuffixed_basename(input_file) + '.o', unsuffixed_basename(input_file) + '.bc') - - if specified_target: - assert len(input_files) == 1, 'fatal error: cannot specify -o with -c with multiple files' - shutil.move(unsuffixed_basename(input_files[0]) + '.' + final_suffix, unsuffixed_basename(specified_target) + '.' + final_suffix) + shutil.move(in_temp(unsuffixed_basename(input_file) + '.o'), unsuffixed_basename(input_file) + '.' + final_suffix) + else: + if len(input_files) == 1: + shutil.move(in_temp(unsuffixed_basename(input_files[0]) + '.o'), specified_target) + else: + assert not has_dash_c, 'fatal error: cannot specify -o with -c with multiple files' + # We have a specified target (-o <target>), which is not JavaScript or HTML, and + # we have multiple files: Link them. TODO: Pass complex linker args along + shared.Building.link(map(lambda input_file: in_temp(unsuffixed_basename(input_file) + '.o'), input_files), specified_target) exit(0) ## Continue on to create JavaScript + if DEBUG: print >> sys.stderr, 'emcc: generating JavaScript' + # First, combine the bitcode files if there are several if len(input_files) > 1: - shared.Building.link(map(lambda input_file: unsuffixed_basename(input_file) + '.o', input_files), target_basename + '.bc') + shared.Building.link(map(lambda input_file: in_temp(unsuffixed_basename(input_file) + '.o'), input_files), in_temp(target_basename + '.bc')) else: - shutil.move(unsuffixed_basename(input_files[0]) + '.o', target_basename + '.bc') + shutil.move(in_temp(unsuffixed_basename(input_files[0]) + '.o'), in_temp(target_basename + '.bc')) # Apply -s settings in newargs here (after -Ox, so they can override it) @@ -328,51 +377,48 @@ if not header: key, value = change.split('=') exec('shared.Settings.' + key + ' = ' + value) - temp_files = shared.TempFiles() - temp_files.note(target_basename + '.bc') - try: - shared.Building.emscripten(target_basename + '.bc', append_ext=False) - shutil.move(target_basename + '.bc.o.js', target_basename + '.js') - if SAVE_FILES: shutil.copyfile(target_basename + '.js', 'save_' + target_basename + '.js') - - if opt_level >= 1: - # js optimizer - shared.Building.js_optimizer(target_basename + '.js', 'loopOptimizer') - shutil.move(target_basename + '.js.jo.js', target_basename + '.js') - if SAVE_FILES: shutil.copyfile(target_basename + '.js', 'save_' + target_basename + '.jo.js') - - # eliminator - shared.Building.eliminator(target_basename + '.js') - shutil.move(target_basename + '.js.el.js', target_basename + '.js') - if SAVE_FILES: shutil.copyfile(target_basename + '.js', 'save_' + target_basename + '.jo.el.js') - - if opt_level >= 3: - # closure - shared.Building.closure_compiler(target_basename + '.js') - shutil.move(target_basename + '.js.cc.js', target_basename + '.js') - if SAVE_FILES: shutil.copyfile(target_basename + '.js', 'save_' + target_basename + '.jo.el.cc.js') - - if opt_level >= 1: - # js optimizer - shared.Building.js_optimizer(target_basename + '.js', 'simplifyExpressions') - shutil.move(target_basename + '.js.jo.js', target_basename + '.js') - if SAVE_FILES: shutil.copyfile(target_basename + '.js', 'save_' + target_basename + '.jo.el.cc.jo.js') - - # If we were asked to also generate HTML, do that - if final_suffix == 'html': - shell = open(shared.path_from_root('src', 'shell.html')).read() - html = open(target_basename + '.html', 'w') - html.write(shell.replace('{{{ SCRIPT_CODE }}}', open(target_basename + '.js').read())) - html.close() - temp_files.note(target_basename + '.js') - - finally: - temp_files.clean() + if opt_level >= 2: + print >> sys.stderr, 'Warning: The relooper optimization can be very slow.' - exit(0) + final = shared.Building.emscripten(in_temp(target_basename + '.bc'), append_ext=False) -else: # header or such - if DEBUG: print >> sys.stderr, 'Just copy.' - shutil.copy(sys.argv[-1], sys.argv[-2]) - exit(0) + if opt_level >= 1: + # js optimizer + if DEBUG: print >> sys.stderr, 'emcc: running pre-closure post-opts' + final = shared.Building.js_optimizer(final, 'loopOptimizer') + + # eliminator + final = shared.Building.eliminator(final) + + # js optimizer pre-pass + final = shared.Building.js_optimizer(final, 'simplifyExpressionsPre') + + if closure: + if DEBUG: print >> sys.stderr, 'emcc: running closure' + final = shared.Building.closure_compiler(final) + + if opt_level >= 1: + # js optimizer post-pass + if DEBUG: print >> sys.stderr, 'emcc: running post-closure post-opts' + final = shared.Building.js_optimizer(final, 'simplifyExpressionsPost') + + # If we were asked to also generate HTML, do that + if final_suffix == 'html': + if DEBUG: print >> sys.stderr, 'emcc: generating HTML' + shell = open(shared.path_from_root('src', 'shell.html')).read() + html = open(target_basename + '.html', 'w') + html.write(shell.replace('{{{ SCRIPT_CODE }}}', open(final).read())) + html.close() + else: + # copy final JS to output + shutil.move(final, target_basename + '.js') + +finally: + if not TEMP_DIR: + try: + shutil.rmtree(temp_dir) + except: + pass + else: + print >> sys.stderr, 'emcc saved files are in:', temp_dir diff --git a/emconfigure b/emconfigure new file mode 100755 index 00000000..b52d90c2 --- /dev/null +++ b/emconfigure @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +''' +This is a helper script. See emcc. +''' + +import os, sys +from tools import shared + +shared.Building.configure(sys.argv[1:]) + @@ -1,19 +1,37 @@ #!/usr/bin/env python ''' -emcc - linker helper script +emld - linker helper script =========================== This script acts as a frontend replacement for the ld linker. See emcc. + +We could use the compiler code for this, but here we want to be careful to use all the linker flags we have been passed, sending them to ld. ''' +import os, sys +from tools import shared + +DEBUG = os.environ.get('EMCC_DEBUG') + +if DEBUG: + print >> sys.stderr, 'emld:', sys.argv + ALLOWED_LINK_ARGS = ['-f', '-help', '-o', '-print-after', '-print-after-all', '-print-before', '-print-before-all', '-time-passes', '-v', '-verify-dom-info', '-version' ] TWO_PART_DISALLOWED_LINK_ARGS = ['-L'] # Ignore thingsl like |-L .| -# +# Check for specified target +target = None +for i in range(len(sys.argv)-1): + if sys.argv[i].startswith('-o='): + raise Exception('Invalid syntax: do not use -o=X, use -o X') + + if sys.argv[i] == '-o': + target = sys.argv[i+1] + sys.argv = sys.argv[:i] + sys.argv[i+2:] + break -# We could use the compiler code for this, but here we want to be careful to use all the linker flags we have been passed, sending them to ld call = shared.LLVM_LD newargs = ['-disable-opt'] i = 0 @@ -37,6 +55,6 @@ if target: actual_target = unsuffixed(target) + '.bc' newargs.append('-o=' + actual_target) -if DEBUG: print >> sys.stderr, "Running:", call, ' '.join(newargs) -Popen([call] + newargs).communicate() +if DEBUG: print >> sys.stderr, "emld running:", call, ' '.join(newargs) +os.execvp(call, [call] + newargs) diff --git a/emlibtool b/emlibtool new file mode 100755 index 00000000..1220b93d --- /dev/null +++ b/emlibtool @@ -0,0 +1,11 @@ +#!/usr/bin/env python + +''' +This is a helper script. See emcc. +''' + +import os, sys +from tools import shared + +raise Exception('TODO: emlibtool') + diff --git a/src/library.js b/src/library.js index e225591e..2a4a5e7d 100644 --- a/src/library.js +++ b/src/library.js @@ -2214,7 +2214,7 @@ LibraryManager.library = { _formatString: function(format, varargs) { var textIndex = format; var argIndex = 0; - var getNextArg = function(type) { + function getNextArg(type) { // NOTE: Explicitly ignoring type safety. Otherwise this fails: // int x = 4; printf("%c\n", (char)x); var ret; @@ -2231,7 +2231,7 @@ LibraryManager.library = { } argIndex += Runtime.getNativeFieldSize(type); return Number(ret); - }; + } var ret = []; var curr, next, currArg; diff --git a/src/library_sdl.js b/src/library_sdl.js index 9e305bec..5797ccec 100644 --- a/src/library_sdl.js +++ b/src/library_sdl.js @@ -83,7 +83,7 @@ // solution here is to have a singleIteration() function which is a single loop // iteration, and from JS to do something like setInterval(_singleIteration, 1/30) // -// * SQL_Quit does nothing. +// * SDL_Quit does nothing. mergeInto(LibraryManager.library, { $SDL__deps: ['$Browser'], @@ -265,7 +265,7 @@ mergeInto(LibraryManager.library, { }, SDL_Quit: function() { - print('SQL_Quit called (and ignored)'); + print('SDL_Quit called (and ignored)'); }, SDL_LockSurface: function(surf) { diff --git a/tests/runner.py b/tests/runner.py index a2ca7305..d5489dd6 100644 --- a/tests/runner.py +++ b/tests/runner.py @@ -4878,6 +4878,11 @@ TT = %s del T # T is just a shape for the specific subclasses, we don't test it itself class other(RunnerCore): + def test_reminder(self): + raise Exception('''Fix emmaken.py and emconfiguren.py, they should work but mention they are deprecated + Test emconfigure + configure in test_zlib looks broken''') + def test_emcc(self): def clear(): for name in os.listdir(self.get_dir()): @@ -4916,27 +4921,31 @@ Options that are modified or new in %s include: # emcc src.cpp -c and emcc src.cpp -o src.[o|bc] ==> should give a .bc file for args in [['-c'], ['-o', 'src.o'], ['-o', 'src.bc']]: - target = args[1] if len(args) == 2 else 'hello_world.bc' + target = args[1] if len(args) == 2 else 'hello_world.o' clear() output = Popen([compiler, path_from_root('tests', 'hello_world' + suffix)] + args, stdout=PIPE, stderr=PIPE).communicate() assert len(output[0]) == 0, output[0] - assert os.path.exists(target), 'Expected %s to exist since args are %s : %s' % (target, str(args), output) + assert os.path.exists(target), 'Expected %s to exist since args are %s : %s' % (target, str(args), '\n'.join(output)) self.assertContained('hello, world!', self.run_llvm_interpreter([target])) # Optimization: emcc src.cpp -o something.js [-Ox]. -O0 is the same as not specifying any optimization setting - for params, opt_level, bc_params in [ # bc params are used after compiling to bitcode - (['-o', 'something.js'], 0, None), - (['-o', 'something.js', '-O0'], 0, None), - (['-o', 'something.js', '-O1'], 1, None), - (['-o', 'something.js', '-O2'], 2, None), - (['-o', 'something.js', '-O3'], 3, None), + for params, opt_level, bc_params, closure in [ # bc params are used after compiling to bitcode + (['-o', 'something.js'], 0, None, 0), + (['-o', 'something.js', '-O0'], 0, None, 0), + (['-o', 'something.js', '-O1'], 1, None, 0), + (['-o', 'something.js', '-O1', '--closure', '1'], 1, None, 1), + (['-o', 'something.js', '-O2'], 2, None, 1), + (['-o', 'something.js', '-O2', '--closure', '0'], 2, None, 0), + (['-o', 'something.js', '-O3'], 3, None, 1), + (['-o', 'something.js', '-O3', '--closure', '0'], 3, None, 0), # and, test compiling to bitcode first - (['-o', 'something.bc'], 0, []), - (['-o', 'something.bc'], 0, ['-O0']), - (['-o', 'something.bc'], 1, ['-O1']), - (['-o', 'something.bc'], 2, ['-O2']), - (['-o', 'something.bc'], 3, ['-O3']), + (['-o', 'something.bc'], 0, [], 0), + (['-o', 'something.bc'], 0, ['-O0'], 0), + (['-o', 'something.bc'], 1, ['-O1'], 0), + (['-o', 'something.bc'], 2, ['-O2'], 1), + (['-o', 'something.bc'], 3, ['-O3'], 1), ]: + #print params, opt_level, bc_params, closure clear() output = Popen([compiler, path_from_root('tests', 'hello_world_loop.cpp')] + params, stdout=PIPE, stderr=PIPE).communicate() @@ -4952,16 +4961,17 @@ Options that are modified or new in %s include: # Verify optimization level etc. in the generated code # XXX these are quite sensitive, and will need updating when code generation changes generated = open('something.js').read() # TODO: parse out the _main function itself, not support code, if the tests below need that some day - assert ('(__label__)' in generated) == (opt_level <= 1), 'relooping should be in opt >= 2' - assert ('assert(STACKTOP < STACK_MAX)' in generated) == (opt_level == 0), 'assertions should be in opt == 0' - assert ('|0)/2)|0)' in generated or '| 0) / 2 | 0)' in generated) == (opt_level <= 2), 'corrections should be in opt <= 2' assert 'new Uint16Array' in generated and 'new Uint32Array' in generated, 'typed arrays 2 should be used by default' assert 'SAFE_HEAP' not in generated, 'safe heap should not be used by default' assert ': while(' not in generated, 'when relooping we also js-optimize, so there should be no labelled whiles' - if opt_level >= 3: + if closure: assert 'Module._main = ' in generated, 'closure compiler should have been run' else: # closure has not been run, we can do some additional checks. TODO: figure out how to do these even with closure + assert 'Module._main = ' not in generated, 'closure compiler should not have been run' + # XXX find a way to test this: assert ('& 255' in generated or '&255' in generated) == (opt_level <= 2), 'corrections should be in opt <= 2' + assert ('(__label__)' in generated) == (opt_level <= 1), 'relooping should be in opt >= 2' + assert ('assert(STACKTOP < STACK_MAX)' in generated) == (opt_level == 0), 'assertions should be in opt == 0' assert 'var $i;' in generated, 'micro opts should always be on' if opt_level >= 1: assert 'HEAP8[HEAP32[' in generated, 'eliminator should create compound expressions, and fewer one-time vars' assert ('_puts(' in generated) == (opt_level >= 1), 'with opt >= 1, llvm opts are run and they should optimize printf to puts' @@ -5002,25 +5012,36 @@ Options that are modified or new in %s include: assert 'fatal error' in output[1], output[1] continue - assert os.path.exists('twopart_main.bc'), '\n'.join(output) - assert os.path.exists('twopart_side.bc'), '\n'.join(output) + assert os.path.exists('twopart_main.o'), '\n'.join(output) + assert os.path.exists('twopart_side.o'), '\n'.join(output) assert not os.path.exists(target), 'We should only have created bitcode here: ' + '\n'.join(output) # Compiling one of them alone is expected to fail - output = Popen([compiler, 'twopart_main.bc'] + args, stdout=PIPE, stderr=PIPE).communicate() + output = Popen([compiler, 'twopart_main.o'] + args, stdout=PIPE, stderr=PIPE).communicate() assert os.path.exists(target), '\n'.join(output) #print '\n'.join(output) self.assertContained('is not a function', run_js(target, stderr=STDOUT)) try_delete(target) # Combining those bc files into js should work - output = Popen([compiler, 'twopart_main.bc', 'twopart_side.bc'] + args, stdout=PIPE, stderr=PIPE).communicate() + output = Popen([compiler, 'twopart_main.o', 'twopart_side.o'] + args, stdout=PIPE, stderr=PIPE).communicate() assert os.path.exists(target), '\n'.join(output) self.assertContained('side got: hello from main, over', run_js(target)) + # Combining bc files into another bc should also work + try_delete(target) + assert not os.path.exists(target) + output = Popen([compiler, 'twopart_main.o', 'twopart_side.o', '-o', 'combined.bc'] + args, stdout=PIPE, stderr=PIPE).communicate() + assert os.path.exists('combined.bc'), '\n'.join(output) + self.assertContained('side got: hello from main, over', self.run_llvm_interpreter(['combined.bc'])) + + # TODO: compile .ll inputs to emcc into .bc + # TODO: dlmalloc in emcc (just pass the arg to emscripten.py) # TODO: test normal project linking, static and dynamic: get_library should not need to be told what to link! # TODO: when ready, switch tools/shared building to use emcc over emmaken # TODO: when this is done, more test runner to test these (i.e., test all -Ox thoroughly) + # TODO: emscripten tutorial with emcc + # TODO: deprecate llvm optimizations etc. in emscripten.py. # Finally, do some web browser tests def run_browser(html_file, message): @@ -5069,7 +5090,7 @@ Options that are modified or new in %s include: def test_js_optimizer(self): input = open(path_from_root('tools', 'test-js-optimizer.js')).read() expected = open(path_from_root('tools', 'test-js-optimizer-output.js')).read() - output = Popen([NODE_JS, JS_OPTIMIZER, 'unGlobalize', 'removeAssignsToUndefined', 'simplifyExpressions', 'loopOptimizer'], + output = Popen([NODE_JS, JS_OPTIMIZER, 'unGlobalize', 'removeAssignsToUndefined', 'simplifyExpressionsPre', 'simplifyExpressionsPost', 'loopOptimizer'], stdin=PIPE, stdout=PIPE).communicate(input)[0] self.assertIdentical(expected, output.replace('\n\n', '\n')) @@ -5110,8 +5131,6 @@ else: print 'Benchmarking JS engine:', JS_ENGINE Building.COMPILER_TEST_OPTS = [] - # TODO: Use other js optimizer options, like remove assigns to undefined (seems to slow us down more than speed us up) - POST_OPTIMIZATIONS = [['js-optimizer', 'loopOptimizer'], 'eliminator', 'closure', ['js-optimizer', 'simplifyExpressions']] TEST_REPS = 10 TOTAL_TESTS = 7 diff --git a/tools/emconfiguren.py b/tools/emconfiguren.py index abe41564..d549908b 100755 --- a/tools/emconfiguren.py +++ b/tools/emconfiguren.py @@ -1,5 +1,7 @@ #!/usr/bin/env python +raise Exception('emconfiguren is deprecated!') + ''' This is a helper script for emmaken.py. See docs in that file for more info. ''' diff --git a/tools/emmaken.py b/tools/emmaken.py index a509b940..89785bc5 100755 --- a/tools/emmaken.py +++ b/tools/emmaken.py @@ -1,5 +1,7 @@ #!/usr/bin/env python +raise Exception('emmaken is deprecated!') + ''' emmaken - the emscripten make proxy tool ======================================== diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index d22de39c..bf971951 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -78,6 +78,7 @@ function traverse(node, pre, post, stack) { if (stack) len = stack.length; var result = pre(node, type, stack); if (result == true) return true; + if (typeof result == 'object') node = result; // Continue processing on this node if (stack && len == stack.length) stack.push(0); } for (var i = 0; i < node.length; i++) { @@ -259,8 +260,74 @@ function removeUnneededLabelSettings(ast) { }); } -// Various expression simplifications -function simplifyExpressions(ast) { +// Various expression simplifications. Pre run before closure (where we still have metadata), Post run after. + +function simplifyExpressionsPre(ast) { + // When there is a bunch of math like (((8+5)|0)+12)|0, only the external |0 is needed, one correction is enough. + // At each node, ((X|0)+Y)|0 can be transformed into (X+Y): The inner corrections are not needed + // TODO: Is the same is true for 0xff, 0xffff? + + function simplifyBitops(ast) { + var SAFE_BINARY_OPS = set('+', '-', '*', '/', '%', '|'); + var ZERO = ['num', 0]; + var rerun = true; + while (rerun) { + rerun = false; + traverseGenerated(ast, function(node, type, stack) { + if (type == 'binary' && node[1] == '|' && (jsonCompare(node[2], ZERO) || jsonCompare(node[3], ZERO))) { + stack.push(1); // From here on up, no need for this kind of correction, it's done at the top + + // We might be able to remove this correction + for (var i = stack.length-2; i >= 0; i--) { + if (stack[i] == 1) { + // Great, we can eliminate + rerun = true; + return jsonCompare(node[2], ZERO) ? node[3] : node[2]; + } else if (stack[i] == -1) { + break; // Too bad, we can't + } + } + } else if ((type == 'binary' && node[1] in SAFE_BINARY_OPS) || type == 'num' || type == 'name') { + stack.push(0); // This node is safe in that it does not interfere with this optimization + } else { + stack.push(-1); // This node is dangerous! Give up if you see this before you see '1' + } + }, null, []); + } + } + + // The most common mathop is addition, e.g. in getelementptr done repeatedly. We can join all of those, + // by doing (num+num) ==> newnum, and (name+num)+num = name+newnum + function joinAdditions(ast) { + var rerun = true; + while (rerun) { + rerun = false; + traverseGenerated(ast, function(node, type) { + if (type == 'binary' && node[1] == '+') { + if (node[2][0] == 'num' && node[3][0] == 'num') { + rerun = true; + return ['num', node[2][1] + node[3][1]]; + } + for (var i = 2; i <= 3; i++) { + var ii = 5-i; + for (var j = 2; j <= 3; j++) { + if (node[i][0] == 'num' && node[ii][0] == 'binary' && node[ii][1] == '+' && node[ii][j][0] == 'num') { + rerun = true; + node[ii][j][1] += node[i][1]; + return node[ii]; + } + } + } + } + }); + } + } + + simplifyBitops(ast); + joinAdditions(ast); +} + +function simplifyExpressionsPost(ast) { // We often have branchings that are simplified so one end vanishes, and // we then get // if (!(x < 5)) @@ -373,7 +440,8 @@ var passes = { unGlobalize: unGlobalize, removeAssignsToUndefined: removeAssignsToUndefined, //removeUnneededLabelSettings: removeUnneededLabelSettings, - simplifyExpressions: simplifyExpressions, + simplifyExpressionsPre: simplifyExpressionsPre, + simplifyExpressionsPost: simplifyExpressionsPost, loopOptimizer: loopOptimizer }; diff --git a/tools/shared.py b/tools/shared.py index b61552ba..cecfd4ac 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -28,6 +28,10 @@ DEMANGLER = path_from_root('third_party', 'demangler.py') NAMESPACER = path_from_root('tools', 'namespacer.py') EMCC = path_from_root('emcc') EMXX = path_from_root('em++') +EMAR = path_from_root('emar') +EMLD = path_from_root('emld') +EMRANLIB = path_from_root('emranlib') +EMLIBTOOL = path_from_root('emlibtool') EMMAKEN = path_from_root('tools', 'emmaken.py') AUTODEBUGGER = path_from_root('tools', 'autodebugger.py') DFE = path_from_root('tools', 'dead_function_eliminator.py') @@ -217,7 +221,11 @@ class Building: @staticmethod def get_building_env(): env = os.environ.copy() - env['RANLIB'] = env['AR'] = env['CXX'] = env['CC'] = env['LIBTOOL'] = EMMAKEN + env['CC'] = EMCC + env['CXX'] = EMXX + env['AR'] = EMAR + env['RANLIB'] = EMRANLIB + env['LIBTOOL'] = EMLIBTOOL 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. @@ -369,6 +377,8 @@ class Building: if output_processor is not None: output_processor(open(filename + '.o.js').read()) + return filename + '.o.js' + @staticmethod def pick_llvm_opts(optimization_level, safe=True): ''' @@ -472,6 +482,7 @@ class Building: f = open(filename, 'w') f.write(output) f.close() + return filename @staticmethod def eliminator(filename): @@ -486,6 +497,7 @@ class Building: f = open(filename, 'w') f.write(output) |