diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/js-optimizer.js | 18 | ||||
-rw-r--r-- | tools/shared.py | 112 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-outline1-output.js | 133 | ||||
-rw-r--r-- | tools/test-js-optimizer-asm-outline1.js | 40 | ||||
-rw-r--r-- | tools/test-js-optimizer-regs-output.js | 16 | ||||
-rw-r--r-- | tools/test-js-optimizer-regs.js | 2 | ||||
-rw-r--r-- | tools/validate_asmjs.py | 82 |
7 files changed, 351 insertions, 52 deletions
diff --git a/tools/js-optimizer.js b/tools/js-optimizer.js index 788a76ed..e567ebff 100644 --- a/tools/js-optimizer.js +++ b/tools/js-optimizer.js @@ -1728,6 +1728,15 @@ function getStackBumpSize(ast) { function registerize(ast) { traverseGeneratedFunctions(ast, function(fun) { if (asm) var asmData = normalizeAsm(fun); + if (!asm) { + var hasFunction = false; + traverse(fun, function(node, type) { + if (type === 'function') hasFunction = true; + }); + if (hasFunction) { + return; // inline assembly, and not asm (where we protect it in normalize/denormalize), so abort registerize pass + } + } // Add parameters as a first (fake) var (with assignment), so they get taken into consideration var params = {}; // note: params are special, they can never share a register between them (see later) if (fun[2] && fun[2].length) { @@ -3032,6 +3041,9 @@ function outline(ast) { } var ignore = []; traverse(func, function(node) { + if (node[0] === 'while' && node[2][0] !== 'block') { + node[2] = ['block', [node[2]]]; // so we have a list of statements and can flatten while(1) switch + } var stats = getStatements(node); if (stats) { for (var i = 0; i < stats.length; i++) { @@ -3708,8 +3720,10 @@ function outline(ast) { } } } - ret.push(func); - printErr('... resulting sizes of ' + func[1] + ' is ' + ret.map(measureSize) + '\n'); + if (ret) { + ret.push(func); + printErr('... resulting sizes of ' + func[1] + ' is ' + ret.map(measureSize) + '\n'); + } } denormalizeAsm(func, asmData); }); diff --git a/tools/shared.py b/tools/shared.py index 2e11d736..94daadae 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -546,15 +546,13 @@ def get_llvm_target(): return os.environ.get('EMCC_LLVM_TARGET') or 'le32-unknown-nacl' # 'i386-pc-linux-gnu' LLVM_TARGET = get_llvm_target() +# COMPILER_OPTS: options passed to clang when generating bitcode for us try: COMPILER_OPTS # Can be set in EM_CONFIG, optionally except: COMPILER_OPTS = [] -# Force a simple, standard target as much as possible: target 32-bit linux, and disable various flags that hint at other platforms -COMPILER_OPTS = COMPILER_OPTS + ['-m32', '-U__i386__', '-U__i386', '-Ui386', - '-U__SSE__', '-U__SSE_MATH__', '-U__SSE2__', '-U__SSE2_MATH__', '-U__MMX__', - '-DEMSCRIPTEN', '-D__EMSCRIPTEN__', '-U__STRICT_ANSI__', - '-D__IEEE_LITTLE_ENDIAN', '-fno-math-errno', +COMPILER_OPTS = COMPILER_OPTS + ['-m32', '-DEMSCRIPTEN', '-D__EMSCRIPTEN__', + '-fno-math-errno', #'-fno-threadsafe-statics', # disabled due to issue 1289 '-target', LLVM_TARGET] @@ -562,6 +560,11 @@ if LLVM_TARGET == 'le32-unknown-nacl': COMPILER_OPTS = filter(lambda opt: opt != '-m32', COMPILER_OPTS) # le32 target is 32-bit anyhow, no need for -m32 COMPILER_OPTS += ['-U__native_client__', '-U__pnacl__', '-U__ELF__'] # The nacl target is originally used for Google Native Client. Emscripten is not NaCl, so remove the platform #define, when using their triple. +# Remove various platform specific defines, and set little endian +COMPILER_STANDARDIZATION_OPTS = ['-U__i386__', '-U__i386', '-Ui386', '-U__STRICT_ANSI__', '-D__IEEE_LITTLE_ENDIAN', + '-U__SSE__', '-U__SSE_MATH__', '-U__SSE2__', '-U__SSE2_MATH__', '-U__MMX__', + '-U__APPLE__', '-U__linux__'] + USE_EMSDK = not os.environ.get('EMMAKEN_NO_SDK') if USE_EMSDK: @@ -578,9 +581,8 @@ if USE_EMSDK: '-Xclang', '-isystem' + path_from_root('system', 'include', 'gfx'), '-Xclang', '-isystem' + path_from_root('system', 'include', 'net'), '-Xclang', '-isystem' + path_from_root('system', 'include', 'SDL'), - ] + [ - '-U__APPLE__', '-U__linux__' ] + EMSDK_OPTS += COMPILER_STANDARDIZATION_OPTS if LLVM_TARGET != 'le32-unknown-nacl': EMSDK_CXX_OPTS = ['-nostdinc++'] # le32 target does not need -nostdinc++ else: @@ -589,6 +591,7 @@ if USE_EMSDK: else: EMSDK_OPTS = [] EMSDK_CXX_OPTS = [] + COMPILER_OPTS += COMPILER_STANDARDIZATION_OPTS #print >> sys.stderr, 'SDK opts', ' '.join(EMSDK_OPTS) #print >> sys.stderr, 'Compiler opts', ' '.join(COMPILER_OPTS) @@ -817,35 +820,52 @@ class Building: env['EMSCRIPTEN'] = path_from_root() return env + # Finds the given executable 'program' in PATH. Operates like the Unix tool 'which'. + @staticmethod + def which(program): + import os + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + + if WINDOWS and not '.' in fname: + if is_exe(exe_file + '.exe'): + return exe_file + '.exe' + if is_exe(exe_file + '.cmd'): + return exe_file + '.cmd' + if is_exe(exe_file + '.bat'): + return exe_file + '.bat' + + return None + @staticmethod def handle_CMake_toolchain(args, env): - CMakeToolchain = ('''# the name of the target operating system -SET(CMAKE_SYSTEM_NAME Linux) - -# which C and C++ compiler to use -SET(CMAKE_C_COMPILER %(winfix)s$EMSCRIPTEN_ROOT/emcc) -SET(CMAKE_CXX_COMPILER %(winfix)s$EMSCRIPTEN_ROOT/em++) -SET(CMAKE_AR %(winfix)s$EMSCRIPTEN_ROOT/emar) -SET(CMAKE_RANLIB %(winfix)s$EMSCRIPTEN_ROOT/emranlib) -SET(CMAKE_C_FLAGS $CFLAGS) -SET(CMAKE_CXX_FLAGS $CXXFLAGS) - -# here is the target environment located -SET(CMAKE_FIND_ROOT_PATH $EMSCRIPTEN_ROOT/system/include ) - -# adjust the default behaviour of the FIND_XXX() commands: -# search headers and libraries in the target environment, search -# programs in the host environment -set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH) -set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS else 'python ' }) \ - .replace('$EMSCRIPTEN_ROOT', path_from_root('').replace('\\', '/')) \ - .replace('$CFLAGS', env['CFLAGS']) \ - .replace('$CXXFLAGS', env['CFLAGS']) - toolchainFile = mkstemp(suffix='.cmaketoolchain.txt', dir=configuration.TEMP_DIR)[1] - open(toolchainFile, 'w').write(CMakeToolchain) - args.append('-DCMAKE_TOOLCHAIN_FILE=%s' % os.path.abspath(toolchainFile)) + + def has_substr(array, substr): + for arg in array: + if substr in arg: + return True + return False + + # Append the Emscripten toolchain file if the user didn't specify one. + if not has_substr(args, '-DCMAKE_TOOLCHAIN_FILE'): + args.append('-DCMAKE_TOOLCHAIN_FILE=' + path_from_root('cmake', 'Platform', 'Emscripten.cmake')) + + # On Windows specify MinGW Makefiles if we have MinGW and no other toolchain was specified, to avoid CMake + # pulling in a native Visual Studio, or Unix Makefiles. + if WINDOWS and not '-G' in args and Building.which('mingw32-make'): + args += ['-G', 'MinGW Makefiles'] + return args @staticmethod @@ -876,6 +896,13 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e logging.error('Executable to run not specified.') sys.exit(1) #args += ['VERBOSE=1'] + + # On Windows prefer building with mingw32-make instead of make, if it exists. + if WINDOWS and args[0] == 'make': + mingw32_make = Building.which('mingw32-make') + if mingw32_make: + args[0] = mingw32_make + try: process = Popen(args, stdout=stdout, stderr=stderr, env=env) process.communicate() @@ -916,13 +943,13 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e # except: # pass env = Building.get_building_env(native) - log_to_file = os.getenv('EM_BUILD_VERBOSE') == None or int(os.getenv('EM_BUILD_VERBOSE')) == 0 + verbose_level = int(os.getenv('EM_BUILD_VERBOSE')) if os.getenv('EM_BUILD_VERBOSE') != None else 0 for k, v in env_init.iteritems(): env[k] = v if configure: # Useful in debugging sometimes to comment this out (and the lines below up to and including the |link| call) try: - Building.configure(configure + configure_args, env=env, stdout=open(os.path.join(project_dir, 'configure_'), 'w') if log_to_file else None, - stderr=open(os.path.join(project_dir, 'configure_err'), 'w') if log_to_file else None) + Building.configure(configure + configure_args, env=env, stdout=open(os.path.join(project_dir, 'configure_'), 'w') if verbose_level < 2 else None, + stderr=open(os.path.join(project_dir, 'configure_err'), 'w') if verbose_level < 1 else None) except subprocess.CalledProcessError, e: pass # Ignore exit code != 0 def open_make_out(i, mode='r'): @@ -930,13 +957,16 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e def open_make_err(i, mode='r'): return open(os.path.join(project_dir, 'make_err' + str(i)), mode) - + + if verbose_level >= 3: + make_args += ['VERBOSE=1'] + for i in range(2): # FIXME: Sad workaround for some build systems that need to be run twice to succeed (e.g. poppler) with open_make_out(i, 'w') as make_out: with open_make_err(i, 'w') as make_err: try: - Building.make(make + make_args, stdout=make_out if log_to_file else None, - stderr=make_err if log_to_file else None, env=env) + Building.make(make + make_args, stdout=make_out if verbose_level < 2 else None, + stderr=make_err if verbose_level < 1 else None, env=env) except subprocess.CalledProcessError, e: pass # Ignore exit code != 0 try: @@ -948,7 +978,7 @@ set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)''' % { 'winfix': '' if not WINDOWS e break except Exception, e: if i > 0: - if log_to_file: + if verbose_level == 0: # Due to the ugly hack above our best guess is to output the first run with open_make_err(0) as ferr: for line in ferr: diff --git a/tools/test-js-optimizer-asm-outline1-output.js b/tools/test-js-optimizer-asm-outline1-output.js index 612da16a..27f93d8a 100644 --- a/tools/test-js-optimizer-asm-outline1-output.js +++ b/tools/test-js-optimizer-asm-outline1-output.js @@ -372,6 +372,57 @@ function switchh() { } STACKTOP = sp; } +function switchh2() { + var helper$0 = 0, helper$1 = 0, sp = 0; + sp = STACKTOP; + STACKTOP = STACKTOP + 280 | 0; + while (1) { + helper$0 = 1; + helper$1 = x; + if (helper$0) { + helper$0 = 0; + switch (helper$1 | 0) { + case 0: + f(0); + g(); + break; + default: + { + helper$0 = 1; + } + } + } + HEAP32[sp + 8 >> 2] = helper$0; + HEAP32[sp + 16 >> 2] = helper$1; + HEAP32[sp + 40 >> 2] = 0; + HEAP32[sp + 44 >> 2] = 0; + switchh2$2(sp); + helper$0 = HEAP32[sp + 8 >> 2] | 0; + tempValue = HEAP32[sp + 40 >> 2] | 0; + tempInt = HEAP32[sp + 44 >> 2] | 0; + tempDouble = +HEAPF32[sp + 44 >> 2]; + HEAP32[sp + 40 >> 2] = 0; + HEAP32[sp + 44 >> 2] = 0; + if ((tempValue | 0) == 5) { + STACKTOP = sp; + return; + } + HEAP32[sp + 8 >> 2] = helper$0; + HEAP32[sp + 16 >> 2] = helper$1; + HEAP32[sp + 32 >> 2] = 0; + HEAP32[sp + 36 >> 2] = 0; + switchh2$1(sp); + helper$0 = HEAP32[sp + 8 >> 2] | 0; + if (helper$0) { + helper$0 = 0; + HEAP32[sp + 16 >> 2] = helper$1; + HEAP32[sp + 24 >> 2] = 0; + HEAP32[sp + 28 >> 2] = 0; + switchh2$0(sp); + } + } + STACKTOP = sp; +} function lin$0(sp) { sp = sp | 0; c(14); @@ -793,4 +844,86 @@ function switchh$2(sp) { } while (0); HEAP32[sp + 8 >> 2] = helper$0; } +function switchh2$0(sp) { + sp = sp | 0; + var helper$1 = 0; + helper$1 = HEAP32[sp + 16 >> 2] | 0; + switch (helper$1 | 0) { + case 4: + f(4); + g(); + case 5: + f(5); + g(); + case 6: + f(6); + g(); + default: + print(9); + } +} +function switchh2$1(sp) { + sp = sp | 0; + var helper$0 = 0, helper$1 = 0; + helper$0 = HEAP32[sp + 8 >> 2] | 0; + helper$1 = HEAP32[sp + 16 >> 2] | 0; + if (helper$0) { + helper$0 = 0; + switch (helper$1 | 0) { + case 21: + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 3: + f(3); + g(); + break; + default: + { + helper$0 = 1; + } + } + } + HEAP32[sp + 8 >> 2] = helper$0; +} +function switchh2$2(sp) { + sp = sp | 0; + var helper$0 = 0, helper$1 = 0; + helper$0 = HEAP32[sp + 8 >> 2] | 0; + helper$1 = HEAP32[sp + 16 >> 2] | 0; + OL : do { + if (helper$0) { + helper$0 = 0; + switch (helper$1 | 0) { + case 1: + f(1); + g(); + return; + default: + { + helper$0 = 1; + } + } + } + if (helper$0) { + helper$0 = 0; + switch (helper$1 | 0) { + case 2: + f(2); + g(); + break; + default: + { + helper$0 = 1; + } + } + } + } while (0); + HEAP32[sp + 8 >> 2] = helper$0; +} diff --git a/tools/test-js-optimizer-asm-outline1.js b/tools/test-js-optimizer-asm-outline1.js index 4282ec8e..b7ec9011 100644 --- a/tools/test-js-optimizer-asm-outline1.js +++ b/tools/test-js-optimizer-asm-outline1.js @@ -307,5 +307,45 @@ function switchh() { } } } +function switchh2() { + while (1) switch (x) { + case 0: + f(0); + g(); + break; + case 1: + f(1); + g(); + return; + case 2: + f(2); + g(); + break; + case 21: // gotta keem em unseparated + case 22: + case 23: + case 24: + case 25: + case 26: + case 27: + case 28: + case 29: + case 3: // these too + f(3); + g(); + break; + case 4: + f(4); + g(); + case 5: + f(5); + g(); + case 6: + f(6); + g(); + default: + print(9); + } +} // EMSCRIPTEN_GENERATED_FUNCTIONS // EXTRA_INFO: { "sizeToOutline": 30, "allowCostlyOutlines": 1 } diff --git a/tools/test-js-optimizer-regs-output.js b/tools/test-js-optimizer-regs-output.js index 149ca984..6f67bcec 100644 --- a/tools/test-js-optimizer-regs-output.js +++ b/tools/test-js-optimizer-regs-output.js @@ -175,19 +175,19 @@ function fcntl_open() { return null; } function ex() { - var r1, r2; - r1 = STACKTOP; + var __stackBase__ = STACKTOP; STACKTOP += 4; - r2 = r1; - r1 = _puts(STRING_TABLE._str17 | 0); - r1 = r2 | 0; - r2 = 0; + var $e1 = __stackBase__; + var $puts = _puts(STRING_TABLE._str17 | 0); + var $x41 = $e1 | 0; + var $i_04 = 0; while (1) { - r1 = _printf(STRING_TABLE.__str15 | 0, (tempInt = STACKTOP, STACKTOP += 4, HEAP32[tempInt >> 2] = r2, tempInt)); + var $i_04; + var $call1 = _printf(STRING_TABLE.__str15 | 0, (tempInt = STACKTOP, STACKTOP += 4, HEAP32[tempInt >> 2] = $i_04, tempInt)); ((function() { try { __THREW__ = false; - return __Z5magici(r2); + return __Z5magici($i_04); } catch (e) { if (typeof e != "number") throw e; if (ABORT) throw e; diff --git a/tools/test-js-optimizer-regs.js b/tools/test-js-optimizer-regs.js index 00303786..bc0ebb5a 100644 --- a/tools/test-js-optimizer-regs.js +++ b/tools/test-js-optimizer-regs.js @@ -190,7 +190,7 @@ function ex() { while (1) { var $i_04; var $call1 = _printf(STRING_TABLE.__str15 | 0, (tempInt = STACKTOP, STACKTOP += 4, HEAP32[tempInt >> 2] = $i_04, tempInt)); - ((function() { + ((function() { // prevents registerize, looks like inline asm try { __THREW__ = false; return __Z5magici($i_04); diff --git a/tools/validate_asmjs.py b/tools/validate_asmjs.py new file mode 100644 index 00000000..ea909fbd --- /dev/null +++ b/tools/validate_asmjs.py @@ -0,0 +1,82 @@ +#!/usr/bin/python + +# This is a helper script to validate a file for asm.js. + +# cmdline usage: 'python validate_asmjs.py filename.{html/js}' +# Prints a line starting with 'OK: ' on success, and returns process exit code 0. +# On failure, prints a line starting with 'FAIL: ', and returns a nonzero process exit code. + +# python usage: 'validate_asmjs("filename.{html/js}", muteOutput=True/False)' +# Returns True/False depending on whether the file was valid asm.js. + +# This script depends on the SpiderMonkey JS engine, which must be present in PATH in order for this script to function. + +import subprocess, sys, re, tempfile, os, time +import shared + +# Looks up SpiderMonkey engine using the variable SPIDERMONKEY_ENGINE in ~/.emscripten, and if not set up there, via PATH. +def find_spidermonkey_engine(): + sm_engine = shared.SPIDERMONKEY_ENGINE if hasattr(shared, 'SPIDERMONKEY_ENGINE') else [''] + if not sm_engine or len(sm_engine[0]) == 0 or not os.path.exists(sm_engine[0]): + sm_engine[0] = shared.Building.which('js') + if sm_engine[0] == None: + return ['js-not-found'] + return sm_engine + +# Given a .js file, returns True/False depending on if that file is valid asm.js +def validate_asmjs_jsfile(filename, muteOutput): + process = subprocess.Popen(find_spidermonkey_engine() + ['-c', filename], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) + (stdout, stderr) = process.communicate() + if not muteOutput: + if len(stdout.strip()) > 0: + print stdout.strip() + if len(stderr.strip()) > 0: + # Pretty-print the output not to contain a spurious warning. + stderr = stderr.replace('warning: successfully compiled asm.js', ' successfully compiled asm.js') + + print >> sys.stderr, stderr.strip() + if 'successfully compiled asm.js' in stderr: + return True + else: + return False + +# This tool takes as input a file built with Emscripten (either .html or .js) and validates it for asm.js. +# Returns True/False denoting whether the file was valid asm.js. In case of a .html file, all <script>content</script> tags are searched, +# and the ones containing a "use asm" section are validated. +def validate_asmjs(filename, muteOutput): + if filename.endswith('.html'): + html = open(filename, 'r').read() + matches = re.findall('''<\w*script\w*.*?>(.*?)<\w*/script\w*>''', html, re.DOTALL | re.MULTILINE) + numAsmJsBlocks = 0 + for match in matches: + if '"use asm"' in match: + numAsmJsBlocks = numAsmJsBlocks + 1 + tmp_js = tempfile.mkstemp(suffix='.js') + os.write(tmp_js[0], match) + os.close(tmp_js[0]) + valid_asmjs = validate_asmjs_jsfile(tmp_js[1], muteOutput) + os.remove(tmp_js[1]) + if not valid_asmjs: + return False + if numAsmJsBlocks == 0: + if not muteOutput: + print >> sys.stderr, 'Error: the file does not contain any "use asm" modules.' + return False + else: + return True + else: + return validate_asmjs_jsfile(filename, muteOutput) + +def main(): + if len(sys.argv) < 2: + print 'Usage: validate_asmjs <filename>' + return 2 + if validate_asmjs(sys.argv[1], muteOutput=False): + print "OK: File '" + sys.argv[1] + "' validates as asm.js" + return 0 + else: + print "FAIL: File '" + sys.argv[1] + "' is not valid asm.js" + return 1 + +if __name__ == '__main__': + sys.exit(main()) |